diff options
Diffstat (limited to 'samples/DotNetOpenAuth.ApplicationBlock/OAuth1')
3 files changed, 571 insertions, 0 deletions
diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/GoogleConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/GoogleConsumer.cs new file mode 100644 index 0000000..a7c062e --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/GoogleConsumer.cs @@ -0,0 +1,264 @@ +//----------------------------------------------------------------------- +// <copyright file="GoogleConsumer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +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; + + /// <summary> + /// A consumer capable of communicating with Google Data APIs. + /// </summary> + public class GoogleConsumer : Consumer { + /// <summary> + /// The Consumer to use for accessing Google data APIs. + /// </summary> + 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"); + + /// <summary> + /// A mapping between Google's applications and their URI scope values. + /// </summary> + private static readonly Dictionary<Applications, string> DataScopeUris = new Dictionary<Applications, string> { + { 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" }, + }; + + /// <summary> + /// The URI to get contacts once authorization is granted. + /// </summary> + private static readonly Uri GetContactsEndpoint = new Uri("http://www.google.com/m8/feeds/contacts/default/full/"); + + /// <summary> + /// Initializes a new instance of the <see cref="GoogleConsumer"/> class. + /// </summary> + 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(); + } + + /// <summary> + /// The many specific authorization scopes Google offers. + /// </summary> + [Flags] + public enum Applications : long { + /// <summary> + /// The Gmail address book. + /// </summary> + Contacts = 0x1, + + /// <summary> + /// Appointments in Google Calendar. + /// </summary> + Calendar = 0x2, + + /// <summary> + /// Blog post authoring. + /// </summary> + Blogger = 0x4, + + /// <summary> + /// Google Finance + /// </summary> + Finance = 0x8, + + /// <summary> + /// Google Gmail + /// </summary> + Gmail = 0x10, + + /// <summary> + /// Google Health + /// </summary> + Health = 0x20, + + /// <summary> + /// Google OpenSocial + /// </summary> + OpenSocial = 0x40, + + /// <summary> + /// Picasa Web + /// </summary> + PicasaWeb = 0x80, + + /// <summary> + /// Google Spreadsheets + /// </summary> + Spreadsheets = 0x100, + + /// <summary> + /// Webmaster Tools + /// </summary> + WebmasterTools = 0x200, + + /// <summary> + /// YouTube service + /// </summary> + YouTube = 0x400, + + /// <summary> + /// Google Docs + /// </summary> + DocumentsList = 0x800, + + /// <summary> + /// Google Book Search + /// </summary> + BookSearch = 0x1000, + + /// <summary> + /// Google Base + /// </summary> + GoogleBase = 0x2000, + + /// <summary> + /// Google Analytics + /// </summary> + Analytics = 0x4000, + + /// <summary> + /// Google Maps + /// </summary> + Maps = 0x8000, + } + + /// <summary> + /// Gets the scope URI in Google's format. + /// </summary> + /// <param name="scope">The scope, which may include one or several Google applications.</param> + /// <returns>A space-delimited list of URIs for the requested Google applications.</returns> + public static string GetScopeUri(Applications scope) { + return string.Join(" ", Util.GetIndividualFlags(scope).Select(app => DataScopeUris[(Applications)app]).ToArray()); + } + + /// <summary> + /// Requests authorization from Google to access data from a set of Google applications. + /// </summary> + /// <param name="requestedAccessScope">The requested access scope.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A task that completes with the asynchronous operation. + /// </returns> + public Task<Uri> RequestUserAuthorizationAsync(Applications requestedAccessScope, CancellationToken cancellationToken = default(CancellationToken)) { + var extraParameters = new Dictionary<string, string> { + { "scope", GetScopeUri(requestedAccessScope) }, + }; + Uri callback = Util.GetCallbackUrlFromContext(); + return this.RequestUserAuthorizationAsync(callback, extraParameters, cancellationToken); + } + + /// <summary> + /// Gets the Gmail address book's contents. + /// </summary> + /// <param name="accessToken">The access token previously retrieved.</param> + /// <param name="maxResults">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.</param> + /// <param name="startIndex">The 1-based index of the first result to be retrieved (for paging).</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// An XML document returned by Google. + /// </returns> + public async Task<XDocument> 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<string, string>() { + { "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. + } + } + } + } + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/TwitterConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/TwitterConsumer.cs new file mode 100644 index 0000000..7c9f678 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/TwitterConsumer.cs @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------- +// <copyright file="TwitterConsumer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Globalization; + using System.IO; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Runtime.Serialization.Json; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using System.Xml; + using System.Xml.Linq; + using System.Xml.XPath; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + + using Newtonsoft.Json.Linq; + + /// <summary> + /// A consumer capable of communicating with Twitter. + /// </summary> + public class TwitterConsumer : Consumer { + /// <summary> + /// The description of Twitter's OAuth protocol URIs for use with actually reading/writing + /// a user's private Twitter data. + /// </summary> + public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/authorize", + "https://api.twitter.com/oauth/access_token"); + + /// <summary> + /// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature. + /// </summary> + public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription( + "https://api.twitter.com/oauth/request_token", + "https://api.twitter.com/oauth/authenticate", + "https://api.twitter.com/oauth/access_token"); + + /// <summary> + /// The URI to get a user's favorites. + /// </summary> + private static readonly Uri GetFavoritesEndpoint = new Uri("http://twitter.com/favorites.xml"); + + /// <summary> + /// The URI to get the data on the user's home page. + /// </summary> + private static readonly Uri GetFriendTimelineStatusEndpoint = new Uri("https://api.twitter.com/1.1/statuses/home_timeline.json"); + + private static readonly Uri UpdateProfileBackgroundImageEndpoint = new Uri("http://twitter.com/account/update_profile_background_image.xml"); + + private static readonly Uri UpdateProfileImageEndpoint = new Uri("http://twitter.com/account/update_profile_image.xml"); + + private static readonly Uri VerifyCredentialsEndpoint = new Uri("http://api.twitter.com/1/account/verify_credentials.xml"); + + /// <summary> + /// Initializes a new instance of the <see cref="TwitterConsumer"/> class. + /// </summary> + public TwitterConsumer() { + this.ServiceProvider = ServiceDescription; + this.ConsumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"]; + this.ConsumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]; + this.TemporaryCredentialStorage = HttpContext.Current != null + ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() + : new MemoryTemporaryCredentialStorage(); + } + + /// <summary> + /// Initializes a new instance of the <see cref="TwitterConsumer"/> class. + /// </summary> + /// <param name="consumerKey">The consumer key.</param> + /// <param name="consumerSecret">The consumer secret.</param> + public TwitterConsumer(string consumerKey, string consumerSecret) + : this() { + this.ConsumerKey = consumerKey; + this.ConsumerSecret = consumerSecret; + } + + /// <summary> + /// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file. + /// </summary> + public static bool IsTwitterConsumerConfigured { + get { + return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerKey"]) && + !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerSecret"]); + } + } + + public static Consumer CreateConsumer(bool forWeb = true) { + string consumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"]; + string consumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"]; + if (IsTwitterConsumerConfigured) { + ITemporaryCredentialStorage storage = forWeb ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() : new MemoryTemporaryCredentialStorage(); + return new Consumer(consumerKey, consumerSecret, ServiceDescription, storage) { + HostFactories = new TwitterHostFactories(), + }; + } else { + throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings."); + } + } + + /// <summary> + /// Prepares a redirect that will send the user to Twitter to sign in. + /// </summary> + /// <param name="forceNewLogin">if set to <c>true</c> the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// The redirect message. + /// </returns> + public static async Task<Uri> StartSignInWithTwitterAsync(bool forceNewLogin = false, CancellationToken cancellationToken = default(CancellationToken)) { + var redirectParameters = new Dictionary<string, string>(); + if (forceNewLogin) { + redirectParameters["force_login"] = "true"; + } + Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_"); + + var consumer = CreateConsumer(); + consumer.ServiceProvider = SignInWithTwitterServiceDescription; + Uri redirectUrl = await consumer.RequestUserAuthorizationAsync(callback, cancellationToken: cancellationToken); + return redirectUrl; + } + + /// <summary> + /// Checks the incoming web request to see if it carries a Twitter authentication response, + /// and provides the user's Twitter screen name and unique id if available. + /// </summary> + /// <param name="completeUrl">The URL that came back from the service provider to complete the authorization.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns> + /// A tuple with the screen name and Twitter unique user ID if successful; otherwise <c>null</c>. + /// </returns> + public static async Task<Tuple<string, int>> TryFinishSignInWithTwitterAsync(Uri completeUrl = null, CancellationToken cancellationToken = default(CancellationToken)) { + var consumer = CreateConsumer(); + consumer.ServiceProvider = SignInWithTwitterServiceDescription; + var response = await consumer.ProcessUserAuthorizationAsync(completeUrl ?? HttpContext.Current.Request.Url, cancellationToken: cancellationToken); + if (response == null) { + return null; + } + + string screenName = response.ExtraData["screen_name"]; + int userId = int.Parse(response.ExtraData["user_id"]); + return Tuple.Create(screenName, userId); + } + + public async Task<JArray> GetUpdatesAsync(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + if (string.IsNullOrEmpty(accessToken.Token)) { + throw new ArgumentNullException("accessToken.Token"); + } + + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (var response = await httpClient.GetAsync(GetFriendTimelineStatusEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string jsonString = await response.Content.ReadAsStringAsync(); + var json = JArray.Parse(jsonString); + return json; + } + } + } + + public async Task<XDocument> GetFavorites(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + if (string.IsNullOrEmpty(accessToken.Token)) { + throw new ArgumentNullException("accessToken.Token"); + } + + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.GetAsync(GetFavoritesEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + return XDocument.Parse(await response.Content.ReadAsStringAsync()); + } + } + } + + public async Task<XDocument> UpdateProfileBackgroundImageAsync(AccessToken accessToken, string image, bool tile, CancellationToken cancellationToken) { + var imageAttachment = new StreamContent(File.OpenRead(image)); + imageAttachment.Headers.ContentType = new MediaTypeHeaderValue("image/" + Path.GetExtension(image).Substring(1).ToLowerInvariant()); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UpdateProfileBackgroundImageEndpoint); + var content = new MultipartFormDataContent(); + content.Add(imageAttachment, "image"); + content.Add(new StringContent(tile.ToString().ToLowerInvariant()), "tile"); + request.Content = content; + request.Headers.ExpectContinue = false; + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string responseString = await response.Content.ReadAsStringAsync(); + return XDocument.Parse(responseString); + } + } + } + + public Task<XDocument> UpdateProfileImageAsync(AccessToken accessToken, string pathToImage, CancellationToken cancellationToken = default(CancellationToken)) { + string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant(); + return this.UpdateProfileImageAsync(accessToken, File.OpenRead(pathToImage), contentType, cancellationToken); + } + + public async Task<XDocument> UpdateProfileImageAsync(AccessToken accessToken, Stream image, string contentType, CancellationToken cancellationToken = default(CancellationToken)) { + var imageAttachment = new StreamContent(image); + imageAttachment.Headers.ContentType = new MediaTypeHeaderValue(contentType); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UpdateProfileImageEndpoint); + var content = new MultipartFormDataContent(); + content.Add(imageAttachment, "image", "twitterPhoto"); + request.Content = content; + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) { + response.EnsureSuccessStatusCode(); + string responseString = await response.Content.ReadAsStringAsync(); + return XDocument.Parse(responseString); + } + } + } + + public async Task<XDocument> VerifyCredentialsAsync(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + using (var httpClient = this.CreateHttpClient(accessToken)) { + using (var response = await httpClient.GetAsync(VerifyCredentialsEndpoint, cancellationToken)) { + response.EnsureSuccessStatusCode(); + using (var stream = await response.Content.ReadAsStreamAsync()) { + return XDocument.Load(XmlReader.Create(stream)); + } + } + } + } + + public async Task<string> GetUsername(AccessToken accessToken, CancellationToken cancellationToken = default(CancellationToken)) { + XDocument xml = await this.VerifyCredentialsAsync(accessToken, cancellationToken); + XPathNavigator nav = xml.CreateNavigator(); + return nav.SelectSingleNode("/user/screen_name").Value; + } + + private class TwitterHostFactories : IHostFactories { + private static readonly IHostFactories underlyingFactories = new DefaultOAuthHostFactories(); + + public HttpMessageHandler CreateHttpMessageHandler() { + return new WebRequestHandler(); + } + + public HttpClient CreateHttpClient(HttpMessageHandler handler = null) { + var client = underlyingFactories.CreateHttpClient(handler); + + // Twitter can't handle the Expect 100 Continue HTTP header. + client.DefaultRequestHeaders.ExpectContinue = false; + return client; + } + } + } +} diff --git a/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/YammerConsumer.cs b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/YammerConsumer.cs new file mode 100644 index 0000000..1dff5b6 --- /dev/null +++ b/samples/DotNetOpenAuth.ApplicationBlock/OAuth1/YammerConsumer.cs @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------- +// <copyright file="YammerConsumer.cs" company="Outercurve Foundation"> +// Copyright (c) Outercurve Foundation. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.ApplicationBlock { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Linq; + using System.Net; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using DotNetOpenAuth.OAuth.ChannelElements; + using DotNetOpenAuth.OAuth.Messages; + + public class YammerConsumer : Consumer { + /// <summary> + /// The Consumer to use for accessing Google data APIs. + /// </summary> + public static readonly ServiceProviderDescription ServiceDescription = + new ServiceProviderDescription( + "https://www.yammer.com/oauth/request_token", + "https://www.yammer.com/oauth/authorize", + "https://www.yammer.com/oauth/access_token"); + + public YammerConsumer() { + this.ServiceProvider = ServiceDescription; + this.ConsumerKey = ConfigurationManager.AppSettings["YammerConsumerKey"]; + this.ConsumerSecret = ConfigurationManager.AppSettings["YammerConsumerSecret"]; + this.TemporaryCredentialStorage = HttpContext.Current != null + ? (ITemporaryCredentialStorage)new CookieTemporaryCredentialStorage() + : new MemoryTemporaryCredentialStorage(); + } + + /// <summary> + /// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file. + /// </summary> + public static bool IsConsumerConfigured { + get { + return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["yammerConsumerKey"]) && + !string.IsNullOrEmpty(ConfigurationManager.AppSettings["yammerConsumerSecret"]); + } + } + } +} |