//-----------------------------------------------------------------------
//
// 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.
}
}
}
}
}
}