Update libs
This commit is contained in:
+294
-138
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
|
||||
Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -22,6 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import urllib
|
||||
import time
|
||||
import random
|
||||
@@ -31,12 +32,24 @@ import binascii
|
||||
import httplib2
|
||||
|
||||
try:
|
||||
from urlparse import parse_qs, parse_qsl
|
||||
from urlparse import parse_qs
|
||||
parse_qs # placate pyflakes
|
||||
except ImportError:
|
||||
from cgi import parse_qs, parse_qsl
|
||||
# fall back for Python 2.5
|
||||
from cgi import parse_qs
|
||||
|
||||
try:
|
||||
from hashlib import sha1
|
||||
sha = sha1
|
||||
except ImportError:
|
||||
# hashlib was added in Python 2.5
|
||||
import sha
|
||||
|
||||
VERSION = '1.0' # Hi Blaine!
|
||||
import _version
|
||||
|
||||
__version__ = _version.__version__
|
||||
|
||||
OAUTH_VERSION = '1.0' # Hi Blaine!
|
||||
HTTP_METHOD = 'GET'
|
||||
SIGNATURE_METHOD = 'PLAINTEXT'
|
||||
|
||||
@@ -44,7 +57,7 @@ SIGNATURE_METHOD = 'PLAINTEXT'
|
||||
class Error(RuntimeError):
|
||||
"""Generic exception class."""
|
||||
|
||||
def __init__(self, message='OAuth error occured.'):
|
||||
def __init__(self, message='OAuth error occurred.'):
|
||||
self._message = message
|
||||
|
||||
@property
|
||||
@@ -55,18 +68,94 @@ class Error(RuntimeError):
|
||||
def __str__(self):
|
||||
return self._message
|
||||
|
||||
|
||||
class MissingSignature(Error):
|
||||
pass
|
||||
|
||||
|
||||
def build_authenticate_header(realm=''):
|
||||
"""Optional WWW-Authenticate header (401 error)"""
|
||||
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
||||
|
||||
|
||||
def build_xoauth_string(url, consumer, token=None):
|
||||
"""Build an XOAUTH string for use in SMTP/IMPA authentication."""
|
||||
request = Request.from_consumer_and_token(consumer, token,
|
||||
"GET", url)
|
||||
|
||||
signing_method = SignatureMethod_HMAC_SHA1()
|
||||
request.sign_request(signing_method, consumer, token)
|
||||
|
||||
params = []
|
||||
for k, v in sorted(request.iteritems()):
|
||||
if v is not None:
|
||||
params.append('%s="%s"' % (k, escape(v)))
|
||||
|
||||
return "%s %s %s" % ("GET", url, ','.join(params))
|
||||
|
||||
|
||||
def to_unicode(s):
|
||||
""" Convert to unicode, raise exception with instructive error
|
||||
message if s is not unicode, ascii, or utf-8. """
|
||||
if not isinstance(s, unicode):
|
||||
if not isinstance(s, str):
|
||||
raise TypeError('You are required to pass either unicode or string here, not: %r (%s)' % (type(s), s))
|
||||
try:
|
||||
s = s.decode('utf-8')
|
||||
except UnicodeDecodeError, le:
|
||||
raise TypeError('You are required to pass either a unicode object or a utf-8 string here. You passed a Python string object which contained non-utf-8: %r. The UnicodeDecodeError that resulted from attempting to interpret it as utf-8 was: %s' % (s, le,))
|
||||
return s
|
||||
|
||||
def to_utf8(s):
|
||||
return to_unicode(s).encode('utf-8')
|
||||
|
||||
def to_unicode_if_string(s):
|
||||
if isinstance(s, basestring):
|
||||
return to_unicode(s)
|
||||
else:
|
||||
return s
|
||||
|
||||
def to_utf8_if_string(s):
|
||||
if isinstance(s, basestring):
|
||||
return to_utf8(s)
|
||||
else:
|
||||
return s
|
||||
|
||||
def to_unicode_optional_iterator(x):
|
||||
"""
|
||||
Raise TypeError if x is a str containing non-utf8 bytes or if x is
|
||||
an iterable which contains such a str.
|
||||
"""
|
||||
if isinstance(x, basestring):
|
||||
return to_unicode(x)
|
||||
|
||||
try:
|
||||
l = list(x)
|
||||
except TypeError, e:
|
||||
assert 'is not iterable' in str(e)
|
||||
return x
|
||||
else:
|
||||
return [ to_unicode(e) for e in l ]
|
||||
|
||||
def to_utf8_optional_iterator(x):
|
||||
"""
|
||||
Raise TypeError if x is a str or if x is an iterable which
|
||||
contains a str.
|
||||
"""
|
||||
if isinstance(x, basestring):
|
||||
return to_utf8(x)
|
||||
|
||||
try:
|
||||
l = list(x)
|
||||
except TypeError, e:
|
||||
assert 'is not iterable' in str(e)
|
||||
return x
|
||||
else:
|
||||
return [ to_utf8_if_string(e) for e in l ]
|
||||
|
||||
def escape(s):
|
||||
"""Escape a URL including any /."""
|
||||
return urllib.quote(s, safe='~')
|
||||
|
||||
return urllib.quote(s.encode('utf-8'), safe='~')
|
||||
|
||||
def generate_timestamp():
|
||||
"""Get seconds since epoch (UTC)."""
|
||||
@@ -114,10 +203,8 @@ class Consumer(object):
|
||||
raise ValueError("Key and secret must be set.")
|
||||
|
||||
def __str__(self):
|
||||
data = {
|
||||
'oauth_consumer_key': self.key,
|
||||
'oauth_consumer_secret': self.secret
|
||||
}
|
||||
data = {'oauth_consumer_key': self.key,
|
||||
'oauth_consumer_secret': self.secret}
|
||||
|
||||
return urllib.urlencode(data)
|
||||
|
||||
@@ -216,7 +303,7 @@ class Token(object):
|
||||
try:
|
||||
token.callback_confirmed = params['oauth_callback_confirmed'][0]
|
||||
except KeyError:
|
||||
pass # 1.0, no callback confirmed.
|
||||
pass # 1.0, no callback confirmed.
|
||||
return token
|
||||
|
||||
def __str__(self):
|
||||
@@ -250,36 +337,41 @@ class Request(dict):
|
||||
|
||||
"""
|
||||
|
||||
http_method = HTTP_METHOD
|
||||
http_url = None
|
||||
version = VERSION
|
||||
|
||||
def __init__(self, method=HTTP_METHOD, url=None, parameters=None):
|
||||
if method is not None:
|
||||
self.method = method
|
||||
|
||||
version = OAUTH_VERSION
|
||||
|
||||
def __init__(self, method=HTTP_METHOD, url=None, parameters=None,
|
||||
body='', is_form_encoded=False):
|
||||
if url is not None:
|
||||
self.url = url
|
||||
|
||||
self.url = to_unicode(url)
|
||||
self.method = method
|
||||
if parameters is not None:
|
||||
self.update(parameters)
|
||||
|
||||
for k, v in parameters.iteritems():
|
||||
k = to_unicode(k)
|
||||
v = to_unicode_optional_iterator(v)
|
||||
self[k] = v
|
||||
self.body = body
|
||||
self.is_form_encoded = is_form_encoded
|
||||
|
||||
|
||||
@setter
|
||||
def url(self, value):
|
||||
parts = urlparse.urlparse(value)
|
||||
scheme, netloc, path = parts[:3]
|
||||
|
||||
# Exclude default port numbers.
|
||||
if scheme == 'http' and netloc[-3:] == ':80':
|
||||
netloc = netloc[:-3]
|
||||
elif scheme == 'https' and netloc[-4:] == ':443':
|
||||
netloc = netloc[:-4]
|
||||
|
||||
if scheme != 'http' and scheme != 'https':
|
||||
raise ValueError("Unsupported URL %s (%s)." % (value, scheme))
|
||||
|
||||
value = '%s://%s%s' % (scheme, netloc, path)
|
||||
self.__dict__['url'] = value
|
||||
if value is not None:
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(value)
|
||||
|
||||
# Exclude default port numbers.
|
||||
if scheme == 'http' and netloc[-3:] == ':80':
|
||||
netloc = netloc[:-3]
|
||||
elif scheme == 'https' and netloc[-4:] == ':443':
|
||||
netloc = netloc[:-4]
|
||||
if scheme not in ('http', 'https'):
|
||||
raise ValueError("Unsupported URL %s (%s)." % (value, scheme))
|
||||
|
||||
# Normalized URL excludes params, query, and fragment.
|
||||
self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None))
|
||||
else:
|
||||
self.normalized_url = None
|
||||
self.__dict__['url'] = None
|
||||
|
||||
@setter
|
||||
def method(self, value):
|
||||
@@ -309,17 +401,44 @@ class Request(dict):
|
||||
|
||||
def to_postdata(self):
|
||||
"""Serialize as post data for a POST request."""
|
||||
return self.encode_postdata(self)
|
||||
d = {}
|
||||
for k, v in self.iteritems():
|
||||
d[k.encode('utf-8')] = to_utf8_optional_iterator(v)
|
||||
|
||||
def encode_postdata(self, data):
|
||||
# tell urlencode to deal with sequence values and map them correctly
|
||||
# to resulting querystring. for example self["k"] = ["v1", "v2"] will
|
||||
# result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D
|
||||
return urllib.urlencode(data, True)
|
||||
|
||||
return urllib.urlencode(d, True).replace('+', '%20')
|
||||
|
||||
def to_url(self):
|
||||
"""Serialize as a URL for a GET request."""
|
||||
return '%s?%s' % (self.url, self.to_postdata())
|
||||
base_url = urlparse.urlparse(self.url)
|
||||
try:
|
||||
query = base_url.query
|
||||
except AttributeError:
|
||||
# must be python <2.5
|
||||
query = base_url[4]
|
||||
query = parse_qs(query)
|
||||
for k, v in self.items():
|
||||
query.setdefault(k, []).append(v)
|
||||
|
||||
try:
|
||||
scheme = base_url.scheme
|
||||
netloc = base_url.netloc
|
||||
path = base_url.path
|
||||
params = base_url.params
|
||||
fragment = base_url.fragment
|
||||
except AttributeError:
|
||||
# must be python <2.5
|
||||
scheme = base_url[0]
|
||||
netloc = base_url[1]
|
||||
path = base_url[2]
|
||||
params = base_url[3]
|
||||
fragment = base_url[5]
|
||||
|
||||
url = (scheme, netloc, path, params,
|
||||
urllib.urlencode(query, True), fragment)
|
||||
return urlparse.urlunparse(url)
|
||||
|
||||
def get_parameter(self, parameter):
|
||||
ret = self.get(parameter)
|
||||
@@ -327,20 +446,52 @@ class Request(dict):
|
||||
raise Error('Parameter not found: %s' % parameter)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def get_normalized_parameters(self):
|
||||
"""Return a string that contains the parameters that must be signed."""
|
||||
items = [(k, v) for k, v in self.items() if k != 'oauth_signature']
|
||||
encoded_str = urllib.urlencode(sorted(items), True)
|
||||
items = []
|
||||
for key, value in self.iteritems():
|
||||
if key == 'oauth_signature':
|
||||
continue
|
||||
# 1.0a/9.1.1 states that kvp must be sorted by key, then by value,
|
||||
# so we unpack sequence values into multiple items for sorting.
|
||||
if isinstance(value, basestring):
|
||||
items.append((to_utf8_if_string(key), to_utf8(value)))
|
||||
else:
|
||||
try:
|
||||
value = list(value)
|
||||
except TypeError, e:
|
||||
assert 'is not iterable' in str(e)
|
||||
items.append((to_utf8_if_string(key), to_utf8_if_string(value)))
|
||||
else:
|
||||
items.extend((to_utf8_if_string(key), to_utf8_if_string(item)) for item in value)
|
||||
|
||||
# Include any query string parameters from the provided URL
|
||||
query = urlparse.urlparse(self.url)[4]
|
||||
|
||||
url_items = self._split_url_string(query).items()
|
||||
url_items = [(to_utf8(k), to_utf8(v)) for k, v in url_items if k != 'oauth_signature' ]
|
||||
items.extend(url_items)
|
||||
|
||||
items.sort()
|
||||
encoded_str = urllib.urlencode(items)
|
||||
# Encode signature parameters per Oauth Core 1.0 protocol
|
||||
# spec draft 7, section 3.6
|
||||
# (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6)
|
||||
# Spaces must be encoded with "%20" instead of "+"
|
||||
return encoded_str.replace('+', '%20')
|
||||
|
||||
return encoded_str.replace('+', '%20').replace('%7E', '~')
|
||||
|
||||
def sign_request(self, signature_method, consumer, token):
|
||||
"""Set the signature parameter to the result of sign."""
|
||||
|
||||
if not self.is_form_encoded:
|
||||
# according to
|
||||
# http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
|
||||
# section 4.1.1 "OAuth Consumers MUST NOT include an
|
||||
# oauth_body_hash parameter on requests with form-encoded
|
||||
# request bodies."
|
||||
self['oauth_body_hash'] = base64.b64encode(sha(self.body).digest())
|
||||
|
||||
if 'oauth_consumer_key' not in self:
|
||||
self['oauth_consumer_key'] = consumer.key
|
||||
|
||||
@@ -398,7 +549,8 @@ class Request(dict):
|
||||
|
||||
@classmethod
|
||||
def from_consumer_and_token(cls, consumer, token=None,
|
||||
http_method=HTTP_METHOD, http_url=None, parameters=None):
|
||||
http_method=HTTP_METHOD, http_url=None, parameters=None,
|
||||
body='', is_form_encoded=False):
|
||||
if not parameters:
|
||||
parameters = {}
|
||||
|
||||
@@ -414,8 +566,11 @@ class Request(dict):
|
||||
|
||||
if token:
|
||||
parameters['oauth_token'] = token.key
|
||||
if token.verifier:
|
||||
parameters['oauth_verifier'] = token.verifier
|
||||
|
||||
return Request(http_method, http_url, parameters)
|
||||
return Request(http_method, http_url, parameters, body=body,
|
||||
is_form_encoded=is_form_encoded)
|
||||
|
||||
@classmethod
|
||||
def from_token_and_callback(cls, token, callback=None,
|
||||
@@ -451,12 +606,82 @@ class Request(dict):
|
||||
@staticmethod
|
||||
def _split_url_string(param_str):
|
||||
"""Turn URL string into parameters."""
|
||||
parameters = parse_qs(param_str, keep_blank_values=False)
|
||||
parameters = parse_qs(param_str.encode('utf-8'), keep_blank_values=True)
|
||||
for k, v in parameters.iteritems():
|
||||
parameters[k] = urllib.unquote(v[0])
|
||||
return parameters
|
||||
|
||||
|
||||
class Client(httplib2.Http):
|
||||
"""OAuthClient is a worker to attempt to execute a request."""
|
||||
|
||||
def __init__(self, consumer, token=None, cache=None, timeout=None,
|
||||
proxy_info=None):
|
||||
|
||||
if consumer is not None and not isinstance(consumer, Consumer):
|
||||
raise ValueError("Invalid consumer.")
|
||||
|
||||
if token is not None and not isinstance(token, Token):
|
||||
raise ValueError("Invalid token.")
|
||||
|
||||
self.consumer = consumer
|
||||
self.token = token
|
||||
self.method = SignatureMethod_HMAC_SHA1()
|
||||
|
||||
httplib2.Http.__init__(self, cache=cache, timeout=timeout, proxy_info=proxy_info)
|
||||
|
||||
def set_signature_method(self, method):
|
||||
if not isinstance(method, SignatureMethod):
|
||||
raise ValueError("Invalid signature method.")
|
||||
|
||||
self.method = method
|
||||
|
||||
def request(self, uri, method="GET", body='', headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
|
||||
DEFAULT_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded'
|
||||
|
||||
if not isinstance(headers, dict):
|
||||
headers = {}
|
||||
|
||||
if method == "POST":
|
||||
headers['Content-Type'] = headers.get('Content-Type',
|
||||
DEFAULT_POST_CONTENT_TYPE)
|
||||
|
||||
is_form_encoded = \
|
||||
headers.get('Content-Type') == 'application/x-www-form-urlencoded'
|
||||
|
||||
if is_form_encoded and body:
|
||||
parameters = parse_qs(body)
|
||||
else:
|
||||
parameters = None
|
||||
|
||||
req = Request.from_consumer_and_token(self.consumer,
|
||||
token=self.token, http_method=method, http_url=uri,
|
||||
parameters=parameters, body=body, is_form_encoded=is_form_encoded)
|
||||
|
||||
req.sign_request(self.method, self.consumer, self.token)
|
||||
|
||||
schema, rest = urllib.splittype(uri)
|
||||
if rest.startswith('//'):
|
||||
hierpart = '//'
|
||||
else:
|
||||
hierpart = ''
|
||||
host, rest = urllib.splithost(rest)
|
||||
|
||||
realm = schema + ':' + hierpart + host
|
||||
|
||||
if is_form_encoded:
|
||||
body = req.to_postdata()
|
||||
elif method == "GET":
|
||||
uri = req.to_url()
|
||||
else:
|
||||
headers.update(req.to_header(realm=realm))
|
||||
|
||||
return httplib2.Http.request(self, uri, method=method, body=body,
|
||||
headers=headers, redirections=redirections,
|
||||
connection_type=connection_type)
|
||||
|
||||
|
||||
class Server(object):
|
||||
"""A skeletal implementation of a service provider, providing protected
|
||||
resources to requests from authorized consumers.
|
||||
@@ -467,7 +692,7 @@ class Server(object):
|
||||
"""
|
||||
|
||||
timestamp_threshold = 300 # In seconds, five minutes.
|
||||
version = VERSION
|
||||
version = OAUTH_VERSION
|
||||
signature_methods = None
|
||||
|
||||
def __init__(self, signature_methods=None):
|
||||
@@ -480,7 +705,7 @@ class Server(object):
|
||||
def verify_request(self, request, consumer, token):
|
||||
"""Verifies an api call and checks all the parameters."""
|
||||
|
||||
version = self._get_version(request)
|
||||
self._check_version(request)
|
||||
self._check_signature(request, consumer, token)
|
||||
parameters = request.get_nonoauth_parameters()
|
||||
return parameters
|
||||
@@ -489,15 +714,18 @@ class Server(object):
|
||||
"""Optional support for the authenticate header."""
|
||||
return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
||||
|
||||
def _check_version(self, request):
|
||||
"""Verify the correct version of the request for this server."""
|
||||
version = self._get_version(request)
|
||||
if version and version != self.version:
|
||||
raise Error('OAuth version %s not supported.' % str(version))
|
||||
|
||||
def _get_version(self, request):
|
||||
"""Verify the correct version request for this server."""
|
||||
"""Return the version of the request for this server."""
|
||||
try:
|
||||
version = request.get_parameter('oauth_version')
|
||||
except:
|
||||
version = VERSION
|
||||
|
||||
if version and version != self.version:
|
||||
raise Error('OAuth version %s not supported.' % str(version))
|
||||
version = OAUTH_VERSION
|
||||
|
||||
return version
|
||||
|
||||
@@ -539,8 +767,6 @@ class Server(object):
|
||||
raise Error('Invalid signature. Expected signature base '
|
||||
'string: %s' % base)
|
||||
|
||||
built = signature_method.sign(request, consumer, token)
|
||||
|
||||
def _check_timestamp(self, timestamp):
|
||||
"""Verify that timestamp is recentish."""
|
||||
timestamp = int(timestamp)
|
||||
@@ -548,75 +774,8 @@ class Server(object):
|
||||
lapsed = now - timestamp
|
||||
if lapsed > self.timestamp_threshold:
|
||||
raise Error('Expired timestamp: given %d and now %s has a '
|
||||
'greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
|
||||
|
||||
|
||||
class Client(httplib2.Http):
|
||||
"""OAuthClient is a worker to attempt to execute a request."""
|
||||
|
||||
def __init__(self, consumer, token=None, cache=None, timeout=None,
|
||||
proxy_info=None):
|
||||
|
||||
if consumer is not None and not isinstance(consumer, Consumer):
|
||||
raise ValueError("Invalid consumer.")
|
||||
|
||||
if token is not None and not isinstance(token, Token):
|
||||
raise ValueError("Invalid token.")
|
||||
|
||||
self.consumer = consumer
|
||||
self.token = token
|
||||
self.method = SignatureMethod_HMAC_SHA1()
|
||||
|
||||
httplib2.Http.__init__(self, cache=cache, timeout=timeout,
|
||||
proxy_info=proxy_info)
|
||||
|
||||
def set_signature_method(self, method):
|
||||
if not isinstance(method, SignatureMethod):
|
||||
raise ValueError("Invalid signature method.")
|
||||
|
||||
self.method = method
|
||||
|
||||
def request(self, uri, method="GET", body=None, headers=None,
|
||||
redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None,
|
||||
force_auth_header=False):
|
||||
|
||||
if not isinstance(headers, dict):
|
||||
headers = {}
|
||||
|
||||
if body and method == "POST":
|
||||
parameters = dict(parse_qsl(body))
|
||||
elif method == "GET":
|
||||
parsed = urlparse.urlparse(uri)
|
||||
parameters = parse_qs(parsed.query)
|
||||
else:
|
||||
parameters = None
|
||||
|
||||
req = Request.from_consumer_and_token(self.consumer, token=self.token,
|
||||
http_method=method, http_url=uri, parameters=parameters)
|
||||
|
||||
req.sign_request(self.method, self.consumer, self.token)
|
||||
|
||||
if force_auth_header:
|
||||
# ensure we always send Authorization
|
||||
headers.update(req.to_header())
|
||||
|
||||
if method == "POST":
|
||||
if not force_auth_header:
|
||||
body = req.to_postdata()
|
||||
else:
|
||||
body = req.encode_postdata(req.get_nonoauth_parameters())
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
elif method == "GET":
|
||||
if not force_auth_header:
|
||||
uri = req.to_url()
|
||||
else:
|
||||
if not force_auth_header:
|
||||
# don't call update twice.
|
||||
headers.update(req.to_header())
|
||||
|
||||
return httplib2.Http.request(self, uri, method=method, body=body,
|
||||
headers=headers, redirections=redirections,
|
||||
connection_type=connection_type)
|
||||
'greater difference than threshold %d' % (timestamp, now,
|
||||
self.timestamp_threshold))
|
||||
|
||||
|
||||
class SignatureMethod(object):
|
||||
@@ -657,11 +816,14 @@ class SignatureMethod(object):
|
||||
|
||||
class SignatureMethod_HMAC_SHA1(SignatureMethod):
|
||||
name = 'HMAC-SHA1'
|
||||
|
||||
|
||||
def signing_base(self, request, consumer, token):
|
||||
if not hasattr(request, 'normalized_url') or request.normalized_url is None:
|
||||
raise ValueError("Base URL for request is not set.")
|
||||
|
||||
sig = (
|
||||
escape(request.method),
|
||||
escape(request.url),
|
||||
escape(request.normalized_url),
|
||||
escape(request.get_normalized_parameters()),
|
||||
)
|
||||
|
||||
@@ -675,17 +837,12 @@ class SignatureMethod_HMAC_SHA1(SignatureMethod):
|
||||
"""Builds the base signature string."""
|
||||
key, raw = self.signing_base(request, consumer, token)
|
||||
|
||||
# HMAC object.
|
||||
try:
|
||||
import hashlib # 2.5
|
||||
hashed = hmac.new(key, raw, hashlib.sha1)
|
||||
except ImportError:
|
||||
import sha # Deprecated
|
||||
hashed = hmac.new(key, raw, sha)
|
||||
hashed = hmac.new(key, raw, sha)
|
||||
|
||||
# Calculate the digest base 64.
|
||||
return binascii.b2a_base64(hashed.digest())[:-1]
|
||||
|
||||
|
||||
class SignatureMethod_PLAINTEXT(SignatureMethod):
|
||||
|
||||
name = 'PLAINTEXT'
|
||||
@@ -701,4 +858,3 @@ class SignatureMethod_PLAINTEXT(SignatureMethod):
|
||||
def sign(self, request, consumer, token):
|
||||
key, raw = self.signing_base(request, consumer, token)
|
||||
return raw
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# This is the version of this source code.
|
||||
|
||||
manual_verstr = "1.5"
|
||||
|
||||
|
||||
|
||||
auto_build_num = "211"
|
||||
|
||||
|
||||
|
||||
verstr = manual_verstr + "." + auto_build_num
|
||||
try:
|
||||
from pyutil.version_class import Version as pyutil_Version
|
||||
__version__ = pyutil_Version(verstr)
|
||||
except (ImportError, ValueError):
|
||||
# Maybe there is no pyutil installed.
|
||||
from distutils.version import LooseVersion as distutils_Version
|
||||
__version__ = distutils_Version(verstr)
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import oauth2
|
||||
import imaplib
|
||||
|
||||
|
||||
class IMAP4_SSL(imaplib.IMAP4_SSL):
|
||||
"""IMAP wrapper for imaplib.IMAP4_SSL that implements XOAUTH."""
|
||||
|
||||
def authenticate(self, url, consumer, token):
|
||||
if consumer is not None and not isinstance(consumer, oauth2.Consumer):
|
||||
raise ValueError("Invalid consumer.")
|
||||
|
||||
if token is not None and not isinstance(token, oauth2.Token):
|
||||
raise ValueError("Invalid token.")
|
||||
|
||||
imaplib.IMAP4_SSL.authenticate(self, 'XOAUTH',
|
||||
lambda x: oauth2.build_xoauth_string(url, consumer, token))
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import oauth2
|
||||
import smtplib
|
||||
import base64
|
||||
|
||||
|
||||
class SMTP(smtplib.SMTP):
|
||||
"""SMTP wrapper for smtplib.SMTP that implements XOAUTH."""
|
||||
|
||||
def authenticate(self, url, consumer, token):
|
||||
if consumer is not None and not isinstance(consumer, oauth2.Consumer):
|
||||
raise ValueError("Invalid consumer.")
|
||||
|
||||
if token is not None and not isinstance(token, oauth2.Token):
|
||||
raise ValueError("Invalid token.")
|
||||
|
||||
self.docmd('AUTH', 'XOAUTH %s' % \
|
||||
base64.b64encode(oauth2.build_xoauth_string(url, consumer, token)))
|
||||
Reference in New Issue
Block a user