//----------------------------------------------------------------------- // // 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.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; 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 async Task RequestAuthorizationAsync(WebConsumer consumer, Applications requestedAccessScope, CancellationToken cancellationToken = default(CancellationToken)) { if (consumer == null) { throw new ArgumentNullException("consumer"); } var extraParameters = new Dictionary { { "scope", GetScopeUri(requestedAccessScope) }, }; Uri callback = Util.GetCallbackUrlFromContext(); var request = await consumer.PrepareRequestUserAuthorizationAsync(callback, extraParameters, null, cancellationToken); var redirectingResponse = await consumer.Channel.PrepareResponseAsync(request, cancellationToken); redirectingResponse.Send(); } /// /// 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 URI to redirect to and the request token. public static Task> RequestAuthorization(DesktopConsumer consumer, Applications requestedAccessScope, CancellationToken cancellationToken = default(CancellationToken)) { if (consumer == null) { throw new ArgumentNullException("consumer"); } var extraParameters = new Dictionary { { "scope", GetScopeUri(requestedAccessScope) }, }; return consumer.RequestUserAuthorizationAsync(extraParameters, null, cancellationToken); } /// /// 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 async Task GetContactsAsync(ConsumerBase consumer, string accessToken, int maxResults = 25, int startIndex = 1, CancellationToken cancellationToken = default(CancellationToken)) { 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 = await consumer.PrepareAuthorizedRequestAsync(GetContactsEndpoint, accessToken, extraData, cancellationToken); // Enable gzip compression. Google only compresses the response for recognized user agent headers. - Mike Lim var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }; request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse("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")); using (var httpClient = consumer.Channel.HostFactories.CreateHttpClient(handler)) { using (var response = await httpClient.SendAsync(request, cancellationToken)) { string body = await response.Content.ReadAsStringAsync(); XDocument result = XDocument.Parse(body); return result; } } } public static async Task PostBlogEntryAsync(ConsumerBase consumer, string accessToken, string blogUrl, string title, XElement body, CancellationToken cancellationToken) { 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(); ms.Seek(0, SeekOrigin.Begin); var request = await consumer.PrepareAuthorizedRequestAsync(new MessageReceivingEndpoint(feedUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), accessToken, cancellationToken); request.Content = new StreamContent(ms); request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/atom+xml"); request.Method = HttpMethod.Post; using (var httpClient = consumer.Channel.HostFactories.CreateHttpClient()) { using (var response = await httpClient.SendAsync(request, cancellationToken)) { if (response.StatusCode == HttpStatusCode.Created) { // Success } else { // Error! response.EnsureSuccessStatusCode(); // throw some meaningful exception. } } } } /// /// 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()); } } }