Mercurial > public > pelican-blog
comparison content/Coding/008-oauth-python-gdata.rst @ 4:7ce6393e6d30
Adding converted blog posts from old blog.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Thu, 30 Jan 2014 21:45:03 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
3:c3115da3ff73 | 4:7ce6393e6d30 |
---|---|
1 Implementing OAuth using Google's Python Client Library | |
2 ####################################################### | |
3 | |
4 :date: 2011-07-04 13:00 | |
5 :tags: Python, OAuth, Google, GData | |
6 :slug: implementing-oauth-using-google-s-python-client-library | |
7 :author: Brian Neal | |
8 | |
9 My Django_ powered website allows users to submit events for a site calendar | |
10 that is built upon Google Calendar. After an admin approves events, I use | |
11 Google's `Python Client Library`_ to add, delete, or update events on the Google | |
12 calendar associated with my personal Google account. I wrote this application a | |
13 few years ago, and it used the ClientLogin_ method for authentication. I | |
14 recently decided to upgrade this to the OAuth_ authentication method. The | |
15 ClientLogin method isn't very secure and it doesn't play well with Google's | |
16 `two-step verification`_. After hearing about a friend who had his GMail account | |
17 compromised and all his email deleted I decided it was long past due to get | |
18 two-step verification on my account. But first I needed to upgrade my web | |
19 application to OAuth. | |
20 | |
21 In this post I'll boil down the code I used to implement the elaborate OAuth | |
22 dance. It really isn't that much code, but the Google documentation is somewhat | |
23 confusing and scattered across a bewildering number of documents. I found at | |
24 least one error in the documentation that I will point out. Although I am using | |
25 Django, I will omit details specific to Django where I can. | |
26 | |
27 In addition to switching from ClientLogin to OAuth, I also upgraded to version | |
28 2.0 of the Google Data API. This had more implications for my calendar-specific | |
29 code, and perhaps I can go over that in a future post. | |
30 | |
31 Getting started and registering with Google | |
32 =========================================== | |
33 | |
34 To understand the basics of OAuth, I suggest you read `OAuth 1.0 for Web | |
35 Applications`_. I decided to go for maximum security and use RSA-SHA1 signing on | |
36 all my requests to Google. This requires that I verify my domain and then | |
37 `register my application`_ with Google, which includes uploading a security | |
38 certificate. Google provides documentation that describes how you can `create a | |
39 self-signing private key and certificate`_ using OpenSSL. | |
40 | |
41 Fetching a Request Token and authorizing access | |
42 =============================================== | |
43 | |
44 To perform the first part of the OAuth dance, you must ask Google for a request | |
45 token. When you make this request, you state the "scope" of your future work by | |
46 listing the Google resources you are going to access. In our case, this is the | |
47 calendar resources. You also provide a "consumer key" that Google assigned to | |
48 you when you registered your application. This allows Google to retrieve the | |
49 security certificate you previously uploaded when you registered. This is very | |
50 important because this request is going to be signed with your private key. | |
51 Fortunately the Python library takes care of all the signing details, you simply | |
52 must provide your private key in PEM format. And finally, you provide a | |
53 "callback URL" that Google will send your browser to after you (or your users) | |
54 have manually authorized this request. | |
55 | |
56 Once you have received the request token from Google, you have to squirrel it | |
57 away somewhere, then redirect your (or your user's) browser to a Google | |
58 authorization page. Once the user has authorized your application, Google sends | |
59 the browser to the callback URL to continue the process. Here I show the | |
60 distilled code I used that asks for a request token, then sends the user to the | |
61 authorization page. | |
62 | |
63 .. sourcecode:: python | |
64 | |
65 import gdata.gauth | |
66 from gdata.calendar_resource.client import CalendarResourceClient | |
67 | |
68 USER_AGENT = 'mydomain-myapp-v1' # my made up user agent string | |
69 | |
70 client = CalendarResourceClient(None, source=USER_AGENT) | |
71 | |
72 # obtain my private key that I saved previously on the filesystem: | |
73 with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f: | |
74 rsa_key = f.read() | |
75 | |
76 # Ask for a request token: | |
77 # scopes - a list of scope strings that the request token is for. See | |
78 # http://code.google.com/apis/gdata/faq.html#AuthScopes | |
79 # callback_url - URL to send the user after authorizing our app | |
80 | |
81 scopes = ['https://www.google.com/calendar/feeds/'] | |
82 callback_url = 'http://example.com/some/url/to/callback' | |
83 | |
84 request_token = client.GetOAuthToken( | |
85 scopes, | |
86 callback_url, | |
87 settings.GOOGLE_OAUTH_CONSUMER_KEY, # from the registration process | |
88 rsa_private_key=rsa_key) | |
89 | |
90 # Before redirecting, save the request token somewhere; here I place it in | |
91 # the session (this line is Django specific): | |
92 request.session[REQ_TOKEN_SESSION_KEY] = request_token | |
93 | |
94 # Generate the authorization URL. | |
95 # Despite the documentation, don't do this: | |
96 # auth_url = request_token.generate_authorization_url(domain=None) | |
97 # Do this instead if you are not using a Google Apps domain: | |
98 auth_url = request_token.generate_authorization_url() | |
99 | |
100 # Now redirect the user somehow to the auth_url; here is how you might do | |
101 # it in Django: | |
102 return HttpResponseRedirect(auth_url) | |
103 | |
104 A couple of notes on the above: | |
105 | |
106 * You don't have to use ``CalendarResourceClient``, it just made the most sense | |
107 for me since I am doing calendar stuff later on. Any class that inherits from | |
108 ``gdata.client.GDClient`` will work. You might be able to use that class | |
109 directly. Google uses ``gdata.docs.client.DocsClient`` in their examples. | |
110 * I chose to store my private key in a file rather than the database. If you do | |
111 so, it's probably a good idea to make the file readable only to the user your | |
112 webserver runs your application as. | |
113 * After getting the request token you must save it somehow. You can save it in | |
114 the session, the database, or perhaps a file. Since this is only temporary, I | |
115 chose to save it in the session. The code I have here is Django specific. | |
116 * When generating the authorization URL, don't pass in ``domain=None`` if you | |
117 aren't using a Google Apps domain like the documentation states. This appears | |
118 to be an error in the documentation. Just omit it and let it use the default | |
119 value of ``"default"`` (see the source code). | |
120 * After using the request token to generate the authorization URL, redirect the | |
121 browser to it. | |
122 | |
123 Extracting and upgrading to an Access Token | |
124 =========================================== | |
125 | |
126 The user will then be taken to a Google authorization page. The page will show the | |
127 user what parts of their Google account your application is trying to access | |
128 using the information you provided in the ``scopes`` parameter. If the user | |
129 accepts, Google will then redirect the browser to your callback URL where we can | |
130 complete the process. | |
131 | |
132 The code running at our callback URL must retrieve the request token that we | |
133 saved earlier, and combine that with certain ``GET`` parameters Google attached | |
134 to our callback URL. This is all done for us by the Python library. We then send | |
135 this new token back to Google to upgrade it to an actual access token. If this | |
136 succeeds, we can then save this new access token in our database for use in | |
137 subsequent Google API operations. The access token is a Python object, so you | |
138 can serialize it use the pickle module, or use routines provided by Google | |
139 (shown below). | |
140 | |
141 .. sourcecode:: python | |
142 | |
143 # Code running at our callback URL: | |
144 # Retrieve the request token we saved earlier in our session | |
145 saved_token = request.session[REQ_TOKEN_SESSION_KEY] | |
146 | |
147 # Authorize it by combining it with GET parameters received from Google | |
148 request_token = gdata.gauth.AuthorizeRequestToken(saved_token, | |
149 request.build_absolute_uri()) | |
150 | |
151 # Upgrade it to an access token | |
152 client = CalendarResourceClient(None, source=USER_AGENT) | |
153 access_token = client.GetAccessToken(request_token) | |
154 | |
155 # Now save access_token somewhere, e.g. a database. So first serialize it: | |
156 access_token_str = gdata.gauth.TokenToBlob(access_token) | |
157 | |
158 # Save to database (details omitted) | |
159 | |
160 Some notes on the above code: | |
161 | |
162 * Once called back, our code must retrieve the request token we saved in our | |
163 session. The code shown is specific to Django. | |
164 * We then combine this saved request token with certain ``GET`` parameters that | |
165 Google added to our callback URL. The ``AuthorizeRequestToken`` function takes care of | |
166 those details for us. The second argument to that function requires the full URL | |
167 including ``GET`` parameters as a string. Here I populate that argument by | |
168 using a Django-specific method of retrieving that information. | |
169 * Finally, you upgrade your token to an access token by making one last call to | |
170 Google. You should now save a serialized version of this access token in your | |
171 database for future use. | |
172 | |
173 Using your shiny new Access Token | |
174 ================================= | |
175 | |
176 Once you have saved your access token, you won't have to do this crazy dance | |
177 again until the token either expires, or the user revokes your application's | |
178 access to the Google account. To use it in a calendar operation, for example, | |
179 you simply retrieve it from your database, deserialize it, and then use it to | |
180 create a ``CalendarClient``. | |
181 | |
182 .. sourcecode:: python | |
183 | |
184 from gdata.calendar.client import CalendarClient | |
185 | |
186 # retrieve access token from the database: | |
187 access_token_str = ... | |
188 access_token = gdata.gauth.TokenFromBlob(access_token_str) | |
189 | |
190 client = CalendarClient(source=USER_AGENT, auth_token=access_token) | |
191 | |
192 # now use client to make calendar operations... | |
193 | |
194 Conclusion | |
195 ========== | |
196 | |
197 The main reason I wrote this blog post is I wanted to show a concrete example of | |
198 using RSA-SHA1 and version 2.0 of the Google API together. All of the | |
199 information I have presented is in the Google documentation, but it is spread | |
200 across several documents and jumbled up with example code for version 1.0 and | |
201 HMAC-SHA1. Do not be afraid to look at the source code for the Python client | |
202 library. Despite Google's strange habit of ignoring PEP-8_ and using | |
203 LongJavaLikeMethodNames, the code is logical and easy to read. Their library is | |
204 built up in layers, and you may have to dip down a few levels to find out what | |
205 is going on, but it is fairly straightforward to read if you combine it with | |
206 their online documentation. | |
207 | |
208 I hope someone finds this useful. Your feedback is welcome. | |
209 | |
210 | |
211 .. _Django: http://djangoproject.com | |
212 .. _Python Client Library: http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html | |
213 .. _ClientLogin: http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html#AuthClientLogin | |
214 .. _OAuth: http://code.google.com/apis/gdata/docs/auth/oauth.html | |
215 .. _two-step verification: http://googleblog.blogspot.com/2011/02/advanced-sign-in-security-for-your.html | |
216 .. _OAuth 1.0 for Web Applications: http://code.google.com/apis/accounts/docs/OAuth.html | |
217 .. _register my application: http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html | |
218 .. _create a self-signing private key and certificate: http://code.google.com/apis/gdata/docs/auth/oauth.html#GeneratingKeyCert | |
219 .. _PEP-8: http://www.python.org/dev/peps/pep-0008/ |