p@21
|
1 # Overview
|
p@21
|
2
|
p@21
|
3 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:
|
p@21
|
4
|
p@21
|
5 * 100% unit test coverage.
|
p@21
|
6 * The <code>DataStore</code> 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.
|
p@21
|
7 * Classes are no longer prefixed with <code>OAuth</code>.
|
p@21
|
8 * The <code>Request</code> class now extends from <code>dict</code>.
|
p@21
|
9 * The library is likely no longer compatible with Python 2.3.
|
p@21
|
10 * The <code>Client</code> class works and extends from <code>httplib2</code>. It's a thin wrapper that handles automatically signing any normal HTTP request you might wish to make.
|
p@21
|
11
|
p@21
|
12 # Signing a Request
|
p@21
|
13
|
p@21
|
14 import oauth2 as oauth
|
p@21
|
15 import time
|
p@21
|
16
|
p@21
|
17 # Set the API endpoint
|
p@21
|
18 url = "http://example.com/photos"
|
p@21
|
19
|
p@21
|
20 # Set the base oauth_* parameters along with any other parameters required
|
p@21
|
21 # for the API call.
|
p@21
|
22 params = {
|
p@21
|
23 'oauth_version': "1.0",
|
p@21
|
24 'oauth_nonce': oauth.generate_nonce(),
|
p@21
|
25 'oauth_timestamp': int(time.time())
|
p@21
|
26 'user': 'joestump',
|
p@21
|
27 'photoid': 555555555555
|
p@21
|
28 }
|
p@21
|
29
|
p@21
|
30 # Set up instances of our Token and Consumer. The Consumer.key and
|
p@21
|
31 # Consumer.secret are given to you by the API provider. The Token.key and
|
p@21
|
32 # Token.secret is given to you after a three-legged authentication.
|
p@21
|
33 token = oauth.Token(key="tok-test-key", secret="tok-test-secret")
|
p@21
|
34 consumer = oauth.Consumer(key="con-test-key", secret="con-test-secret")
|
p@21
|
35
|
p@21
|
36 # Set our token/key parameters
|
p@21
|
37 params['oauth_token'] = token.key
|
p@21
|
38 params['oauth_consumer_key'] = consumer.key
|
p@21
|
39
|
p@21
|
40 # Create our request. Change method, etc. accordingly.
|
p@21
|
41 req = oauth.Request(method="GET", url=url, parameters=params)
|
p@21
|
42
|
p@21
|
43 # Sign the request.
|
p@21
|
44 signature_method = oauth.SignatureMethod_HMAC_SHA1()
|
p@21
|
45 req.sign_request(signature_method, consumer, token)
|
p@21
|
46
|
p@21
|
47 # Using the Client
|
p@21
|
48
|
p@21
|
49 The <code>oauth2.Client</code> is based on <code>httplib2</code> and works just as you'd expect it to. The only difference is the first two arguments to the constructor are an instance of <code>oauth2.Consumer</code> and <code>oauth2.Token</code> (<code>oauth2.Token</code> is only needed for three-legged requests).
|
p@21
|
50
|
p@21
|
51 import oauth2 as oauth
|
p@21
|
52
|
p@21
|
53 # Create your consumer with the proper key/secret.
|
p@21
|
54 consumer = oauth.Consumer(key="your-twitter-consumer-key",
|
p@21
|
55 secret="your-twitter-consumer-secret")
|
p@21
|
56
|
p@21
|
57 # Request token URL for Twitter.
|
p@21
|
58 request_token_url = "http://twitter.com/oauth/request_token"
|
p@21
|
59
|
p@21
|
60 # Create our client.
|
p@21
|
61 client = oauth.Client(consumer)
|
p@21
|
62
|
p@21
|
63 # The OAuth Client request works just like httplib2 for the most part.
|
p@21
|
64 resp, content = client.request(request_token_url, "GET")
|
p@21
|
65 print resp
|
p@21
|
66 print content
|
p@21
|
67
|
p@21
|
68 # Twitter Three-legged OAuth Example
|
p@21
|
69
|
p@21
|
70 Below is an example of how one would go through a three-legged OAuth flow to
|
p@21
|
71 gain access to protected resources on Twitter. This is a simple CLI script, but
|
p@21
|
72 can be easily translated to a web application.
|
p@21
|
73
|
p@21
|
74 import urlparse
|
p@21
|
75 import oauth2 as oauth
|
p@21
|
76
|
p@21
|
77 consumer_key = 'my_key_from_twitter'
|
p@21
|
78 consumer_secret = 'my_secret_from_twitter'
|
p@21
|
79
|
p@21
|
80 request_token_url = 'http://twitter.com/oauth/request_token'
|
p@21
|
81 access_token_url = 'http://twitter.com/oauth/access_token'
|
p@21
|
82 authorize_url = 'http://twitter.com/oauth/authorize'
|
p@21
|
83
|
p@21
|
84 consumer = oauth.Consumer(consumer_key, consumer_secret)
|
p@21
|
85 client = oauth.Client(consumer)
|
p@21
|
86
|
p@21
|
87 # Step 1: Get a request token. This is a temporary token that is used for
|
p@21
|
88 # having the user authorize an access token and to sign the request to obtain
|
p@21
|
89 # said access token.
|
p@21
|
90
|
p@21
|
91 resp, content = client.request(request_token_url, "GET")
|
p@21
|
92 if resp['status'] != '200':
|
p@21
|
93 raise Exception("Invalid response %s." % resp['status'])
|
p@21
|
94
|
p@21
|
95 request_token = dict(urlparse.parse_qsl(content))
|
p@21
|
96
|
p@21
|
97 print "Request Token:"
|
p@21
|
98 print " - oauth_token = %s" % request_token['oauth_token']
|
p@21
|
99 print " - oauth_token_secret = %s" % request_token['oauth_token_secret']
|
p@21
|
100 print
|
p@21
|
101
|
p@21
|
102 # Step 2: Redirect to the provider. Since this is a CLI script we do not
|
p@21
|
103 # redirect. In a web application you would redirect the user to the URL
|
p@21
|
104 # below.
|
p@21
|
105
|
p@21
|
106 print "Go to the following link in your browser:"
|
p@21
|
107 print "%s?oauth_token=%s" % (authorize_url, request_token['oauth_token'])
|
p@21
|
108 print
|
p@21
|
109
|
p@21
|
110 # After the user has granted access to you, the consumer, the provider will
|
p@21
|
111 # redirect you to whatever URL you have told them to redirect to. You can
|
p@21
|
112 # usually define this in the oauth_callback argument as well.
|
p@21
|
113 accepted = 'n'
|
p@21
|
114 while accepted.lower() == 'n':
|
p@21
|
115 accepted = raw_input('Have you authorized me? (y/n) ')
|
p@21
|
116 oauth_verifier = raw_input('What is the PIN? ')
|
p@21
|
117
|
p@21
|
118 # Step 3: Once the consumer has redirected the user back to the oauth_callback
|
p@21
|
119 # URL you can request the access token the user has approved. You use the
|
p@21
|
120 # request token to sign this request. After this is done you throw away the
|
p@21
|
121 # request token and use the access token returned. You should store this
|
p@21
|
122 # access token somewhere safe, like a database, for future use.
|
p@21
|
123 token = oauth.Token(request_token['oauth_token'],
|
p@21
|
124 request_token['oauth_token_secret'])
|
p@21
|
125 token.set_verifier(oauth_verifier)
|
p@21
|
126 client = oauth.Client(consumer, token)
|
p@21
|
127
|
p@21
|
128 resp, content = client.request(access_token_url, "POST")
|
p@21
|
129 access_token = dict(urlparse.parse_qsl(content))
|
p@21
|
130
|
p@21
|
131 print "Access Token:"
|
p@21
|
132 print " - oauth_token = %s" % access_token['oauth_token']
|
p@21
|
133 print " - oauth_token_secret = %s" % access_token['oauth_token_secret']
|
p@21
|
134 print
|
p@21
|
135 print "You may now access protected resources using the access tokens above."
|
p@21
|
136 print
|
p@21
|
137
|
p@21
|
138 # Logging into Django w/ Twitter
|
p@21
|
139
|
p@21
|
140 Twitter also has the ability to authenticate a user [via an OAuth flow](http://apiwiki.twitter.com/Sign-in-with-Twitter). This
|
p@21
|
141 flow is exactly like the three-legged OAuth flow, except you send them to a
|
p@21
|
142 slightly different URL to authorize them.
|
p@21
|
143
|
p@21
|
144 In this example we'll look at how you can implement this login flow using
|
p@21
|
145 Django and python-oauth2.
|
p@21
|
146
|
p@21
|
147 ## Set up a Profile model
|
p@21
|
148
|
p@21
|
149 You'll need a place to store all of your Twitter OAuth credentials after the
|
p@21
|
150 user has logged in. In your app's `models.py` file you should add something
|
p@21
|
151 that resembles the following model.
|
p@21
|
152
|
p@21
|
153 class Profile(models.Model):
|
p@21
|
154 user = models.ForeignKey(User)
|
p@21
|
155 oauth_token = models.CharField(max_length=200)
|
p@21
|
156 oauth_secret = models.CharField(max_length=200)
|
p@21
|
157
|
p@21
|
158 ## Set up your Django views
|
p@21
|
159
|
p@21
|
160 ### `urls.py`
|
p@21
|
161
|
p@21
|
162 Your `urls.py` should look something like the following. Basically, you need to
|
p@21
|
163 have a login URL, a callback URL that Twitter will redirect your users back to,
|
p@21
|
164 and a logout URL.
|
p@21
|
165
|
p@21
|
166 In this example `^login/` and `twitter_login` will send the user to Twitter to
|
p@21
|
167 be logged in, `^login/authenticated/` and `twitter_authenticated` will confirm
|
p@21
|
168 the login, create the account if necessary, and log the user into the
|
p@21
|
169 application, and `^logout`/ logs the user out in the `twitter_logout` view.
|
p@21
|
170
|
p@21
|
171
|
p@21
|
172 from django.conf.urls.defaults import *
|
p@21
|
173 from django.contrib import admin
|
p@21
|
174 from mytwitterapp.views import twitter_login, twitter_logout, \
|
p@21
|
175 twitter_authenticated
|
p@21
|
176
|
p@21
|
177 admin.autodiscover()
|
p@21
|
178
|
p@21
|
179 urlpatterns = patterns('',
|
p@21
|
180 url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
p@21
|
181 url(r'^admin/', include(admin.site.urls)),
|
p@21
|
182 url(r'^login/?$', twitter_login),
|
p@21
|
183 url(r'^logout/?$', twitter_logout),
|
p@21
|
184 url(r'^login/authenticated/?$', twitter_authenticated),
|
p@21
|
185 )
|
p@21
|
186
|
p@21
|
187 ### `views.py`
|
p@21
|
188
|
p@21
|
189 *NOTE:* The following code was coded for Python 2.4 so some of the libraries
|
p@21
|
190 and code here might need to be updated if you are using Python 2.6+.
|
p@21
|
191
|
p@21
|
192 # Python
|
p@21
|
193 import oauth2 as oauth
|
p@21
|
194 import cgi
|
p@21
|
195
|
p@21
|
196 # Django
|
p@21
|
197 from django.shortcuts import render_to_response
|
p@21
|
198 from django.http import HttpResponseRedirect
|
p@21
|
199 from django.conf import settings
|
p@21
|
200 from django.contrib.auth import authenticate, login, logout
|
p@21
|
201 from django.contrib.auth.models import User
|
p@21
|
202 from django.contrib.auth.decorators import login_required
|
p@21
|
203
|
p@21
|
204 # Project
|
p@21
|
205 from mytwitterapp.models import Profile
|
p@21
|
206
|
p@21
|
207 # It's probably a good idea to put your consumer's OAuth token and
|
p@21
|
208 # OAuth secret into your project's settings.
|
p@21
|
209 consumer = oauth.Consumer(settings.TWITTER_TOKEN, settings.TWITTER_SECRET)
|
p@21
|
210 client = oauth.Client(consumer)
|
p@21
|
211
|
p@21
|
212 request_token_url = 'http://twitter.com/oauth/request_token'
|
p@21
|
213 access_token_url = 'http://twitter.com/oauth/access_token'
|
p@21
|
214
|
p@21
|
215 # This is the slightly different URL used to authenticate/authorize.
|
p@21
|
216 authenticate_url = 'http://twitter.com/oauth/authenticate'
|
p@21
|
217
|
p@21
|
218 def twitter_login(request):
|
p@21
|
219 # Step 1. Get a request token from Twitter.
|
p@21
|
220 resp, content = client.request(request_token_url, "GET")
|
p@21
|
221 if resp['status'] != '200':
|
p@21
|
222 raise Exception("Invalid response from Twitter.")
|
p@21
|
223
|
p@21
|
224 # Step 2. Store the request token in a session for later use.
|
p@21
|
225 request.session['request_token'] = dict(cgi.parse_qsl(content))
|
p@21
|
226
|
p@21
|
227 # Step 3. Redirect the user to the authentication URL.
|
p@21
|
228 url = "%s?oauth_token=%s" % (authenticate_url,
|
p@21
|
229 request.session['request_token']['oauth_token'])
|
p@21
|
230
|
p@21
|
231 return HttpResponseRedirect(url)
|
p@21
|
232
|
p@21
|
233
|
p@21
|
234 @login_required
|
p@21
|
235 def twitter_logout(request):
|
p@21
|
236 # Log a user out using Django's logout function and redirect them
|
p@21
|
237 # back to the homepage.
|
p@21
|
238 logout(request)
|
p@21
|
239 return HttpResponseRedirect('/')
|
p@21
|
240
|
p@21
|
241 def twitter_authenticated(request):
|
p@21
|
242 # Step 1. Use the request token in the session to build a new client.
|
p@21
|
243 token = oauth.Token(request.session['request_token']['oauth_token'],
|
p@21
|
244 request.session['request_token']['oauth_token_secret'])
|
p@21
|
245 client = oauth.Client(consumer, token)
|
p@21
|
246
|
p@21
|
247 # Step 2. Request the authorized access token from Twitter.
|
p@21
|
248 resp, content = client.request(access_token_url, "GET")
|
p@21
|
249 if resp['status'] != '200':
|
p@21
|
250 print content
|
p@21
|
251 raise Exception("Invalid response from Twitter.")
|
p@21
|
252
|
p@21
|
253 """
|
p@21
|
254 This is what you'll get back from Twitter. Note that it includes the
|
p@21
|
255 user's user_id and screen_name.
|
p@21
|
256 {
|
p@21
|
257 'oauth_token_secret': 'IcJXPiJh8be3BjDWW50uCY31chyhsMHEhqJVsphC3M',
|
p@21
|
258 'user_id': '120889797',
|
p@21
|
259 'oauth_token': '120889797-H5zNnM3qE0iFoTTpNEHIz3noL9FKzXiOxwtnyVOD',
|
p@21
|
260 'screen_name': 'heyismysiteup'
|
p@21
|
261 }
|
p@21
|
262 """
|
p@21
|
263 access_token = dict(cgi.parse_qsl(content))
|
p@21
|
264
|
p@21
|
265 # Step 3. Lookup the user or create them if they don't exist.
|
p@21
|
266 try:
|
p@21
|
267 user = User.objects.get(username=access_token['screen_name'])
|
p@21
|
268 except User.DoesNotExist:
|
p@21
|
269 # When creating the user I just use their screen_name@twitter.com
|
p@21
|
270 # for their email and the oauth_token_secret for their password.
|
p@21
|
271 # These two things will likely never be used. Alternatively, you
|
p@21
|
272 # can prompt them for their email here. Either way, the password
|
p@21
|
273 # should never be used.
|
p@21
|
274 user = User.objects.create_user(access_token['screen_name'],
|
p@21
|
275 '%s@twitter.com' % access_token['screen_name'],
|
p@21
|
276 access_token['oauth_token_secret'])
|
p@21
|
277
|
p@21
|
278 # Save our permanent token and secret for later.
|
p@21
|
279 profile = Profile()
|
p@21
|
280 profile.user = user
|
p@21
|
281 profile.oauth_token = access_token['oauth_token']
|
p@21
|
282 profile.oauth_secret = access_token['oauth_token_secret']
|
p@21
|
283 profile.save()
|
p@21
|
284
|
p@21
|
285 # Authenticate the user and log them in using Django's pre-built
|
p@21
|
286 # functions for these things.
|
p@21
|
287 user = authenticate(username=access_token['screen_name'],
|
p@21
|
288 password=access_token['oauth_token_secret'])
|
p@21
|
289 login(request, user)
|
p@21
|
290
|
p@21
|
291 return HttpResponseRedirect('/')
|
p@21
|
292
|
p@21
|
293
|
p@21
|
294 ### `settings.py`
|
p@21
|
295
|
p@21
|
296 * 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.
|
p@21
|
297 * 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.
|
p@21
|
298
|
p@21
|
299 # XOAUTH for IMAP and SMTP
|
p@21
|
300
|
p@21
|
301 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.
|
p@21
|
302
|
p@21
|
303 ## IMAP
|
p@21
|
304
|
p@21
|
305 import oauth2 as oauth
|
p@21
|
306 import oauth2.clients.imap as imaplib
|
p@21
|
307
|
p@21
|
308 # Set up your Consumer and Token as per usual. Just like any other
|
p@21
|
309 # three-legged OAuth request.
|
p@21
|
310 consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
|
p@21
|
311 token = oauth.Token('your_users_3_legged_token',
|
p@21
|
312 'your_users_3_legged_token_secret')
|
p@21
|
313
|
p@21
|
314 # Setup the URL according to Google's XOAUTH implementation. Be sure
|
p@21
|
315 # to replace the email here with the appropriate email address that
|
p@21
|
316 # you wish to access.
|
p@21
|
317 url = "https://mail.google.com/mail/b/your_users_email@gmail.com/imap/"
|
p@21
|
318
|
p@21
|
319 conn = imaplib.IMAP4_SSL('imap.googlemail.com')
|
p@21
|
320 conn.debug = 4
|
p@21
|
321
|
p@21
|
322 # This is the only thing in the API for impaplib.IMAP4_SSL that has
|
p@21
|
323 # changed. You now authenticate with the URL, consumer, and token.
|
p@21
|
324 conn.authenticate(url, consumer, token)
|
p@21
|
325
|
p@21
|
326 # Once authenticated everything from the impalib.IMAP4_SSL class will
|
p@21
|
327 # work as per usual without any modification to your code.
|
p@21
|
328 conn.select('INBOX')
|
p@21
|
329 print conn.list()
|
p@21
|
330
|
p@21
|
331
|
p@21
|
332 ## SMTP
|
p@21
|
333
|
p@21
|
334 import oauth2 as oauth
|
p@21
|
335 import oauth2.clients.smtp as smtplib
|
p@21
|
336
|
p@21
|
337 # Set up your Consumer and Token as per usual. Just like any other
|
p@21
|
338 # three-legged OAuth request.
|
p@21
|
339 consumer = oauth.Consumer('your_consumer_key', 'your_consumer_secret')
|
p@21
|
340 token = oauth.Token('your_users_3_legged_token',
|
p@21
|
341 'your_users_3_legged_token_secret')
|
p@21
|
342
|
p@21
|
343 # Setup the URL according to Google's XOAUTH implementation. Be sure
|
p@21
|
344 # to replace the email here with the appropriate email address that
|
p@21
|
345 # you wish to access.
|
p@21
|
346 url = "https://mail.google.com/mail/b/your_users_email@gmail.com/smtp/"
|
p@21
|
347
|
p@21
|
348 conn = smtplib.SMTP('smtp.googlemail.com', 587)
|
p@21
|
349 conn.set_debuglevel(True)
|
p@21
|
350 conn.ehlo('test')
|
p@21
|
351 conn.starttls()
|
p@21
|
352
|
p@21
|
353 # Again the only thing modified from smtplib.SMTP is the authenticate
|
p@21
|
354 # method, which works identically to the imaplib.IMAP4_SSL method.
|
p@21
|
355 conn.authenticate(url, consumer, token)
|
p@21
|
356
|
p@21
|
357
|