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/