Getting unread mail details from Google Apps via 2-legged OAuth in .NET

Don’t get me wrong, I’m very glad that the Google Apps .NET API library exists. Unfortunately, the documentation can be a little obscure, and there isn’t a lot in the way of example code around for it, especially when it comes to OAuth. Even the official documentation page has only a single .NET example, and that uses an API call that is deprecated in the latest version.

To make matters worse, getting user data out of Gmail isn’t really supported at all by the Google Apps API. You can change settings and provision accounts, but not get any actual email. However, there is a single feed for Gmail to retrieve unread email details in Atom format, and you can use it with 2-legged OAuth. This isn’t mentioned at all on the page that lists the API scopes, and there’s no official support for it. But it works.

The big advantage of using 2-legged OAuth, in case you’re unaware, is that you (as the Google Apps administrator) can create an application which retrieves data for your users without them having to supply their credentials or go through the standard OAuth approval mechanism. This is especially advantageous in, say, a web portal application in a school. You need to set up 2-legged OAuth in the control panel to get your consumer key and secret, but that’s the easy part.

So… no example code, and no documentation. Sounds like fun.

Not really, no. But it’s actually quite simple once you figure it out, and in fact only needs a single object from the official API – everything else can be done with standard .NET 2.0 functions. Here’s a very basic implementation that retrieves the total number of unread messages:


Imports Microsoft.VisualBasic
Imports Google.GData.Client
Imports System.Net
Imports System.IO
Imports System.Xml

Dim sAppName As String = "domain.com"
Dim sConsumerKey As String = "domain.com"
Dim sConsumerSecret As String = "mysecretstring"
Dim sUser As String = "test"
Dim sDomain As String = "domain.com"

Dim oURI As New Uri("https://mail.google.com/mail/feed/atom/")
Dim oToken = New OAuth2LeggedAuthenticator(sAppName, sConsumerKey, sConsumerSecret, sUser, sDomain)
Dim oReq As HttpWebRequest = oToken.CreateHttpWebRequest("GET", oURI)

Dim oReader As New StreamReader(oReq.GetResponse.GetResponseStream)
Dim oDoc As New XmlDocument
oDoc.LoadXml(oReader.ReadToEnd)

OAuth2LeggedAuthenticator is the only object we’re using from the Google API here. The above example loads the entire feed into a System.Xml.XmlDocument object, from which you can browse the DOM using method calls such as .Item() (as in the above example) or using XPath via the .SelectSingleNode() or .SelectNodes() calls. If you choose the latter, you’ll need to use a namespace with your XPath queries due to the fact that the feed specifies the http://purl.org/atom/ns# namespace. Here’s an example where I grab the modified date of the first unread email:


Dim sNS As String = oDoc.DocumentElement.Attributes("xmlns").Value
Dim oNsMgr = New XmlNamespaceManager(oDoc.NameTable)
oNsMgr.AddNamespace("gm", sNS)

Dim MostRecent As DateTime = DateTime.Parse(oDoc.SelectSingleNode("gm:feed/gm:entry[1]/gm:modified", oNsMgr).InnerText)

In the example, I’ve used gm as my namespace prefix, but you can use whatever you want so long as it’s unique in the document.

A third method, which is particularly useful if you’re coding in Visual Web Developer, is to throw the entire raw XML back out via the .OuterText() call and into a System.Web.UI.WebControls.Xml control, where you can then parse it with an XSLT. I use this to present users with a tabular summary of their unread messages on our intranet portal, complete with direct links into the messages in their inbox.

Enjoy!

Tags: , , , ,

About The Angry Technician

The Angry Technician is an experienced IT professional in the UK education sector. Normally found in various states of annoyance on his blog. All views are those of his imaginary pet dog, Howard.

8 responses to “Getting unread mail details from Google Apps via 2-legged OAuth in .NET”

  1. Parlance says :

    I know you setup Exchange a while back and I would assume you’re using that happily for your staff email; are you implying you’re using Google Apps for student email? As you mentioned yourself there is no way to actually retrieve email for any account in your Google Apps domain without their credentials. Were there any battles there in regards to giving up control of being able to easily see what students were using their email for?

    • AngryTechnician says :

      We are currently evaluating Google Apps for our student email, but a decision has not been made. We are also looking at Live@edu. On the issue over access to pupil email, we aren’t planning to routinely monitor their usage (though we could set up keyword monitoring using Postini), but rather handle misuse as part of our broader e-safety curriculum. We feel it’s important to prepare pupils for the real world, where we won’t have access to their personal email.

      However, if we need to gain access to a pupil’s inbox without their co-operation, for example to investigate a particular incident report, there is a simple solution for us. We will be synchronising user passwords 1-way from our AD using Google Apps Directory Sync, instead of using SSO. If we need access to a mailbox, we can reset the password in the Google Apps console, log in, then resync it with the AD when we are done.

  2. Parlance says :

    So installing the password filter DLL on your DCs doesn’t bother you? You and I both know from your blog posts that Google’s desktop software is garbage, and they understand even less about Windows server software than they do about Windows desktop software, so installing something in so delicate and trusted a place made me pretty leery.

    There’s the other problem that your total options for the password format are SHA1, MD5, or plain text, unsalted. Why Google would choose these formats, unsalted even is beyond me. Plain text is well, plain text, but MD5 and SHA1 can be brute forced on highly available CUDA GPUs in under 30 seconds in most cases, which means they might as well be plain text. Ostensibly you are leaving the password in a plain text attribute on both Google’s servers and yours.

    • AngryTechnician says :

      It’s not ideal, that’s for certain. The filter DLL didn’t make me that worried; no more than installing any other software on a DC would.

      Generally I think Google’s servers are a relatively safe place to leave the passwords. It’s highly improbable an attacker could access the hashes directly, and if the public interface is vulnerable to brute force attacks, the storage method is irrelevant. The AD is the more likely risk, but that is at least vulnerable to local users only. I have investigated placing ACLs on the division attribute in AD, but the utter mess of explicit Allow entries that AD places on user objects by default makes it unfeasible to manage without an automated tool, and I haven’t implemented it yet.

  3. Geraldo says :

    Hi there,
    Very nice article. This is just what I’m looking for, thanks for writing it.

    I’m getting “Cannot send a content-body with this verb-type.” error on GetRequestStream line. Do you have any idead why?

    My project Is C# but I’ve managed to “translate” your code with no dificulty.

    Thanks in advance.

  4. Geraldo says :

    Nevermind… When I was translating your method I forgot to include GetResponse() so Instead of writing:
    oReq.GetResponse().GetResponseStream()
    I wrote:
    oReq.GetResponseStream()

    Pretty enlighting the message “Cannot send a content-body with this verb-type” for this error, right? :)
    Also how lucky am I the the HttpWebRequest object having a GetResponseStream method, right? lol.

    Well… Thanks a lot for your example. It work’d perfectly for me.

  5. Bharat says :

    I want to read my own Gmail account Inbox mail..for that giving consumer key=anonymous ,consumer secret=anonymous and signature method=HMAC-SHA1…it gives error..The remote server returned an error: (401) Unauthorized…..Please help me…Thanks in Advance.