diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/content/Coding/008-oauth-python-gdata.rst	Thu Jan 30 21:45:03 2014 -0600
@@ -0,0 +1,219 @@
+Implementing OAuth using Google's Python Client Library
+#######################################################
+
+:date: 2011-07-04 13:00
+:tags: Python, OAuth, Google, GData
+:slug: implementing-oauth-using-google-s-python-client-library
+:author: Brian Neal
+
+My Django_ powered website allows users to submit events for a site calendar
+that is built upon Google Calendar.  After an admin approves events, I use
+Google's `Python Client Library`_ to add, delete, or update events on the Google
+calendar associated with my personal Google account. I wrote this application a
+few years ago, and it used the ClientLogin_ method for authentication. I
+recently decided to upgrade this to the OAuth_ authentication method. The
+ClientLogin method isn't very secure and it doesn't play well with Google's
+`two-step verification`_. After hearing about a friend who had his GMail account
+compromised and all his email deleted I decided it was long past due to get
+two-step verification on my account. But first I needed to upgrade my web
+application to OAuth.
+
+In this post I'll boil down the code I used to implement the elaborate OAuth
+dance. It really isn't that much code, but the Google documentation is somewhat
+confusing and scattered across a bewildering number of documents. I found at
+least one error in the documentation that I will point out. Although I am using
+Django, I will omit details specific to Django where I can.
+
+In addition to switching from ClientLogin to OAuth, I also upgraded to version
+2.0 of the Google Data API. This had more implications for my calendar-specific
+code, and perhaps I can go over that in a future post.
+
+Getting started and registering with Google
+===========================================
+
+To understand the basics of OAuth, I suggest you read `OAuth 1.0 for Web
+Applications`_. I decided to go for maximum security and use RSA-SHA1 signing on
+all my requests to Google. This requires that I verify my domain and then
+`register my application`_ with Google, which includes uploading a security
+certificate. Google provides documentation that describes how you can `create a
+self-signing private key and certificate`_ using OpenSSL.
+
+Fetching a Request Token and authorizing access
+===============================================
+
+To perform the first part of the OAuth dance, you must ask Google for a request
+token. When you make this request, you state the "scope" of your future work by
+listing the Google resources you are going to access. In our case, this is the
+calendar resources. You also provide a "consumer key" that Google assigned to
+you when you registered your application. This allows Google to retrieve the
+security certificate you previously uploaded when you registered. This is very
+important because this request is going to be signed with your private key.
+Fortunately the Python library takes care of all the signing details, you simply
+must provide your private key in PEM format. And finally, you provide a
+"callback URL" that Google will send your browser to after you (or your users)
+have manually authorized this request.
+
+Once you have received the request token from Google, you have to squirrel it
+away somewhere, then redirect your (or your user's) browser to a Google
+authorization page. Once the user has authorized your application, Google sends
+the browser to the callback URL to continue the process. Here I show the
+distilled code I used that asks for a request token, then sends the user to the
+authorization page.
+
+.. sourcecode:: python
+
+    import gdata.gauth
+    from gdata.calendar_resource.client import CalendarResourceClient
+
+    USER_AGENT = 'mydomain-myapp-v1' # my made up user agent string
+
+    client = CalendarResourceClient(None, source=USER_AGENT)
+
+    # obtain my private key that I saved previously on the filesystem:
+    with open(settings.GOOGLE_OAUTH_PRIVATE_KEY_PATH, 'r') as f:
+        rsa_key = f.read()
+
+    # Ask for a request token:
+    # scopes - a list of scope strings that the request token is for. See
+    # http://code.google.com/apis/gdata/faq.html#AuthScopes
+    # callback_url - URL to send the user after authorizing our app
+
+    scopes = ['https://www.google.com/calendar/feeds/']
+    callback_url = 'http://example.com/some/url/to/callback'
+
+    request_token = client.GetOAuthToken(
+            scopes,
+            callback_url,
+            settings.GOOGLE_OAUTH_CONSUMER_KEY, # from the registration process
+            rsa_private_key=rsa_key)
+
+    # Before redirecting, save the request token somewhere; here I place it in
+    # the session (this line is Django specific):
+    request.session[REQ_TOKEN_SESSION_KEY] = request_token
+
+    # Generate the authorization URL.
+    # Despite the documentation, don't do this:
+    #    auth_url = request_token.generate_authorization_url(domain=None)
+    # Do this instead if you are not using a Google Apps domain:
+    auth_url = request_token.generate_authorization_url()
+
+    # Now redirect the user somehow to the auth_url; here is how you might do
+    # it in Django:
+    return HttpResponseRedirect(auth_url)
+
+A couple of notes on the above:
+
+* You don't have to use ``CalendarResourceClient``, it just made the most sense
+  for me since I am doing calendar stuff later on. Any class that inherits from
+  ``gdata.client.GDClient`` will work. You might be able to use that class
+  directly. Google uses ``gdata.docs.client.DocsClient`` in their examples.
+* I chose to store my private key in a file rather than the database. If you do
+  so, it's probably a good idea to make the file readable only to the user your
+  webserver runs your application as.
+* After getting the request token you must save it somehow. You can save it in
+  the session, the database, or perhaps a file. Since this is only temporary, I
+  chose to save it in the session. The code I have here is Django specific.
+* When generating the authorization URL, don't pass in ``domain=None`` if you
+  aren't using a Google Apps domain like the documentation states. This appears 
+  to be an error in the documentation. Just omit it and let it use the default
+  value of ``"default"`` (see the source code).
+* After using the request token to generate the authorization URL, redirect the
+  browser to it.
+
+Extracting and upgrading to an Access Token
+===========================================
+
+The user will then be taken to a Google authorization page. The page will show the
+user what parts of their Google account your application is trying to access
+using the information you provided in the ``scopes`` parameter. If the user
+accepts, Google will then redirect the browser to your callback URL where we can
+complete the process.
+
+The code running at our callback URL must retrieve the request token that we
+saved earlier, and combine that with certain ``GET`` parameters Google attached
+to our callback URL. This is all done for us by the Python library. We then send
+this new token back to Google to upgrade it to an actual access token. If this
+succeeds, we can then save this new access token in our database for use in
+subsequent Google API operations. The access token is a Python object, so you
+can serialize it use the pickle module, or use routines provided by Google
+(shown below).
+
+.. sourcecode:: python
+
+    # Code running at our callback URL:
+    # Retrieve the request token we saved earlier in our session
+    saved_token = request.session[REQ_TOKEN_SESSION_KEY]
+
+    # Authorize it by combining it with GET parameters received from Google
+    request_token = gdata.gauth.AuthorizeRequestToken(saved_token,
+                        request.build_absolute_uri())
+
+    # Upgrade it to an access token
+    client = CalendarResourceClient(None, source=USER_AGENT)
+    access_token = client.GetAccessToken(request_token)
+
+    # Now save access_token somewhere, e.g. a database. So first serialize it:
+    access_token_str =  gdata.gauth.TokenToBlob(access_token)
+
+    # Save to database (details omitted)
+
+Some notes on the above code:
+
+* Once called back, our code must retrieve the request token we saved in our
+  session. The code shown is specific to Django.
+* We then combine this saved request token with certain ``GET`` parameters that
+  Google added to our callback URL. The ``AuthorizeRequestToken`` function takes care of
+  those details for us. The second argument to that function requires the full URL
+  including ``GET`` parameters as a string. Here I populate that argument by
+  using a Django-specific method of retrieving that information.
+* Finally, you upgrade your token to an access token by making one last call to
+  Google. You should now save a serialized version of this access token in your
+  database for future use.
+
+Using your shiny new Access Token
+=================================
+
+Once you have saved your access token, you won't have to do this crazy dance
+again until the token either expires, or the user revokes your application's
+access to the Google account. To use it in a calendar operation, for example,
+you simply retrieve it from your database, deserialize it, and then use it to
+create a ``CalendarClient``.
+
+.. sourcecode:: python
+  
+    from gdata.calendar.client import CalendarClient
+
+    # retrieve access token from the database:
+    access_token_str = ...
+    access_token = gdata.gauth.TokenFromBlob(access_token_str)
+
+    client = CalendarClient(source=USER_AGENT, auth_token=access_token)
+
+    # now use client to make calendar operations...
+
+Conclusion
+==========
+
+The main reason I wrote this blog post is I wanted to show a concrete example of
+using RSA-SHA1 and version 2.0 of the Google API together. All of the
+information I have presented is in the Google documentation, but it is spread
+across several documents and jumbled up with example code for version 1.0 and
+HMAC-SHA1.  Do not be afraid to look at the source code for the Python client
+library.  Despite Google's strange habit of ignoring PEP-8_ and using
+LongJavaLikeMethodNames, the code is logical and easy to read. Their library is
+built up in layers, and you may have to dip down a few levels to find out what
+is going on, but it is fairly straightforward to read if you combine it with
+their online documentation.
+
+I hope someone finds this useful. Your feedback is welcome.
+
+
+.. _Django: http://djangoproject.com
+.. _Python Client Library: http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html
+.. _ClientLogin: http://code.google.com/apis/calendar/data/2.0/developers_guide_python.html#AuthClientLogin
+.. _OAuth: http://code.google.com/apis/gdata/docs/auth/oauth.html
+.. _two-step verification: http://googleblog.blogspot.com/2011/02/advanced-sign-in-security-for-your.html
+.. _OAuth 1.0 for Web Applications: http://code.google.com/apis/accounts/docs/OAuth.html
+.. _register my application: http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html
+.. _create a self-signing private key and certificate: http://code.google.com/apis/gdata/docs/auth/oauth.html#GeneratingKeyCert
+.. _PEP-8: http://www.python.org/dev/peps/pep-0008/