summaryrefslogtreecommitdiffstats
path: root/samples/DotNetOpenAuth.ApplicationBlock/OAuth1
diff options
context:
space:
mode:
Diffstat (limited to 'samples/DotNetOpenAuth.ApplicationBlock/OAuth1')
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/OAuth1/GoogleConsumer.cs264
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/OAuth1/TwitterConsumer.cs256
-rw-r--r--samples/DotNetOpenAuth.ApplicationBlock/OAuth1/YammerConsumer.cs51
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"]);
+ }
+ }
+ }
+}