diff -Nru python-oauth2-1.2.0/debian/changelog python-oauth2-6.0.1/debian/changelog --- python-oauth2-1.2.0/debian/changelog 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/changelog 2011-05-20 00:24:23.000000000 +0000 @@ -1,11 +1,11 @@ -python-oauth2 (1.2.0-2) unstable; urgency=low +python-oauth2 (6.0.1-0ubuntu4) natty; urgency=low - * debian/control: Added python-httplib2 to Depends:. (Closes: #605455) + * For Natty - -- TANIGUCHI Takaki Tue, 30 Nov 2010 16:45:25 +0900 + -- Martin Owens (DoctorMO) Thu, 19 May 2011 20:12:27 -0400 -python-oauth2 (1.2.0-1) unstable; urgency=low +python-oauth2 (6.0.1-0ubuntu1) maverick; urgency=low - * Initial release (Closes: #602061) + * Python build for OAuth 2.0 draft 10 (hopefully) - -- TANIGUCHI Takaki Mon, 01 Nov 2010 17:20:34 +0900 + -- Martin Owens (DoctorMO) Wed, 18 May 2011 00:32:19 -0400 diff -Nru python-oauth2-1.2.0/debian/compat python-oauth2-6.0.1/debian/compat --- python-oauth2-1.2.0/debian/compat 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/compat 2011-05-20 00:24:23.000000000 +0000 @@ -1 +1 @@ -7 +5 diff -Nru python-oauth2-1.2.0/debian/control python-oauth2-6.0.1/debian/control --- python-oauth2-1.2.0/debian/control 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/control 2011-05-20 00:24:23.000000000 +0000 @@ -1,19 +1,23 @@ Source: python-oauth2 Section: python -Priority: extra -Maintainer: TANIGUCHI Takaki -Uploaders: Debian Python Modules Team -Build-Depends: debhelper (>= 7.0.50~), python-all, python-support, python-setuptools -Standards-Version: 3.9.1 -Homepage: http://github.com/simplegeo/python-oauth2 -#Vcs-Git: git://git.debian.org/collab-maint/python-oauth2.git -#Vcs-Browser: http://git.debian.org/?p=collab-maint/python-oauth2.git;a=summary +Priority: optional +Maintainer: Martin Owens +Standards-Version: 3.8.0 +XS-Python-Version: all +Build-Depends: debhelper (>= 4.1.13), cdbs (>= 0.4.49), python, python-central, python-setuptools, python-support +Homepage: http://www.google.com/ Package: python-oauth2 Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-httplib2 +XB-Python-Version: ${python:Versions} +Depends: ${python:Depends}, python-httplib2 Provides: ${python:Provides} -Description: Library for OAuth version 1.0a (forked from python-oauth) - python-oauth2 implements OAuth, which is an open protocol to allow API - authentication in a simple and standard method from desktop and web - applications. This was forked from python-oauth +Suggests: +Description: A Python OAuth class with several useful + features. + . + * 100% unit test coverage. + * The Request class now extends from dict. + * The Client class works and extends from httplib2. + It's a thin wrapper that handles automatically signing + any normal HTTP request you might wish to make. diff -Nru python-oauth2-1.2.0/debian/copyright python-oauth2-6.0.1/debian/copyright --- python-oauth2-1.2.0/debian/copyright 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/copyright 2011-05-20 00:24:23.000000000 +0000 @@ -1,44 +1,3 @@ -This work was packaged for Debian by: - TANIGUCHI Takaki on Mon, 01 Nov 2010 17:20:34 +0900 +Copyright (c) 2010 Martin OWens -It was downloaded from: - - http://pypi.python.org/pypi/oauth2/1.2.0 - -Upstream Author: - - Joe Stump - -Copyright: - -Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel - -License: - -The MIT License - -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. - -The Debian packaging is: - - Copyright (C) 2010 TANIGUCHI Takaki - -and is licensed under the GPL version 3, -see "/usr/share/common-licenses/GPL-3". diff -Nru python-oauth2-1.2.0/debian/pycompat python-oauth2-6.0.1/debian/pycompat --- python-oauth2-1.2.0/debian/pycompat 1970-01-01 00:00:00.000000000 +0000 +++ python-oauth2-6.0.1/debian/pycompat 2011-05-20 00:24:23.000000000 +0000 @@ -0,0 +1 @@ +2 diff -Nru python-oauth2-1.2.0/debian/pyversions python-oauth2-6.0.1/debian/pyversions --- python-oauth2-1.2.0/debian/pyversions 1970-01-01 00:00:00.000000000 +0000 +++ python-oauth2-6.0.1/debian/pyversions 2011-05-20 00:24:23.000000000 +0000 @@ -0,0 +1 @@ +2.5- diff -Nru python-oauth2-1.2.0/debian/rules python-oauth2-6.0.1/debian/rules --- python-oauth2-1.2.0/debian/rules 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/rules 2011-05-20 00:24:23.000000000 +0000 @@ -1,13 +1,8 @@ #!/usr/bin/make -f -# -*- makefile -*- -# Sample debian/rules that uses debhelper. -# This file was originally written by Joey Hess and Craig Small. -# As a special exception, when this file is copied by dh-make into a -# dh-make output file, you may use that output file without restriction. -# This special exception was added by Craig Small in version 0.37 of dh-make. +# -*- mode: makefile; coding: utf-8 -*- -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 +DEB_PYTHON_SYSTEM = pysupport + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/python-distutils.mk -%: - dh $@ diff -Nru python-oauth2-1.2.0/debian/source/format python-oauth2-6.0.1/debian/source/format --- python-oauth2-1.2.0/debian/source/format 2011-05-20 00:24:23.000000000 +0000 +++ python-oauth2-6.0.1/debian/source/format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -3.0 (quilt) diff -Nru python-oauth2-1.2.0/debian/watch python-oauth2-6.0.1/debian/watch --- python-oauth2-1.2.0/debian/watch 2010-11-30 07:45:36.000000000 +0000 +++ python-oauth2-6.0.1/debian/watch 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -version=3 -http://pypi.python.org/packages/source/o/oauth2/oauth2-(.*).tar.gz diff -Nru python-oauth2-1.2.0/oauth2/clients/imap.py python-oauth2-6.0.1/oauth2/clients/imap.py --- python-oauth2-1.2.0/oauth2/clients/imap.py 2010-05-14 17:29:08.000000000 +0000 +++ python-oauth2-6.0.1/oauth2/clients/imap.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,40 +0,0 @@ -""" -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 -Nru python-oauth2-1.2.0/oauth2/clients/smtp.py python-oauth2-6.0.1/oauth2/clients/smtp.py --- python-oauth2-1.2.0/oauth2/clients/smtp.py 2010-05-14 17:28:48.000000000 +0000 +++ python-oauth2-6.0.1/oauth2/clients/smtp.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -""" -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 -Nru python-oauth2-1.2.0/oauth2/__init__.py python-oauth2-6.0.1/oauth2/__init__.py --- python-oauth2-1.2.0/oauth2/__init__.py 2010-05-14 17:34:26.000000000 +0000 +++ python-oauth2-6.0.1/oauth2/__init__.py 2011-05-18 03:57:43.000000000 +0000 @@ -1,25 +1,24 @@ +# +# Now AGPLv3 was The MIT License +# +# Copyright (c) 2007 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel +# Copyright (c) 2011 Martin Owens +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Affero GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# """ -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. +OAuth2-compatible version of the annoyingly-named python-oauth2 """ import urllib @@ -35,8 +34,24 @@ except ImportError: from cgi import parse_qs, parse_qsl +try: + # Python 2.6? + import json + simplejson = json +except ImportError: + try: + # Have simplejson? + import simplejson + except ImportError: + # Have django or are running in the Google App Engine? + from django.utils import simplejson + -VERSION = '1.0' # Hi Blaine! +__name__ = 'oauth2' +__version__ = '6.0.1' + +# OAuth Version +VERSION = '2.0' HTTP_METHOD = 'GET' SIGNATURE_METHOD = 'PLAINTEXT' @@ -55,32 +70,14 @@ def __str__(self): return self._message - class MissingSignature(Error): pass - def build_authenticate_header(realm=''): """Optional WWW-Authenticate header (401 error)""" return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} -def build_xoauth_string(url, consumer, token=None): - """Build an XOAUTH string for use in SMTP/IMPA authentication.""" - request = Request.from_consumer_and_token(consumer, token, - "GET", url) - - signing_method = SignatureMethod_HMAC_SHA1() - request.sign_request(signing_method, consumer, token) - - params = [] - for k, v in sorted(request.iteritems()): - if v is not None: - params.append('%s="%s"' % (k, escape(v))) - - return "%s %s %s" % ("GET", url, ','.join(params)) - - def escape(s): """Escape a URL including any /.""" return urllib.quote(s, safe='~') @@ -132,8 +129,10 @@ raise ValueError("Key and secret must be set.") def __str__(self): - data = {'oauth_consumer_key': self.key, - 'oauth_consumer_secret': self.secret} + data = { + 'oauth_consumer_key': self.key, + 'oauth_consumer_secret': self.secret + } return urllib.urlencode(data) @@ -232,7 +231,7 @@ try: token.callback_confirmed = params['oauth_callback_confirmed'][0] except KeyError: - pass # 1.0, no callback confirmed. + pass # 1.0, no callback confirmed. return token def __str__(self): @@ -263,9 +262,7 @@ 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 = VERSION def __init__(self, method=HTTP_METHOD, url=None, parameters=None): @@ -489,69 +486,6 @@ 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=None, headers=None, - redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): - DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded' - - if not isinstance(headers, dict): - headers = {} - - is_multipart = method == 'POST' and headers.get('Content-Type', - DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE - - if body and method == "POST" and not is_multipart: - parameters = dict(parse_qsl(body)) - else: - parameters = None - - req = Request.from_consumer_and_token(self.consumer, - token=self.token, http_method=method, http_url=uri, - parameters=parameters) - - req.sign_request(self.method, self.consumer, self.token) - - if method == "POST": - headers['Content-Type'] = headers.get('Content-Type', - DEFAULT_CONTENT_TYPE) - if is_multipart: - headers.update(req.to_header()) - else: - body = req.to_postdata() - elif method == "GET": - uri = req.to_url() - else: - headers.update(req.to_header()) - - 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. @@ -643,8 +577,68 @@ 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)) + 'greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) + + +class Client(httplib2.Http): + """OAuthClient is a worker to attempt to execute a request.""" + + def __init__(self, consumer, token=None, cache=None, timeout=None, + proxy_info=None): + + if consumer is not None and not isinstance(consumer, Consumer): + raise ValueError("Invalid consumer.") + + if token is not None and not isinstance(token, Token): + raise ValueError("Invalid token.") + + self.consumer = consumer + self.token = token + self.method = SignatureMethod_HMAC_SHA1() + + httplib2.Http.__init__(self, cache=cache, timeout=timeout, + proxy_info=proxy_info) + + def set_signature_method(self, method): + if not isinstance(method, SignatureMethod): + raise ValueError("Invalid signature method.") + + self.method = method + + def request(self, uri, method="GET", body=None, headers=None, + redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): + DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded' + + if not isinstance(headers, dict): + headers = {} + + is_multipart = method == 'POST' and headers.get('Content-Type', DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE + + if body and method == "POST" and not is_multipart: + parameters = dict(parse_qsl(body)) + else: + parameters = None + + req = Request.from_consumer_and_token(self.consumer, token=self.token, + http_method=method, http_url=uri, parameters=parameters) + + req.sign_request(self.method, self.consumer, self.token) + + + if method == "POST": + headers['Content-Type'] = headers.get('Content-Type', DEFAULT_CONTENT_TYPE) + if is_multipart: + headers.update(req.to_header()) + else: + body = req.to_postdata() + elif method == "GET": + uri = req.to_url() + else: + headers.update(req.to_header()) + + return httplib2.Http.request(self, uri, method=method, body=body, + headers=headers, redirections=redirections, + connection_type=connection_type) class SignatureMethod(object): @@ -714,7 +708,6 @@ # Calculate the digest base 64. return binascii.b2a_base64(hashed.digest())[:-1] - class SignatureMethod_PLAINTEXT(SignatureMethod): name = 'PLAINTEXT' @@ -730,3 +723,139 @@ def sign(self, request, consumer, token): key, raw = self.signing_base(request, consumer, token) return raw + +class Client2(object): + """Client for OAuth 2.0 draft spec + https://svn.tools.ietf.org/html/draft-hammer-oauth2-15~ + """ + + def __init__(self, client_id, client_secret, oauth_base_url, + redirect_uri=None, cache=None, timeout=None, proxy_info=None): + + self.client_id = client_id + self.client_secret = client_secret + self.redirect_uri = redirect_uri + self.oauth_base_url = oauth_base_url + + if self.client_id is None or self.client_secret is None or \ + self.oauth_base_url is None: + raise ValueError("Client_id and client_secret must be set.") + + self.http = httplib2.Http(cache=cache, timeout=timeout, + proxy_info=proxy_info) + + @staticmethod + def _split_url_string(param_str): + """Turn URL string into parameters.""" + parameters = parse_qs(param_str, keep_blank_values=False) + for key, val in parameters.iteritems(): + parameters[key] = urllib.unquote(val[0]) + return parameters + + def authorization_url(self, redirect_uri=None, params=None, state=None, + immediate=None, endpoint='authorize'): + """Get the URL to redirect the user for client authorization + https://svn.tools.ietf.org/html/draft-hammer-oauth2-15#section-3.5.2.1 + """ + + # prepare required args + args = { + 'response_type': 'code', + 'client_id': self.client_id, + } + + # prepare optional args + redirect_uri = redirect_uri or self.redirect_uri + if redirect_uri is not None: + args['redirect_uri'] = redirect_uri + if state is not None: + args['state'] = state + if immediate is not None: + args['immediate'] = str(immediate).lower() + + args.update(params or {}) + + return '%s?%s' % (urlparse.urljoin(self.oauth_base_url, endpoint), + urllib.urlencode(args)) + + def access_token(self, code, redirect_uri, grant_type=None, + endpoint='access_token'): + """Get an access token from the supplied code + https://svn.tools.ietf.org/html/draft-hammer-oauth2-15#section-3.5.2.2 + """ + + # prepare required args + if code is None: + raise ValueError("Code must be set.") + if redirect_uri is None: + raise ValueError("Redirect_uri must be set.") + args = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'code': code, + 'redirect_uri': redirect_uri, + } + + # prepare optional args + if grant_type is not None: + args['grant_type'] = grant_type + + uri = urlparse.urljoin(self.oauth_base_url, endpoint) + if args: + uri = '%s?%s' % (uri, urllib.urlencode(args)) + return self._json_request(uri) + + def _json_request(self, uri, method=None, data=None, headers=None): + """Make a uri request and deal with errors""" + if method == 'POST' and data: + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + } + + response, content = self.http.request(uri, + method=method, body=data, headers=headers) + if not response.status == 200: + raise Error(str(response.status)) + response = simplejson.loads(content) + error = response.pop('error', None) + if error is not None: + raise Error("%s: %s" % (error, + response.get('error_description', str(response)))) + return response + + + def refresh_token(self, refresh_token, endpoint='access_token', grant_type='refresh_token'): + """Get a new access token from the supplied refresh token + https://svn.tools.ietf.org/html/draft-hammer-oauth2-00#section-4 + """ + + if refresh_token is None: + raise ValueError("Refresh_token must be set.") + + # prepare required args + args = { + 'grant_type': grant_type, + 'client_id': self.client_id, + 'client_secret': self.client_secret, + 'refresh_token': refresh_token, + } + + uri = urlparse.urljoin(self.oauth_base_url, endpoint) + body = urllib.urlencode(args) + return self._json_request(uri, method='POST', data=body) + + def request(self, uri, access_token=None, method='GET', body=None, + headers=None, params=None, token_param='oauth_token'): + """Make a request to the OAuth API""" + args = params or {} + if access_token is not None and method == 'GET': + args[token_param] = access_token + elif access_token is None and method == 'GET': + args.update({ + 'client_id': self.client_id, + 'client_secret': self.client_secret, + }) + if args: + uri = '%s?%s' % (uri, urllib.urlencode(args)) + return self.http.request(uri, method=method, body=body, headers=headers) + diff -Nru python-oauth2-1.2.0/oauth2.egg-info/PKG-INFO python-oauth2-6.0.1/oauth2.egg-info/PKG-INFO --- python-oauth2-1.2.0/oauth2.egg-info/PKG-INFO 2010-05-14 17:55:39.000000000 +0000 +++ python-oauth2-6.0.1/oauth2.egg-info/PKG-INFO 2011-05-18 04:31:37.000000000 +0000 @@ -1,11 +1,11 @@ Metadata-Version: 1.0 Name: oauth2 -Version: 1.2.0 -Summary: Library for OAuth version 1.0a. -Home-page: http://github.com/simplegeo/python-oauth2 -Author: Joe Stump -Author-email: joe@simplegeo.com -License: MIT License +Version: 6.0.1 +Summary: Library for OAuth version 2.0 draft 10. +Home-page: http://www.google.com +Author: Martin Owens +Author-email: doctormo@gmail.com +License: MIT Description: UNKNOWN Keywords: oauth Platform: UNKNOWN diff -Nru python-oauth2-1.2.0/oauth2.egg-info/SOURCES.txt python-oauth2-6.0.1/oauth2.egg-info/SOURCES.txt --- python-oauth2-1.2.0/oauth2.egg-info/SOURCES.txt 2010-05-14 17:55:39.000000000 +0000 +++ python-oauth2-6.0.1/oauth2.egg-info/SOURCES.txt 2011-05-18 04:31:37.000000000 +0000 @@ -1,12 +1,8 @@ -setup.cfg +README.txt setup.py oauth2/__init__.py oauth2.egg-info/PKG-INFO oauth2.egg-info/SOURCES.txt oauth2.egg-info/dependency_links.txt oauth2.egg-info/requires.txt -oauth2.egg-info/top_level.txt -oauth2.egg-info/zip-safe -oauth2/clients/__init__.py -oauth2/clients/imap.py -oauth2/clients/smtp.py \ No newline at end of file +oauth2.egg-info/top_level.txt \ No newline at end of file diff -Nru python-oauth2-1.2.0/oauth2.egg-info/zip-safe python-oauth2-6.0.1/oauth2.egg-info/zip-safe --- python-oauth2-1.2.0/oauth2.egg-info/zip-safe 2010-03-18 22:43:40.000000000 +0000 +++ python-oauth2-6.0.1/oauth2.egg-info/zip-safe 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ - diff -Nru python-oauth2-1.2.0/PKG-INFO python-oauth2-6.0.1/PKG-INFO --- python-oauth2-1.2.0/PKG-INFO 2010-05-14 17:55:39.000000000 +0000 +++ python-oauth2-6.0.1/PKG-INFO 2011-05-18 04:31:37.000000000 +0000 @@ -1,11 +1,11 @@ Metadata-Version: 1.0 Name: oauth2 -Version: 1.2.0 -Summary: Library for OAuth version 1.0a. -Home-page: http://github.com/simplegeo/python-oauth2 -Author: Joe Stump -Author-email: joe@simplegeo.com -License: MIT License +Version: 6.0.1 +Summary: Library for OAuth version 2.0 draft 10. +Home-page: http://www.google.com +Author: Martin Owens +Author-email: doctormo@gmail.com +License: MIT Description: UNKNOWN Keywords: oauth Platform: UNKNOWN diff -Nru python-oauth2-1.2.0/README.txt python-oauth2-6.0.1/README.txt --- python-oauth2-1.2.0/README.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-oauth2-6.0.1/README.txt 2011-05-12 19:42:14.000000000 +0000 @@ -0,0 +1,288 @@ +# Documentation + +# Signing a Request + + import oauth2 as oauth + import time + + # Set the API endpoint + url = "http://example.com/photos" + + # Set the base oauth_* parameters along with any other parameters required + # for the API call. + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()) + 'user': 'joestump', + 'photoid': 555555555555 + } + + # Set up instances of our Token and Consumer. The Consumer.key and + # Consumer.secret are given to you by the API provider. The Token.key and + # Token.secret is given to you after a three-legged authentication. + token = oauth.Token(key="tok-test-key", secret="tok-test-secret") + consumer = oauth.Consumer(key="con-test-key", secret="con-test-secret") + + # Set our token/key parameters + params['oauth_token'] = tok.key + params['oauth_consumer_key'] = con.key + + # Create our request. Change method, etc. accordingly. + req = oauth.Request(method="GET", url=url, parameters=params) + + # Sign the request. + signature_method = oauth.SignatureMethod_HMAC_SHA1() + req.sign_request(signature_method, consumer, token) + +# Using the Client + +The oauth2.Client is based on httplib2 and works just as you'd expect it to. The only difference is the first two arguments to the constructor are an instance of oauth2.Consumer and oauth2.Token (oauth2.Token is only needed for three-legged requests). + + import oauth2 as oauth + + # Create your consumer with the proper key/secret. + consumer = oauth.Consumer(key="your-twitter-consumer-key", + secret="your-twitter-consumer-secret") + + # Request token URL for Twitter. + request_token_url = "http://twitter.com/oauth/request_token" + + # Create our client. + client = oauth.Client(consumer) + + # The OAuth Client request works just like httplib2 for the most part. + resp, content = client.request(request_token_url, "GET") + print resp + print content + +# Twitter Three-legged OAuth Example + +Below is an example of how one would go through a three-legged OAuth flow to +gain access to protected resources on Twitter. This is a simple CLI script, but +can be easily translated to a web application. + + import urlparse + import oauth2 as oauth + + consumer_key = 'my_key_from_twitter' + consumer_secret = 'my_secret_from_twitter' + + request_token_url = 'http://twitter.com/oauth/request_token' + access_token_url = 'http://twitter.com/oauth/access_token' + authorize_url = 'http://twitter.com/oauth/authorize' + + consumer = oauth.Consumer(consumer_key, consumer_secret) + client = oauth.Client(consumer) + + # Step 1: Get a request token. This is a temporary token that is used for + # having the user authorize an access token and to sign the request to obtain + # said access token. + + resp, content = client.request(request_token_url, "GET") + if resp['status'] != '200': + raise Exception("Invalid response %s." % resp['status']) + + request_token = dict(urlparse.parse_qsl(content)) + + print "Request Token:" + print " - oauth_token = %s" % request_token['oauth_token'] + print " - oauth_token_secret = %s" % request_token['oauth_token_secret'] + print + + # Step 2: Redirect to the provider. Since this is a CLI script we do not + # redirect. In a web application you would redirect the user to the URL + # below. + + print "Go to the following link in your browser:" + print "%s?oauth_token=%s" % (authorize_url, request_token['oauth_token']) + print + + # After the user has granted access to you, the consumer, the provider will + # redirect you to whatever URL you have told them to redirect to. You can + # usually define this in the oauth_callback argument as well. + accepted = 'n' + while accepted.lower() == 'n': + accepted = raw_input('Have you authorized me? (y/n) ') + oauth_verifier = raw_input('What is the PIN? ') + + # Step 3: Once the consumer has redirected the user back to the oauth_callback + # URL you can request the access token the user has approved. You use the + # request token to sign this request. After this is done you throw away the + # request token and use the access token returned. You should store this + # access token somewhere safe, like a database, for future use. + token = oauth.Token(request_token['oauth_token'], + request_token['oauth_token_secret']) + token.set_verifier(oauth_verifier) + client = oauth.Client(consumer, token) + + resp, content = client.request(access_token_url, "POST") + access_token = dict(urlparse.parse_qsl(content)) + + print "Access Token:" + print " - oauth_token = %s" % access_token['oauth_token'] + print " - oauth_token_secret = %s" % access_token['oauth_token_secret'] + print + print "You may now access protected resources using the access tokens above." + print + +# Logging into Django w/ Twitter + +Twitter also has the ability to authenticate a user [via an OAuth flow](http://apiwiki.twitter.com/Sign-in-with-Twitter). This +flow is exactly like the three-legged OAuth flow, except you send them to a +slightly different URL to authorize them. + +In this example we'll look at how you can implement this login flow using +Django and python-oauth2. + +## Set up a Profile model + +You'll need a place to store all of your Twitter OAuth credentials after the +user has logged in. In your app's `models.py` file you should add something +that resembles the following model. + + class Profile(models.Model): + user = models.ForeignKey(User) + oauth_token = models.CharField(max_length=200) + oauth_secret = models.CharField(max_length=200) + +## Set up your Django views + +### `urls.py` + +Your `urls.py` should look something like the following. Basically, you need to +have a login URL, a callback URL that Twitter will redirect your users back to, +and a logout URL. + +In this example `^login/` and `twitter_login` will send the user to Twitter to +be logged in, `^login/authenticated/` and `twitter_authenticated` will confirm +the login, create the account if necessary, and log the user into the +application, and `^logout`/ logs the user out in the `twitter_logout` view. + + + from django.conf.urls.defaults import * + from django.contrib import admin + from mytwitterapp.views import twitter_login, twitter_logout, \ + twitter_authenticated + + admin.autodiscover() + + urlpatterns = patterns('', + url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + url(r'^admin/', include(admin.site.urls)), + url(r'^login/?$', twitter_login), + url(r'^logout/?$', twitter_logout), + url(r'^login/authenticated/?$', twitter_authenticated), + ) + +### `views.py` + +*NOTE:* The following code was coded for Python 2.4 so some of the libraries +and code here might need to be updated if you are using Python 2.6+. + + # Python + import oauth2 as oauth + import cgi + + # Django + from django.shortcuts import render_to_response + from django.http import HttpResponseRedirect + from django.conf import settings + from django.contrib.auth import authenticate, login, logout + from django.contrib.auth.models import User + from django.contrib.auth.decorators import login_required + + # Project + from mytwitterapp.models import Profile + + # It's probably a good idea to put your consumer's OAuth token and + # OAuth secret into your project's settings. + consumer = oauth.Consumer(settings.TWITTER_TOKEN, settings.TWITTER_SECRET) + client = oauth.Client(consumer) + + request_token_url = 'http://twitter.com/oauth/request_token' + access_token_url = 'http://twitter.com/oauth/access_token' + + # This is the slightly different URL used to authenticate/authorize. + authenticate_url = 'http://twitter.com/oauth/authenticate' + + def twitter_login(request): + # Step 1. Get a request token from Twitter. + resp, content = client.request(request_token_url, "GET") + if resp['status'] != '200': + raise Exception("Invalid response from Twitter.") + + # Step 2. Store the request token in a session for later use. + request.session['request_token'] = dict(cgi.parse_qsl(content)) + + # Step 3. Redirect the user to the authentication URL. + url = "%s?oauth_token=%s" % (authenticate_url, + request.session['request_token']['oauth_token']) + + return HttpResponseRedirect(url) + + + @login_required + def twitter_logout(request): + # Log a user out using Django's logout function and redirect them + # back to the homepage. + logout(request) + return HttpResponseRedirect('/') + + def twitter_authenticated(request): + # Step 1. Use the request token in the session to build a new client. + token = oauth.Token(request.session['request_token']['oauth_token'], + request.session['request_token']['oauth_token_secret']) + client = oauth.Client(consumer, token) + + # Step 2. Request the authorized access token from Twitter. + resp, content = client.request(access_token_url, "GET") + if resp['status'] != '200': + print content + raise Exception("Invalid response from Twitter.") + + """ + This is what you'll get back from Twitter. Note that it includes the + user's user_id and screen_name. + { + 'oauth_token_secret': 'IcJXPiJh8be3BjDWW50uCY31chyhsMHEhqJVsphC3M', + 'user_id': '120889797', + 'oauth_token': '120889797-H5zNnM3qE0iFoTTpNEHIz3noL9FKzXiOxwtnyVOD', + 'screen_name': 'heyismysiteup' + } + """ + access_token = dict(cgi.parse_qsl(content)) + + # Step 3. Lookup the user or create them if they don't exist. + try: + user = User.objects.get(username=access_token['screen_name']) + except User.DoesNotExist: + # When creating the user I just use their screen_name@twitter.com + # for their email and the oauth_token_secret for their password. + # These two things will likely never be used. Alternatively, you + # can prompt them for their email here. Either way, the password + # should never be used. + user = User.objects.create_user(access_token['screen_name'], + '%s@twitter.com' % access_token['screen_name'], + access_token['oauth_token_secret']) + + # Save our permanent token and secret for later. + profile = Profile() + profile.user = user + profile.oauth_token = access_token['oauth_token'] + profile.oauth_secret = access_token['oauth_token_secret'] + profile.save() + + # Authenticate the user and log them in using Django's pre-built + # functions for these things. + user = authenticate(username=access_token['screen_name'], + password=access_token['oauth_token_secret']) + login(request, user) + + return HttpResponseRedirect('/') + + +### `settings.py` + +* You'll likely want to set `LOGIN_URL` to `/login/` so that users are properly redirected to your Twitter login handler when you use `@login_required` in other parts of your Django app. +* You can also set `AUTH_PROFILE_MODULE = 'mytwitterapp.Profile'` so that you can easily access the Twitter OAuth token/secret for that user using the `User.get_profile()` method in Django. diff -Nru python-oauth2-1.2.0/setup.cfg python-oauth2-6.0.1/setup.cfg --- python-oauth2-1.2.0/setup.cfg 2010-05-14 17:55:39.000000000 +0000 +++ python-oauth2-6.0.1/setup.cfg 2011-05-18 04:31:37.000000000 +0000 @@ -3,6 +3,3 @@ tag_date = 0 tag_svn_revision = 0 -[aliases] -test = nosetests - diff -Nru python-oauth2-1.2.0/setup.py python-oauth2-6.0.1/setup.py --- python-oauth2-1.2.0/setup.py 2010-05-14 17:39:33.000000000 +0000 +++ python-oauth2-6.0.1/setup.py 2011-05-18 04:31:24.000000000 +0000 @@ -3,14 +3,13 @@ from setuptools import setup, find_packages setup(name="oauth2", - version="1.2.0", - description="Library for OAuth version 1.0a.", - author="Joe Stump", - author_email="joe@simplegeo.com", - url="http://github.com/simplegeo/python-oauth2", + version="6.0.1", + description="Library for OAuth version 2.0 draft 10.", + author="Martin Owens", + author_email="doctormo@gmail.com", + url="http://www.google.com", packages = find_packages(), install_requires = ['httplib2'], - license = "MIT License", + license = "MIT", keywords="oauth", - zip_safe = True, - tests_require=['nose', 'coverage', 'mox']) +)