From ba686ffc803b3f6271493956966f3371dd6bf76f Mon Sep 17 00:00:00 2001 From: Kiwamu Ishikura Date: Tue, 2 Apr 2013 13:45:18 +0700 Subject: [PATCH 1/5] adapted to python 3 --- oauth2/__init__.py | 107 +++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 835270e3..35203d79 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -23,27 +23,13 @@ """ import base64 -import urllib import time import random -import urlparse +import urllib.parse import hmac import binascii import httplib2 - -try: - from urlparse import parse_qs - parse_qs # placate pyflakes -except ImportError: - # 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 +from hashlib import sha1 import _version @@ -87,7 +73,7 @@ def build_xoauth_string(url, consumer, token=None): request.sign_request(signing_method, consumer, token) params = [] - for k, v in sorted(request.iteritems()): + for k, v in sorted(request.items()): if v is not None: params.append('%s="%s"' % (k, escape(v))) @@ -97,26 +83,25 @@ def build_xoauth_string(url, consumer, token=None): 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,)) + 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 as 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): + if isinstance(s, str): return to_unicode(s) else: return s def to_utf8_if_string(s): - if isinstance(s, basestring): + if isinstance(s, str): return to_utf8(s) else: return s @@ -126,12 +111,12 @@ 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): + if isinstance(x, str): return to_unicode(x) try: l = list(x) - except TypeError, e: + except TypeError as e: assert 'is not iterable' in str(e) return x else: @@ -142,12 +127,12 @@ 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): + if isinstance(x, str): return to_utf8(x) try: l = list(x) - except TypeError, e: + except TypeError as e: assert 'is not iterable' in str(e) return x else: @@ -155,7 +140,7 @@ def to_utf8_optional_iterator(x): def escape(s): """Escape a URL including any /.""" - return urllib.quote(s.encode('utf-8'), safe='~') + return urllib.parse.quote(s.encode('utf-8'), safe='~') def generate_timestamp(): """Get seconds since epoch (UTC).""" @@ -206,7 +191,7 @@ def __str__(self): data = {'oauth_consumer_key': self.key, 'oauth_consumer_secret': self.secret} - return urllib.urlencode(data) + return urllib.parse.urlencode(data) class Token(object): @@ -250,13 +235,13 @@ def set_verifier(self, verifier=None): def get_callback_url(self): if self.callback and self.verifier: # Append the oauth_verifier. - parts = urlparse.urlparse(self.callback) + parts = urllib.parse.urlparse(self.callback) scheme, netloc, path, params, query, fragment = parts[:6] if query: query = '%s&oauth_verifier=%s' % (query, self.verifier) else: query = 'oauth_verifier=%s' % self.verifier - return urlparse.urlunparse((scheme, netloc, path, params, + return urllib.parse.urlunparse((scheme, netloc, path, params, query, fragment)) return self.callback @@ -274,7 +259,7 @@ def to_string(self): if self.callback_confirmed is not None: data['oauth_callback_confirmed'] = self.callback_confirmed - return urllib.urlencode(data) + return urllib.parse.urlencode(data) @staticmethod def from_string(s): @@ -284,7 +269,7 @@ def from_string(s): if not len(s): raise ValueError("Invalid parameter string.") - params = parse_qs(s, keep_blank_values=False) + params = urllib.parse.parse_qs(s, keep_blank_values=False) if not len(params): raise ValueError("Invalid parameter string.") @@ -345,7 +330,7 @@ def __init__(self, method=HTTP_METHOD, url=None, parameters=None, self.url = to_unicode(url) self.method = method if parameters is not None: - for k, v in parameters.iteritems(): + for k, v in parameters.items(): k = to_unicode(k) v = to_unicode_optional_iterator(v) self[k] = v @@ -357,7 +342,7 @@ def __init__(self, method=HTTP_METHOD, url=None, parameters=None, def url(self, value): self.__dict__['url'] = value if value is not None: - scheme, netloc, path, params, query, fragment = urlparse.urlparse(value) + scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(value) # Exclude default port numbers. if scheme == 'http' and netloc[-3:] == ':80': @@ -368,7 +353,7 @@ def url(self, value): 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)) + self.normalized_url = urllib.parse.urlunparse((scheme, netloc, path, None, None, None)) else: self.normalized_url = None self.__dict__['url'] = None @@ -382,7 +367,7 @@ def _get_timestamp_nonce(self): def get_nonoauth_parameters(self): """Get any non-OAuth parameters.""" - return dict([(k, v) for k, v in self.iteritems() + return dict([(k, v) for k, v in self.items() if not k.startswith('oauth_')]) def to_header(self, realm=''): @@ -402,23 +387,23 @@ def to_header(self, realm=''): def to_postdata(self): """Serialize as post data for a POST request.""" d = {} - for k, v in self.iteritems(): + for k, v in self.items(): d[k.encode('utf-8')] = to_utf8_optional_iterator(v) # 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(d, True).replace('+', '%20') + return urllib.parse.urlencode(d, True).replace('+', '%20') def to_url(self): """Serialize as a URL for a GET request.""" - base_url = urlparse.urlparse(self.url) + base_url = urllib.parse.urlparse(self.url) try: query = base_url.query except AttributeError: # must be python <2.5 query = base_url[4] - query = parse_qs(query) + query = urllib.parse.parse_qs(query) for k, v in self.items(): query.setdefault(k, []).append(v) @@ -437,8 +422,8 @@ def to_url(self): fragment = base_url[5] url = (scheme, netloc, path, params, - urllib.urlencode(query, True), fragment) - return urlparse.urlunparse(url) + urllib.parse.urlencode(query, True), fragment) + return urllib.parse.urlunparse(url) def get_parameter(self, parameter): ret = self.get(parameter) @@ -450,31 +435,31 @@ def get_parameter(self, parameter): def get_normalized_parameters(self): """Return a string that contains the parameters that must be signed.""" items = [] - for key, value in self.iteritems(): + for key, value in self.items(): 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): + if isinstance(value, str): items.append((to_utf8_if_string(key), to_utf8(value))) else: try: value = list(value) - except TypeError, e: + except TypeError as 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] + query = urllib.parse.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) + encoded_str = urllib.parse.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) @@ -490,7 +475,7 @@ def sign_request(self, signature_method, consumer, token): # 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()) + self['oauth_body_hash'] = base64.b64encode(sha1(self.body).digest()) if 'oauth_consumer_key' not in self: self['oauth_consumer_key'] = consumer.key @@ -538,7 +523,7 @@ def from_request(cls, http_method, http_url, headers=None, parameters=None, parameters.update(query_params) # URL parameters. - param_str = urlparse.urlparse(http_url)[4] # query + param_str = urllib.parse.urlparse(http_url)[4] # query url_params = cls._split_url_string(param_str) parameters.update(url_params) @@ -600,15 +585,15 @@ def _split_header(header): # Split key-value. param_parts = param.split('=', 1) # Remove quotes and unescape the value. - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) + params[param_parts[0]] = urllib.parse.unquote(param_parts[1].strip('\"')) return params @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" - parameters = parse_qs(param_str.encode('utf-8'), keep_blank_values=True) - for k, v in parameters.iteritems(): - parameters[k] = urllib.unquote(v[0]) + parameters = urllib.parse.parse_qs(param_str.encode('utf-8'), keep_blank_values=True) + for k, v in parameters.items(): + parameters[k] = urllib.parse.unquote(v[0]) return parameters @@ -651,7 +636,7 @@ def request(self, uri, method="GET", body='', headers=None, headers.get('Content-Type') == 'application/x-www-form-urlencoded' if is_form_encoded and body: - parameters = parse_qs(body) + parameters = urllib.parse.parse_qs(body) else: parameters = None @@ -661,12 +646,12 @@ def request(self, uri, method="GET", body='', headers=None, req.sign_request(self.method, self.consumer, self.token) - schema, rest = urllib.splittype(uri) + schema, rest = urllib.parse.splittype(uri) if rest.startswith('//'): hierpart = '//' else: hierpart = '' - host, rest = urllib.splithost(rest) + host, rest = urllib.parse.splithost(rest) realm = schema + ':' + hierpart + host @@ -837,7 +822,7 @@ def sign(self, request, consumer, token): """Builds the base signature string.""" key, raw = self.signing_base(request, consumer, token) - hashed = hmac.new(key, raw, sha) + hashed = hmac.new(key, raw, sha1) # Calculate the digest base 64. return binascii.b2a_base64(hashed.digest())[:-1] From 57adf94fbcd66e2fd793c739b8908321bc7aa183 Mon Sep 17 00:00:00 2001 From: Kiwamu Ishikura Date: Tue, 2 Apr 2013 14:03:19 +0700 Subject: [PATCH 2/5] adopted to python3 --- build/lib/oauth2/__init__.py | 845 +++++++++++++++++ build/lib/oauth2/_version.py | 18 + build/lib/oauth2/clients/__init__.py | 0 build/lib/oauth2/clients/imap.py | 40 + build/lib/oauth2/clients/smtp.py | 41 + build/lib/tests/__init__.py | 0 build/lib/tests/test_oauth.py | 1301 ++++++++++++++++++++++++++ dist/oauth2-1.5.211-py3.3.egg | Bin 0 -> 53952 bytes setup.py | 2 +- tests/test_oauth.py | 120 ++- 10 files changed, 2302 insertions(+), 65 deletions(-) create mode 100644 build/lib/oauth2/__init__.py create mode 100644 build/lib/oauth2/_version.py create mode 100644 build/lib/oauth2/clients/__init__.py create mode 100644 build/lib/oauth2/clients/imap.py create mode 100644 build/lib/oauth2/clients/smtp.py create mode 100644 build/lib/tests/__init__.py create mode 100644 build/lib/tests/test_oauth.py create mode 100644 dist/oauth2-1.5.211-py3.3.egg diff --git a/build/lib/oauth2/__init__.py b/build/lib/oauth2/__init__.py new file mode 100644 index 00000000..35203d79 --- /dev/null +++ b/build/lib/oauth2/__init__.py @@ -0,0 +1,845 @@ +""" +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 base64 +import time +import random +import urllib.parse +import hmac +import binascii +import httplib2 +from hashlib import sha1 + +import _version + +__version__ = _version.__version__ + +OAUTH_VERSION = '1.0' # Hi Blaine! +HTTP_METHOD = 'GET' +SIGNATURE_METHOD = 'PLAINTEXT' + + +class Error(RuntimeError): + """Generic exception class.""" + + def __init__(self, message='OAuth error occurred.'): + self._message = message + + @property + def message(self): + """A hack to get around the deprecation errors in 2.6.""" + return self._message + + 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.items()): + 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, 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 as 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, str): + return to_unicode(s) + else: + return s + +def to_utf8_if_string(s): + if isinstance(s, str): + 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, str): + return to_unicode(x) + + try: + l = list(x) + except TypeError as 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, str): + return to_utf8(x) + + try: + l = list(x) + except TypeError as 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.parse.quote(s.encode('utf-8'), safe='~') + +def generate_timestamp(): + """Get seconds since epoch (UTC).""" + return int(time.time()) + + +def generate_nonce(length=8): + """Generate pseudorandom number.""" + return ''.join([str(random.randint(0, 9)) for i in range(length)]) + + +def generate_verifier(length=8): + """Generate pseudorandom number.""" + return ''.join([str(random.randint(0, 9)) for i in range(length)]) + + +class Consumer(object): + """A consumer of OAuth-protected services. + + The OAuth consumer is a "third-party" service that wants to access + protected resources from an OAuth service provider on behalf of an end + user. It's kind of the OAuth client. + + Usually a consumer must be registered with the service provider by the + developer of the consumer software. As part of that process, the service + provider gives the consumer a *key* and a *secret* with which the consumer + software can identify itself to the service. The consumer will include its + key in each request to identify itself, but will use its secret only when + signing requests, to prove that the request is from that particular + registered consumer. + + Once registered, the consumer can then use its consumer credentials to ask + the service provider for a request token, kicking off the OAuth + authorization process. + """ + + key = None + secret = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + + if self.key is None or self.secret is None: + raise ValueError("Key and secret must be set.") + + def __str__(self): + data = {'oauth_consumer_key': self.key, + 'oauth_consumer_secret': self.secret} + + return urllib.parse.urlencode(data) + + +class Token(object): + """An OAuth credential used to request authorization or a protected + resource. + + Tokens in OAuth comprise a *key* and a *secret*. The key is included in + requests to identify the token being used, but the secret is used only in + the signature, to prove that the requester is who the server gave the + token to. + + When first negotiating the authorization, the consumer asks for a *request + token* that the live user authorizes with the service provider. The + consumer then exchanges the request token for an *access token* that can + be used to access protected resources. + """ + + key = None + secret = None + callback = None + callback_confirmed = None + verifier = None + + def __init__(self, key, secret): + self.key = key + self.secret = secret + + if self.key is None or self.secret is None: + raise ValueError("Key and secret must be set.") + + def set_callback(self, callback): + self.callback = callback + self.callback_confirmed = 'true' + + def set_verifier(self, verifier=None): + if verifier is not None: + self.verifier = verifier + else: + self.verifier = generate_verifier() + + def get_callback_url(self): + if self.callback and self.verifier: + # Append the oauth_verifier. + parts = urllib.parse.urlparse(self.callback) + scheme, netloc, path, params, query, fragment = parts[:6] + if query: + query = '%s&oauth_verifier=%s' % (query, self.verifier) + else: + query = 'oauth_verifier=%s' % self.verifier + return urllib.parse.urlunparse((scheme, netloc, path, params, + query, fragment)) + return self.callback + + def to_string(self): + """Returns this token as a plain string, suitable for storage. + + The resulting string includes the token's secret, so you should never + send or store this string where a third party can read it. + """ + + data = { + 'oauth_token': self.key, + 'oauth_token_secret': self.secret, + } + + if self.callback_confirmed is not None: + data['oauth_callback_confirmed'] = self.callback_confirmed + return urllib.parse.urlencode(data) + + @staticmethod + def from_string(s): + """Deserializes a token from a string like one returned by + `to_string()`.""" + + if not len(s): + raise ValueError("Invalid parameter string.") + + params = urllib.parse.parse_qs(s, keep_blank_values=False) + if not len(params): + raise ValueError("Invalid parameter string.") + + try: + key = params['oauth_token'][0] + except Exception: + raise ValueError("'oauth_token' not found in OAuth request.") + + try: + secret = params['oauth_token_secret'][0] + except Exception: + raise ValueError("'oauth_token_secret' not found in " + "OAuth request.") + + token = Token(key, secret) + try: + token.callback_confirmed = params['oauth_callback_confirmed'][0] + except KeyError: + pass # 1.0, no callback confirmed. + return token + + def __str__(self): + return self.to_string() + + +def setter(attr): + name = attr.__name__ + + def getter(self): + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) + + def deleter(self): + del self.__dict__[name] + + return property(getter, attr, deleter) + + +class Request(dict): + + """The parameters and information for an HTTP request, suitable for + authorizing with OAuth credentials. + + When a consumer wants to access a service's protected resources, it does + so using a signed HTTP request identifying itself (the consumer) with its + key, and providing an access token authorized by the end user to access + those resources. + + """ + + 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 = to_unicode(url) + self.method = method + if parameters is not None: + for k, v in parameters.items(): + 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): + self.__dict__['url'] = value + if value is not None: + scheme, netloc, path, params, query, fragment = urllib.parse.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 = urllib.parse.urlunparse((scheme, netloc, path, None, None, None)) + else: + self.normalized_url = None + self.__dict__['url'] = None + + @setter + def method(self, value): + self.__dict__['method'] = value.upper() + + def _get_timestamp_nonce(self): + return self['oauth_timestamp'], self['oauth_nonce'] + + def get_nonoauth_parameters(self): + """Get any non-OAuth parameters.""" + return dict([(k, v) for k, v in self.items() + if not k.startswith('oauth_')]) + + def to_header(self, realm=''): + """Serialize as a header for an HTTPAuth request.""" + oauth_params = ((k, v) for k, v in self.items() + if k.startswith('oauth_')) + stringy_params = ((k, escape(str(v))) for k, v in oauth_params) + header_params = ('%s="%s"' % (k, v) for k, v in stringy_params) + params_header = ', '.join(header_params) + + auth_header = 'OAuth realm="%s"' % realm + if params_header: + auth_header = "%s, %s" % (auth_header, params_header) + + return {'Authorization': auth_header} + + def to_postdata(self): + """Serialize as post data for a POST request.""" + d = {} + for k, v in self.items(): + d[k.encode('utf-8')] = to_utf8_optional_iterator(v) + + # 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.parse.urlencode(d, True).replace('+', '%20') + + def to_url(self): + """Serialize as a URL for a GET request.""" + base_url = urllib.parse.urlparse(self.url) + try: + query = base_url.query + except AttributeError: + # must be python <2.5 + query = base_url[4] + query = urllib.parse.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.parse.urlencode(query, True), fragment) + return urllib.parse.urlunparse(url) + + def get_parameter(self, parameter): + ret = self.get(parameter) + if ret is None: + 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 = [] + for key, value in self.items(): + 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, str): + items.append((to_utf8_if_string(key), to_utf8(value))) + else: + try: + value = list(value) + except TypeError as 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 = urllib.parse.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.parse.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').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(sha1(self.body).digest()) + + if 'oauth_consumer_key' not in self: + self['oauth_consumer_key'] = consumer.key + + if token and 'oauth_token' not in self: + self['oauth_token'] = token.key + + self['oauth_signature_method'] = signature_method.name + self['oauth_signature'] = signature_method.sign(self, consumer, token) + + @classmethod + def make_timestamp(cls): + """Get seconds since epoch (UTC).""" + return str(int(time.time())) + + @classmethod + def make_nonce(cls): + """Generate pseudorandom number.""" + return str(random.randint(0, 100000000)) + + @classmethod + def from_request(cls, http_method, http_url, headers=None, parameters=None, + query_string=None): + """Combines multiple parameter sources.""" + if parameters is None: + parameters = {} + + # Headers + if headers and 'Authorization' in headers: + auth_header = headers['Authorization'] + # Check that the authorization header is OAuth. + if auth_header[:6] == 'OAuth ': + auth_header = auth_header[6:] + try: + # Get the parameters from the header. + header_params = cls._split_header(auth_header) + parameters.update(header_params) + except: + raise Error('Unable to parse OAuth parameters from ' + 'Authorization header.') + + # GET or POST query string. + if query_string: + query_params = cls._split_url_string(query_string) + parameters.update(query_params) + + # URL parameters. + param_str = urllib.parse.urlparse(http_url)[4] # query + url_params = cls._split_url_string(param_str) + parameters.update(url_params) + + if parameters: + return cls(http_method, http_url, parameters) + + return None + + @classmethod + def from_consumer_and_token(cls, consumer, token=None, + http_method=HTTP_METHOD, http_url=None, parameters=None, + body='', is_form_encoded=False): + if not parameters: + parameters = {} + + defaults = { + 'oauth_consumer_key': consumer.key, + 'oauth_timestamp': cls.make_timestamp(), + 'oauth_nonce': cls.make_nonce(), + 'oauth_version': cls.version, + } + + defaults.update(parameters) + parameters = defaults + + if token: + parameters['oauth_token'] = token.key + if token.verifier: + parameters['oauth_verifier'] = token.verifier + + 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, + http_method=HTTP_METHOD, http_url=None, parameters=None): + + if not parameters: + parameters = {} + + parameters['oauth_token'] = token.key + + if callback: + parameters['oauth_callback'] = callback + + return cls(http_method, http_url, parameters) + + @staticmethod + def _split_header(header): + """Turn Authorization: header into parameters.""" + params = {} + parts = header.split(',') + for param in parts: + # Ignore realm parameter. + if param.find('realm') > -1: + continue + # Remove whitespace. + param = param.strip() + # Split key-value. + param_parts = param.split('=', 1) + # Remove quotes and unescape the value. + params[param_parts[0]] = urllib.parse.unquote(param_parts[1].strip('\"')) + return params + + @staticmethod + def _split_url_string(param_str): + """Turn URL string into parameters.""" + parameters = urllib.parse.parse_qs(param_str.encode('utf-8'), keep_blank_values=True) + for k, v in parameters.items(): + parameters[k] = urllib.parse.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 = urllib.parse.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.parse.splittype(uri) + if rest.startswith('//'): + hierpart = '//' + else: + hierpart = '' + host, rest = urllib.parse.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. + + This class implements the logic to check requests for authorization. You + can use it with your web server or web framework to protect certain + resources with OAuth. + """ + + timestamp_threshold = 300 # In seconds, five minutes. + version = OAUTH_VERSION + signature_methods = None + + def __init__(self, signature_methods=None): + self.signature_methods = signature_methods or {} + + def add_signature_method(self, signature_method): + self.signature_methods[signature_method.name] = signature_method + return self.signature_methods + + def verify_request(self, request, consumer, token): + """Verifies an api call and checks all the parameters.""" + + self._check_version(request) + self._check_signature(request, consumer, token) + parameters = request.get_nonoauth_parameters() + return parameters + + def build_authenticate_header(self, realm=''): + """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): + """Return the version of the request for this server.""" + try: + version = request.get_parameter('oauth_version') + except: + version = OAUTH_VERSION + + return version + + def _get_signature_method(self, request): + """Figure out the signature with some defaults.""" + try: + signature_method = request.get_parameter('oauth_signature_method') + except: + signature_method = SIGNATURE_METHOD + + try: + # Get the signature method object. + signature_method = self.signature_methods[signature_method] + except: + signature_method_names = ', '.join(self.signature_methods.keys()) + raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) + + return signature_method + + def _get_verifier(self, request): + return request.get_parameter('oauth_verifier') + + def _check_signature(self, request, consumer, token): + timestamp, nonce = request._get_timestamp_nonce() + self._check_timestamp(timestamp) + signature_method = self._get_signature_method(request) + + try: + signature = request.get_parameter('oauth_signature') + except: + raise MissingSignature('Missing oauth_signature.') + + # Validate the signature. + valid = signature_method.check(request, consumer, token, signature) + + if not valid: + key, base = signature_method.signing_base(request, consumer, token) + + raise Error('Invalid signature. Expected signature base ' + 'string: %s' % base) + + def _check_timestamp(self, timestamp): + """Verify that timestamp is recentish.""" + timestamp = int(timestamp) + now = int(time.time()) + 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 SignatureMethod(object): + """A way of signing requests. + + The OAuth protocol lets consumers and service providers pick a way to sign + requests. This interface shows the methods expected by the other `oauth` + modules for signing requests. Subclass it and implement its methods to + provide a new way to sign requests. + """ + + def signing_base(self, request, consumer, token): + """Calculates the string that needs to be signed. + + This method returns a 2-tuple containing the starting key for the + signing and the message to be signed. The latter may be used in error + messages to help clients debug their software. + + """ + raise NotImplementedError + + def sign(self, request, consumer, token): + """Returns the signature for the given request, based on the consumer + and token also provided. + + You should use your implementation of `signing_base()` to build the + message to sign. Otherwise it may be less useful for debugging. + + """ + raise NotImplementedError + + def check(self, request, consumer, token, signature): + """Returns whether the given signature is the correct signature for + the given consumer and token signing the given request.""" + built = self.sign(request, consumer, token) + return built == signature + + +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.normalized_url), + escape(request.get_normalized_parameters()), + ) + + key = '%s&' % escape(consumer.secret) + if token: + key += escape(token.secret) + raw = '&'.join(sig) + return key, raw + + def sign(self, request, consumer, token): + """Builds the base signature string.""" + key, raw = self.signing_base(request, consumer, token) + + hashed = hmac.new(key, raw, sha1) + + # Calculate the digest base 64. + return binascii.b2a_base64(hashed.digest())[:-1] + + +class SignatureMethod_PLAINTEXT(SignatureMethod): + + name = 'PLAINTEXT' + + def signing_base(self, request, consumer, token): + """Concatenates the consumer key and secret with the token's + secret.""" + sig = '%s&' % escape(consumer.secret) + if token: + sig = sig + escape(token.secret) + return sig, sig + + def sign(self, request, consumer, token): + key, raw = self.signing_base(request, consumer, token) + return raw diff --git a/build/lib/oauth2/_version.py b/build/lib/oauth2/_version.py new file mode 100644 index 00000000..9d779eaa --- /dev/null +++ b/build/lib/oauth2/_version.py @@ -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) diff --git a/build/lib/oauth2/clients/__init__.py b/build/lib/oauth2/clients/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build/lib/oauth2/clients/imap.py b/build/lib/oauth2/clients/imap.py new file mode 100644 index 00000000..68b7cd8c --- /dev/null +++ b/build/lib/oauth2/clients/imap.py @@ -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)) diff --git a/build/lib/oauth2/clients/smtp.py b/build/lib/oauth2/clients/smtp.py new file mode 100644 index 00000000..3e7bf0b0 --- /dev/null +++ b/build/lib/oauth2/clients/smtp.py @@ -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))) diff --git a/build/lib/tests/__init__.py b/build/lib/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build/lib/tests/test_oauth.py b/build/lib/tests/test_oauth.py new file mode 100644 index 00000000..c6efeeba --- /dev/null +++ b/build/lib/tests/test_oauth.py @@ -0,0 +1,1301 @@ +# -*- coding: utf-8 -*- + +""" +The MIT License + +Copyright (c) 2009 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 sys +import os +import unittest +import oauth2 as oauth +import random +import time +import urllib.parse +from types import ListType +import mock +import httplib2 + +sys.path[0:0] = [os.path.join(os.path.dirname(__file__), ".."),] + + +class TestError(unittest.TestCase): + def test_message(self): + try: + raise oauth.Error + except oauth.Error as e: + self.assertEqual(e.message, 'OAuth error occurred.') + + msg = 'OMG THINGS BROKE!!!!' + try: + raise oauth.Error(msg) + except oauth.Error as e: + self.assertEqual(e.message, msg) + + def test_str(self): + try: + raise oauth.Error + except oauth.Error as e: + self.assertEquals(str(e), 'OAuth error occurred.') + +class TestGenerateFunctions(unittest.TestCase): + def test_build_auth_header(self): + header = oauth.build_authenticate_header() + self.assertEqual(header['WWW-Authenticate'], 'OAuth realm=""') + self.assertEqual(len(header), 1) + realm = 'http://example.myrealm.com/' + header = oauth.build_authenticate_header(realm) + self.assertEqual(header['WWW-Authenticate'], 'OAuth realm="%s"' % + realm) + self.assertEqual(len(header), 1) + + def test_build_xoauth_string(self): + consumer = oauth.Consumer('consumer_token', 'consumer_secret') + token = oauth.Token('user_token', 'user_secret') + url = "https://mail.google.com/mail/b/joe@example.com/imap/" + xoauth_string = oauth.build_xoauth_string(url, consumer, token) + + method, oauth_url, oauth_string = xoauth_string.split(' ') + + self.assertEqual("GET", method) + self.assertEqual(url, oauth_url) + + returned = {} + parts = oauth_string.split(',') + for part in parts: + var, val = part.split('=') + returned[var] = val.strip('"') + + self.assertEquals('HMAC-SHA1', returned['oauth_signature_method']) + self.assertEquals('user_token', returned['oauth_token']) + self.assertEquals('consumer_token', returned['oauth_consumer_key']) + self.assertTrue('oauth_signature' in returned, 'oauth_signature') + + def test_escape(self): + string = 'http://whatever.com/~someuser/?test=test&other=other' + self.assert_('~' in oauth.escape(string)) + string = '../../../../../../../etc/passwd' + self.assert_('../' not in oauth.escape(string)) + + def test_gen_nonce(self): + nonce = oauth.generate_nonce() + self.assertEqual(len(nonce), 8) + nonce = oauth.generate_nonce(20) + self.assertEqual(len(nonce), 20) + + def test_gen_verifier(self): + verifier = oauth.generate_verifier() + self.assertEqual(len(verifier), 8) + verifier = oauth.generate_verifier(16) + self.assertEqual(len(verifier), 16) + + def test_gen_timestamp(self): + exp = int(time.time()) + now = oauth.generate_timestamp() + self.assertEqual(exp, now) + +class TestConsumer(unittest.TestCase): + def setUp(self): + self.key = 'my-key' + self.secret = 'my-secret' + self.consumer = oauth.Consumer(key=self.key, secret=self.secret) + + def test_init(self): + self.assertEqual(self.consumer.key, self.key) + self.assertEqual(self.consumer.secret, self.secret) + + def test_basic(self): + self.assertRaises(ValueError, lambda: oauth.Consumer(None, None)) + self.assertRaises(ValueError, lambda: oauth.Consumer('asf', None)) + self.assertRaises(ValueError, lambda: oauth.Consumer(None, 'dasf')) + + def test_str(self): + res = dict(urllib.parse.parse_qsl(str(self.consumer))) + self.assertTrue('oauth_consumer_key' in res) + self.assertTrue('oauth_consumer_secret' in res) + self.assertEquals(res['oauth_consumer_key'], self.consumer.key) + self.assertEquals(res['oauth_consumer_secret'], self.consumer.secret) + +class TestToken(unittest.TestCase): + def setUp(self): + self.key = 'my-key' + self.secret = 'my-secret' + self.token = oauth.Token(self.key, self.secret) + + def test_basic(self): + self.assertRaises(ValueError, lambda: oauth.Token(None, None)) + self.assertRaises(ValueError, lambda: oauth.Token('asf', None)) + self.assertRaises(ValueError, lambda: oauth.Token(None, 'dasf')) + + def test_init(self): + self.assertEqual(self.token.key, self.key) + self.assertEqual(self.token.secret, self.secret) + self.assertEqual(self.token.callback, None) + self.assertEqual(self.token.callback_confirmed, None) + self.assertEqual(self.token.verifier, None) + + def test_set_callback(self): + self.assertEqual(self.token.callback, None) + self.assertEqual(self.token.callback_confirmed, None) + cb = 'http://www.example.com/my-callback' + self.token.set_callback(cb) + self.assertEqual(self.token.callback, cb) + self.assertEqual(self.token.callback_confirmed, 'true') + self.token.set_callback(None) + self.assertEqual(self.token.callback, None) + # TODO: The following test should probably not pass, but it does + # To fix this, check for None and unset 'true' in set_callback + # Additionally, should a confirmation truly be done of the callback? + self.assertEqual(self.token.callback_confirmed, 'true') + + def test_set_verifier(self): + self.assertEqual(self.token.verifier, None) + v = oauth.generate_verifier() + self.token.set_verifier(v) + self.assertEqual(self.token.verifier, v) + self.token.set_verifier() + self.assertNotEqual(self.token.verifier, v) + self.token.set_verifier('') + self.assertEqual(self.token.verifier, '') + + def test_get_callback_url(self): + self.assertEqual(self.token.get_callback_url(), None) + + self.token.set_verifier() + self.assertEqual(self.token.get_callback_url(), None) + + cb = 'http://www.example.com/my-callback?save=1&return=true' + v = oauth.generate_verifier() + self.token.set_callback(cb) + self.token.set_verifier(v) + url = self.token.get_callback_url() + verifier_str = '&oauth_verifier=%s' % v + self.assertEqual(url, '%s%s' % (cb, verifier_str)) + + cb = 'http://www.example.com/my-callback-no-query' + v = oauth.generate_verifier() + self.token.set_callback(cb) + self.token.set_verifier(v) + url = self.token.get_callback_url() + verifier_str = '?oauth_verifier=%s' % v + self.assertEqual(url, '%s%s' % (cb, verifier_str)) + + def test_to_string(self): + string = 'oauth_token_secret=%s&oauth_token=%s' % (self.secret, + self.key) + self.assertEqual(self.token.to_string(), string) + + self.token.set_callback('http://www.example.com/my-callback') + string += '&oauth_callback_confirmed=true' + self.assertEqual(self.token.to_string(), string) + + def _compare_tokens(self, new): + self.assertEqual(self.token.key, new.key) + self.assertEqual(self.token.secret, new.secret) + # TODO: What about copying the callback to the new token? + # self.assertEqual(self.token.callback, new.callback) + self.assertEqual(self.token.callback_confirmed, + new.callback_confirmed) + # TODO: What about copying the verifier to the new token? + # self.assertEqual(self.token.verifier, new.verifier) + + def test_to_string(self): + tok = oauth.Token('tooken', 'seecret') + self.assertEqual(str(tok), 'oauth_token_secret=seecret&oauth_token=tooken') + + def test_from_string(self): + self.assertRaises(ValueError, lambda: oauth.Token.from_string('')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('blahblahblah')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('blah=blah')) + + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token_secret=asfdasf')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token_secret=')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=asfdasf')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=&oauth_token_secret=')) + self.assertRaises(ValueError, lambda: oauth.Token.from_string('oauth_token=tooken%26oauth_token_secret=seecret')) + + string = self.token.to_string() + new = oauth.Token.from_string(string) + self._compare_tokens(new) + + self.token.set_callback('http://www.example.com/my-callback') + string = self.token.to_string() + new = oauth.Token.from_string(string) + self._compare_tokens(new) + +class ReallyEqualMixin: + def failUnlessReallyEqual(self, a, b, msg=None): + self.failUnlessEqual(a, b, msg=msg) + self.failUnlessEqual(type(a), type(b), msg="a :: %r, b :: %r, %r" % (a, b, msg)) + +class TestFuncs(unittest.TestCase): + def test_to_unicode(self): + self.failUnlessRaises(TypeError, oauth.to_unicode, '\xae') + self.failUnlessRaises(TypeError, oauth.to_unicode_optional_iterator, '\xae') + self.failUnlessRaises(TypeError, oauth.to_unicode_optional_iterator, ['\xae']) + + self.failUnlessEqual(oauth.to_unicode(':-)'), ':-)') + self.failUnlessEqual(oauth.to_unicode(b'\u00ae'), b'\u00ae') + self.failUnlessEqual(oauth.to_unicode('\xc2\xae'), b'\u00ae') + self.failUnlessEqual(oauth.to_unicode_optional_iterator([':-)']), [':-)']) + self.failUnlessEqual(oauth.to_unicode_optional_iterator([b'\u00ae']), [b'\u00ae']) + +class TestRequest(unittest.TestCase, ReallyEqualMixin): + def test_setter(self): + url = "http://example.com" + method = "GET" + req = oauth.Request(method) + self.assertTrue(not hasattr(req, 'url') or req.url is None) + self.assertTrue(not hasattr(req, 'normalized_url') or req.normalized_url is None) + + def test_deleter(self): + url = "http://example.com" + method = "GET" + req = oauth.Request(method, url) + + try: + del req.url + url = req.url + self.fail("AttributeError should have been raised on empty url.") + except AttributeError: + pass + except Exception as e: + self.fail(str(e)) + + def test_url(self): + url1 = "http://example.com:80/foo.php" + url2 = "https://example.com:443/foo.php" + exp1 = "http://example.com/foo.php" + exp2 = "https://example.com/foo.php" + method = "GET" + + req = oauth.Request(method, url1) + self.assertEquals(req.normalized_url, exp1) + self.assertEquals(req.url, url1) + + req = oauth.Request(method, url2) + self.assertEquals(req.normalized_url, exp2) + self.assertEquals(req.url, url2) + + def test_bad_url(self): + request = oauth.Request() + try: + request.url = "ftp://example.com" + self.fail("Invalid URL scheme was accepted.") + except ValueError: + pass + + def test_unset_consumer_and_token(self): + consumer = oauth.Consumer('my_consumer_key', 'my_consumer_secret') + token = oauth.Token('my_key', 'my_secret') + request = oauth.Request("GET", "http://example.com/fetch.php") + request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, + token) + + self.assertEquals(consumer.key, request['oauth_consumer_key']) + self.assertEquals(token.key, request['oauth_token']) + + def test_no_url_set(self): + consumer = oauth.Consumer('my_consumer_key', 'my_consumer_secret') + token = oauth.Token('my_key', 'my_secret') + request = oauth.Request() + + try: + try: + request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), + consumer, token) + except TypeError: + self.fail("Signature method didn't check for a normalized URL.") + except ValueError: + pass + + def test_url_query(self): + url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10" + normalized_url = urlparse.urlunparse(urlparse.urlparse(url)[:3] + (None, None, None)) + method = "GET" + + req = oauth.Request(method, url) + self.assertEquals(req.url, url) + self.assertEquals(req.normalized_url, normalized_url) + + def test_get_parameter(self): + url = "http://example.com" + method = "GET" + params = {'oauth_consumer' : 'asdf'} + req = oauth.Request(method, url, parameters=params) + + self.assertEquals(req.get_parameter('oauth_consumer'), 'asdf') + self.assertRaises(oauth.Error, req.get_parameter, 'blah') + + def test_get_nonoauth_parameters(self): + + oauth_params = { + 'oauth_consumer': 'asdfasdfasdf' + } + + other_params = { + 'foo': 'baz', + 'bar': 'foo', + 'multi': ['FOO','BAR'], + 'uni_utf8': b'\xae', + 'uni_unicode': b'\u00ae', + 'uni_unicode_2': 'åÅøØ', + } + + params = oauth_params + params.update(other_params) + + req = oauth.Request("GET", "http://example.com", params) + self.assertEquals(other_params, req.get_nonoauth_parameters()) + + def test_to_header(self): + realm = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", realm, params) + header, value = list(req.to_header(realm).items())[0] + + parts = value.split('OAuth ') + vars = parts[1].split(', ') + self.assertTrue(len(vars), (len(params) + 1)) + + res = {} + for v in vars: + var, val = v.split('=') + res[var] = urllib.parse.unquote(val.strip('"')) + + self.assertEquals(realm, res['realm']) + del res['realm'] + + self.assertTrue(len(res), len(params)) + + for key, val in res.items(): + self.assertEquals(val, params.get(key)) + + def test_to_postdata_nonascii(self): + realm = "http://sp.example.com/" + + params = { + 'nonasciithing': b'q\xbfu\xe9 ,aasp u?..a.s', + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", realm, params) + + self.failUnlessReallyEqual(req.to_postdata(), 'nonasciithing=q%C2%BFu%C3%A9%20%2Caasp%20u%3F..a.s&oauth_nonce=4572616e48616d6d65724c61686176&oauth_timestamp=137131200&oauth_consumer_key=0685bd9184jfhq22&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_token=ad180jjd733klru7&oauth_signature=wOJIO9A2W5mFwDgiDvZbTSMK%252FPY%253D') + + def test_to_postdata(self): + realm = "http://sp.example.com/" + + params = { + 'multi': ['FOO','BAR'], + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", realm, params) + + flat = [('multi','FOO'),('multi','BAR')] + del params['multi'] + flat.extend(list(params.items())) + kf = lambda x: x[0] + self.assertEquals(sorted(flat, key=kf), sorted(urllib.parse.parse_qsl(req.to_postdata()), key=kf)) + + def test_to_url(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", url, params) + exp = urlparse.urlparse("%s?%s" % (url, urllib.parse.urlencode(params))) + res = urlparse.urlparse(req.to_url()) + self.assertEquals(exp.scheme, res.scheme) + self.assertEquals(exp.netloc, res.netloc) + self.assertEquals(exp.path, res.path) + + a = urllib.parse.parse_qs(exp.query) + b = urllib.parse.parse_qs(res.query) + self.assertEquals(a, b) + + def test_to_url_with_query(self): + url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", url, params) + # Note: the url above already has query parameters, so append new ones with & + exp = urlparse.urlparse("%s&%s" % (url, urllib.parse.urlencode(params))) + res = urlparse.urlparse(req.to_url()) + self.assertEquals(exp.scheme, res.scheme) + self.assertEquals(exp.netloc, res.netloc) + self.assertEquals(exp.path, res.path) + + a = urllib.parse.parse_qs(exp.query) + b = urllib.parse.parse_qs(res.query) + self.assertTrue('alt' in b) + self.assertTrue('max-contacts' in b) + self.assertEquals(b['alt'], ['json']) + self.assertEquals(b['max-contacts'], ['10']) + self.assertEquals(a, b) + + def test_signature_base_string_nonascii_nonutf8(self): + consumer = oauth.Consumer('consumer_token', 'consumer_secret') + + url = b'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc\u2766,+CA' + req = oauth.Request("GET", url) + self.failUnlessReallyEqual(req.normalized_url, 'http://api.simplegeo.com/1.0/places/address.json') + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=') + + url = 'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc\xe2\x9d\xa6,+CA' + req = oauth.Request("GET", url) + self.failUnlessReallyEqual(req.normalized_url, 'http://api.simplegeo.com/1.0/places/address.json') + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=') + + url = 'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc%E2%9D%A6,+CA' + req = oauth.Request("GET", url) + self.failUnlessReallyEqual(req.normalized_url, 'http://api.simplegeo.com/1.0/places/address.json') + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=') + + url = b'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc%E2%9D%A6,+CA' + req = oauth.Request("GET", url) + self.failUnlessReallyEqual(req.normalized_url, 'http://api.simplegeo.com/1.0/places/address.json') + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=') + + def test_signature_base_string_with_query(self): + url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10" + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + req = oauth.Request("GET", url, params) + self.assertEquals(req.normalized_url, 'https://www.google.com/m8/feeds/contacts/default/full/') + self.assertEquals(req.url, 'https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10') + normalized_params = urllib.parse.parse_qsl(req.get_normalized_parameters()) + self.assertTrue(len(normalized_params), len(params) + 2) + normalized_params = dict(normalized_params) + for key, value in params.items(): + if key == 'oauth_signature': + continue + self.assertEquals(value, normalized_params[key]) + self.assertEquals(normalized_params['alt'], 'json') + self.assertEquals(normalized_params['max-contacts'], '10') + + def test_get_normalized_parameters_empty(self): + url = "http://sp.example.com/?empty=" + + req = oauth.Request("GET", url) + + res = req.get_normalized_parameters() + + expected='empty=' + + self.assertEquals(expected, res) + + def test_get_normalized_parameters_duplicate(self): + url = "http://example.com/v2/search/videos?oauth_nonce=79815175&oauth_timestamp=1295397962&oauth_consumer_key=mykey&oauth_signature_method=HMAC-SHA1&q=car&oauth_version=1.0&offset=10&oauth_signature=spWLI%2FGQjid7sQVd5%2FarahRxzJg%3D" + + req = oauth.Request("GET", url) + + res = req.get_normalized_parameters() + + expected='oauth_consumer_key=mykey&oauth_nonce=79815175&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1295397962&oauth_version=1.0&offset=10&q=car' + + self.assertEquals(expected, res) + + def test_get_normalized_parameters_from_url(self): + # example copied from + # https://github.com/ciaranj/node-oauth/blob/master/tests/oauth.js + # which in turns says that it was copied from + # http://oauth.net/core/1.0/#sig_base_example . + url = "http://photos.example.net/photos?file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original" + + req = oauth.Request("GET", url) + + res = req.get_normalized_parameters() + + expected = 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original' + + self.assertEquals(expected, res) + + def test_signing_base(self): + # example copied from + # https://github.com/ciaranj/node-oauth/blob/master/tests/oauth.js + # which in turns says that it was copied from + # http://oauth.net/core/1.0/#sig_base_example . + url = "http://photos.example.net/photos?file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original" + + req = oauth.Request("GET", url) + + sm = oauth.SignatureMethod_HMAC_SHA1() + + consumer = oauth.Consumer('dpf43f3p2l4k3l03', 'foo') + key, raw = sm.signing_base(req, consumer, None) + + expected = 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal' + self.assertEquals(expected, raw) + + def test_get_normalized_parameters(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'multi': ['FOO','BAR', b'\u00ae', '\xc2\xae'], + 'multi_same': ['FOO','FOO'], + 'uni_utf8_bytes': '\xc2\xae', + 'uni_unicode_object': b'\u00ae' + } + + req = oauth.Request("GET", url, params) + + res = req.get_normalized_parameters() + + expected='multi=BAR&multi=FOO&multi=%C2%AE&multi=%C2%AE&multi_same=FOO&multi_same=FOO&oauth_consumer_key=0685bd9184jfhq22&oauth_nonce=4572616e48616d6d65724c61686176&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131200&oauth_token=ad180jjd733klru7&oauth_version=1.0&uni_unicode_object=%C2%AE&uni_utf8_bytes=%C2%AE' + + self.assertEquals(expected, res) + + def test_get_normalized_parameters_ignores_auth_signature(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_signature': "some-random-signature-%d" % random.randint(1000, 2000), + 'oauth_token': "ad180jjd733klru7", + } + + req = oauth.Request("GET", url, params) + + res = req.get_normalized_parameters() + + self.assertNotEquals(urllib.parse.urlencode(sorted(params.items())), res) + + foo = params.copy() + del foo["oauth_signature"] + self.assertEqual(urllib.parse.urlencode(sorted(foo.items())), res) + + def test_set_signature_method(self): + consumer = oauth.Consumer('key', 'secret') + client = oauth.Client(consumer) + + class Blah: + pass + + try: + client.set_signature_method(Blah()) + self.fail("Client.set_signature_method() accepted invalid method.") + except ValueError: + pass + + m = oauth.SignatureMethod_HMAC_SHA1() + client.set_signature_method(m) + self.assertEquals(m, client.method) + + def test_get_normalized_string_escapes_spaces_properly(self): + url = "http://sp.example.com/" + params = { + "some_random_data": random.randint(100, 1000), + "data": "This data with a random number (%d) has spaces!" % random.randint(1000, 2000), + } + + req = oauth.Request("GET", url, params) + res = req.get_normalized_parameters() + expected = urllib.parse.urlencode(sorted(params.items())).replace('+', '%20') + self.assertEqual(expected, res) + + @mock.patch('oauth2.Request.make_timestamp') + @mock.patch('oauth2.Request.make_nonce') + def test_request_nonutf8_bytes(self, mock_make_nonce, mock_make_timestamp): + mock_make_nonce.return_value = 5 + mock_make_timestamp.return_value = 6 + + tok = oauth.Token(key="tok-test-key", secret="tok-test-secret") + con = oauth.Consumer(key="con-test-key", secret="con-test-secret") + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_token': tok.key, + 'oauth_consumer_key': con.key + } + + # If someone passes a sequence of bytes which is not ascii for + # url, we'll raise an exception as early as possible. + url = "http://sp.example.com/\x92" # It's actually cp1252-encoding... + self.assertRaises(TypeError, oauth.Request, method="GET", url=url, parameters=params) + + # And if they pass an unicode, then we'll use it. + url = b'http://sp.example.com/\u2019' + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'cMzvCkhvLL57+sTIxLITTHfkqZk=') + + # And if it is a utf-8-encoded-then-percent-encoded non-ascii + # thing, we'll decode it and use it. + url = "http://sp.example.com/%E2%80%99" + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'yMLKOyNKC/DkyhUOb8DLSvceEWE=') + + # Same thing with the params. + url = "http://sp.example.com/" + + # If someone passes a sequence of bytes which is not ascii in + # params, we'll raise an exception as early as possible. + params['non_oauth_thing'] = '\xae', # It's actually cp1252-encoding... + self.assertRaises(TypeError, oauth.Request, method="GET", url=url, parameters=params) + + # And if they pass a unicode, then we'll use it. + params['non_oauth_thing'] = b'\u2019' + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_signature'], '0GU50m0v60CVDB5JnoBXnvvvKx4=') + + # And if it is a utf-8-encoded non-ascii thing, we'll decode + # it and use it. + params['non_oauth_thing'] = '\xc2\xae' + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_signature'], 'pqOCu4qvRTiGiXB8Z61Jsey0pMM=') + + + # Also if there are non-utf8 bytes in the query args. + url = "http://sp.example.com/?q=\x92" # cp1252 + self.assertRaises(TypeError, oauth.Request, method="GET", url=url, parameters=params) + + def test_request_hash_of_body(self): + tok = oauth.Token(key="token", secret="tok-test-secret") + con = oauth.Consumer(key="consumer", secret="con-test-secret") + + # Example 1a from Appendix A.1 of + # http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html + # Except that we get a differetn result than they do. + + params = { + 'oauth_version': "1.0", + 'oauth_token': tok.key, + 'oauth_nonce': 10288510250934, + 'oauth_timestamp': 1236874155, + 'oauth_consumer_key': con.key + } + + url = "http://www.example.com/resource" + req = oauth.Request(method="PUT", url=url, parameters=params, body="Hello World!", is_form_encoded=False) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_body_hash'], 'Lve95gjOVATpfV8EL5X4nxwjKHE=') + self.failUnlessReallyEqual(req['oauth_signature'], 't+MX8l/0S8hdbVQL99nD0X1fPnM=') + # oauth-bodyhash.html A.1 has + # '08bUFF%2Fjmp59mWB7cSgCYBUpJ0U%3D', but I don't see how that + # is possible. + + # Example 1b + params = { + 'oauth_version': "1.0", + 'oauth_token': tok.key, + 'oauth_nonce': 10369470270925, + 'oauth_timestamp': 1236874236, + 'oauth_consumer_key': con.key + } + + req = oauth.Request(method="PUT", url=url, parameters=params, body="Hello World!", is_form_encoded=False) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_body_hash'], 'Lve95gjOVATpfV8EL5X4nxwjKHE=') + self.failUnlessReallyEqual(req['oauth_signature'], 'CTFmrqJIGT7NsWJ42OrujahTtTc=') + + # Appendix A.2 + params = { + 'oauth_version': "1.0", + 'oauth_token': tok.key, + 'oauth_nonce': 8628868109991, + 'oauth_timestamp': 1238395022, + 'oauth_consumer_key': con.key + } + + req = oauth.Request(method="GET", url=url, parameters=params, is_form_encoded=False) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, None) + self.failUnlessReallyEqual(req['oauth_body_hash'], '2jmj7l5rSw0yVb/vlWAYkK/YBwk=') + self.failUnlessReallyEqual(req['oauth_signature'], 'Zhl++aWSP0O3/hYQ0CuBc7jv38I=') + + + def test_sign_request(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200" + } + + tok = oauth.Token(key="tok-test-key", secret="tok-test-secret") + con = oauth.Consumer(key="con-test-key", secret="con-test-secret") + + params['oauth_token'] = tok.key + params['oauth_consumer_key'] = con.key + req = oauth.Request(method="GET", url=url, parameters=params) + + methods = { + 'DX01TdHws7OninCLK9VztNTH1M4=': oauth.SignatureMethod_HMAC_SHA1(), + 'con-test-secret&tok-test-secret': oauth.SignatureMethod_PLAINTEXT() + } + + for exp, method in methods.items(): + req.sign_request(method, con, tok) + self.assertEquals(req['oauth_signature_method'], method.name) + self.assertEquals(req['oauth_signature'], exp) + + # Also if there are non-ascii chars in the URL. + url = "http://sp.example.com/\xe2\x80\x99" # utf-8 bytes + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, tok) + self.assertEquals(req['oauth_signature'], 'loFvp5xC7YbOgd9exIO6TxB7H4s=') + + url = b'http://sp.example.com/\u2019' # Python unicode object + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, tok) + self.assertEquals(req['oauth_signature'], 'loFvp5xC7YbOgd9exIO6TxB7H4s=') + + # Also if there are non-ascii chars in the query args. + url = "http://sp.example.com/?q=\xe2\x80\x99" # utf-8 bytes + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, tok) + self.assertEquals(req['oauth_signature'], 'IBw5mfvoCsDjgpcsVKbyvsDqQaU=') + + url = b'http://sp.example.com/?q=\u2019' # Python unicode object + req = oauth.Request(method="GET", url=url, parameters=params) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), con, tok) + self.assertEquals(req['oauth_signature'], 'IBw5mfvoCsDjgpcsVKbyvsDqQaU=') + + def test_from_request(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + req = oauth.Request("GET", url, params) + headers = req.to_header() + + # Test from the headers + req = oauth.Request.from_request("GET", url, headers) + self.assertEquals(req.method, "GET") + self.assertEquals(req.url, url) + + self.assertEquals(params, req.copy()) + + # Test with bad OAuth headers + bad_headers = { + 'Authorization' : 'OAuth this is a bad header' + } + + self.assertRaises(oauth.Error, oauth.Request.from_request, "GET", + url, bad_headers) + + # Test getting from query string + qs = urllib.parse.urlencode(params) + req = oauth.Request.from_request("GET", url, query_string=qs) + + exp = urllib.parse.parse_qs(qs, keep_blank_values=False) + for k, v in exp.items(): + exp[k] = urllib.parse.unquote(v[0]) + + self.assertEquals(exp, req.copy()) + + # Test that a boned from_request() call returns None + req = oauth.Request.from_request("GET", url) + self.assertEquals(None, req) + + def test_from_token_and_callback(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': "137131200", + 'oauth_consumer_key': "0685bd9184jfhq22", + 'oauth_signature_method': "HMAC-SHA1", + 'oauth_token': "ad180jjd733klru7", + 'oauth_signature': "wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + } + + tok = oauth.Token(key="tok-test-key", secret="tok-test-secret") + req = oauth.Request.from_token_and_callback(tok) + self.assertFalse('oauth_callback' in req) + self.assertEquals(req['oauth_token'], tok.key) + + req = oauth.Request.from_token_and_callback(tok, callback=url) + self.assertTrue('oauth_callback' in req) + self.assertEquals(req['oauth_callback'], url) + + def test_from_consumer_and_token(self): + url = "http://sp.example.com/" + + tok = oauth.Token(key="tok-test-key", secret="tok-test-secret") + tok.set_verifier('this_is_a_test_verifier') + con = oauth.Consumer(key="con-test-key", secret="con-test-secret") + req = oauth.Request.from_consumer_and_token(con, token=tok, + http_method="GET", http_url=url) + + self.assertEquals(req['oauth_token'], tok.key) + self.assertEquals(req['oauth_consumer_key'], con.key) + self.assertEquals(tok.verifier, req['oauth_verifier']) + +class SignatureMethod_Bad(oauth.SignatureMethod): + name = "BAD" + + def signing_base(self, request, consumer, token): + return "" + + def sign(self, request, consumer, token): + return "invalid-signature" + + +class TestServer(unittest.TestCase): + def setUp(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': "1.0", + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': int(time.time()), + 'bar': 'blerg', + 'multi': ['FOO','BAR'], + 'foo': 59 + } + + self.consumer = oauth.Consumer(key="consumer-key", + secret="consumer-secret") + self.token = oauth.Token(key="token-key", secret="token-secret") + + params['oauth_token'] = self.token.key + params['oauth_consumer_key'] = self.consumer.key + self.request = oauth.Request(method="GET", url=url, parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + self.request.sign_request(signature_method, self.consumer, self.token) + + def test_init(self): + server = oauth.Server(signature_methods={'HMAC-SHA1' : oauth.SignatureMethod_HMAC_SHA1()}) + self.assertTrue('HMAC-SHA1' in server.signature_methods) + self.assertTrue(isinstance(server.signature_methods['HMAC-SHA1'], + oauth.SignatureMethod_HMAC_SHA1)) + + server = oauth.Server() + self.assertEquals(server.signature_methods, {}) + + def test_add_signature_method(self): + server = oauth.Server() + res = server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + self.assertTrue(len(res) == 1) + self.assertTrue('HMAC-SHA1' in res) + self.assertTrue(isinstance(res['HMAC-SHA1'], + oauth.SignatureMethod_HMAC_SHA1)) + + res = server.add_signature_method(oauth.SignatureMethod_PLAINTEXT()) + self.assertTrue(len(res) == 2) + self.assertTrue('PLAINTEXT' in res) + self.assertTrue(isinstance(res['PLAINTEXT'], + oauth.SignatureMethod_PLAINTEXT)) + + def test_verify_request(self): + server = oauth.Server() + server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + + parameters = server.verify_request(self.request, self.consumer, + self.token) + + self.assertTrue('bar' in parameters) + self.assertTrue('foo' in parameters) + self.assertTrue('multi' in parameters) + self.assertEquals(parameters['bar'], 'blerg') + self.assertEquals(parameters['foo'], 59) + self.assertEquals(parameters['multi'], ['FOO','BAR']) + + def test_build_authenticate_header(self): + server = oauth.Server() + headers = server.build_authenticate_header('example.com') + self.assertTrue('WWW-Authenticate' in headers) + self.assertEquals('OAuth realm="example.com"', + headers['WWW-Authenticate']) + + def test_no_version(self): + url = "http://sp.example.com/" + + params = { + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': int(time.time()), + 'bar': 'blerg', + 'multi': ['FOO','BAR'], + 'foo': 59 + } + + self.consumer = oauth.Consumer(key="consumer-key", + secret="consumer-secret") + self.token = oauth.Token(key="token-key", secret="token-secret") + + params['oauth_token'] = self.token.key + params['oauth_consumer_key'] = self.consumer.key + self.request = oauth.Request(method="GET", url=url, parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + self.request.sign_request(signature_method, self.consumer, self.token) + + server = oauth.Server() + server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + + parameters = server.verify_request(self.request, self.consumer, + self.token) + + def test_invalid_version(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': '222.9922', + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': int(time.time()), + 'bar': 'blerg', + 'multi': ['foo','bar'], + 'foo': 59 + } + + consumer = oauth.Consumer(key="consumer-key", + secret="consumer-secret") + token = oauth.Token(key="token-key", secret="token-secret") + + params['oauth_token'] = token.key + params['oauth_consumer_key'] = consumer.key + request = oauth.Request(method="GET", url=url, parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + request.sign_request(signature_method, consumer, token) + + server = oauth.Server() + server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + + self.assertRaises(oauth.Error, server.verify_request, request, consumer, token) + + def test_invalid_signature_method(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': '1.0', + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': int(time.time()), + 'bar': 'blerg', + 'multi': ['FOO','BAR'], + 'foo': 59 + } + + consumer = oauth.Consumer(key="consumer-key", + secret="consumer-secret") + token = oauth.Token(key="token-key", secret="token-secret") + + params['oauth_token'] = token.key + params['oauth_consumer_key'] = consumer.key + request = oauth.Request(method="GET", url=url, parameters=params) + + signature_method = SignatureMethod_Bad() + request.sign_request(signature_method, consumer, token) + + server = oauth.Server() + server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + + self.assertRaises(oauth.Error, server.verify_request, request, + consumer, token) + + def test_missing_signature(self): + url = "http://sp.example.com/" + + params = { + 'oauth_version': '1.0', + 'oauth_nonce': "4572616e48616d6d65724c61686176", + 'oauth_timestamp': int(time.time()), + 'bar': 'blerg', + 'multi': ['FOO','BAR'], + 'foo': 59 + } + + consumer = oauth.Consumer(key="consumer-key", + secret="consumer-secret") + token = oauth.Token(key="token-key", secret="token-secret") + + params['oauth_token'] = token.key + params['oauth_consumer_key'] = consumer.key + request = oauth.Request(method="GET", url=url, parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + request.sign_request(signature_method, consumer, token) + del request['oauth_signature'] + + server = oauth.Server() + server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1()) + + self.assertRaises(oauth.MissingSignature, server.verify_request, + request, consumer, token) + + +# Request Token: http://oauth-sandbox.sevengoslings.net/request_token +# Auth: http://oauth-sandbox.sevengoslings.net/authorize +# Access Token: http://oauth-sandbox.sevengoslings.net/access_token +# Two-legged: http://oauth-sandbox.sevengoslings.net/two_legged +# Three-legged: http://oauth-sandbox.sevengoslings.net/three_legged +# Key: bd37aed57e15df53 +# Secret: 0e9e6413a9ef49510a4f68ed02cd +class TestClient(unittest.TestCase): +# oauth_uris = { +# 'request_token': '/request_token.php', +# 'access_token': '/access_token.php' +# } + + oauth_uris = { + 'request_token': '/request_token', + 'authorize': '/authorize', + 'access_token': '/access_token', + 'two_legged': '/two_legged', + 'three_legged': '/three_legged' + } + + consumer_key = 'bd37aed57e15df53' + consumer_secret = '0e9e6413a9ef49510a4f68ed02cd' + host = 'http://oauth-sandbox.sevengoslings.net' + + def setUp(self): + self.consumer = oauth.Consumer(key=self.consumer_key, + secret=self.consumer_secret) + + self.body = { + 'foo': 'bar', + 'bar': 'foo', + 'multi': ['FOO','BAR'], + 'blah': 599999 + } + + def _uri(self, type): + uri = self.oauth_uris.get(type) + if uri is None: + raise KeyError("%s is not a valid OAuth URI type." % type) + + return "%s%s" % (self.host, uri) + + def create_simple_multipart_data(self, data): + boundary = '---Boundary-%d' % random.randint(1,1000) + crlf = '\r\n' + items = [] + for key, value in data.items(): + items += [ + '--'+boundary, + 'Content-Disposition: form-data; name="%s"'%str(key), + '', + str(value), + ] + items += ['', '--'+boundary+'--', ''] + content_type = 'multipart/form-data; boundary=%s' % boundary + return content_type, crlf.join(items) + + def test_init(self): + class Blah(): + pass + + try: + client = oauth.Client(Blah()) + self.fail("Client.__init__() accepted invalid Consumer.") + except ValueError: + pass + + consumer = oauth.Consumer('token', 'secret') + try: + client = oauth.Client(consumer, Blah()) + self.fail("Client.__init__() accepted invalid Token.") + except ValueError: + pass + + def test_access_token_get(self): + """Test getting an access token via GET.""" + client = oauth.Client(self.consumer, None) + resp, content = client.request(self._uri('request_token'), "GET") + + self.assertEquals(int(resp['status']), 200) + + def test_access_token_post(self): + """Test getting an access token via POST.""" + client = oauth.Client(self.consumer, None) + resp, content = client.request(self._uri('request_token'), "POST") + + self.assertEquals(int(resp['status']), 200) + + res = dict(urllib.parse.parse_qsl(content)) + self.assertTrue('oauth_token' in res) + self.assertTrue('oauth_token_secret' in res) + + def _two_legged(self, method): + client = oauth.Client(self.consumer, None) + + return client.request(self._uri('two_legged'), method, + body=urllib.parse.urlencode(self.body)) + + def test_two_legged_post(self): + """A test of a two-legged OAuth POST request.""" + resp, content = self._two_legged("POST") + + self.assertEquals(int(resp['status']), 200) + + def test_two_legged_get(self): + """A test of a two-legged OAuth GET request.""" + resp, content = self._two_legged("GET") + self.assertEquals(int(resp['status']), 200) + + @mock.patch('httplib2.Http.request') + def test_multipart_post_does_not_alter_body(self, mockHttpRequest): + random_result = random.randint(1,100) + + data = { + 'rand-%d'%random.randint(1,100):random.randint(1,100), + } + content_type, body = self.create_simple_multipart_data(data) + + client = oauth.Client(self.consumer, None) + uri = self._uri('two_legged') + + def mockrequest(cl, ur, **kw): + self.failUnless(cl is client) + self.failUnless(ur is uri) + self.failUnlessEqual(frozenset(kw.keys()), frozenset(['method', 'body', 'redirections', 'connection_type', 'headers'])) + self.failUnlessEqual(kw['body'], body) + self.failUnlessEqual(kw['connection_type'], None) + self.failUnlessEqual(kw['method'], 'POST') + self.failUnlessEqual(kw['redirections'], httplib2.DEFAULT_MAX_REDIRECTS) + self.failUnless(isinstance(kw['headers'], dict)) + + return random_result + + mockHttpRequest.side_effect = mockrequest + + result = client.request(uri, 'POST', headers={'Content-Type':content_type}, body=body) + self.assertEqual(result, random_result) + + @mock.patch('httplib2.Http.request') + def test_url_with_query_string(self, mockHttpRequest): + uri = 'http://example.com/foo/bar/?show=thundercats&character=snarf' + client = oauth.Client(self.consumer, None) + random_result = random.randint(1,100) + + def mockrequest(cl, ur, **kw): + self.failUnless(cl is client) + self.failUnlessEqual(frozenset(kw.keys()), frozenset(['method', 'body', 'redirections', 'connection_type', 'headers'])) + self.failUnlessEqual(kw['body'], '') + self.failUnlessEqual(kw['connection_type'], None) + self.failUnlessEqual(kw['method'], 'GET') + self.failUnlessEqual(kw['redirections'], httplib2.DEFAULT_MAX_REDIRECTS) + self.failUnless(isinstance(kw['headers'], dict)) + + req = oauth.Request.from_consumer_and_token(self.consumer, None, + http_method='GET', http_url=uri, parameters={}) + req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), self.consumer, None) + expected = urllib.parse.parse_qsl(urlparse.urlparse(req.to_url()).query) + actual = urllib.parse.parse_qsl(urlparse.urlparse(ur).query) + self.failUnlessEqual(len(expected), len(actual)) + actual = dict(actual) + for key, value in expected: + if key not in ('oauth_signature', 'oauth_nonce', 'oauth_timestamp'): + self.failUnlessEqual(actual[key], value) + + return random_result + + mockHttpRequest.side_effect = mockrequest + + client.request(uri, 'GET') + + @mock.patch('httplib2.Http.request') + @mock.patch('oauth2.Request.from_consumer_and_token') + def test_multiple_values_for_a_key(self, mockReqConstructor, mockHttpRequest): + client = oauth.Client(self.consumer, None) + + request = oauth.Request("GET", "http://example.com/fetch.php", parameters={'multi': ['1', '2']}) + mockReqConstructor.return_value = request + + client.request('http://whatever', 'POST', body='multi=1&multi=2') + + self.failUnlessEqual(mockReqConstructor.call_count, 1) + self.failUnlessEqual(mockReqConstructor.call_args[1]['parameters'], {'multi': ['1', '2']}) + + self.failUnless('multi=1' in mockHttpRequest.call_args[1]['body']) + self.failUnless('multi=2' in mockHttpRequest.call_args[1]['body']) + +if __name__ == "__main__": + unittest.main() diff --git a/dist/oauth2-1.5.211-py3.3.egg b/dist/oauth2-1.5.211-py3.3.egg new file mode 100644 index 0000000000000000000000000000000000000000..bbdc8d45d055051dd4750ce6c9032b9aeef503b0 GIT binary patch literal 53952 zcmZ6RL$GMU)})VZ+qP}nwr$(CZQHiZdu-drJ?Q&)M|8i}gEgo@R_**^RYb*JsUQsu zf&u^l00E%r5G0fgw#EefZw2_TaQ~I4xHzq(oR~bliK&CBor$TPv8TR`rJc1ior{Od z5fFgFf2nP9vi1l5so?&}5&u)IApL)~k&TzN8xnvCeRGD=Cy)#tF;lBu21S98Q0QVC zBvBrHuox?xy1^u{>VBGkddp)k@emummLTN1piTLRu1uH=+Ap}7`P!O!($viQ9*8k) zS?E-j0M#KGc%_Xz_*~{AfHs`sVT)X@^3q0$U2@|%X>~xv{TqjsG!(xnl`m`N9t8r8 zFVwIW_{}mIvFM7{7pmuPe@D{n#`?mTX_E$?f%PQU2sd{{{AS)-)ux}Sx%f_efKxs{ zQztCD_#y8I{NHw8_X7H${@H~1SE&DK*U8k;)zZoIe?yw4pro9blwtb+S#im4Sf>3G zss3a2Km7kMX=Qm;C1FwJ{{d_pzX%m1fFQc}jgI;mS`k4&eBp_Z+gj-A<|;PX*T+;c z;fopOeCyj0wE4G$#g$~~YS5KW1fRS>59;*S_=}Q!H)m6qu$BXOw@Y^w)RwPe% z?n*ZATo=7gC|-v!omk^A8$=kljn@?;?UF|WX94Uc*oP)`&{Mz?Qj@gBR&5!DFQ9Z0 zl6p%2S9lk_Y%ZD?@V_A-5E9tI|NDN}e}(p+K3wb_^lePtOl|(h$+SET?Ign?l_V`4 z<>`N-FtYparu(n9@v?NFbv86J{jWyjv<(v4)=m2n!Uq6IaR2~7`gf?kp{t7p6TQB^ zrJbdVzCN9U=MJy6^A<tc)TC$|4E|yjhO&z6@ zu##JkwsZgYCtv`8#N?|<_3fn&r8rTbSn;Ag%f4Ip4%Tcb2Mo@^NBB> z{=D>Jrpst`Zf<^?-p%!GggjGL^tY-L^;6z^y-ZZIQ>(`7h+)cJL~=#OOmPW0-#GFE zU;l5pj8-jjCjDViM6=3BXvVaT-cIxtkCZg$O{6AW>4X{3=gC?V`IL;vMX$zGPXOG8 zD(0wVj$X8c3_vkj3(Xlo*@W4NZi=)852~V~NiqkJhf$WS8gyowV&u{6p$J-$^3d_7 zd6js#bg+zRO3R_jKKHlO_UH+Zb}CW2Xy!O_$Re^Mm!z8Vh+x0mp=gcRz^zDG5vYJV z=nTNab)+a;toeI?i6yPNx}?sGw-FZcd9^AeJhYF*^X&moTeA)dlcW{^Ay|)P9jeamfhzx=$LZq+7Uhw#fy{7$&MW ztx?!HRd*^2`e_ggHK3{(Ai9BOZIzemz!dqR6jjh*hvVjuxEB{EoPJ8fy<;w zU$_B0T%2D1P{pwD;bKLI;vr_nFBtuijq$;9c@7n82e|coB0rI6j!lHAVqWTSMkCIiC-X5pj-PfS;RqxVU%+ ztiR*p#meuS!J2t-GHdkU=EHdyIe0mQg1h;6_^(so1#o2>LjKw(l1TcY(-ibF z(H}%v7#xmJS!8ju-Ay_|;p(-Ta6t_5QJg-=m!$V6b%O>lUeK9sD_B?YM~&y%XN4~v zD_mo#SpH$Af>O_-4cNCJLOjU$#t&?Ovn0REpBz}ZG4O$3<97|C;Rh5Jxwt+YSTS?* z;j7We8l%OF_vjuwRze|D@P-vP6H6DmPaNsikib&{$I`3Mt{ zw`5LaV#W(_rXMpNVSzz${2P2Z)NTN|Um^*A z&zR-~dZxXZW*8+-$n&3PD>$*43*yGUJE~kTs$)5K@mi zW6GHBjtwx;z6mN;4>QmJTvpTR_1nPI&5X0l*TJbhq~$qz5#*dWF!e#7>0AAdBuUjSuWvaovU(geR85?;TEv6#Xy5#l_CpN zhTE7%6CW^i={G@Lf#^4<3gClW;GA9i7aT)Et_QYMC{7X@fQgnjgahA1o)O5-N7E^` zf@BB)VKhwpD4xEh!3B%b4ONl|frhvb%x2&n=8Sua(?YOkluI1D7vQN*G!v&TGUIIJ zNmC#oxPc%fa&&^n$+`lsJlWyNpH7UN9|$$GNVh{E{2@R&4`!7q$Pquzt3!Hro7@&9 zy=SE14*;_t07?5$lcf0ezidJ= zdV(FL6=HdII?}3q`O(zbNx#OeE&~B`;ed5al}J}6c-9U5L40q^+eK${Ym)BDu2CbR+fn`9 zb#1_ZPbs?%+uReiFlqQLR&g^ePh6iW(qyxckPjd`E6=a#zyN-|S$OiX!RPz3O-%)u zT*DRUvH<(J^tWbpiq)h4G&)BF4n&`hyFm(Ore{`?4o5(2%Y<|=)Z!8_i6|h z5c*nSY+Hc=I0TPQpLp)ddGmk$vU9mf3)PWjj*}MY($bmS#wS*I{R^}f@bA9h$Mr5D z`(H4r8kbP<^sgSd(p#Sgn8uqkdgT9RV>E*I)_+yOVOK#V20C^!n33bVK?{WR1Y0;thzpWF*& z_NoM~5fq%*#_V!rY*KT^Ct(&r&JJi3E9?UW8}d>F3vG`elvRub3o%6wVy{l*qgPW% z-cnwbB;q6JkHI=IHqs}eX}O%|46t&zheFdK(c2w=L(fG-q7zUKpwMGTvJtvDyUn&xV<1Isn zsd0&ygqg>Wz__84N6I}LfCGUK6B|!jFtIy=bdk{QqH zUnK=EP?l$XK?a0@_v?;_)%jUy8YWqSr-&bhn$yjayKKZ3Xzc#xlv118v-^c5An32s z(;9=)47<4vjtsC=5ewMu2Z~FUje+tIOsQ48=Hz&{^)%VZ>HpS~ZJo>*L^mBr2$XV+ zqoAp?l&vkNDiaJVS`cy$?LHr25WAzqGutswp@tL1M6ESKpT>#YssYjPSaI68& zJc*!UB7LBu0cWBznhBbgsYA~+6LP99ShU97wMWoo19yLtnk-O@t{`n@h1WOpn$uT6 zF?WP}mO60-3pvgJPhMgB=Ai^Tph-dKQ!rNF*H`f%#h@#WWLOXwtXsJkQM{pi6eyt@ zNX#zQrNwy}Q6?l&dI+k`4hE>e0&*jTwXaZveZ8aH3}66Jb_><)3NxpG5LgP$*|cwE zfBe-fA9!ml3(AW7xtt#*D)?F79w;#>_dQL2g#aL0XLOYrYcT+tF=%Hk8 zaG4E4uF`xkJFEtr7zQ}4!?#GusDmCe9VKv{f)YY-uC>wiotelqN|RpYgDSBMMpF^9 zTA(`#fD!p^Lki`|CRN7prycDC>R5<1LFMlx%p+X0djRez#R&e}G1w^+khf;o>kQRS z>30H|c<_VQ0Cu5RY@Ni)*IQSejRLoIE9jv|VQ#)w|9+B+6Y&iBhft5nujc;idkFJO zn5+fyp!6!GR$sD#!5}kHF}9_*q+y<37D*pqYob&r*vv~+6K%g5&H6X13!1oT1E8$A zLYBg;8%{}kz(@+INKx{TLhQ)^e)H(QLXcm)KQ+UV2l0uMPpZhp>^oW^`u7_Gm4+r3 zG-c}7Z@Yoor&e?=1H9vb!|}MHUR&+)9RElCBjY&9GwQ<@MP~2-ra|Iu^i-qX@-Va z!M6q{IKbR=7V|PE=_H(?lpFj-tFntE2Bo20h&kVsAXhDrPPTG1I|z|eqnnm)DNH(f zC$oUz@ajlsnKda)5<@6}1a7L^EU9r4(^(%$L@iGa#U)aG zSXVh&VJIFi%*v(?F$;A$BtfQx#9dPuRnq53#l?oAS_pWlMS|b#B6aUIrzPO8s6=v1 zmA(5b2=Gij*3o6$ma1$Dhog_R&$oYN9NpI|D#U#)hul1Dm#GK3XVl(0yqfNiT z=dNw9^X>=9dEA7?%_9UmEZ(;{JQF-dE&EEl-EOQBratl%a0#UF3dtX7Q zw`Rn|3UK3JS~R?LezAy^(R>S^6AY$BR|yf~){uv;VvV3=3WnHO9(J)>;Ttl=Nk|%^ zd%}+D?O`36yn2PXD$2pCFh(;gAG59TpweH9U@P0AkG)9ZnPcXvcIFmqG(2_Um^5rl zeE?|)N-?j1dB|vnH4~PqM&4znT>-iVkDV zUTzWiVBPd3$#JCS@ZMd-Xl5u-d(T(JEVL#ifxxF4-U{l=3SATKD7pd)j^Nd0Fypo4 zOt^lYVKp3>3km;fGtl3S(>fD{Wc&(7=)eKF=YUMxl@)cm)8Z+6Xf`>2$Z z&hD!yK>CDg zR4p#@xf80flkE_@B1@#H%yO>e=D-bR578a z_8Pjyibp6vOg1`3b^BTdEj8;EM``PX961+jO#En`>^x$-bRLD8*f65j4Le7AE~9%OSZQ8Gy!~ zRnT59n3T4b?0qCQgbH1yWm*2WH+nUjGxymd$5r&sv@CO<$4UR|srNx=J*;!@`$JQo z#mtAibY(rF?g>rOyVl2RIEX%XxTSSI9 zNy%i{#iJ5*4L@%4o_eRr82iq7$^)KID%2^hvTmVzS0{>>CEGWJ;DH5W(m@qp4hO&q z&JzMv))vXl`L2P|%JoXd>}^;Fm}AUR>a&Rv`*vj8FVbyTHxTlP5}~hicMZZB#mQwJ zVsWqO`;cH1c-_QEF3JQ>a(32Av@(M=G>er*kH=%nWYBp1K-))hGZnV0$gv7Lm&r0$ zopL<|NXtb5F1nyo!h-LNnuaXS`Sv=`=_rgUe2V5nIanlWdaamIb{*Pe2VhieXsQ%F;Vo>6 z!ls^Jl?A_v{mQ=K5)L$IJfdu4e0FjH6$Np&EYnseO_~!?nI{YNsC)~gpyd(%5W8A7 zoSE2bTl+MPbQD9{3C+yo)s1JTL|f(6DvuV8NuQ|7#^%RGW>7PzgN@6)Kw|*eVwEKM z#>AvuaQ8+TGI@t^)~v{1Q(CdMh~?vIwfVeXmXUH5*qCq2=s`{(k+InY+Z#oK`FYx% zDiUjW*%b~decq!3tT*`V$C$o)WLbimj7LG3yHqV`B3F-G(=|xkO9=7}qwWsbc1OYX zz`t#bot$IHFa=-aHJnHY6+VhrnWy)^#gwW+B%|? zl{%)Zn_s_H=F*N9KE1iE<>(&0=N2_Kzln+Mf|Z>OPu-cNCO$7;%3#v0oDN?4o?FI# zbPf*;b%QR_2B{1f2$if@*~)=E3jTi9IGQ!5^UFV%v(ru%;;uCg!kiR9|c+sJ6HTtzAu)03B7* zWx-c-xJ2b5tF6FSbbOnkp{|mbXio2iku6=o(*Z3b*AQKj?M>bm99B~h*@0JVX**O0 zYvvcasLlip&l&X(>UTGF>GO|z?%=!8b@IcUj4u}@U6wTozZD__1&`pTU?+y&pofXo zSMVg{j|g8EZq~WkPhNkp_#`t~*NVGN)iw3TQLzss;%v8=?IfpGn=I|uDLk#YWzbux zwX}TA)xx^H+^0F9#!b> zC+2o|=u86#{cHK~6I5QJ3AO4>%dY}ChCd8%oub{puK>ofh@fDQOW>(|*1*f|3x!9n zQ|pv=Y028HcOXVXSr&c*zEN(6)($cu3jcuUiKSyjj#G>B6~HZ7qto8nQto0T~{;YcHEx7We>#P4R*SN zi^gT&ukUr!_A09Q`<{XLr|g}Ys7zfaoQ`lMV}BsdGbeSb<}HGoRWCR(r+NOm@sAPB zlF?e>JFcc0nANXIRjLX@*UC|Yb%Go;iX!2w= zsZuGCT3Ly_LUY0%+EKZrj+aTP>e8C<7_t)^LLRZqaaw9T!-np`?!nLT$Bu;u>yMQW zyMvbxe=lN6N>AC2dtk=r*Dmxjlwlv~Iu92wdOW|FM6fb0A_+X*C~fVv-QM1|QTSUt z9xfG}Mre?zt-poP<+k~`_yxh#zzQqZ(r-p6bvJH_0yL=y$!!eT5;-w1V{f9n?y+_F zT9eyOo7NJ%+{sq3z^s?@j3HZ|!G~i3d>90Cuh|GwTv&0fw3p!^tSl;=R`20CT4odr z@*Fk^)u}`zTVMV4)64hphjsgA<@*Zm>*CFAXnRAjBI&VI;n;GD2?bJ&r$c^$l%oUE zwFhm!XWM_+Wv=$I^WLJj%N14#o`ZtI?AdA1ahtB%;Tb*xv0fZn!}YmR0M?!B%S!g3 z4C)2&!+xiSJSWR@K~zrNmN4l?nKk28&rxjeBnWUo1*vh^9BJ3uRq?3y;za*hytC`I z8e|o^TB$dSjoQj$ILGciJvaOty*0l!u={pdF^P#=Xy4IN+A*k1sTlNTNY>PagHA7A znQMKgdB%`cPe4%<4gIW2vjPZ9i@eIpu%E>cX6943A<=Z@5i~%S0$L|v)|m}iwzUj_ zfj4?Q&EX+@iN}EC{wTGFpYYI*ImP!Gb@>UH6PhuBb0yl-sd8r4qx_=E>%~q$QQ-@{ipl*%pgFIs{iaoHJ59r#`JCay* z;gSn|%2wYTW!r-VYdd6*`+LL&(Hkmk(Z4U6zo8n-eb1rgD?El z^au;HjK?YF|D(KzGe*CPBcQ5HB?Gu~#~E&Z`YtIk{@M5^7u)4#+RkQ#O+i$3R1xg> zV!6WBQT4d7vyqDCIcX}b)pp%<(_>-o5YHLAnfZfNt1=Pl?#Mc(xG7ctc#w5}Lr87% zCidwK+&2QDHTUlgRvl6RiOTBF+b5W(k4h?6I=wj`~ zz?1{eW*_D+$h@~Pq}XU(>{(?Nty=4z37;nHnuB6<3>NAq1fq9~4KIPpj9@EuKk$o) z$HLG6W#Cs#Cq|21oQ`VJNM}}96IRiV7Pft(ZhP7RK}IV#mX``dYh4gHeec*Cc2Crc zld3dlsL{J^Wt|PnmEbV=i;j4K+hJ;iVKdj8n#{?dM8;^2MftZRobGDu7MvqRlVW(0kW%|KLb)u1DO(t~ET}X^2(7 zDyIhbiqTV-o-#ciM4e#q5K-PG54L1ZR9j%jg`%r~qmPuk!8X%xyIHy8E=ZSJghg3( zwvj0J#ooH0mU_LKy^Bg~CD4$xmC=aMF5szHO6bOz2QP{sI7Cb#b;Asxc|{^!UEb(( z`A8FY9Fog*QL8*vb^sF>+_!p?{eGkdQ8wKJeec3UH++A`wk{<>i^+D{bBz=_x8>rB zCfQI9gPbXDZ0l@-At*H0!m7mO@({|uib3hBX_rLAe`AEHNDWPKO)ErlEy`G&>+Ce6 zMyhL_LRpPhWtU)Bi@f}~dJ1%W#224Np_N5l`lxm>Gtl@4ebaSl0!Tiqx;R?tGRs5v zcSGKE$fR9ctpHWJv=G@EUEeIH$wW=%CFkOr9tG-?bCQ-jpZ$<>tFtR?=%vEBX*$7> z>WK7gH2RrQ{ommq6nrg|Ov-q6vu4&VZz*Lo6pD)S{6AIq_GU>VtwfKaUL6lcT;_^Y z+y-1~>Reck*K)MA1>g3G5?We(_r*ufriztyX{USSvbD4|X19bct#SaiYz@2jy;H*< zDF+7jUzk@)fZi7T7e<%k$zt3I2Z-raeWKxl_=v1_x~5AXZdoN6FnK7>1XRMiS&^t} zm$#P3L|vS>UdkuC)Wo61`!JyCABCZ-3o>cde$~oP|B!_|G}UuI*;Vc_$1jLN{?rr~ zzjuup9sde;-znHWyX$LYOe9y6Na`b*Ne1K1qeYZ=WN7rr+oAr5mLbrXhznmY?rAys z*cflW#{(&{@*lmQy52}peVgYFRjV&%#Dlqbv|Qp0jP9=9o@imR_?uJ;g5eUj{9-M= zK@zIklxMt4#lo^@U5@oeFYAz3+fla|G5OrlAWJX4IZ_M{?9t;JC?kU<3;hp%nsa_q~6U_O^d&=WH5-O{pkNJz{RPPC&>2@Z6}N>0(2dl z*FlorB5++QQnRin)Ub=9VCofGzBm^0)7TN6XCokCO3?udWS_ zc$S9io5KVqoM;phrKRb#Z7r!H6Kj%b?dnx>ue5Z^sIFxNaV5U>8YX;swn_4ErlNa1 z#cg7A=t=LkZUO+b4V)lcCrJkYWRd~C6TIM~gM4~A0pKGN2t-1@_k?hQaKL^70lXyJ zp1i8-i%fD+FR>|N4sC2#^;Li0=TCpt9(^o|f8XEtHUCw#BD$Dgr~5bVdv>o`v?1g} z#xJd*X3=4!c-j}6=&*vwBGQpPZ7j%JNWmJrDGX)q3H$_E2_N&a%*7v=a{CO7u|mvgM}x@R3i= zPX{DCtHI>V)4wY!- zJrIfuAN>8&j+WDI_p`KZZowUUF~3HS;+0*v;FZ?(+^T%{dY!(n*=TjTe&>zeqqWs- z?A$SB{v5adt;e0wj^>R#x4YFL*f%}es`?eIrQ6_w+g+%3|8w@JQE$HvOcBKet1iLg zx{t#Co}0R`R*a^7+|#fHTi9;A0M&Z8BMBLYM6&KIamGv4&c*zS6+^Iq&e>yGFK|x=>)IWK zj5$RS%WP^}*_MbQiw>p8ZG@8fQXjM!OqUP4^y3*po9kP!Vpq}NYIm)?0OKgU`XvSw zwC#9bENNgsXVqYIPw4i6T9Q>$^NBP*!n&`gA%oq5%@C899fWN7q6`m$wT5lmg{kBK&(iGX_w=0z$A@H0FOc1T z(zumBM!t7-PEK36|DNv-T^|;4_bcRdd*97%)0E7r+HH6GJJ&rn{zXiwo&m0y2OJPC zm42dQqo~fx)*YF;ouk|CfURf7pWVsS{xs?Gu(qoVa^20+cQD`fW`>q_X20Fb)O5Jp z(eGgI=YXEw3$dbhPSu89n7ZECm{P*j3T1JB1?=gFZa2T(9xRJuek*)~Ugv)s`|6cP zJLT)^J>JdhcXmcY;?D}roAq%qPsR)nVZ?r$^Y!pX zOK#}Je0MgswCf8R?qJl{)CyR$#5fsBqWg5Pb9CFx)y&Wyf$VH$@OSTuSb`)ytwf?w z&pgp`sM=YM5D$Ve^S5SMn9Mq>IxclHSxjm^E-Odb%e`3z{ zn0;Yv-l|&THazz}>7wnnobG3D$R=Kwh4$7WJ?xesPCvpA%D*hb7j0GbpIT);WLM(~ zzAxxAD1Bb~0Q;hG4+?X)#6%SLY*w=uou}(_?)=&vHNVrX*6a3~)Yt7@t`hvM)2UJe zR3BTh>g=7Bt#r2u@v?S>t5i&1SzC6|w!#VK*}1X;$#p+4AJeT~VMp+;`E1mN*2WbA z=QCJxJ72>j{lg;K@UcUEVto=5sC_!{mr29;ad*+!m5bP`=J9ASEUQ(6zI=X#3L)6) zC99E7GXG?JYB}LnwpDZMxq3w|Q7>S`1?yd_XpKBqOW5zMD-dm{84}|851e*D&(U&C zX753x6LPFt?`i}=Ek5xo_N^@ry_@OG)K>A_yTD_md8ox5{_O7G=$`Pg{WH2hMW>fW z4VyKixeN6&oceC7dYc{1pKpCykBrn$-r!r;f*~Mv#rMbv*zn3skP~;CMDT7{ey5P` zfS$hZ_XnaQS_9Ca4UE19Jyv=6UCQe8{lnVNJghSnv>Gf#jlS|@b+VCRo@v1@^%mz{qY8B1SIVIKJ(rKY2BWT+!d~2h!Qs_BJS(fF%hnU zB|_aEiSUNx6TC;!lJDbRj_U&W;d#Z|;7zQ014J3(iOJB*8-{cBg%!ph0K-rCvc%yA zMa2*`no$Y31~iC-bbGp8?vNT-nClp!8+J`)B^>edBRxpN&o@f>admnwpp^M$TK4%b z0MfTO!RDzAt(&263_xm}H9*novD(?b$Yi_1Cc60;`t@F3cQUb64NP|nzmW&mtSG97 z0!0Q^KJWx35&mW)9?VEpUo5R@b>Fb_dOa6=)!rMaQu7jQ*D4=MdY^cHb|{% zzv_uT=atRY8@zB1Qw@mQM4q)pOhFU@Fg6McNEZ+bUOG#&gs$@mu~3IQfJb&WSZ4Q= zIWYp5Du(bQtd|Nj%|uYH59BUI^Ep@bFi+0X*yojQqUJ>gaTEYShZoLR}x)s zZ;B>b@wK}ui|-e?32?7UY}v3ZxR3g~P&?XSZ{SyGb#4ow9dL1;P9&NJMW`(GN zt%~8kp9e>z6Ry>KyF@lbDwyYG**#0UC>DJX>lP`ldrolt>J}AA6=b~u zm_2}4J796Yq)K2Ws~#psAB%LG zL3qO}P@!uqO@dXwFpcgo2{}U65ecN~-ZMJ;@tQx0nw3}vx&i0mttVmmW8sO|uKTf7 z)XJ-Hj*T+tDw$GIY(zsuG!Ix`HGg7!x#F6UmJ|#~_*+Mld16A-G@0+l@CZNx_)n%m z5Wj^@*02@Ib~kPO-?6Rg(M8Uju->(k(ChyI7$aQNHS0VWa#HrwL){7Kagb^>K(`WO zOZ)-?ZkhjO(V+SY{lF~kSfzH z=WV!^xD9tLn5VUNgkpBr3(wa?Y6KX&rQc?(!LZQlRAJ*VRkB`D^T|rYcl6BqZCHdz zqa=<{O_Z0H=c;`-ZBmT1c~3_L2z<{kDWl2FBE>oKjEuxLan!A%m4{ru8M8Ysjs%84 zlmUUDx+1&EdYWX47^U%c~Xv#3z9{D|gnEpQapDcD=Aih*=pj zi<5LwMke4)@oL@{`NTj{=>@z4*|WV8Hk#F`nr3L2SjH6|4N4CP?b8|zjmuV!*@4NG zANj?im*E~EMHGJJ6A zz&x2)JrWU|`OD=SOu@v=0x?DMCJt(acgD=)@yXy1E|l2V#S?X0mCwT6mO3mjo8Xcf|_iLGW@0i|I=h)WipifT?@+1>Ilk)-fnI#JhDNESm4 zQF-*VcE;SQ$FFifU|aesUftqHGm38J#OS9%P6MI>yqo~bW+ztc?E$NM6fz=J-PzuT z1ABdgJ%~4;guaD%dIxe%I_73&*UkK*IGpP6s*_aPec{FJ(jHKgr<$2Rv9_SGVN4&! zfK_l=;=Zl=)%Bf9#?qTLE~87gPUNRh5`(bPbH1UW!IXPiPubg?t4~!~L{9qykO>Py zau`ji0zjbicrvC6cPv&s62^lnZ!1jm#Oet?`Ev3Ua!{9BuI$P52^-|$U|?$&!Bx{B zp{Rf8$d(!p*vkQbF0o6*bQld1N-aGsLI>eDs;B@1O+jt%9YP9a2k~vab_stPtR$z& zY<^-15p~PdrTfD6#4aK+C041D`$AtV$PeDU8)6_toK#Wv#r<^5yn<|pwVE&UnMk4h zM|$9`V&m-Z_cph8v>Wc!?)+&=QWZ5os}1s-AY7?9W;=TVKql0Z%_FUyc5NJC zCG%r*-n`_ZbZz{r5%9;m{A^_g8ReeIrU~J%8N)NT-2;(Hw{lWhk|x#NnK1Hpq?#3bV+*+z4%DJ6i=uo z_2`YKRFw+6NmZISLigqGTSQh{!~ukuqg=>5U>tEUrBI5bjtGYgvrwd4rj9k1yle7s z1W;D?Y{Dz(C(EQ|3CC%sb(G3B;5GA`D z1lw{|{0T4AforWNTF*BFPVXjN7GkJGk=aF-r~TFRg~wQ>6lL(BT#GLB48o{a01Nz7 zHk#@Ug-@ppCVz4Sa5DtHxA1^mC2Z$HxJ^%3g$KYT z^`~|?19sS@Bpr9W?6}OvlFFFrx!g}Ktm>JcR0VAZqN}V}d<;9u-;&Eup5mb<%_nZk}FN^q3G(gh5?kc}P zcgD9lSj9qt#w7I>F6Z)M3;4)Ff$hDFOPVGjYZd&QNDBmSWet#ApqZ#!5g=h#0%FlvgX%6HhtfmWtcjOQAn)+3m$V>C@FjQ1BYSeFgs(u9At1%SgW z=cA~0MBBzmb@Bi#cmXl44WCjbMm?E6zBM$`LP5by0_7JPCDA6QiR7v#@T-V|%@REn zh}zEB4##gGs|+Ko0Zi0!gQUg^lYmqo%QYZTL+t-__W<#;`|i&Kl#anc(K)oQ|2a$xvP?=L8BkkOOySa8MTHC4Q z6B#4B0y9;baGxEV&rs2`3LkD59~sgPNHC#GRK(`y!_4G2Mz?Ak>AUSTB-my2ltNjk?xkO;VG?)dumncfMwR?(8ro_O`-xBkJq zi@HdK0@bNVFIVsYq|Py8R>F#~qiYLKo-R4Mh&*A!;8RQYtJbyto$`6G{PTo z%qAoS_p?U_o90yf@khea=1V1s^Q15_`Bx0zBI47Q@P{ZybTs3qOrTA zs4SSbE;GNPWB~OHgt!4P>zP_vkl@&kLcO<2prhgz4@mnOiw|b5W%Xw zIbVPV_z?rJ9|n*0$%yn868~NRg_yHq4Zy39D9(EGSR3nKGDPG}Z)tC)JK-^dwcv z^`j5R@%J<>(CvkiYwgxorPmJ>@tZSHFpzxWS2L;5O4x$FdHS;9mowLx#Gj!;`SFwTTf@G=v9}{T+Avd*5*BaB*WQvCM!-5M zmp$J1Jon4X*x?Y)XbHSNJIcE16>&e8Xd=ND)u_p2YY}Q6m%Dh`u!+9cpPh(dAca8# zd3oQgOHn&hId%w}#>D1yFE4RSs7>48K3s317$pnq9iNt;ko18~BYzb%848RoJ8Z-L ziS6n5Ip!V+hF0PgX!TSQ7#J?7d|xE$Uwy;eO`%!`87HQ6L~45^(ig3A?L4uSZttZU z(n&34u9sdhU@6!fsUtDywm0>SH)Z6^il@cY4F{w-QUiRSL9PgHf-BatquH?Og_cNEWYgKNBn8PZn-o;Nv13;4|dvLXJjbYa_N|D#~x_cu}D( zfqg3-U#)!*2~iD|lxCNyo&A!5pgyysy4dbDVzIwYP2BW{P%Sl5`B_-0e%IpYu~al6 z;tV|rMDI2+MIt!vSeZ0@m$S$v%}5B02;SAiBP5$roy+EQj5!-3q#=tT4k7YDFO$#N zrzHI!JeNXJSawiA?h{?(G{+=ErjGP6o$D*baBl*_ZhAm9hwR(M=C(%CU5^I=7Ay=F zLNjrfz-Wd2rs;8|d+_Y!h~IQcIP%0w{NR*G!d=otgYa6Kfj=Sg3S{er=BCkw_x2J% ze_U-7bLsCx!bMB1O;CGc-q<+yQ>f@b3rWU~_NIG|_<6Ho^9qv#&Va8XjX@5`0kk|% z5TcYFaBDs0HxI(Kp#+{F3hXhjpuTYAqX3htRIIqbmR)pGmL6V~T^f$4o7-_NW}Or7 zbG^h&QktKAd|i_pY6ZCIy%Z2*uVZjFCQVH87&r>+R#=CosJFM&*;4SygbQEGcCMYF zAD2fhV_d~!4`VJ|6BqVKC~}MDz(v__O0gux&98qWhG!*t+iQ~=P{YWXL-9!q5Cu!D z-h1=Gur`89i=?lSUl9$A#BPdG+v6JY9pI0x4JITR$Z=kM1?M&az~*;Z9oEauAvhw` z#RJGQ^94a_*rTl41tvtBQ(Xc&+50gss$jl28G(*3JN22rfIv0^54=JcKIIT}Z&-h}1l$#% zxrV>1vs5-h#!yd4^MFqxv4P316A;237(xsJdtmGQ4Jt8RO`abazHzUZB~oJZ!4M0C z$Cn~`9UP8WC&je+PFSZE=(oG{GieI!Z$RR zkR79#^b1}e&jp%t>=u2wXjrFY@)zHCEyGsh42wE=KH310k+eGE0Ef~nXaI*ICCcT* zX40cEr^IAL^z%Yp>K&uo1rRa+UVl=z$`#x8of#Vpp;KPow3c`ARE8;U166C#39c&@ zH@r@+-^Au*^-T^#P!Eai58|$o;kFranrL@j#v1Q6<35iO{WZ2)Jp2{ENpk%oB`K$q zdOKP#|sG^^c7GQaoSPqKn}lki`24lI?PIddkc@I z3v+bx9Oo`qUO!#X%&BQbZOWh>9^)b>nIrj0`p7}wbA_MHW2L%CyJ$J9N#;OfMUbRa z9Eb255D$y&1w36D36cm+JmkY9%_woeOe!&Aj3O9fykCR1 zn$Yzb4f^a}QasP<+g%q()kf%boiMGfQNju&?2r<_MsqN0ay8~+} z?W&Unz#dLB1d#)z7*!?cY}w?^>N8xswZbvd2^LNtDv%gf6iCdN(zs_s;65X3xL@2U zzzSroC+Hs`xPaX z{VO zlpIv#I4ay_TFieGyI8%QIso}J&Lhez^B#~UAaL6wT+ry)78!Bqtcoce&ozH%SO>OK zPcf_VERonI0IG6Ma^pA+AU?G;))}Tcv~1nY=1SO0<{X?lA`shE0E=pjs;=?qNlV0r znJ$fzTL3Xt#}^DgK1lQ*sXn=;ay}{0W+|0CkQa3T5UEx1Y_v5_QQr#B%eR*~b7|TBNdX1ZK%UNs||i6y~E035Y)?$Va{Hr%+s< zini?pj!5}38k%O1&Nx(zA}+*8iaDMRlEE)jb5nbRmacvyQ;MMHAs|G*-mn|lWc7Om ziqO!}!50pVe0))Q6D{T)w+O_{P`ejl1iu�n4nmHnlC)vxeWNw;(|qwN!oh;vCL= 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 Date: Wed, 3 Apr 2013 20:01:21 +0700 Subject: [PATCH 3/5] detect not only str but also bytes change generation of nonce --- build/lib/oauth2/__init__.py | 39 ++++++++++++++++++---------------- dist/oauth2-1.5.211-py3.3.egg | Bin 53952 -> 54970 bytes oauth2/__init__.py | 39 ++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/build/lib/oauth2/__init__.py b/build/lib/oauth2/__init__.py index 35203d79..ea70b73c 100644 --- a/build/lib/oauth2/__init__.py +++ b/build/lib/oauth2/__init__.py @@ -31,7 +31,7 @@ import httplib2 from hashlib import sha1 -import _version +import oauth2._version __version__ = _version.__version__ @@ -83,25 +83,26 @@ def build_xoauth_string(url, consumer, token=None): 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, str): + if not isinstance(s, (bytes, 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 as 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,)) + if isinstance(s, bytes): + try: + s = s.decode('utf-8') + except UnicodeDecodeError as 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, str): + if isinstance(s, (bytes, str)): return to_unicode(s) else: return s def to_utf8_if_string(s): - if isinstance(s, str): + if isinstance(s, (bytes, str)): return to_utf8(s) else: return s @@ -111,7 +112,7 @@ 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, str): + if isinstance(x, (bytes, str)): return to_unicode(x) try: @@ -127,7 +128,7 @@ 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, str): + if isinstance(x, (bytes, str)): return to_utf8(x) try: @@ -140,7 +141,7 @@ def to_utf8_optional_iterator(x): def escape(s): """Escape a URL including any /.""" - return urllib.parse.quote(s.encode('utf-8'), safe='~') + return urllib.parse.quote(s, safe='~') def generate_timestamp(): """Get seconds since epoch (UTC).""" @@ -388,7 +389,8 @@ def to_postdata(self): """Serialize as post data for a POST request.""" d = {} for k, v in self.items(): - d[k.encode('utf-8')] = to_utf8_optional_iterator(v) + #d[k.encode('utf-8')] = to_utf8_optional_iterator(v) + d[k] = to_unicode_optional_iterator(v) # tell urlencode to deal with sequence values and map them correctly # to resulting querystring. for example self["k"] = ["v1", "v2"] will @@ -475,7 +477,7 @@ def sign_request(self, signature_method, consumer, token): # 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(sha1(self.body).digest()) + self['oauth_body_hash'] = to_unicode(base64.b64encode(sha1(to_utf8(self.body)).digest())) if 'oauth_consumer_key' not in self: self['oauth_consumer_key'] = consumer.key @@ -494,7 +496,8 @@ def make_timestamp(cls): @classmethod def make_nonce(cls): """Generate pseudorandom number.""" - return str(random.randint(0, 100000000)) + #return str(random.randint(0, 100000000)) + return base64.b64encode(("%0x" % random.getrandbits(256)).encode("utf-8"))[:32] @classmethod def from_request(cls, http_method, http_url, headers=None, parameters=None, @@ -623,7 +626,7 @@ def set_signature_method(self, 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' + DEFAULT_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=UTF-8' if not isinstance(headers, dict): headers = {} @@ -633,7 +636,7 @@ def request(self, uri, method="GET", body='', headers=None, DEFAULT_POST_CONTENT_TYPE) is_form_encoded = \ - headers.get('Content-Type') == 'application/x-www-form-urlencoded' + headers.get('Content-Type') == 'application/x-www-form-urlencoded;charset=UTF-8' if is_form_encoded and body: parameters = urllib.parse.parse_qs(body) @@ -822,10 +825,10 @@ def sign(self, request, consumer, token): """Builds the base signature string.""" key, raw = self.signing_base(request, consumer, token) - hashed = hmac.new(key, raw, sha1) + hashed = hmac.new(to_utf8(key), to_utf8(raw), sha1) # Calculate the digest base 64. - return binascii.b2a_base64(hashed.digest())[:-1] + return to_unicode(binascii.b2a_base64(hashed.digest())[:-1]) class SignatureMethod_PLAINTEXT(SignatureMethod): diff --git a/dist/oauth2-1.5.211-py3.3.egg b/dist/oauth2-1.5.211-py3.3.egg index bbdc8d45d055051dd4750ce6c9032b9aeef503b0..404890aaf0505b48d58508eacc58752f9f9acd24 100644 GIT binary patch delta 21606 zcmV(-K-|B;r31RH0}W710|XQR000O8ZJ&dY4LbvEpM#N0>;r9|gOfS|IsgOhgw z#RF}hgOfi3at>^tgF-HKIKZJF006;plfMESf8#ci-}Ni7O3hMAWcrcGB4fJMZ?z7J@=w+^CauBAJYkYUq1eeXRX#LFBfT5rA5xtip_b+uOHb>ndCK}_SmfCoE0+$ z{mL8OV|Brj{E;nrSwV;5x=zwO&2L!3CV){3TGn#_Qx&uNJ}Eggp0cE>ibD&hVXj#qQI3UaV?X@~SS=3Bu^HG@oRv zDdM1BXXzr9BVczTQ`G{Xs|sd-c=p($n5HxQpVPFKtLrSS<~=q|5%l${hKChCBzpI7 z9@|C9DxPI6fRF;-G*^QiwL#RENYGl6sKO`r^J3x7B&}MrRhh$(occ@)AQ_GLe@{NC z@d-Ac64p zZ{Z^~of3zyYFPL*$=I?eX*_?LF^vCl$j(pRk3J91hV1a1ot~Zic=&GkjJ!%hCiL24bRWn$yw|0==9U!5S|?#AAGua zcX<4Py@h_qC&225!0-Tcbi!~z3F>foj=+wFX9pkQ>)`F-r^C^gUhDng=on$XKRIIq zb~-p49UfeK8l17yi?h>{^C1lX4!|BC9=|_>QHDpu<53Kw!ZSAf5x%hVe~*JtpKz$w z-~zBeL;Tpm$?2D~!w(-v?BmI&cSCsib_kda-hLX2pORe9-YD09?bG=q`Q7TJRkPh;Oy`m3G)8z1e@*cF#raSJWbcN9PXG&+=h#`kxYY*j(n=S&-0V83_?y@2 zTb(Yr`V8t|S}fGJRheb!b-YZa6A}YoDHoPr?8mEqv22Ry49Lwpt{(wEQ_*p zw#tzp^rgGsV(jBkS?F1KMMTDyOQF8{Usj z1~6I15h5!llT}&re`y>^;P`{>T%?28peVGxD0^klUr0P zH@s#^S*%D;aOk!y`9$bSLZkw%!1m%dGy?^pW7Ks2e>VRyZjmayp zb0wOxTb}oii<}!o-(ojV)$AwYTqb&gwlC2_;R2i=jZU{gHx8IJb>w@4_b)3jg1{4J z;!}*GHCE$}e?eTwG<3&9rDxazWB^n(;M)~zS;1le90zU01=xNOtRi|rlRyxCm z6&f~%lr?V*=r~y}c|Pq#uPVKE9T9=26#`B2382(to$JS%!~ZahO+A6k=y54@Stt5Z ztQh(ve@O7O6#fW`Ame~0h>aYc90b9s=p^=V#C`S(#OhVmCGyq)6KDY=cDo8K7bU{T z!lf=B-Ni$esNyLnvPLw^sH&oePI6<%0D&kzTD;xR)& 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 delta 20928 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 diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 35203d79..ea70b73c 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -31,7 +31,7 @@ import httplib2 from hashlib import sha1 -import _version +import oauth2._version __version__ = _version.__version__ @@ -83,25 +83,26 @@ def build_xoauth_string(url, consumer, token=None): 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, str): + if not isinstance(s, (bytes, 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 as 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,)) + if isinstance(s, bytes): + try: + s = s.decode('utf-8') + except UnicodeDecodeError as 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, str): + if isinstance(s, (bytes, str)): return to_unicode(s) else: return s def to_utf8_if_string(s): - if isinstance(s, str): + if isinstance(s, (bytes, str)): return to_utf8(s) else: return s @@ -111,7 +112,7 @@ 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, str): + if isinstance(x, (bytes, str)): return to_unicode(x) try: @@ -127,7 +128,7 @@ 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, str): + if isinstance(x, (bytes, str)): return to_utf8(x) try: @@ -140,7 +141,7 @@ def to_utf8_optional_iterator(x): def escape(s): """Escape a URL including any /.""" - return urllib.parse.quote(s.encode('utf-8'), safe='~') + return urllib.parse.quote(s, safe='~') def generate_timestamp(): """Get seconds since epoch (UTC).""" @@ -388,7 +389,8 @@ def to_postdata(self): """Serialize as post data for a POST request.""" d = {} for k, v in self.items(): - d[k.encode('utf-8')] = to_utf8_optional_iterator(v) + #d[k.encode('utf-8')] = to_utf8_optional_iterator(v) + d[k] = to_unicode_optional_iterator(v) # tell urlencode to deal with sequence values and map them correctly # to resulting querystring. for example self["k"] = ["v1", "v2"] will @@ -475,7 +477,7 @@ def sign_request(self, signature_method, consumer, token): # 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(sha1(self.body).digest()) + self['oauth_body_hash'] = to_unicode(base64.b64encode(sha1(to_utf8(self.body)).digest())) if 'oauth_consumer_key' not in self: self['oauth_consumer_key'] = consumer.key @@ -494,7 +496,8 @@ def make_timestamp(cls): @classmethod def make_nonce(cls): """Generate pseudorandom number.""" - return str(random.randint(0, 100000000)) + #return str(random.randint(0, 100000000)) + return base64.b64encode(("%0x" % random.getrandbits(256)).encode("utf-8"))[:32] @classmethod def from_request(cls, http_method, http_url, headers=None, parameters=None, @@ -623,7 +626,7 @@ def set_signature_method(self, 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' + DEFAULT_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=UTF-8' if not isinstance(headers, dict): headers = {} @@ -633,7 +636,7 @@ def request(self, uri, method="GET", body='', headers=None, DEFAULT_POST_CONTENT_TYPE) is_form_encoded = \ - headers.get('Content-Type') == 'application/x-www-form-urlencoded' + headers.get('Content-Type') == 'application/x-www-form-urlencoded;charset=UTF-8' if is_form_encoded and body: parameters = urllib.parse.parse_qs(body) @@ -822,10 +825,10 @@ def sign(self, request, consumer, token): """Builds the base signature string.""" key, raw = self.signing_base(request, consumer, token) - hashed = hmac.new(key, raw, sha1) + hashed = hmac.new(to_utf8(key), to_utf8(raw), sha1) # Calculate the digest base 64. - return binascii.b2a_base64(hashed.digest())[:-1] + return to_unicode(binascii.b2a_base64(hashed.digest())[:-1]) class SignatureMethod_PLAINTEXT(SignatureMethod): From a6b31832ae0427e27e6eda3ce3d121e385c7e9d5 Mon Sep 17 00:00:00 2001 From: Kiwamu Ishikura Date: Wed, 17 Apr 2013 08:33:10 +0700 Subject: [PATCH 4/5] adapted to python3.3 --- build/lib/oauth2/__init__.py | 2 +- dist/oauth2-1.5.211-py3.3.egg | Bin 54970 -> 54965 bytes oauth2/__init__.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/lib/oauth2/__init__.py b/build/lib/oauth2/__init__.py index ea70b73c..d8ff20ba 100644 --- a/build/lib/oauth2/__init__.py +++ b/build/lib/oauth2/__init__.py @@ -594,7 +594,7 @@ def _split_header(header): @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" - parameters = urllib.parse.parse_qs(param_str.encode('utf-8'), keep_blank_values=True) + parameters = urllib.parse.parse_qs(param_str, keep_blank_values=True) for k, v in parameters.items(): parameters[k] = urllib.parse.unquote(v[0]) return parameters diff --git a/dist/oauth2-1.5.211-py3.3.egg b/dist/oauth2-1.5.211-py3.3.egg index 404890aaf0505b48d58508eacc58752f9f9acd24..5f3125001e9d87e5c348d65e3038ab26dc2d8585 100644 GIT binary patch delta 14879 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 Y?L~YDq(l(OA_gdGkrtYH`hP|KKW}B90{{R3 delta 14940 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 diff --git a/oauth2/__init__.py b/oauth2/__init__.py index ea70b73c..d8ff20ba 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -594,7 +594,7 @@ def _split_header(header): @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" - parameters = urllib.parse.parse_qs(param_str.encode('utf-8'), keep_blank_values=True) + parameters = urllib.parse.parse_qs(param_str, keep_blank_values=True) for k, v in parameters.items(): parameters[k] = urllib.parse.unquote(v[0]) return parameters From 47fcd95ad2d29ae85c0a03cc39ddee7ffe921f3d Mon Sep 17 00:00:00 2001 From: Thomas Schaper Date: Mon, 26 Oct 2020 14:38:47 +0100 Subject: [PATCH 5/5] Fix setup.py version detection --- oauth2/_version.py | 2 +- setup.py | 71 +++++++++++++++++++++++----------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/oauth2/_version.py b/oauth2/_version.py index 9d779eaa..1968f88d 100644 --- a/oauth2/_version.py +++ b/oauth2/_version.py @@ -4,7 +4,7 @@ -auto_build_num = "211" +auto_build_num = "212" diff --git a/setup.py b/setup.py index 234e9533..5e3ecf85 100755 --- a/setup.py +++ b/setup.py @@ -1,40 +1,39 @@ #!/usr/bin/env python -from setuptools import setup, find_packages -import os, re +import os +import re -PKG='oauth2' -VERSIONFILE = os.path.join('oauth2', '_version.py') -verstr = "unknown" -try: - verstrline = open(VERSIONFILE, "rt").read() -except EnvironmentError: - pass # Okay, there is no version file. +from setuptools import find_packages, setup + +PKG = 'oauth2' +VERSIONFILE = os.path.join(os.path.dirname(__file__), 'oauth2', '_version.py') +verstrline = open(VERSIONFILE, "rt").read() +MVSRE = r'''^manual_verstr *= *['"]([^'"]*)['"]''' +mo = re.search(MVSRE, verstrline, re.M) +if mo: + mverstr = mo.group(1) +else: + print("unable to find version in %s" % (VERSIONFILE, )) + raise RuntimeError( + "if %s.py exists, it must be well-formed" % (VERSIONFILE, )) +AVSRE = r'''^auto_build_num *= *['"]([^'"]*)['"]''' +mo = re.search(AVSRE, verstrline, re.M) +if mo: + averstr = mo.group(1) else: - MVSRE = r"^manual_verstr *= *['\"]([^'\"]*)['\"]" - mo = re.search(MVSRE, verstrline, re.M) - if mo: - mverstr = mo.group(1) - else: - print("unable to find version in %s" % (VERSIONFILE,)) - raise RuntimeError("if %s.py exists, it must be well-formed" % (VERSIONFILE,)) - AVSRE = r"^auto_build_num *= *['\"]([^'\"]*)['\"]" - mo = re.search(AVSRE, verstrline, re.M) - if mo: - averstr = mo.group(1) - else: - averstr = '' - verstr = '.'.join([mverstr, averstr]) + averstr = '' +verstr = '.'.join([mverstr, averstr]) -setup(name=PKG, - version=verstr, - description="library for OAuth version 1.0", - author="Joe Stump", - author_email="joe@simplegeo.com", - url="http://github.com/simplegeo/python-oauth2", - packages = find_packages(), - install_requires = ['httplib2'], - license = "MIT License", - keywords="oauth", - zip_safe = True, - test_suite="tests", - tests_require=['coverage', 'mock']) +setup( + name=PKG, + version=verstr, + description="library for OAuth version 1.0", + author="Joe Stump", + author_email="joe@simplegeo.com", + url="http://github.com/simplegeo/python-oauth2", + packages=find_packages(), + install_requires=['httplib2'], + license="MIT License", + keywords="oauth", + zip_safe=True, + test_suite="tests", + tests_require=['coverage', 'mock'])