diff --git a/.gitignore b/.gitignore
index 74df2303..01a9178a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
*.py?
*.egg-info
*.swp
+.coverage
+coverage.xml
+nosetests.xml
+.tox
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..70231fa0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+language: python
+python:
+ - "2.6"
+ - "2.7"
+ - "3.4"
+ - "3.5"
+ - "3.6"
+ - "3.7-dev"
+ - "nightly"
+install:
+ - pip install -r requirements.txt
+ - pip install codecov pytest-cov
+script:
+ - py.test --cov=oauth2
+after_success:
+ - codecov
diff --git a/README.md b/README.md
index 3dca1e34..67ad4222 100644
--- a/README.md
+++ b/README.md
@@ -1,357 +1,36 @@
-# Overview
-
-This code was originally forked from [Leah Culver and Andy Smith's oauth.py code](http://github.com/leah/python-oauth/). Some of the tests come from a [fork by Vic Fryzel](http://github.com/shellsage/python-oauth), while a revamped Request class and more tests were merged in from [Mark Paschal's fork](http://github.com/markpasc/python-oauth). A number of notable differences exist between this code and its forefathers:
-
-* 100% unit test coverage.
-* The DataStore object has been completely ripped out. While creating unit tests for the library I found several substantial bugs with the implementation and confirmed with Andy Smith that it was never fully baked.
-* Classes are no longer prefixed with OAuth.
-* The Request class now extends from dict.
-* The library is likely no longer compatible with Python 2.3.
-* 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.
-
-# 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'] = token.key
- params['oauth_consumer_key'] = consumer.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.
+[](https://gitter.im/joestump/python-oauth2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://travis-ci.org/joestump/python-oauth2) [](https://codecov.io/gh/joestump/python-oauth2)  
- class Profile(models.Model):
- user = models.ForeignKey(User)
- oauth_token = models.CharField(max_length=200)
- oauth_secret = models.CharField(max_length=200)
+## Note: This library implements OAuth 1.0 and *not OAuth 2.0*.
-## 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))
+# Overview
+python-oauth2 is a python oauth library fully compatible with python versions: 2.6, 2.7, 3.3 and 3.4. This library is depended on by many other downstream packages such as Flask-Oauth.
- # Step 3. Redirect the user to the authentication URL.
- url = "%s?oauth_token=%s" % (authenticate_url,
- request.session['request_token']['oauth_token'])
+# Installing
- return HttpResponseRedirect(url)
+You can install `oauth2` via [the PIP package](https://pypi.python.org/pypi/oauth2).
+ $ pip install oauth2
- @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('/')
+We recommend using [virtualenv](https://virtualenv.pypa.io/en/latest/).
- 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.
-
-# XOAUTH for IMAP and SMTP
-
-Gmail supports OAuth over IMAP and SMTP via a standard they call XOAUTH. This allows you to authenticate against Gmail's IMAP and SMTP servers using an OAuth token and secret. It also has the added benefit of allowing you to use vanilla SMTP and IMAP libraries. The `python-oauth2` package provides both IMAP and SMTP libraries that implement XOAUTH and wrap `imaplib.IMAP4_SSL` and `smtplib.SMTP`. This allows you to connect to Gmail with OAuth credentials using standard Python libraries.
-
-## IMAP
-
- import oauth2 as oauth
- import oauth2.clients.imap as imaplib
-
- # Set up your Consumer and Token as per usual. Just like any other
- # three-legged OAuth request.
- consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
- token = oauth.Token('your_users_3_legged_token',
- 'your_users_3_legged_token_secret')
-
- # Setup the URL according to Google's XOAUTH implementation. Be sure
- # to replace the email here with the appropriate email address that
- # you wish to access.
- url = "https://mail.google.com/mail/b/your_users_email@gmail.com/imap/"
-
- conn = imaplib.IMAP4_SSL('imap.googlemail.com')
- conn.debug = 4
+# Examples
- # This is the only thing in the API for impaplib.IMAP4_SSL that has
- # changed. You now authenticate with the URL, consumer, and token.
- conn.authenticate(url, consumer, token)
+Examples can be found in the [wiki](https://github.com/joestump/python-oauth2/wiki)
- # Once authenticated everything from the impalib.IMAP4_SSL class will
- # work as per usual without any modification to your code.
- conn.select('INBOX')
- print conn.list()
+# Running tests
+You can run tests using the following at the command line:
+ $ pip install -r requirements.txt
+ $ python setup.py test
-## SMTP
- import oauth2 as oauth
- import oauth2.clients.smtp as smtplib
-
- # Set up your Consumer and Token as per usual. Just like any other
- # three-legged OAuth request.
- consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
- token = oauth.Token('your_users_3_legged_token',
- 'your_users_3_legged_token_secret')
-
- # Setup the URL according to Google's XOAUTH implementation. Be sure
- # to replace the email here with the appropriate email address that
- # you wish to access.
- url = "https://mail.google.com/mail/b/your_users_email@gmail.com/smtp/"
-
- conn = smtplib.SMTP('smtp.googlemail.com', 587)
- conn.set_debuglevel(True)
- conn.ehlo('test')
- conn.starttls()
-
- # Again the only thing modified from smtplib.SMTP is the authenticate
- # method, which works identically to the imaplib.IMAP4_SSL method.
- conn.authenticate(url, consumer, token)
+# History
+This code was originally forked from [Leah Culver and Andy Smith's oauth.py code](http://github.com/leah/python-oauth/). Some of the tests come from a [fork by Vic Fryzel](http://github.com/shellsage/python-oauth), while a revamped Request class and more tests were merged in from [Mark Paschal's fork](http://github.com/markpasc/python-oauth). A number of notable differences exist between this code and its forefathers:
+* 100% unit test coverage.
+* The DataStore object has been completely ripped out. While creating unit tests for the library I found several substantial bugs with the implementation and confirmed with Andy Smith that it was never fully baked.
+* Classes are no longer prefixed with OAuth.
+* The Request class now extends from dict.
+* The library is likely no longer compatible with Python 2.3.
+* 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 --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 4c6a2880..00000000
--- a/debian/changelog
+++ /dev/null
@@ -1,396 +0,0 @@
-python-oauth2 (1.5.170) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Mon, 02 May 2011 23:48:16 +0000
-
-python-oauth2 (1.5.169) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Fri, 15 Apr 2011 21:29:41 +0000
-
-python-oauth2 (1.5.168) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Tue, 05 Apr 2011 23:13:18 +0000
-
-python-oauth2 (1.5.167) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Thu, 17 Feb 2011 21:56:49 +0000
-
-python-oauth2 (1.5.166) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * fix encoding of non-ascii data into postdata, really fixes #48 this
- time (comes with test)
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 15 Feb 2011 17:23:00 +0000
-
-python-oauth2 (1.5.165) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * get_normalized_parameters() is required to exclude any
- oauth_signature that was already there, and it is required to
- preserve duplicate keys, even with identical values ref #46, fixes
- pull/49, thanks @zyegfryed
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 07 Feb 2011 19:24:56 +0000
-
-python-oauth2 (1.5.164) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * make it actually inspect the request, and fix the test to require
- the right things of the request ref #46, ref pull/49, thanks
- @zyegfryed
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 07 Feb 2011 18:53:56 +0000
-
-python-oauth2 (1.5.163) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * add test of #46; fixes #46
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Fri, 04 Feb 2011 18:14:56 +0000
-
-python-oauth2 (1.5.162) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * import parse_qs from cgi if it can't be imported from urlparse, thus
- regaining compatibility with Python 2.5 fixes #47 thanks to @vtsao,
- @subsume, @worksology
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Fri, 04 Feb 2011 17:50:56 +0000
-
-python-oauth2 (1.5.161) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * Include all parameters from URL, even ones that begin with "oauth_",
- in signature base. effectively reverts
- https://github.com/simplegeo/python-
- oauth2/commit/50ca9578f598faff5427e58814e2f45d01d3261c fixes #27
- Thanks to @robhudson for the bug report and help debugging.
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Thu, 03 Feb 2011 18:03:59 +0000
-
-python-oauth2 (1.5.160) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * add another unicode test case to the tests just to show to user
- saevarom on github
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Thu, 03 Feb 2011 00:01:55 +0000
-
-python-oauth2 (1.5.159) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Thu, 03 Feb 2011 00:00:04 +0000
-
-python-oauth2 (1.5.158) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * keep empty params parsed from the URL (fixes #22)
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Wed, 02 Feb 2011 19:34:25 +0000
-
-python-oauth2 (1.5.155) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * use multiple values under the same key from the body (fixes #23)
- Thanks to rogerm, ideasculptor, jiivan, timgraham.
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 01 Feb 2011 23:43:56 +0000
-
-python-oauth2 (1.5.154) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Tue, 01 Feb 2011 18:03:58 +0000
-
-python-oauth2 (1.5.153) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * add work-around for bug unicode-handling in urlparse in Python <
- 2.7, including tests
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 01 Feb 2011 16:31:56 +0000
-
-python-oauth2 (1.5.152) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * another test for handling of non-ascii, non-utf-8 urls
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Fri, 28 Jan 2011 18:14:34 +0000
-
-python-oauth2 (1.5.151) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Thu, 27 Jan 2011 05:31:55 +0000
-
-python-oauth2 (1.5.150) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Wed, 26 Jan 2011 17:15:25 +0000
-
-python-oauth2 (1.5.149) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * add the body-hash header as specified in
- http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-
- bodyhash.html
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Wed, 26 Jan 2011 17:11:34 +0000
-
-python-oauth2 (1.5.148) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * default body is the empty string rather than None; add the Realm
- header
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Wed, 26 Jan 2011 17:02:34 +0000
-
-python-oauth2 (1.5.147) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * pass the body and the is_form_encoding flag in to the Request object
- (for future use in implementing the hash-of-body extension)
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 25 Jan 2011 17:39:34 +0000
-
-python-oauth2 (1.5.146) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * refactor internals of the way Client.request() decides whether this
- is a x-www-form-urlencoded request or not
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 25 Jan 2011 17:30:38 +0000
-
-python-oauth2 (1.5.145) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * update version number from 1.4 to 1.5
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 22:08:36 +0000
-
-python-oauth2 (1.4.144) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * more thorough unit tests for non-ascii urls, params, and query-args-
- in-url
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 22:02:34 +0000
-
-python-oauth2 (1.4.143) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * use two-part version number -- version is manually modified, build-
- number is automatically modified
-
- [ Simple Geebus ]
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 20:21:20 +0000
-
-python-oauth2 (1.4.134) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 18:30:58 +0000
-
-python-oauth2 (1.4.133) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * handle unicode objects and utf-8 strings in url and params and
- encode them to utf-8 when serializing
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 18:25:54 +0000
-
-python-oauth2 (1.4.132) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 17:39:52 +0000
-
-python-oauth2 (1.4.131) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 17:32:18 +0000
-
-python-oauth2 (1.4.130) lucid; urgency=low
-
- * UNRELEASED
-
- -- SimpleGeo Nerds Mon, 24 Jan 2011 17:22:46 +0000
-
-python-oauth2 (1.4.129) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * change manually-managed version number from 1.3 to 1.4
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Fri, 21 Jan 2011 19:52:32 +0000
-
-python-oauth2 (1.3.128) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * raise an exception if the URL passed in is not a unicode object nor
- ascii We can't submit a correct URL with arbitrary bytes -- we have
- to submit a utf-8 encoded unicode string. (Otherwise we'll cause
- either a rejection or a signature mismatch in the server, which is
- what has happened at SimpleGeo.) If the caller passes in non-ascii
- things in a str then it would be better for them to change their
- code to decode it to unicode before passing it in than for us to
- decode it, since they have a better chance of knowing what encoding
- it is in -- if we did it we would be guessing.
- * fix flaws pointed out by ieure, plus test failures I'm not entirely
- sure it is appropriate to allow .url and .normalized_url to be non-
- existent in addition to allowing them to be None, but I don't really
- understand the intent of the url setter.
- * Oh, after we've made sure it can be converted to unicode, just leave
- it as a unicode object after that.
- * raise more specific error message if argument to to_unicode() is
- neither unicode nor str
- * a couple of cleanups suggested by ieure in code review
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Mon, 10 Jan 2011 22:42:35 +0000
-
-python-oauth2 (1.3.127) lucid; urgency=low
-
- [ Zooko Ofsimplegeo ]
- * run tests when building
- * raise exception from a test just to see if hudson is *really*
- running these tests. I don't see the test results.
- * put version number in a file where it can be read and written by
- various things (including hudson)
- * make oauth2.__version__ exist, rename VERSION to OAUTH_VERSION for
- clarity
- * debian/control -- build-dep pyflakes
- * fix pyflakes
- * build-dep requires python-coverage
- * switch from nosetests to the builtin test runner Just because it
- doesn't seem to currently use any feature of nosetest and this way
- we can reduce the dependencies by one. We'll feel free to switch it
- back to using nosetests (and add the dep to debian/control this
- time) if we want to use some extra feature of nose.
- * remove test failure which was inserted just to see if hudson was
- running the tests properly
- * Fix bug inserted by pyflakes inspired cleanup earlier -- it turns
- out that _get_version() wasn't dead code but was having a necessary
- side-effect. Add new method named _check_version() to be clearer
- about what it does compared to _get_version()
- * port tests from mox to mock because they were failing and I don't
- understand mox. now they are working. add mock to the python and
- debian deps. remove apparently left-over line of code that runs
- "sign()" at the end of checking a signature
-
- [ SimpleGeo Nerds ]
-
- -- SimpleGeo Nerds Tue, 04 Jan 2011 19:39:34 +0000
-
-python-oauth2 (1.2.1) lucid; urgency=low
-
- [ Joe Stump ]
- * Fixed a minor spelling error, added tests for
- oauth2.Error.__str__(), and added a test for build_xoauth_string().
- * Added a 'make test' command to the Makefile.
- * Added a bunch of new tests. 100% coverage has lapsed a bit. Fixing
- now.
-
- [ Ian Eure ]
- * Update deb rule in makefile to sign packages and move them to
- dist/deb.
-
- [ Joe Stump ]
- * Implemented a 'fix' for a bug in certain OAuth providers.
- http://bit.ly/aUrH43
-
- [ Ian Eure ]
- * Update packaging.
- * Fix version disparity.
-
- [ coulix ]
- * get normalised parameters should not take into account oauth_ params
-
- [ Mathias Herberts ]
- * Modified get_normalized_parameters so it does not encode '~' for
- full conformance with RFC 5849 section 3.6
-
- [ Peter Bengtsson ]
- * made it python 2.4 compatible
-
- [ Roderic Campbell ]
- * tok != token. con != consumer. May be confusing for people not
- actually reading the code.
-
- [ Ian Eure ]
-
- -- Ian Eure Mon, 28 Jun 2010 12:02:02 -0700
-
-python-oauth2 (1.2.1pre2) unstable; urgency=low
-
- * Fix version disparity between setup.py & debian/changelog.
-
- -- Ian Eure Fri, 11 Jun 2010 16:11:41 -0700
-
-python-oauth2 (1.2.1pre1) unstable; urgency=low
-
- * Make a native package.
- * Increment version.
- * Make debian/rules executable.
- * Update standards-version, maintainers.
- * Remvoe unneeded python-central build dependency.
-
- -- Ian Eure Fri, 11 Jun 2010 16:09:01 -0700
-
-python-oauth2 (1.0.0-1simplegeo01) karmic; urgency=low
-
- * Initial build for SimpleGeo
-
- -- SimpleGeo Nerds Wed, 21 Oct 2009 23:24:00 -0700
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index 7ed6ff82..00000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-5
diff --git a/debian/control b/debian/control
deleted file mode 100644
index de0d42fa..00000000
--- a/debian/control
+++ /dev/null
@@ -1,24 +0,0 @@
-Source: python-oauth2
-Section: python
-Priority: optional
-Maintainer: SimpleGeo Nerds
-Uploaders: Chris Lea , Ian Eure
-Standards-Version: 3.8.4
-XS-Python-Version: all
-Build-Depends: debhelper (>= 4.1.13), cdbs (>= 0.4.49), python, python-setuptools, python-support, pyflakes, python-coverage (>= 2.85), python-mock
-Homepage: http://github.com/simplegeo/python-oauth2
-
-Package: python-oauth2
-Architecture: all
-XB-Python-Version: ${python:Versions}
-Depends: ${python:Depends}, python-httplib2
-Provides: ${python:Provides}
-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 --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 8f5e8bda..00000000
--- a/debian/copyright
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License
-
-Copyright (c) 2007 Leah Culver
-
-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.
\ No newline at end of file
diff --git a/debian/pycompat b/debian/pycompat
deleted file mode 100644
index 0cfbf088..00000000
--- a/debian/pycompat
+++ /dev/null
@@ -1 +0,0 @@
-2
diff --git a/debian/pyversions b/debian/pyversions
deleted file mode 100644
index b3dc41eb..00000000
--- a/debian/pyversions
+++ /dev/null
@@ -1 +0,0 @@
-2.5-
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 4608bb84..00000000
--- a/debian/rules
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/make -f
-# -*- mode: makefile; coding: utf-8 -*-
-
-DEB_PYTHON_SYSTEM = pysupport
-
-include /usr/share/cdbs/1/rules/debhelper.mk
-include /usr/share/cdbs/1/class/python-distutils.mk
-
-build/python-oauth2::
- pyflakes oauth2
- python-coverage run --branch --include=oauth2/* setup.py test
- python-coverage html -d $(ARTIFACTS)/htmlcov
diff --git a/example/appengine_oauth.py b/example/appengine_oauth.py
index 814f9b6f..c14da764 100644
--- a/example/appengine_oauth.py
+++ b/example/appengine_oauth.py
@@ -86,7 +86,7 @@ def is_valid(self):
request = self.get_oauth_request()
client = self.get_client(request)
params = self._server.verify_request(request, client, None)
- except Exception, e:
+ except Exception as e:
raise e
return client
@@ -95,7 +95,7 @@ class SampleHandler(OAuthHandler):
def get(self):
try:
client = self.is_valid()
- except Exception, e:
+ except Exception as e:
self.error(500)
self.response.out.write(e)
diff --git a/example/client.py b/example/client.py
index 34f7dcb9..58524a9d 100644
--- a/example/client.py
+++ b/example/client.py
@@ -41,39 +41,45 @@
CALLBACK_URL = 'http://printer.example.com/request_token_ready'
RESOURCE_URL = 'http://photos.example.net/photos'
-# key and secret granted by the service provider for this consumer application - same as the MockOAuthDataStore
+# key and secret granted by the service provider for this consumer
+# application - same as the MockOAuthDataStore
CONSUMER_KEY = 'key'
CONSUMER_SECRET = 'secret'
# example client using httplib with headers
class SimpleOAuthClient(oauth.OAuthClient):
- def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='', access_token_url='', authorization_url=''):
+ def __init__(self, server, port=httplib.HTTP_PORT, request_token_url='',
+ access_token_url='', authorization_url=''):
self.server = server
self.port = port
self.request_token_url = request_token_url
self.access_token_url = access_token_url
self.authorization_url = authorization_url
- self.connection = httplib.HTTPConnection("%s:%d" % (self.server, self.port))
+ self.connection = httplib.HTTPConnection(
+ "%s:%d" % (self.server, self.port))
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()
@@ -81,85 +87,95 @@ def access_resource(self, oauth_request):
# via post body
# -> some protected resources
headers = {'Content-Type' :'application/x-www-form-urlencoded'}
- self.connection.request('POST', RESOURCE_URL, body=oauth_request.to_postdata(), headers=headers)
+ self.connection.request('POST', RESOURCE_URL,
+ body=oauth_request.to_postdata(),
+ headers=headers)
response = self.connection.getresponse()
return response.read()
def run_example():
# setup
- print '** OAuth Python Library Example **'
- client = SimpleOAuthClient(SERVER, PORT, REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZATION_URL)
+ 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()
signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
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 = 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)
+ 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))
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 = 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)
+ 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..e072f380 100644
--- a/example/server.py
+++ b/example/server.py
@@ -60,7 +60,11 @@ def lookup_token(self, token_type, token):
return None
def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
- if oauth_token and oauth_consumer.key == self.consumer.key and (oauth_token.key == self.request_token.key or oauth_token.key == self.access_token.key) and nonce == self.nonce:
+ if (oauth_token and
+ oauth_consumer.key == self.consumer.key and
+ (oauth_token.key == self.request_token.key or
+ oauth_token.key == self.access_token.key) and
+ nonce == self.nonce):
return self.nonce
return None
@@ -74,7 +78,9 @@ def fetch_request_token(self, oauth_consumer, oauth_callback):
return None
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
- if oauth_consumer.key == self.consumer.key and oauth_token.key == self.request_token.key and oauth_verifier == self.verifier:
+ if (oauth_consumer.key == self.consumer.key and
+ oauth_token.key == self.request_token.key and
+ oauth_verifier == self.verifier):
# want to check here if token is authorized
# for mock store, we assume it is
return self.access_token
@@ -91,8 +97,10 @@ class RequestHandler(BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.oauth_server = oauth.OAuthServer(MockOAuthDataStore())
- self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_PLAINTEXT())
- self.oauth_server.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
+ self.oauth_server.add_signature_method(
+ oauth.OAuthSignatureMethod_PLAINTEXT())
+ self.oauth_server.add_signature_method(
+ oauth.OAuthSignatureMethod_HMAC_SHA1())
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
# example way to send an oauth error
@@ -119,7 +127,8 @@ def do_GET(self):
pass
# construct the oauth request from the request parameters
- oauth_request = oauth.OAuthRequest.from_request(self.command, self.path, headers=self.headers, query_string=postdata)
+ oauth_request = oauth.OAuthRequest.from_request(self.command,
+ self.path, headers=self.headers, query_string=postdata)
# request token
if self.path.startswith(REQUEST_TOKEN_URL):
@@ -131,7 +140,7 @@ def do_GET(self):
self.end_headers()
# return the token
self.wfile.write(token.to_string())
- except oauth.OAuthError, err:
+ except oauth.OAuthError as err:
self.send_oauth_error(err)
return
@@ -148,7 +157,7 @@ def do_GET(self):
self.end_headers()
# return the callback url (to show server has it)
self.wfile.write(token.get_callback_url())
- except oauth.OAuthError, err:
+ except oauth.OAuthError as err:
self.send_oauth_error(err)
return
@@ -162,7 +171,7 @@ def do_GET(self):
self.end_headers()
# return the token
self.wfile.write(token.to_string())
- except oauth.OAuthError, err:
+ except oauth.OAuthError as err:
self.send_oauth_error(err)
return
@@ -170,13 +179,14 @@ def do_GET(self):
if self.path.startswith(RESOURCE_URL):
try:
# verify the request has been oauth authorized
- consumer, token, params = self.oauth_server.verify_request(oauth_request)
+ consumer, token, params = self.oauth_server.verify_request(
+ oauth_request)
# send okay response
self.send_response(200, 'OK')
self.end_headers()
# return the extra parameters - just for something to return
self.wfile.write(str(params))
- except oauth.OAuthError, err:
+ except oauth.OAuthError as err:
self.send_oauth_error(err)
return
@@ -186,10 +196,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/oauth2/__init__.py b/oauth2/__init__.py
index 835270e3..38bf520b 100644
--- a/oauth2/__init__.py
+++ b/oauth2/__init__.py
@@ -23,31 +23,28 @@
"""
import base64
-import urllib
+from hashlib import sha1
import time
import random
-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
-
-try:
- from hashlib import sha1
- sha = sha1
-except ImportError:
- # hashlib was added in Python 2.5
- import sha
-
-import _version
-
-__version__ = _version.__version__
+from ._compat import PY3
+from ._compat import b
+from ._compat import parse_qs
+from ._compat import quote
+from ._compat import STRING_TYPES
+from ._compat import TEXT
+from ._compat import u
+from ._compat import unquote
+from ._compat import unquote_to_bytes
+from ._compat import urlencode
+from ._compat import urlsplit
+from ._compat import urlunsplit
+from ._compat import urlparse
+from ._compat import urlunparse
+from ._version import __version__
OAUTH_VERSION = '1.0' # Hi Blaine!
HTTP_METHOD = 'GET'
@@ -87,7 +84,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 +94,33 @@ 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))
+ if not isinstance(s, TEXT):
+ if not isinstance(s, bytes):
+ raise TypeError('You are required to pass either unicode or '
+ 'bytes 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,))
+ except UnicodeDecodeError as le:
+ raise TypeError('You are required to pass either a unicode '
+ 'object or a utf-8-enccoded bytes string here. '
+ 'You passed a bytes 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, STRING_TYPES):
return to_unicode(s)
else:
return s
def to_utf8_if_string(s):
- if isinstance(s, basestring):
+ if isinstance(s, STRING_TYPES):
return to_utf8(s)
else:
return s
@@ -126,12 +130,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, STRING_TYPES):
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 +146,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, STRING_TYPES):
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 +159,9 @@ def to_utf8_optional_iterator(x):
def escape(s):
"""Escape a URL including any /."""
- return urllib.quote(s.encode('utf-8'), safe='~')
+ if not isinstance(s, bytes):
+ s = s.encode('utf-8')
+ return quote(s, safe='~')
def generate_timestamp():
"""Get seconds since epoch (UTC)."""
@@ -164,12 +170,12 @@ def generate_timestamp():
def generate_nonce(length=8):
"""Generate pseudorandom number."""
- return ''.join([str(random.randint(0, 9)) for i in range(length)])
+ return ''.join([str(random.SystemRandom().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)])
+ return ''.join([str(random.SystemRandom().randint(0, 9)) for i in range(length)])
class Consumer(object):
@@ -206,7 +212,7 @@ def __str__(self):
data = {'oauth_consumer_key': self.key,
'oauth_consumer_secret': self.secret}
- return urllib.urlencode(data)
+ return urlencode(data)
class Token(object):
@@ -250,13 +256,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 = 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 urlunparse((scheme, netloc, path, params,
query, fragment))
return self.callback
@@ -266,15 +272,14 @@ def to_string(self):
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,
- }
+ items = [
+ ('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.urlencode(data)
+ items.append(('oauth_callback_confirmed', self.callback_confirmed))
+ return urlencode(items)
@staticmethod
def from_string(s):
@@ -284,7 +289,7 @@ def from_string(s):
if not len(s):
raise ValueError("Invalid parameter string.")
- params = parse_qs(s, keep_blank_values=False)
+ params = parse_qs(u(s), keep_blank_values=False)
if not len(params):
raise ValueError("Invalid parameter string.")
@@ -340,24 +345,24 @@ class Request(dict):
version = OAUTH_VERSION
def __init__(self, method=HTTP_METHOD, url=None, parameters=None,
- body='', is_form_encoded=False):
+ body=b'', 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.iteritems():
+ 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 = urlparse.urlparse(value)
+ scheme, netloc, path, query, fragment = urlsplit(value)
# Exclude default port numbers.
if scheme == 'http' and netloc[-3:] == ':80':
@@ -368,7 +373,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 = urlunsplit((scheme, netloc, path, None, None))
else:
self.normalized_url = None
self.__dict__['url'] = None
@@ -382,14 +387,14 @@ 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=''):
"""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)
+ stringy_params = ((k, escape(v)) for k, v in oauth_params)
header_params = ('%s="%s"' % (k, v) for k, v in stringy_params)
params_header = ', '.join(header_params)
@@ -401,44 +406,40 @@ def to_header(self, realm=''):
def to_postdata(self):
"""Serialize as post data for a POST request."""
- d = {}
- for k, v in self.iteritems():
- d[k.encode('utf-8')] = to_utf8_optional_iterator(v)
+ items = []
+ for k, v in sorted(self.items()): # predictable for testing
+ items.append((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 urlencode(items, True).replace('+', '%20')
def to_url(self):
"""Serialize as a URL for a GET request."""
- base_url = urlparse.urlparse(self.url)
- try:
- query = base_url.query
- except AttributeError:
- # must be python <2.5
- query = base_url[4]
- query = parse_qs(query)
- for k, v in self.items():
- query.setdefault(k, []).append(v)
-
- try:
+ base_url = urlparse(self.url)
+
+ if PY3:
+ query = parse_qs(base_url.query)
+ for k, v in self.items():
+ query.setdefault(k, []).append(to_utf8_optional_iterator(v))
scheme = base_url.scheme
netloc = base_url.netloc
path = base_url.path
params = base_url.params
fragment = base_url.fragment
- except AttributeError:
- # must be python <2.5
- scheme = base_url[0]
- netloc = base_url[1]
- path = base_url[2]
- params = base_url[3]
- fragment = base_url[5]
-
- url = (scheme, netloc, path, params,
- urllib.urlencode(query, True), fragment)
- return urlparse.urlunparse(url)
+ else:
+ query = parse_qs(to_utf8(base_url.query))
+ for k, v in self.items():
+ query.setdefault(to_utf8(k), []).append(to_utf8_optional_iterator(v))
+ scheme = to_utf8(base_url.scheme)
+ netloc = to_utf8(base_url.netloc)
+ path = to_utf8(base_url.path)
+ params = to_utf8(base_url.params)
+ fragment = to_utf8(base_url.fragment)
+
+ url = (scheme, netloc, path, params, urlencode(query, True), fragment)
+ return urlunparse(url)
def get_parameter(self, parameter):
ret = self.get(parameter)
@@ -450,31 +451,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, STRING_TYPES):
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 = 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' ]
+ url_items = [(to_utf8(k), to_utf8_optional_iterator(v)) for k, v in url_items if k != 'oauth_signature' ]
items.extend(url_items)
items.sort()
- encoded_str = urllib.urlencode(items)
+ encoded_str = urlencode(items, True)
# 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 +491,9 @@ 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())
+ if not self.body:
+ self.body = ''
+ self['oauth_body_hash'] = base64.b64encode(sha1(to_utf8(self.body)).digest())
if 'oauth_consumer_key' not in self:
self['oauth_consumer_key'] = consumer.key
@@ -509,7 +512,7 @@ def make_timestamp(cls):
@classmethod
def make_nonce(cls):
"""Generate pseudorandom number."""
- return str(random.randint(0, 100000000))
+ return str(random.SystemRandom().randint(0, 100000000))
@classmethod
def from_request(cls, http_method, http_url, headers=None, parameters=None,
@@ -519,10 +522,15 @@ def from_request(cls, http_method, http_url, headers=None, parameters=None,
parameters = {}
# Headers
- if headers and 'Authorization' in headers:
- auth_header = headers['Authorization']
+ if headers:
+ auth_header = None
+ for k, v in headers.items():
+ if k.lower() == 'authorization' or \
+ k.upper() == 'HTTP_AUTHORIZATION':
+ auth_header = v
+
# Check that the authorization header is OAuth.
- if auth_header[:6] == 'OAuth ':
+ if auth_header and auth_header[:6] == 'OAuth ':
auth_header = auth_header[6:]
try:
# Get the parameters from the header.
@@ -535,10 +543,11 @@ def from_request(cls, http_method, http_url, headers=None, parameters=None,
# 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
+ param_str = urlparse(http_url)[4] # query
url_params = cls._split_url_string(param_str)
parameters.update(url_params)
@@ -550,7 +559,7 @@ def from_request(cls, http_method, http_url, headers=None, parameters=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):
+ body=b'', is_form_encoded=False):
if not parameters:
parameters = {}
@@ -569,8 +578,8 @@ def from_consumer_and_token(cls, consumer, token=None,
if token.verifier:
parameters['oauth_verifier'] = token.verifier
- return Request(http_method, http_url, parameters, body=body,
- is_form_encoded=is_form_encoded)
+ return cls(http_method, http_url, parameters, body=body,
+ is_form_encoded=is_form_encoded)
@classmethod
def from_token_and_callback(cls, token, callback=None,
@@ -600,23 +609,29 @@ 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]] = 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])
+ if not PY3:
+ # If passed unicode with quoted UTF8, Python2's parse_qs leaves
+ # mojibake'd uniocde after unquoting, so encode first.
+ param_str = b(param_str, 'utf-8')
+ parameters = parse_qs(param_str, keep_blank_values=True)
+ for k, v in parameters.items():
+ if len(v) == 1:
+ parameters[k] = unquote(v[0])
+ else:
+ parameters[k] = sorted([unquote(s) for s in v])
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):
+ def __init__(self, consumer, token=None, **kwargs):
if consumer is not None and not isinstance(consumer, Consumer):
raise ValueError("Invalid consumer.")
@@ -628,7 +643,7 @@ def __init__(self, consumer, token=None, cache=None, timeout=None,
self.token = token
self.method = SignatureMethod_HMAC_SHA1()
- httplib2.Http.__init__(self, cache=cache, timeout=timeout, proxy_info=proxy_info)
+ super(Client, self).__init__(**kwargs)
def set_signature_method(self, method):
if not isinstance(method, SignatureMethod):
@@ -636,7 +651,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=b'', headers=None,
redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None):
DEFAULT_POST_CONTENT_TYPE = 'application/x-www-form-urlencoded'
@@ -661,14 +676,8 @@ def request(self, uri, method="GET", body='', headers=None,
req.sign_request(self.method, self.consumer, self.token)
- schema, rest = urllib.splittype(uri)
- if rest.startswith('//'):
- hierpart = '//'
- else:
- hierpart = ''
- host, rest = urllib.splithost(rest)
-
- realm = schema + ':' + hierpart + host
+ scheme, netloc, path, params, query, fragment = urlparse(uri)
+ realm = urlunparse((scheme, netloc, '', None, None, None))
if is_form_encoded:
body = req.to_postdata()
@@ -731,32 +740,29 @@ def _get_version(self, request):
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 = request.get('oauth_signature_method')
+ if signature_method is None:
signature_method = SIGNATURE_METHOD
try:
# Get the signature method object.
- signature_method = self.signature_methods[signature_method]
- except:
+ return self.signature_methods[signature_method]
+ except KeyError:
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')
+ raise Error('Signature method %s not supported try one of the '
+ 'following: %s'
+ % (signature_method, signature_method_names))
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:
+ signature = request.get('oauth_signature')
+ if signature is None:
raise MissingSignature('Missing oauth_signature.')
+ if isinstance(signature, str):
+ signature = signature.encode('ascii', 'ignore')
# Validate the signature.
valid = signature_method.check(request, consumer, token, signature)
@@ -787,7 +793,7 @@ class SignatureMethod(object):
provide a new way to sign requests.
"""
- def signing_base(self, request, consumer, token):
+ def signing_base(self, request, consumer, token): #pragma NO COVER
"""Calculates the string that needs to be signed.
This method returns a 2-tuple containing the starting key for the
@@ -797,7 +803,7 @@ def signing_base(self, request, consumer, token):
"""
raise NotImplementedError
- def sign(self, request, consumer, token):
+ def sign(self, request, consumer, token): #pragma NO COVER
"""Returns the signature for the given request, based on the consumer
and token also provided.
@@ -818,7 +824,7 @@ 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:
+ if (not hasattr(request, 'normalized_url') or request.normalized_url is None):
raise ValueError("Base URL for request is not set.")
sig = (
@@ -831,13 +837,13 @@ def signing_base(self, request, consumer, token):
if token:
key += escape(token.secret)
raw = '&'.join(sig)
- return key, raw
+ return key.encode('ascii'), raw.encode('ascii')
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]
@@ -857,4 +863,4 @@ def signing_base(self, request, consumer, token):
def sign(self, request, consumer, token):
key, raw = self.signing_base(request, consumer, token)
- return raw
+ return raw.encode('utf8')
diff --git a/oauth2/_compat.py b/oauth2/_compat.py
new file mode 100644
index 00000000..b3f9ad01
--- /dev/null
+++ b/oauth2/_compat.py
@@ -0,0 +1,48 @@
+try:
+ TEXT = unicode
+except NameError: #pragma NO COVER Py3k
+ PY3 = True
+ TEXT = str
+ STRING_TYPES = (str, bytes)
+ def b(x, encoding='ascii'):
+ return bytes(x, encoding)
+else: #pragma NO COVER Python2
+ PY3 = False
+ STRING_TYPES = (unicode, bytes)
+ def b(x, encoding='ascii'):
+ if isinstance(x, unicode):
+ x = x.encode(encoding)
+ return x
+
+def u(x, encoding='ascii'):
+ if isinstance(x, TEXT): #pragma NO COVER
+ return x
+ try:
+ return x.decode(encoding)
+ except AttributeError: #pragma NO COVER
+ raise ValueError('WTF: %s' % x)
+
+try:
+ import urlparse
+except ImportError: #pragma NO COVER Py3k
+ from urllib.parse import parse_qs
+ from urllib.parse import parse_qsl
+ from urllib.parse import quote
+ from urllib.parse import unquote
+ from urllib.parse import unquote_to_bytes
+ from urllib.parse import urlencode
+ from urllib.parse import urlsplit
+ from urllib.parse import urlunsplit
+ from urllib.parse import urlparse
+ from urllib.parse import urlunparse
+else: #pragma NO COVER Python2
+ from urlparse import parse_qs
+ from urlparse import parse_qsl
+ from urllib import quote
+ from urllib import unquote
+ from urllib import urlencode
+ from urlparse import urlsplit
+ from urlparse import urlunsplit
+ from urlparse import urlparse
+ from urlparse import urlunparse
+ unquote_to_bytes = unquote
diff --git a/oauth2/_version.py b/oauth2/_version.py
index 0d74e056..3b813cb2 100644
--- a/oauth2/_version.py
+++ b/oauth2/_version.py
@@ -1,18 +1,19 @@
# This is the version of this source code.
-manual_verstr = "1.5"
+manual_verstr = "1.9"
-auto_build_num = "170"
+auto_build_num = "0.post1"
verstr = manual_verstr + "." + auto_build_num
try:
from pyutil.version_class import Version as pyutil_Version
- __version__ = pyutil_Version(verstr)
-except (ImportError, ValueError):
+except (ImportError, ValueError): #pragma NO COVER
# Maybe there is no pyutil installed.
from distutils.version import LooseVersion as distutils_Version
__version__ = distutils_Version(verstr)
+else: #pragma NO COVER
+ __version__ = pyutil_Version(verstr)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..f65e7d36
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+httplib2==0.9.1
+mock==1.3.0
+pep8==1.6.2
+pytest==2.7.2
diff --git a/setup.cfg b/setup.cfg
index e69de29b..f0dddbe6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -0,0 +1,7 @@
+[nosetests]
+nocapture=1
+cover-package=oauth2
+cover-erase=1
+
+[bdist_wheel]
+universal=1
\ No newline at end of file
diff --git a/setup.py b/setup.py
index acc41e17..6050f666 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)
@@ -27,14 +28,27 @@
setup(name=PKG,
version=verstr,
- description="library for OAuth version 1.0",
+ description="library for OAuth version 1.9",
author="Joe Stump",
author_email="joe@simplegeo.com",
- url="http://github.com/simplegeo/python-oauth2",
- packages = find_packages(),
+ url="http://github.com/joestump/python-oauth2",
+ classifiers=[
+ "Intended Audience :: Developers",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Development Status :: 5 - Production/Stable",
+ "Natural Language :: English",
+ "License :: OSI Approved :: MIT License"
+ ],
+ packages = find_packages(exclude=['tests']),
install_requires = ['httplib2'],
license = "MIT License",
keywords="oauth",
zip_safe = True,
test_suite="tests",
- tests_require=['coverage', 'mock'])
+ tests_require=['mock'])
diff --git a/tests/test_oauth.py b/tests/test_oauth.py
index 099e5794..b9802cbc 100644
--- a/tests/test_oauth.py
+++ b/tests/test_oauth.py
@@ -24,45 +24,55 @@
THE SOFTWARE.
"""
import sys
-import os
-import unittest
-import oauth2 as oauth
import random
import time
-import urllib
-import urlparse
-from types import ListType
-import mock
+import unittest
+
import httplib2
+import mock
-# Fix for python2.5 compatibility
-try:
- from urlparse import parse_qs, parse_qsl
-except ImportError:
- from cgi import parse_qs, parse_qsl
+from oauth2._compat import b
+from oauth2._compat import unquote
+from oauth2._compat import urlencode
+from oauth2._compat import urlparse
+from oauth2._compat import urlunparse
+from oauth2._compat import parse_qs
+from oauth2._compat import parse_qsl
+from oauth2._compat import u
+import oauth2 as oauth
-sys.path[0:0] = [os.path.join(os.path.dirname(__file__), ".."),]
+_UEMPTY = u('')
+_UBLANK = u(' ')
+_BSMILEY = b':-)'
+_USMILEY = u(_BSMILEY)
+_BGLYPH = b'\xae'
+_UGLYPH = u(_BGLYPH, 'latin1')
+_B2019 = b'\xe2\x80\x99' # u'\u2019' encoded to UTF-8
+_U2019 = u(_B2019, 'utf8') # u'\u2019'
+_B2766 = b'\xe2\x9d\xa6' # u'\u2766' encoded to UTF-8
+_U2766 = u(_B2766, 'utf8') # u'\u2766'
+PY3 = sys.version_info >= (3,)
class TestError(unittest.TestCase):
def test_message(self):
try:
raise oauth.Error
- except oauth.Error, e:
+ except oauth.Error as e:
self.assertEqual(e.message, 'OAuth error occurred.')
msg = 'OMG THINGS BROKE!!!!'
try:
raise oauth.Error(msg)
- except oauth.Error, e:
+ except oauth.Error as e:
self.assertEqual(e.message, msg)
def test_str(self):
try:
raise oauth.Error
- except oauth.Error, e:
- self.assertEquals(str(e), 'OAuth error occurred.')
+ except oauth.Error as e:
+ self.assertEqual(str(e), 'OAuth error occurred.')
class TestGenerateFunctions(unittest.TestCase):
def test_build_auth_header(self):
@@ -90,18 +100,18 @@ def test_build_xoauth_string(self):
parts = oauth_string.split(',')
for part in parts:
var, val = part.split('=')
- returned[var] = val.strip('"')
+ 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.assertEqual('HMAC-SHA1', returned['oauth_signature_method'])
+ self.assertEqual('user_token', returned['oauth_token'])
+ self.assertEqual('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))
+ self.assertTrue('~' in oauth.escape(string))
string = '../../../../../../../etc/passwd'
- self.assert_('../' not in oauth.escape(string))
+ self.assertTrue('../' not in oauth.escape(string))
def test_gen_nonce(self):
nonce = oauth.generate_nonce()
@@ -139,8 +149,8 @@ def test_str(self):
res = dict(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)
+ self.assertEqual(res['oauth_consumer_key'], self.consumer.key)
+ self.assertEqual(res['oauth_consumer_secret'], self.consumer.secret)
class TestToken(unittest.TestCase):
def setUp(self):
@@ -207,8 +217,7 @@ def test_get_callback_url(self):
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)
+ string = 'oauth_token=%s&oauth_token_secret=%s' % (self.key, self.secret)
self.assertEqual(self.token.to_string(), string)
self.token.set_callback('http://www.example.com/my-callback')
@@ -225,9 +234,9 @@ def _compare_tokens(self, new):
# TODO: What about copying the verifier to the new token?
# self.assertEqual(self.token.verifier, new.verifier)
- def test_to_string(self):
+ def test___str__(self):
tok = oauth.Token('tooken', 'seecret')
- self.assertEqual(str(tok), 'oauth_token_secret=seecret&oauth_token=tooken')
+ self.assertEqual(str(tok), 'oauth_token=tooken&oauth_token_secret=seecret')
def test_from_string(self):
self.assertRaises(ValueError, lambda: oauth.Token.from_string(''))
@@ -251,43 +260,94 @@ def test_from_string(self):
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))
+ def assertReallyEqual(self, a, b, msg=None):
+ self.assertEqual(a, b, msg=msg)
+ self.assertEqual(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(':-)'), u':-)')
- self.failUnlessEqual(oauth.to_unicode(u'\u00ae'), u'\u00ae')
- self.failUnlessEqual(oauth.to_unicode('\xc2\xae'), u'\u00ae')
- self.failUnlessEqual(oauth.to_unicode_optional_iterator([':-)']), [u':-)'])
- self.failUnlessEqual(oauth.to_unicode_optional_iterator([u'\u00ae']), [u'\u00ae'])
+ self.assertRaises(TypeError, oauth.to_unicode, 0)
+ self.assertRaises(TypeError, oauth.to_unicode, b'\xae')
+ self.assertRaises(TypeError, oauth.to_unicode_optional_iterator, b'\xae')
+ self.assertRaises(TypeError, oauth.to_unicode_optional_iterator, [b'\xae'])
+
+ self.assertEqual(oauth.to_unicode(_BSMILEY), _USMILEY)
+ self.assertEqual(oauth.to_unicode(_UGLYPH), _UGLYPH)
+ self.assertEqual(oauth.to_unicode(b'\xc2\xae'), _UGLYPH)
+
+ def test_to_utf8(self):
+ self.assertRaises(TypeError, oauth.to_utf8, 0)
+ self.assertRaises(TypeError, oauth.to_utf8, b'\x81')
+ self.assertEqual(oauth.to_utf8(_BSMILEY), _BSMILEY)
+ self.assertEqual(oauth.to_utf8(_UGLYPH),
+ _UGLYPH.encode('utf8'))
+
+ def test_to_unicode_if_string(self):
+ self.assertTrue(oauth.to_unicode_if_string(self) is self)
+ self.assertEqual(oauth.to_unicode_if_string(_BSMILEY), _USMILEY)
+
+ def test_to_utf8_if_string(self):
+ self.assertTrue(oauth.to_utf8_if_string(self) is self)
+ self.assertEqual(oauth.to_utf8_if_string(_USMILEY), _BSMILEY)
+ self.assertEqual(oauth.to_utf8_if_string(_UGLYPH),
+ _UGLYPH.encode('utf8'))
+
+ def test_to_unicode_optional_iterator(self):
+ self.assertEqual(oauth.to_unicode_optional_iterator(_BSMILEY),
+ _USMILEY)
+ self.assertEqual(oauth.to_unicode_optional_iterator(_UGLYPH),
+ _UGLYPH)
+ self.assertEqual(oauth.to_unicode_optional_iterator([_BSMILEY]),
+ [_USMILEY])
+ self.assertEqual(oauth.to_unicode_optional_iterator([_UGLYPH]),
+ [_UGLYPH])
+ self.assertEqual(oauth.to_unicode_optional_iterator((_UGLYPH,)),
+ [_UGLYPH])
+ self.assertTrue(oauth.to_unicode_optional_iterator(self) is self)
+
+ def test_to_utf8_optional_iterator(self):
+ self.assertEqual(oauth.to_utf8_optional_iterator(_BSMILEY),
+ _BSMILEY)
+ self.assertEqual(oauth.to_utf8_optional_iterator(_UGLYPH),
+ _UGLYPH.encode('utf8'))
+ self.assertEqual(oauth.to_utf8_optional_iterator([_BSMILEY]),
+ [_BSMILEY])
+ self.assertEqual(oauth.to_utf8_optional_iterator([_USMILEY]),
+ [_BSMILEY])
+ self.assertEqual(oauth.to_utf8_optional_iterator([_UGLYPH]),
+ [_UGLYPH.encode('utf8')])
+ self.assertEqual(oauth.to_utf8_optional_iterator((_UGLYPH,)),
+ [_UGLYPH.encode('utf8')])
+ self.assertTrue(oauth.to_utf8_optional_iterator(self) is self)
class TestRequest(unittest.TestCase, ReallyEqualMixin):
+ def test__init__(self):
+ method = "GET"
+ req = oauth.Request(method)
+ self.assertFalse('url' in req.__dict__)
+ self.assertFalse('normalized_url' in req.__dict__)
+ self.assertRaises(AttributeError, getattr, req, 'url')
+ self.assertRaises(AttributeError, getattr, req, 'normalized_url')
+
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)
+ req = oauth.Request(method, url)
+ self.assertEqual(req.url, url)
+ self.assertEqual(req.normalized_url, url)
+ req.url = url + '/?foo=bar'
+ self.assertEqual(req.url, url + '/?foo=bar')
+ self.assertEqual(req.normalized_url, url + '/')
+ req.url = None
+ self.assertEqual(req.url, None)
+ self.assertEqual(req.normalized_url, 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, e:
- self.fail(str(e))
+ del req.url
+ self.assertRaises(AttributeError, getattr, req, 'url')
def test_url(self):
url1 = "http://example.com:80/foo.php"
@@ -297,12 +357,12 @@ def test_url(self):
method = "GET"
req = oauth.Request(method, url1)
- self.assertEquals(req.normalized_url, exp1)
- self.assertEquals(req.url, url1)
+ self.assertEqual(req.normalized_url, exp1)
+ self.assertEqual(req.url, url1)
req = oauth.Request(method, url2)
- self.assertEquals(req.normalized_url, exp2)
- self.assertEquals(req.url, url2)
+ self.assertEqual(req.normalized_url, exp2)
+ self.assertEqual(req.url, url2)
def test_bad_url(self):
request = oauth.Request()
@@ -319,31 +379,24 @@ def test_unset_consumer_and_token(self):
request.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer,
token)
- self.assertEquals(consumer.key, request['oauth_consumer_key'])
- self.assertEquals(token.key, request['oauth_token'])
+ self.assertEqual(consumer.key, request['oauth_consumer_key'])
+ self.assertEqual(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
+ self.assertRaises(ValueError,
+ request.sign_request,
+ oauth.SignatureMethod_HMAC_SHA1(), consumer, token)
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))
+ url = ("https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10")
+ normalized_url = urlunparse(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)
+ self.assertEqual(req.url, url)
+ self.assertEqual(req.normalized_url, normalized_url)
def test_get_parameter(self):
url = "http://example.com"
@@ -351,7 +404,7 @@ def test_get_parameter(self):
params = {'oauth_consumer' : 'asdf'}
req = oauth.Request(method, url, parameters=params)
- self.assertEquals(req.get_parameter('oauth_consumer'), 'asdf')
+ self.assertEqual(req.get_parameter('oauth_consumer'), 'asdf')
self.assertRaises(oauth.Error, req.get_parameter, 'blah')
def test_get_nonoauth_parameters(self):
@@ -360,10 +413,58 @@ def test_get_nonoauth_parameters(self):
'oauth_consumer': 'asdfasdfasdf'
}
+ other_params = {
+ u('foo'): u('baz'),
+ u('bar'): u('foo'),
+ u('multi'): [u('FOO'), u('BAR')],
+ u('uni_utf8'): u(b'\xae', 'latin1'),
+ u('uni_unicode'): _UGLYPH,
+ u('uni_unicode_2'):
+ u(b'\xc3\xa5\xc3\x85\xc3\xb8\xc3\x98', 'latin1'), # 'åÅøØ'
+ }
+
+ params = oauth_params
+ params.update(other_params)
+
+ req = oauth.Request("GET", "http://example.com", params)
+ self.assertEqual(other_params, req.get_nonoauth_parameters())
+
+ def test_to_url_nonascii(self):
+ url = "http://sp.example.com/"
+
+ params = {
+ 'nonasciithing': u'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", url, params)
+ res = urlparse(req.to_url())
+
+ params['nonasciithing'] = params['nonasciithing'].encode('utf-8')
+ exp = urlparse("%s?%s" % (url, urlencode(params)))
+
+ self.assertEquals(exp.netloc, res.netloc)
+ self.assertEquals(exp.path, res.path)
+
+ a = parse_qs(exp.query)
+ b = parse_qs(res.query)
+ self.assertEquals(a, b)
+
+ def test_to_url_works_with_non_ascii_parameters(self):
+
+ oauth_params = {
+ 'oauth_consumer': 'asdfasdfasdf'
+ }
+
other_params = {
u'foo': u'baz',
u'bar': u'foo',
- u'multi': [u'FOO',u'BAR'],
u'uni_utf8': u'\xae',
u'uni_unicode': u'\u00ae',
u'uni_unicode_2': u'åÅøØ',
@@ -373,7 +474,23 @@ def test_get_nonoauth_parameters(self):
params.update(other_params)
req = oauth.Request("GET", "http://example.com", params)
- self.assertEquals(other_params, req.get_nonoauth_parameters())
+
+ # We need to split out the host and params and check individually since the order is not determinate.
+ url_parts = req.to_url().split("?")
+ host = url_parts[0]
+ params = dict(item.strip().split("=") for item in url_parts[1].split("&"))
+
+ expected_params = {
+ 'uni_utf8': '%C2%AE',
+ 'foo': 'baz',
+ 'bar': 'foo',
+ 'uni_unicode_2': '%C3%A5%C3%85%C3%B8%C3%98',
+ 'uni_unicode': '%C2%AE',
+ 'oauth_consumer': 'asdfasdfasdf'
+ }
+
+ self.assertEquals("http://example.com", host)
+ self.assertEquals(expected_params, params)
def test_to_header(self):
realm = "http://sp.example.com/"
@@ -389,7 +506,7 @@ def test_to_header(self):
}
req = oauth.Request("GET", realm, params)
- header, value = req.to_header(realm).items()[0]
+ header, value = list(req.to_header(realm).items())[0]
parts = value.split('OAuth ')
vars = parts[1].split(', ')
@@ -398,21 +515,21 @@ def test_to_header(self):
res = {}
for v in vars:
var, val = v.split('=')
- res[var] = urllib.unquote(val.strip('"'))
+ res[var] = unquote(val.strip('"'))
- self.assertEquals(realm, res['realm'])
+ self.assertEqual(realm, res['realm'])
del res['realm']
self.assertTrue(len(res), len(params))
for key, val in res.items():
- self.assertEquals(val, params.get(key))
+ self.assertEqual(val, params.get(key))
def test_to_postdata_nonascii(self):
realm = "http://sp.example.com/"
params = {
- 'nonasciithing': u'q\xbfu\xe9 ,aasp u?..a.s',
+ 'nonasciithing': u('q\xbfu\xe9 ,aasp u?..a.s', 'latin1'),
'oauth_version': "1.0",
'oauth_nonce': "4572616e48616d6d65724c61686176",
'oauth_timestamp': "137131200",
@@ -424,7 +541,17 @@ def test_to_postdata_nonascii(self):
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')
+ self.assertReallyEqual(
+ req.to_postdata(),
+ ('nonasciithing=q%C2%BFu%C3%A9%20%2Caasp%20u%3F..a.s'
+ '&oauth_consumer_key=0685bd9184jfhq22'
+ '&oauth_nonce=4572616e48616d6d65724c61686176'
+ '&oauth_signature=wOJIO9A2W5mFwDgiDvZbTSMK%252FPY%253D'
+ '&oauth_signature_method=HMAC-SHA1'
+ '&oauth_timestamp=137131200'
+ '&oauth_token=ad180jjd733klru7'
+ '&oauth_version=1.0'
+ ))
def test_to_postdata(self):
realm = "http://sp.example.com/"
@@ -446,7 +573,9 @@ def test_to_postdata(self):
del params['multi']
flat.extend(params.items())
kf = lambda x: x[0]
- self.assertEquals(sorted(flat, key=kf), sorted(parse_qsl(req.to_postdata()), key=kf))
+ self.assertEqual(
+ sorted(flat, key=kf),
+ sorted(parse_qsl(req.to_postdata()), key=kf))
def test_to_url(self):
url = "http://sp.example.com/"
@@ -462,18 +591,19 @@ def test_to_url(self):
}
req = oauth.Request("GET", url, params)
- exp = urlparse.urlparse("%s?%s" % (url, urllib.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)
+ exp = urlparse("%s?%s" % (url, urlencode(params)))
+ res = urlparse(req.to_url())
+ self.assertEqual(exp.scheme, res.scheme)
+ self.assertEqual(exp.netloc, res.netloc)
+ self.assertEqual(exp.path, res.path)
+
+ exp_parsed = parse_qs(exp.query)
+ res_parsed = parse_qs(res.query)
+ self.assertEqual(exp_parsed, res_parsed)
- a = parse_qs(exp.query)
- b = 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"
+ url = ("https://www.google.com/m8/feeds/contacts/default/full/"
+ "?alt=json&max-contacts=10")
params = {
'oauth_version': "1.0",
@@ -486,50 +616,81 @@ def test_to_url_with_query(self):
}
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.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 = parse_qs(exp.query)
- b = 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):
+ # Note: the url above already has query parameters, so append new
+ # ones with &
+ exp = urlparse("%s&%s" % (url, urlencode(params)))
+ res = urlparse(req.to_url())
+ self.assertEqual(exp.scheme, res.scheme)
+ self.assertEqual(exp.netloc, res.netloc)
+ self.assertEqual(exp.path, res.path)
+
+ exp_q = parse_qs(exp.query)
+ res_q = parse_qs(res.query)
+ self.assertTrue('alt' in res_q)
+ self.assertTrue('max-contacts' in res_q)
+ self.assertEqual(res_q['alt'], ['json'])
+ self.assertEqual(res_q['max-contacts'], ['10'])
+ self.assertEqual(exp_q, res_q)
+
+ def test_signature_base_unicode_nonascii(self):
consumer = oauth.Consumer('consumer_token', 'consumer_secret')
- url = u'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc\u2766,+CA'
+ url = u('http://api.simplegeo.com:80/1.0/places/address.json'
+ '?q=monkeys&category=animal'
+ '&address=41+Decatur+St,+San+Francisc') + _U2766 + u(',+CA')
req = oauth.Request("GET", url)
- self.failUnlessReallyEqual(req.normalized_url, u'http://api.simplegeo.com/1.0/places/address.json')
+ self.assertReallyEqual(
+ req.normalized_url,
+ u('http://api.simplegeo.com/1.0/places/address.json'))
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
- self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=')
+ self.assertReallyEqual(
+ req['oauth_signature'], b'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'
+ def test_signature_base_string_bytes_nonascii_nonutf8(self):
+ consumer = oauth.Consumer('consumer_token', 'consumer_secret')
+
+ url = (b'http://api.simplegeo.com:80/1.0/places/address.json'
+ b'?q=monkeys&category=animal'
+ b'&address=41+Decatur+St,+San+Francisc') + _B2766 + b',+CA'
req = oauth.Request("GET", url)
- self.failUnlessReallyEqual(req.normalized_url, u'http://api.simplegeo.com/1.0/places/address.json')
+ self.assertReallyEqual(
+ req.normalized_url,
+ u('http://api.simplegeo.com/1.0/places/address.json'))
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
- self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=')
+ self.assertReallyEqual( #XXX
+ req['oauth_signature'], b'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'
+ def test_signature_base_bytes_nonascii_nonutf8_urlencoded(self):
+ consumer = oauth.Consumer('consumer_token', 'consumer_secret')
+
+ url = (b'http://api.simplegeo.com:80/1.0/places/address.json'
+ b'?q=monkeys&category=animal'
+ b'&address=41+Decatur+St,+San+Francisc%E2%9D%A6,+CA')
req = oauth.Request("GET", url)
- self.failUnlessReallyEqual(req.normalized_url, u'http://api.simplegeo.com/1.0/places/address.json')
+ self.assertReallyEqual(
+ req.normalized_url,
+ u('http://api.simplegeo.com/1.0/places/address.json'))
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
- self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=')
+ self.assertReallyEqual(
+ req['oauth_signature'], b'WhufgeZKyYpKsI70GZaiDaYwl6g=')
- url = u'http://api.simplegeo.com:80/1.0/places/address.json?q=monkeys&category=animal&address=41+Decatur+St,+San+Francisc%E2%9D%A6,+CA'
+ def test_signature_base_unicode_nonascii_nonutf8_url_encoded(self):
+ consumer = oauth.Consumer('consumer_token', 'consumer_secret')
+
+ url = u('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, u'http://api.simplegeo.com/1.0/places/address.json')
+ self.assertReallyEqual(
+ req.normalized_url,
+ u('http://api.simplegeo.com/1.0/places/address.json'))
req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
- self.failUnlessReallyEqual(req['oauth_signature'], 'WhufgeZKyYpKsI70GZaiDaYwl6g=')
+ self.assertReallyEqual(
+ req['oauth_signature'], b'WhufgeZKyYpKsI70GZaiDaYwl6g=')
def test_signature_base_string_with_query(self):
- url = "https://www.google.com/m8/feeds/contacts/default/full/?alt=json&max-contacts=10"
+ url = ("https://www.google.com/m8/feeds/contacts/default/full/"
+ "?alt=json&max-contacts=10")
params = {
'oauth_version': "1.0",
'oauth_nonce': "4572616e48616d6d65724c61686176",
@@ -540,17 +701,19 @@ def test_signature_base_string_with_query(self):
'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')
+ self.assertEqual(
+ req.normalized_url,
+ 'https://www.google.com/m8/feeds/contacts/default/full/')
+ self.assertEqual(req.url, url)
normalized_params = parse_qsl(req.get_normalized_parameters())
self.assertTrue(len(normalized_params), len(params) + 2)
normalized_params = dict(normalized_params)
- for key, value in params.iteritems():
+ 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')
+ self.assertEqual(value, normalized_params[key])
+ self.assertEqual(normalized_params['alt'], 'json')
+ self.assertEqual(normalized_params['max-contacts'], '10')
def test_get_normalized_parameters_empty(self):
url = "http://sp.example.com/?empty="
@@ -561,40 +724,71 @@ def test_get_normalized_parameters_empty(self):
expected='empty='
- self.assertEquals(expected, res)
+ self.assertEqual(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"
+ 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'
+ 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.assertEqual(expected, res)
+
+ def test_get_normalized_parameters_multiple(self):
+ url = "http://example.com/v2/search/videos?oauth_nonce=79815175&oauth_timestamp=1295397962&oauth_consumer_key=mykey&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&offset=10&oauth_signature=spWLI%2FGQjid7sQVd5%2FarahRxzJg%3D&tag=one&tag=two"
+
+ 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&tag=one&tag=two'
+
+ self.assertEqual(expected, res)
- 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"
+ 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'
+ 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)
+ self.assertEqual(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"
+ 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)
@@ -603,8 +797,15 @@ def test_signing_base(self):
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)
+ expected = b('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.assertEqual(expected, raw)
def test_get_normalized_parameters(self):
url = "http://sp.example.com/"
@@ -616,19 +817,27 @@ def test_get_normalized_parameters(self):
'oauth_consumer_key': "0685bd9184jfhq22",
'oauth_signature_method': "HMAC-SHA1",
'oauth_token': "ad180jjd733klru7",
- 'multi': ['FOO','BAR', u'\u00ae', '\xc2\xae'],
+ 'multi': ['FOO','BAR', _UGLYPH, b'\xc2\xae'],
'multi_same': ['FOO','FOO'],
- 'uni_utf8_bytes': '\xc2\xae',
- 'uni_unicode_object': u'\u00ae'
+ 'uni_utf8_bytes': b'\xc2\xae',
+ 'uni_unicode_object': _UGLYPH
}
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'
+ 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)
+ self.assertEqual(expected, res)
def test_get_normalized_parameters_ignores_auth_signature(self):
url = "http://sp.example.com/"
@@ -647,11 +856,17 @@ def test_get_normalized_parameters_ignores_auth_signature(self):
res = req.get_normalized_parameters()
- self.assertNotEquals(urllib.urlencode(sorted(params.items())), res)
+ self.assertNotEqual(urlencode(sorted(params.items())), res)
foo = params.copy()
del foo["oauth_signature"]
- self.assertEqual(urllib.urlencode(sorted(foo.items())), res)
+ self.assertEqual(urlencode(sorted(foo.items())), res)
+
+ def test_signature_base_string_with_matrix_params(self):
+ url = "http://social.yahooapis.com/v1/user/6677/connections;start=0;count=20"
+ req = oauth.Request("GET", url, None)
+ self.assertEqual(req.normalized_url, 'http://social.yahooapis.com/v1/user/6677/connections;start=0;count=20')
+ self.assertEqual(req.url, 'http://social.yahooapis.com/v1/user/6677/connections;start=0;count=20')
def test_set_signature_method(self):
consumer = oauth.Consumer('key', 'secret')
@@ -668,18 +883,19 @@ class Blah:
m = oauth.SignatureMethod_HMAC_SHA1()
client.set_signature_method(m)
- self.assertEquals(m, client.method)
+ self.assertEqual(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),
+ "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.urlencode(sorted(params.items())).replace('+', '%20')
+ expected = urlencode(sorted(params.items())).replace('+', '%20')
self.assertEqual(expected, res)
@mock.patch('oauth2.Request.make_timestamp')
@@ -698,48 +914,49 @@ def test_request_nonutf8_bytes(self, mock_make_nonce, mock_make_timestamp):
'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)
+ if not PY3:
+ # 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 = u'http://sp.example.com/\u2019'
+ url = u('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=')
+ self.assertReallyEqual(req['oauth_signature'], b'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=')
+ self.assertReallyEqual(req['oauth_signature'], b'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...
+ params['non_oauth_thing'] = b'\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'] = u'\u2019'
+ params['non_oauth_thing'] = _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=')
+ self.assertReallyEqual(req['oauth_signature'], b'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'
+ params['non_oauth_thing'] = b'\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=')
+ self.assertReallyEqual(req['oauth_signature'], b'pqOCu4qvRTiGiXB8Z61Jsey0pMM=')
# Also if there are non-utf8 bytes in the query args.
- url = "http://sp.example.com/?q=\x92" # cp1252
+ url = b"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):
@@ -758,11 +975,11 @@ def test_request_hash_of_body(self):
'oauth_consumer_key': con.key
}
- url = u"http://www.example.com/resource"
- req = oauth.Request(method="PUT", url=url, parameters=params, body="Hello World!", is_form_encoded=False)
+ url = u('http://www.example.com/resource')
+ req = oauth.Request(method="PUT", url=url, parameters=params, body=b"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=')
+ self.assertReallyEqual(req['oauth_body_hash'], b'Lve95gjOVATpfV8EL5X4nxwjKHE=')
+ self.assertReallyEqual(req['oauth_signature'], b't+MX8l/0S8hdbVQL99nD0X1fPnM=')
# oauth-bodyhash.html A.1 has
# '08bUFF%2Fjmp59mWB7cSgCYBUpJ0U%3D', but I don't see how that
# is possible.
@@ -776,10 +993,10 @@ def test_request_hash_of_body(self):
'oauth_consumer_key': con.key
}
- req = oauth.Request(method="PUT", url=url, parameters=params, body="Hello World!", is_form_encoded=False)
+ req = oauth.Request(method="PUT", url=url, parameters=params, body=b"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=')
+ self.assertReallyEqual(req['oauth_body_hash'], b'Lve95gjOVATpfV8EL5X4nxwjKHE=')
+ self.assertReallyEqual(req['oauth_signature'], b'CTFmrqJIGT7NsWJ42OrujahTtTc=')
# Appendix A.2
params = {
@@ -792,8 +1009,8 @@ def test_request_hash_of_body(self):
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=')
+ self.assertReallyEqual(req['oauth_body_hash'], b'2jmj7l5rSw0yVb/vlWAYkK/YBwk=')
+ self.assertReallyEqual(req['oauth_signature'], b'Zhl++aWSP0O3/hYQ0CuBc7jv38I=')
def test_sign_request(self):
@@ -813,36 +1030,93 @@ def test_sign_request(self):
req = oauth.Request(method="GET", url=url, parameters=params)
methods = {
- 'DX01TdHws7OninCLK9VztNTH1M4=': oauth.SignatureMethod_HMAC_SHA1(),
- 'con-test-secret&tok-test-secret': oauth.SignatureMethod_PLAINTEXT()
+ b'DX01TdHws7OninCLK9VztNTH1M4=': oauth.SignatureMethod_HMAC_SHA1(),
+ b'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)
+ self.assertEqual(req['oauth_signature_method'], method.name)
+ self.assertEqual(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
+ url = b"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=')
+ self.assertEqual(req['oauth_signature'], b'loFvp5xC7YbOgd9exIO6TxB7H4s=')
- url = u'http://sp.example.com/\u2019' # Python unicode object
+ url = u('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=')
+ self.assertEqual(req['oauth_signature'], b'loFvp5xC7YbOgd9exIO6TxB7H4s=')
# Also if there are non-ascii chars in the query args.
- url = "http://sp.example.com/?q=\xe2\x80\x99" # utf-8 bytes
+ url = b"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=')
+ self.assertEqual(req['oauth_signature'], b'IBw5mfvoCsDjgpcsVKbyvsDqQaU=')
- url = u'http://sp.example.com/?q=\u2019' # Python unicode object
+ url = u('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=')
+ self.assertEqual(req['oauth_signature'], b'IBw5mfvoCsDjgpcsVKbyvsDqQaU=')
+
+
+ def test_from_request_works_with_wsgi(self):
+ """Make sure WSGI header HTTP_AUTHORIZATION is detected correctly."""
+ 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()
+
+ # Munge the headers
+ headers['HTTP_AUTHORIZATION'] = headers['Authorization']
+ del headers['Authorization']
+
+ # Test from the headers
+ req = oauth.Request.from_request("GET", url, headers)
+ self.assertEqual(req.method, "GET")
+ self.assertEqual(req.url, url)
+ self.assertEqual(params, req.copy())
+
+
+ def test_from_request_is_case_insensitive_checking_for_auth(self):
+ """Checks for the Authorization header should be case insensitive."""
+ 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()
+
+ # Munge the headers
+ headers['authorization'] = headers['Authorization']
+ del headers['Authorization']
+
+ # Test from the headers
+ req = oauth.Request.from_request("GET", url, headers)
+ self.assertEqual(req.method, "GET")
+ self.assertEqual(req.url, url)
+ self.assertEqual(params, req.copy())
+
def test_from_request(self):
url = "http://sp.example.com/"
@@ -862,10 +1136,10 @@ def test_from_request(self):
# Test from the headers
req = oauth.Request.from_request("GET", url, headers)
- self.assertEquals(req.method, "GET")
- self.assertEquals(req.url, url)
+ self.assertEqual(req.method, "GET")
+ self.assertEqual(req.url, url)
- self.assertEquals(params, req.copy())
+ self.assertEqual(params, req.copy())
# Test with bad OAuth headers
bad_headers = {
@@ -876,18 +1150,18 @@ def test_from_request(self):
url, bad_headers)
# Test getting from query string
- qs = urllib.urlencode(params)
+ qs = urlencode(params)
req = oauth.Request.from_request("GET", url, query_string=qs)
exp = parse_qs(qs, keep_blank_values=False)
- for k, v in exp.iteritems():
- exp[k] = urllib.unquote(v[0])
+ for k, v in exp.items():
+ exp[k] = unquote(v[0])
- self.assertEquals(exp, req.copy())
+ self.assertEqual(exp, req.copy())
# Test that a boned from_request() call returns None
req = oauth.Request.from_request("GET", url)
- self.assertEquals(None, req)
+ self.assertEqual(None, req)
def test_from_token_and_callback(self):
url = "http://sp.example.com/"
@@ -905,11 +1179,11 @@ def test_from_token_and_callback(self):
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)
+ self.assertEqual(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)
+ self.assertEqual(req['oauth_callback'], url)
def test_from_consumer_and_token(self):
url = "http://sp.example.com/"
@@ -920,9 +1194,9 @@ def test_from_consumer_and_token(self):
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'])
+ self.assertEqual(req['oauth_token'], tok.key)
+ self.assertEqual(req['oauth_consumer_key'], con.key)
+ self.assertEqual(tok.verifier, req['oauth_verifier'])
class SignatureMethod_Bad(oauth.SignatureMethod):
name = "BAD"
@@ -936,7 +1210,7 @@ def sign(self, request, consumer, token):
class TestServer(unittest.TestCase):
def setUp(self):
- url = "http://sp.example.com/"
+ self.url = "http://sp.example.com/"
params = {
'oauth_version': "1.0",
@@ -953,7 +1227,7 @@ def setUp(self):
params['oauth_token'] = self.token.key
params['oauth_consumer_key'] = self.consumer.key
- self.request = oauth.Request(method="GET", url=url, parameters=params)
+ self.request = oauth.Request(method="GET", url=self.url, parameters=params)
signature_method = oauth.SignatureMethod_HMAC_SHA1()
self.request.sign_request(signature_method, self.consumer, self.token)
@@ -965,7 +1239,7 @@ def test_init(self):
oauth.SignatureMethod_HMAC_SHA1))
server = oauth.Server()
- self.assertEquals(server.signature_methods, {})
+ self.assertEqual(server.signature_methods, {})
def test_add_signature_method(self):
server = oauth.Server()
@@ -991,15 +1265,53 @@ def test_verify_request(self):
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'])
+ self.assertEqual(parameters['bar'], 'blerg')
+ self.assertEqual(parameters['foo'], 59)
+ self.assertEqual(parameters['multi'], ['FOO','BAR'])
+
+ def test_verify_request_query_string(self):
+ server = oauth.Server()
+ server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+
+ signature_method = oauth.SignatureMethod_HMAC_SHA1()
+ request2 = oauth.Request.from_request("GET", self.url, query_string=urlencode(dict(self.request)))
+ request2.sign_request(signature_method, self.consumer, self.token)
+ request3 = oauth.Request.from_request("GET", self.url, query_string=urlencode(dict(request2)))
+
+ parameters = server.verify_request(request3, self.consumer,
+ self.token)
+
+ def test_verify_request_missing_signature(self):
+ from oauth2 import MissingSignature
+ server = oauth.Server()
+ server.add_signature_method(oauth.SignatureMethod_PLAINTEXT())
+ del self.request['oauth_signature_method']
+ del self.request['oauth_signature']
+
+ self.assertRaises(MissingSignature,
+ server.verify_request, self.request, self.consumer, self.token)
+
+ def test_verify_request_invalid_signature(self):
+ server = oauth.Server()
+ server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+ self.request['oauth_signature'] = 'BOGUS'
+
+ self.assertRaises(oauth.Error,
+ server.verify_request, self.request, self.consumer, self.token)
+
+ def test_verify_request_invalid_timestamp(self):
+ server = oauth.Server()
+ server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
+ self.request['oauth_timestamp'] -= 86400
+
+ self.assertRaises(oauth.Error,
+ server.verify_request, self.request, self.consumer, self.token)
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"',
+ self.assertEqual('OAuth realm="example.com"',
headers['WWW-Authenticate'])
def test_no_version(self):
@@ -1165,7 +1477,7 @@ def create_simple_multipart_data(self, data):
boundary = '---Boundary-%d' % random.randint(1,1000)
crlf = '\r\n'
items = []
- for key, value in data.iteritems():
+ for key, value in data.items():
items += [
'--'+boundary,
'Content-Disposition: form-data; name="%s"'%str(key),
@@ -1174,7 +1486,7 @@ def create_simple_multipart_data(self, data):
]
items += ['', '--'+boundary+'--', '']
content_type = 'multipart/form-data; boundary=%s' % boundary
- return content_type, crlf.join(items)
+ return content_type, crlf.join(items).encode('ascii')
def test_init(self):
class Blah():
@@ -1193,40 +1505,52 @@ class Blah():
except ValueError:
pass
+ def test_init_passes_kwargs_to_httplib2(self):
+ class Blah():
+ pass
+
+ consumer = oauth.Consumer('token', 'secret')
+
+ # httplib2 options
+ client = oauth.Client(consumer, None, cache='.cache', timeout=3, disable_ssl_certificate_validation=True)
+ self.assertNotEqual(client.cache, None)
+ self.assertEqual(client.timeout, 3)
+
+
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)
+ self.assertEqual(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)
+ self.assertEqual(int(resp['status']), 200)
res = dict(parse_qsl(content))
- self.assertTrue('oauth_token' in res)
- self.assertTrue('oauth_token_secret' in res)
+ self.assertTrue(b'oauth_token' in res)
+ self.assertTrue(b'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.urlencode(self.body))
+ body = urlencode(self.body).encode('ascii')
+ return client.request(self._uri('two_legged'), method, body=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)
+ self.assertEqual(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)
+ self.assertEqual(int(resp['status']), 200)
@mock.patch('httplib2.Http.request')
def test_multipart_post_does_not_alter_body(self, mockHttpRequest):
@@ -1241,20 +1565,23 @@ def test_multipart_post_does_not_alter_body(self, mockHttpRequest):
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))
+ self.assertTrue(cl is client)
+ self.assertTrue(ur is uri)
+ self.assertEqual(frozenset(kw.keys()), frozenset(['method', 'body', 'redirections', 'connection_type', 'headers']))
+ self.assertEqual(kw['body'], body)
+ self.assertEqual(kw['connection_type'], None)
+ self.assertEqual(kw['method'], 'POST')
+ self.assertEqual(kw['redirections'],
+ httplib2.DEFAULT_MAX_REDIRECTS)
+ self.assertTrue(isinstance(kw['headers'], dict))
return random_result
mockHttpRequest.side_effect = mockrequest
- result = client.request(uri, 'POST', headers={'Content-Type':content_type}, body=body)
+ result = client.request(uri, 'POST',
+ headers={'Content-Type':content_type},
+ body=body)
self.assertEqual(result, random_result)
@mock.patch('httplib2.Http.request')
@@ -1264,24 +1591,30 @@ def test_url_with_query_string(self, mockHttpRequest):
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))
+ self.assertTrue(cl is client)
+ self.assertEqual(frozenset(kw.keys()),
+ frozenset(['method', 'body', 'redirections',
+ 'connection_type', 'headers']))
+ self.assertEqual(kw['body'], b'')
+ self.assertEqual(kw['connection_type'], None)
+ self.assertEqual(kw['method'], 'GET')
+ self.assertEqual(kw['redirections'],
+ httplib2.DEFAULT_MAX_REDIRECTS)
+ self.assertTrue(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 = parse_qsl(urlparse.urlparse(req.to_url()).query)
- actual = parse_qsl(urlparse.urlparse(ur).query)
- self.failUnlessEqual(len(expected), len(actual))
+ req.sign_request(oauth.SignatureMethod_HMAC_SHA1(),
+ self.consumer, None)
+ expected = parse_qsl(
+ urlparse(req.to_url()).query)
+ actual = parse_qsl(urlparse(ur).query)
+ self.assertEqual(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)
+ if key not in ('oauth_signature',
+ 'oauth_nonce', 'oauth_timestamp'):
+ self.assertEqual(actual[key], value)
return random_result
@@ -1299,11 +1632,14 @@ def test_multiple_values_for_a_key(self, mockReqConstructor, mockHttpRequest):
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.assertEqual(mockReqConstructor.call_count, 1)
+ self.assertEqual(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'])
+ self.assertTrue('multi=1' in mockHttpRequest.call_args[1]['body'])
+ self.assertTrue('multi=2' in mockHttpRequest.call_args[1]['body'])
if __name__ == "__main__":
+ import os
+ import sys
+ sys.path[0:0] = [os.path.join(os.path.dirname(__file__), ".."),]
unittest.main()
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..8f949280
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,37 @@
+[tox]
+envlist =
+ py26,py27,py32,py33,cover
+
+[testenv]
+commands =
+ python setup.py test -q
+deps =
+ httplib2
+ coverage
+ mock
+
+[testenv:cover]
+basepython =
+ python2.7
+commands =
+ nosetests --with-xunit --with-xcoverage
+deps =
+ httplib2
+ coverage
+ mock
+ nose
+ nosexcover
+
+# we separate coverage into its own testenv because a) "last run wins" wrt
+# cobertura jenkins reporting and b) pypy and jython can't handle any
+# combination of versions of coverage and nosexcover that i can find.
+
+[testenv:docs]
+basepython =
+ python2.6
+commands =
+ sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
+ sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest
+deps =
+ Sphinx
+ repoze.sphinx.autointerface