From b0bc25ee5001eb61ed735ef6d2de03fb7963085a Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Sun, 14 Jul 2013 18:24:03 -0400 Subject: [PATCH 1/4] Use print_function for Python 3 support PEP 3105 changed `print` from a statement to a function. In order to use `print` with Python 3, all references need to be updated to a function. Python 2, however, raises a `SyntaxError` when treating `print` as a function. Importing `print_function` from __future__ allows `print` as a function on all versions of Python greater than or equal to 2.6. --- example/client.py | 61 ++++++++++++++++++++++++----------------------- example/server.py | 11 +++++---- setup.py | 3 ++- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/example/client.py b/example/client.py index 34f7dcb9..1605c681 100644 --- a/example/client.py +++ b/example/client.py @@ -26,6 +26,7 @@ or find one that works with your web framework. """ +from __future__ import print_function import httplib import time import oauth.oauth as oauth @@ -59,21 +60,21 @@ def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='', access_ def fetch_request_token(self, oauth_request): # via headers # -> OAuthToken - self.connection.request(oauth_request.http_method, self.request_token_url, headers=oauth_request.to_header()) + self.connection.request(oauth_request.http_method, self.request_token_url, headers=oauth_request.to_header()) response = self.connection.getresponse() return oauth.OAuthToken.from_string(response.read()) def fetch_access_token(self, oauth_request): # via headers # -> OAuthToken - self.connection.request(oauth_request.http_method, self.access_token_url, headers=oauth_request.to_header()) + self.connection.request(oauth_request.http_method, self.access_token_url, headers=oauth_request.to_header()) response = self.connection.getresponse() return oauth.OAuthToken.from_string(response.read()) def authorize_token(self, oauth_request): # via url # -> typically just some okay response - self.connection.request(oauth_request.http_method, oauth_request.to_url()) + self.connection.request(oauth_request.http_method, oauth_request.to_url()) response = self.connection.getresponse() return response.read() @@ -88,7 +89,7 @@ def access_resource(self, oauth_request): def run_example(): # setup - print '** OAuth Python Library Example **' + print('** OAuth Python Library Example **') client = SimpleOAuthClient(SERVER, PORT, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL) consumer = oauth.OAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET) signature_method_plaintext = oauth.OAuthSignatureMethod_PLAINTEXT() @@ -96,70 +97,70 @@ def run_example(): pause() # get request token - print '* Obtain a request token ...' + print('* Obtain a request token ...') pause() oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, callback=CALLBACK_URL, http_url=client.request_token_url) oauth_request.sign_request(signature_method_plaintext, consumer, None) - print 'REQUEST (via headers)' - print 'parameters: %s' % str(oauth_request.parameters) + print('REQUEST (via headers)') + print('parameters: %s' % str(oauth_request.parameters)) pause() token = client.fetch_request_token(oauth_request) - print 'GOT' - print 'key: %s' % str(token.key) - print 'secret: %s' % str(token.secret) - print 'callback confirmed? %s' % str(token.callback_confirmed) + print('GOT') + print('key: %s' % str(token.key)) + print('secret: %s' % str(token.secret)) + print('callback confirmed? %s' % str(token.callback_confirmed)) pause() - print '* Authorize the request token ...' + print('* Authorize the request token ...') pause() oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=client.authorization_url) - print 'REQUEST (via url query string)' - print 'parameters: %s' % str(oauth_request.parameters) + print('REQUEST (via url query string)') + print('parameters: %s' % str(oauth_request.parameters)) pause() # this will actually occur only on some callback response = client.authorize_token(oauth_request) - print 'GOT' - print response + print('GOT') + print(response) # sad way to get the verifier import urlparse, cgi query = urlparse.urlparse(response)[4] params = cgi.parse_qs(query, keep_blank_values=False) verifier = params['oauth_verifier'][0] - print 'verifier: %s' % verifier + print('verifier: %s' % verifier) pause() # get access token - print '* Obtain an access token ...' + print('* Obtain an access token ...') pause() oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, verifier=verifier, http_url=client.access_token_url) oauth_request.sign_request(signature_method_plaintext, consumer, token) - print 'REQUEST (via headers)' - print 'parameters: %s' % str(oauth_request.parameters) + print('REQUEST (via headers)') + print('parameters: %s' % str(oauth_request.parameters)) pause() token = client.fetch_access_token(oauth_request) - print 'GOT' - print 'key: %s' % str(token.key) - print 'secret: %s' % str(token.secret) + print('GOT') + print('key: %s' % str(token.key)) + print('secret: %s' % str(token.secret)) pause() # access some protected resources - print '* Access protected resources ...' + print('* Access protected resources ...') pause() parameters = {'file': 'vacation.jpg', 'size': 'original'} # resource specific params oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_method='POST', http_url=RESOURCE_URL, parameters=parameters) oauth_request.sign_request(signature_method_hmac_sha1, consumer, token) - print 'REQUEST (via post body)' - print 'parameters: %s' % str(oauth_request.parameters) + print('REQUEST (via post body)') + print('parameters: %s' % str(oauth_request.parameters)) pause() params = client.access_resource(oauth_request) - print 'GOT' - print 'non-oauth parameters: %s' % params + print('GOT') + print('non-oauth parameters: %s' % params) pause() def pause(): - print '' + print('') time.sleep(1) if __name__ == '__main__': run_example() - print 'Done.' \ No newline at end of file + print('Done.') diff --git a/example/server.py b/example/server.py index 5986b0e2..66767d28 100644 --- a/example/server.py +++ b/example/server.py @@ -22,6 +22,7 @@ THE SOFTWARE. """ +from __future__ import print_function from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import urllib @@ -102,13 +103,13 @@ def send_oauth_error(self, err=None): # return the authenticate header header = oauth.build_authenticate_header(realm=REALM) for k, v in header.iteritems(): - self.send_header(k, v) + self.send_header(k, v) def do_GET(self): # debug info - #print self.command, self.path, self.headers - + #print(self.command, self.path, self.headers) + # get the post data (if any) postdata = None if self.command == 'POST': @@ -186,10 +187,10 @@ def do_POST(self): def main(): try: server = HTTPServer(('', 8080), RequestHandler) - print 'Test server running...' + print('Test server running...') server.serve_forever() except KeyboardInterrupt: server.socket.close() if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/setup.py b/setup.py index acc41e17..7952712b 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import print_function from setuptools import setup, find_packages import os, re @@ -15,7 +16,7 @@ if mo: mverstr = mo.group(1) else: - print "unable to find version in %s" % (VERSIONFILE,) + 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) From e8a3ab78a787b9834d5175cea3452fdacdb8b88c Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Sat, 27 Jul 2013 17:02:41 -0400 Subject: [PATCH 2/4] Remove redundant print In b0bc25e I turned the print statement into the print function. Upon further thought, print isn't needed at all because the exception that is raised immediately after serves the same purpose. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 7952712b..58888609 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -from __future__ import print_function from setuptools import setup, find_packages import os, re @@ -16,7 +15,6 @@ 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) From bed739f15c8319d290551e7155c059a811be6b84 Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Sat, 7 Sep 2013 11:10:36 -0400 Subject: [PATCH 3/4] Update exception catching syntax In order to support newer versions of Python, the old syntax needs to be replaced. Sorry Python 2.5. --- oauth2/__init__.py | 120 ++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index 835270e3..b2a7b847 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -102,7 +102,7 @@ def to_unicode(s): 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: + 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 @@ -131,7 +131,7 @@ def to_unicode_optional_iterator(x): try: l = list(x) - except TypeError, e: + except TypeError as e: assert 'is not iterable' in str(e) return x else: @@ -147,7 +147,7 @@ def to_utf8_optional_iterator(x): try: l = list(x) - except TypeError, e: + except TypeError as e: assert 'is not iterable' in str(e) return x else: @@ -174,11 +174,11 @@ def generate_verifier(length=8): 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 @@ -186,7 +186,7 @@ class Consumer(object): 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. @@ -212,12 +212,12 @@ def __str__(self): 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 @@ -262,7 +262,7 @@ def get_callback_url(self): 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. """ @@ -275,7 +275,7 @@ def to_string(self): if self.callback_confirmed is not None: data['oauth_callback_confirmed'] = self.callback_confirmed return urllib.urlencode(data) - + @staticmethod def from_string(s): """Deserializes a token from a string like one returned by @@ -296,7 +296,7 @@ def from_string(s): try: secret = params['oauth_token_secret'][0] except Exception: - raise ValueError("'oauth_token_secret' not found in " + raise ValueError("'oauth_token_secret' not found in " "OAuth request.") token = Token(key, secret) @@ -312,31 +312,31 @@ def __str__(self): 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, @@ -372,33 +372,33 @@ def url(self, value): 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.iteritems() + return dict([(k, v) for k, v in self.iteritems() 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() + 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 = {} @@ -409,7 +409,7 @@ def to_postdata(self): # 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') - + def to_url(self): """Serialize as a URL for a GET request.""" base_url = urlparse.urlparse(self.url) @@ -421,7 +421,7 @@ def to_url(self): query = parse_qs(query) for k, v in self.items(): query.setdefault(k, []).append(v) - + try: scheme = base_url.scheme netloc = base_url.netloc @@ -435,7 +435,7 @@ def to_url(self): path = base_url[2] params = base_url[3] fragment = base_url[5] - + url = (scheme, netloc, path, params, urllib.urlencode(query, True), fragment) return urlparse.urlunparse(url) @@ -460,7 +460,7 @@ def get_normalized_parameters(self): 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: @@ -500,24 +500,24 @@ def sign_request(self, signature_method, consumer, token): 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'] @@ -531,61 +531,61 @@ def from_request(cls, http_method, http_url, headers=None, parameters=None, 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 = urlparse.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, + + 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, + 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.""" @@ -602,7 +602,7 @@ def _split_header(header): # Remove quotes and unescape the value. params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) return params - + @staticmethod def _split_url_string(param_str): """Turn URL string into parameters.""" @@ -636,7 +636,7 @@ def set_signature_method(self, method): self.method = method - def request(self, uri, method="GET", body='', headers=None, + 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' @@ -644,7 +644,7 @@ def request(self, uri, method="GET", body='', headers=None, headers = {} if method == "POST": - headers['Content-Type'] = headers.get('Content-Type', + headers['Content-Type'] = headers.get('Content-Type', DEFAULT_POST_CONTENT_TYPE) is_form_encoded = \ @@ -655,8 +655,8 @@ def request(self, uri, method="GET", body='', headers=None, else: parameters = None - req = Request.from_consumer_and_token(self.consumer, - token=self.token, http_method=method, http_url=uri, + 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) @@ -685,7 +685,7 @@ def request(self, uri, method="GET", body='', headers=None, 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. @@ -764,7 +764,7 @@ def _check_signature(self, request, consumer, token): if not valid: key, base = signature_method.signing_base(request, consumer, token) - raise Error('Invalid signature. Expected signature base ' + raise Error('Invalid signature. Expected signature base ' 'string: %s' % base) def _check_timestamp(self, timestamp): @@ -774,13 +774,13 @@ def _check_timestamp(self, timestamp): 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, + '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 From 97f39aa6e94147de6fc9afe15d2841babd38cede Mon Sep 17 00:00:00 2001 From: Andy Dirnberger Date: Sat, 7 Sep 2013 11:13:24 -0400 Subject: [PATCH 4/4] Update imports `urlparse` has moved. Python 3 moved it to `urllib.parse`. A try/except can be used to handle supporting both Python 2 and 3. This also changes (and simplifies) how to get `parse_qs`. Since bed739f killed supported for Python 2.5, the old `sha` import can be removed, using only the newer import instead. --- oauth2/__init__.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/oauth2/__init__.py b/oauth2/__init__.py index b2a7b847..735ccef2 100644 --- a/oauth2/__init__.py +++ b/oauth2/__init__.py @@ -26,26 +26,19 @@ import urllib import time import random -import urlparse +try: + import urllib.parse as urlparse +except ImportError: + import urlparse 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 +parse_qs = urlparse.parse_qs -try: - from hashlib import sha1 - sha = sha1 -except ImportError: - # hashlib was added in Python 2.5 - import sha +from hashlib import sha1 as sha -import _version +from . import _version __version__ = _version.__version__