//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; /// /// A consumer capable of communicating with Google Data APIs. /// public static class GoogleConsumer { /// /// The Consumer to use for accessing Google data APIs. /// public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription { RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, }; /// /// A mapping between Google's applications and their URI scope values. /// private static readonly Dictionary DataScopeUris = new Dictionary { { Applications.Analytics, "https://www.google.com/analytics/feeds/" }, { Applications.GoogleBase, "http://www.google.com/base/feeds/" }, { Applications.Blogger, "http://www.blogger.com/feeds" }, { Applications.BookSearch, "http://www.google.com/books/feeds/" }, { Applications.Calendar, "http://www.google.com/calendar/feeds/" }, { Applications.Contacts, "http://www.google.com/m8/feeds/" }, { Applications.DocumentsList, "http://docs.google.com/feeds/" }, { Applications.Finance, "http://finance.google.com/finance/feeds/" }, { Applications.Gmail, "https://mail.google.com/mail/feed/atom" }, { Applications.Health, "https://www.google.com/h9/feeds/" }, { Applications.Maps, "http://maps.google.com/maps/feeds/" }, { Applications.OpenSocial, "http://sandbox.gmodules.com/api/" }, { Applications.PicasaWeb, "http://picasaweb.google.com/data/" }, { Applications.Spreadsheets, "http://spreadsheets.google.com/feeds/" }, { Applications.WebmasterTools, "http://www.google.com/webmasters/tools/feeds/" }, { Applications.YouTube, "http://gdata.youtube.com" }, }; /// /// The URI to get contacts once authorization is granted. /// private static readonly MessageReceivingEndpoint GetContactsEndpoint = new MessageReceivingEndpoint("http://www.google.com/m8/feeds/contacts/default/full/", HttpDeliveryMethods.GetRequest); /// /// The many specific authorization scopes Google offers. /// [Flags] public enum Applications : long { /// /// The Gmail address book. /// Contacts = 0x1, /// /// Appointments in Google Calendar. /// Calendar = 0x2, /// /// Blog post authoring. /// Blogger = 0x4, /// /// Google Finance /// Finance = 0x8, /// /// Google Gmail /// Gmail = 0x10, /// /// Google Health /// Health = 0x20, /// /// Google OpenSocial /// OpenSocial = 0x40, /// /// Picasa Web /// PicasaWeb = 0x80, /// /// Google Spreadsheets /// Spreadsheets = 0x100, /// /// Webmaster Tools /// WebmasterTools = 0x200, /// /// YouTube service /// YouTube = 0x400, /// /// Google Docs /// DocumentsList = 0x800, /// /// Google Book Search /// BookSearch = 0x1000, /// /// Google Base /// GoogleBase = 0x2000, /// /// Google Analytics /// Analytics = 0x4000, /// /// Google Maps /// Maps = 0x8000, } /// /// The service description to use for accessing Google data APIs using an X509 certificate. /// /// The signing certificate. /// A service description that can be used to create an instance of /// or . public static ServiceProviderDescription CreateRsaSha1ServiceDescription(X509Certificate2 signingCertificate) { if (signingCertificate == null) { throw new ArgumentNullException("signingCertificate"); } return new ServiceProviderDescription { RequestTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), UserAuthorizationEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthAuthorizeToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), AccessTokenEndpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetAccessToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest), TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new RsaSha1ConsumerSigningBindingElement(signingCertificate) }, }; } /// /// Requests authorization from Google to access data from a set of Google applications. /// /// The Google consumer previously constructed using or . /// The requested access scope. public static void RequestAuthorization(WebConsumer consumer, Applications requestedAccessScope) { if (consumer == null) { throw new ArgumentNullException("consumer"); } var extraParameters = new Dictionary { { "scope", GetScopeUri(requestedAccessScope) }, }; Uri callback = Util.GetCallbackUrlFromContext(); var request = consumer.PrepareRequestUserAuthorization(callback, extraParameters, null); consumer.Channel.Send(request); } /// /// Requests authorization from Google to access data from a set of Google applications. /// /// The Google consumer previously constructed using or . /// The requested access scope. /// The unauthorized request token assigned by Google. /// The request token public static Uri RequestAuthorization(DesktopConsumer consumer, Applications requestedAccessScope, out string requestToken) { if (consumer == null) { throw new ArgumentNullException("consumer"); } var extraParameters = new Dictionary { { "scope", GetScopeUri(requestedAccessScope) }, }; return consumer.RequestUserAuthorization(extraParameters, null, out requestToken); } /// /// Gets the Gmail address book's contents. /// /// The Google consumer. /// The access token previously retrieved. /// The maximum number of entries to return. If you want to receive all of the contacts, rather than only the default maximum, you can specify a very large number here. /// The 1-based index of the first result to be retrieved (for paging). /// An XML document returned by Google. public static XDocument GetContacts(ConsumerBase consumer, string accessToken, int maxResults/* = 25*/, int startIndex/* = 1*/) { if (consumer == null) { throw new ArgumentNullException("consumer"); } var extraData = new Dictionary() { { "start-index", startIndex.ToString(CultureInfo.InvariantCulture) }, { "max-results", maxResults.ToString(CultureInfo.InvariantCulture) }, }; var request = consumer.PrepareAuthorizedRequest(GetContactsEndpoint, accessToken, extraData); // Enable gzip compression. Google only compresses the response for recognized user agent headers. - Mike Lim request.AutomaticDecompression = DecompressionMethods.GZip; request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.151 Safari/534.16"; var response = consumer.Channel.WebRequestHandler.GetResponse(request); string body = response.GetResponseReader().ReadToEnd(); XDocument result = XDocument.Parse(body); return result; } public static void PostBlogEntry(ConsumerBase consumer, string accessToken, string blogUrl, string title, XElement body) { string feedUrl; var getBlogHome = WebRequest.Create(blogUrl); using (var blogHomeResponse = getBlogHome.GetResponse()) { using (StreamReader sr = new StreamReader(blogHomeResponse.GetResponseStream())) { string homePageHtml = sr.ReadToEnd(); Match m = Regex.Match(homePageHtml, @"http://www.blogger.com/feeds/\d+/posts/default"); Debug.Assert(m.Success, "Posting operation failed."); feedUrl = m.Value; } } const string Atom = "http://www.w3.org/2005/Atom"; XElement entry = new XElement( XName.Get("entry", Atom), new XElement(XName.Get("title", Atom), new XAttribute("type", "text"), title), new XElement(XName.Get("content", Atom), new XAttribute("type", "xhtml"), body), new XElement(XName.Get("category", Atom), new XAttribute("scheme", "http://www.blogger.com/atom/ns#"), new XAttribute("term", "oauthdemo"))); MemoryStream ms = new MemoryStream(); XmlWriterSettings xws = new XmlWriterSettings() { Encoding = Encoding.UTF8, }; XmlWriter xw = XmlWriter.Create(ms, xws); entry.WriteTo(xw); xw.Flush(); WebRequest request = consumer.PrepareAuthorizedRequest(new MessageReceivingEndpoint(feedUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), accessToken); request.ContentType = "application/atom+xml"; request.Method = "POST"; request.ContentLength = ms.Length; ms.Seek(0, SeekOrigin.Begin); using (Stream requestStream = request.GetRequestStream()) { ms.CopyTo(requestStream); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.Created) { // Success } else { // Error! } } } /// /// Gets the scope URI in Google's format. /// /// The scope, which may include one or several Google applications. /// A space-delimited list of URIs for the requested Google applications. public static string GetScopeUri(Applications scope) { return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); } } }