//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.ApplicationBlock { using System; using System.Collections.Generic; using System.Configuration; 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.Web; 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 class GoogleConsumer : Consumer { /// /// The Consumer to use for accessing Google data APIs. /// public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription( "https://www.google.com/accounts/OAuthGetRequestToken", "https://www.google.com/accounts/OAuthAuthorizeToken", "https://www.google.com/accounts/OAuthGetAccessToken"); /// /// 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 Uri GetContactsEndpoint = new Uri("http://www.google.com/m8/feeds/contacts/default/full/"); /// /// Initializes a new instance of the class. /// public GoogleConsumer() { this.ServiceProvider = ServiceDescription; this.ConsumerKey = ConfigurationManager.AppSettings["googleConsumerKey"]; this.ConsumerSecret = ConfigurationManager.AppSettings["googleConsumerSecret"]; this.TemporaryCredentialStorage = HttpContext.Current != null ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() : new MemoryTemporaryCredentialStorage(); } /// /// 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, } /// /// 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()); } /// /// Requests authorization from Google to access data from a set of Google applications. /// /// The requested access scope. /// The cancellation token. /// /// A task that completes with the asynchronous operation. /// public Task RequestUserAuthorizationAsync(Applications requestedAccessScope, CancellationToken cancellationToken = default(CancellationToken)) { var extraParameters = new Dictionary { { "scope", GetScopeUri(requestedAccessScope) }, }; Uri callback = Util.GetCallbackUrlFromContext(); return this.RequestUserAuthorizationAsync(callback, extraParameters, cancellationToken); } /// /// Gets the Gmail address book's contents. /// /// 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). /// The cancellation token. /// /// An XML document returned by Google. /// public async Task GetContactsAsync(AccessToken accessToken, int maxResults = 25, int startIndex = 1, CancellationToken cancellationToken = default(CancellationToken)) { // Enable gzip compression. Google only compresses the response for recognized user agent headers. - Mike Lim var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }; using (var httpClient = this.CreateHttpClient(accessToken, handler)) { var request = new HttpRequestMessage(HttpMethod.Get, GetContactsEndpoint); request.Content = new FormUrlEncodedContent( new Dictionary() { { "start-index", startIndex.ToString(CultureInfo.InvariantCulture) }, { "max-results", maxResults.ToString(CultureInfo.InvariantCulture) }, }); 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 response = await httpClient.SendAsync(request, cancellationToken)) { string body = await response.Content.ReadAsStringAsync(); XDocument result = XDocument.Parse(body); return result; } } } public async Task PostBlogEntryAsync(AccessToken accessToken, string blogUrl, string title, XElement body, CancellationToken cancellationToken = default(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 = new HttpRequestMessage(HttpMethod.Post, feedUrl); request.Content = new StreamContent(ms); request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/atom+xml"); using (var httpClient = this.CreateHttpClient(accessToken)) { using (var response = await httpClient.SendAsync(request, cancellationToken)) { if (response.StatusCode == HttpStatusCode.Created) { // Success } else { // Error! response.EnsureSuccessStatusCode(); // throw some meaningful exception. } } } } } }