//-----------------------------------------------------------------------
//
// Copyright (c) Outercurve Foundation. All rights reserved.
//
//-----------------------------------------------------------------------
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;
///
/// A consumer capable of communicating with Twitter.
///
public static class TwitterConsumer {
///
/// The description of Twitter's OAuth protocol URIs for use with actually reading/writing
/// a user's private Twitter data.
///
public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription {
RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authorize", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
///
/// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
///
public static readonly ServiceProviderDescription SignInWithTwitterServiceDescription = new ServiceProviderDescription {
RequestTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/authenticate", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint("http://twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
};
///
/// The URI to get a user's favorites.
///
private static readonly Uri GetFavoritesEndpoint = new Uri("http://twitter.com/favorites.xml");
///
/// The URI to get the data on the user's home page.
///
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");
///
/// The consumer used for the Sign in to Twitter feature.
///
private static WebConsumer signInConsumer;
///
/// The lock acquired to initialize the field.
///
private static object signInConsumerInitLock = new object();
///
/// Initializes static members of the class.
///
static TwitterConsumer() {
// Twitter can't handle the Expect 100 Continue HTTP header.
ServicePointManager.FindServicePoint(GetFavoritesEndpoint).Expect100Continue = false;
}
///
/// Gets a value indicating whether the Twitter consumer key and secret are set in the web.config file.
///
public static bool IsTwitterConsumerConfigured {
get {
return !string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerKey"]) &&
!string.IsNullOrEmpty(ConfigurationManager.AppSettings["twitterConsumerSecret"]);
}
}
///
/// Gets the consumer to use for the Sign in to Twitter feature.
///
/// The twitter sign in.
private static WebConsumer TwitterSignIn {
get {
if (signInConsumer == null) {
lock (signInConsumerInitLock) {
if (signInConsumer == null) {
signInConsumer = new WebConsumer(SignInWithTwitterServiceDescription, ShortTermUserSessionTokenManager);
}
}
}
return signInConsumer;
}
}
private static InMemoryTokenManager ShortTermUserSessionTokenManager {
get {
var store = HttpContext.Current.Session;
var tokenManager = (InMemoryTokenManager)store["TwitterShortTermUserSessionTokenManager"];
if (tokenManager == null) {
string consumerKey = ConfigurationManager.AppSettings["twitterConsumerKey"];
string consumerSecret = ConfigurationManager.AppSettings["twitterConsumerSecret"];
if (IsTwitterConsumerConfigured) {
tokenManager = new InMemoryTokenManager(consumerKey, consumerSecret);
store["TwitterShortTermUserSessionTokenManager"] = tokenManager;
} else {
throw new InvalidOperationException("No Twitter OAuth consumer key and secret could be found in web.config AppSettings.");
}
}
return tokenManager;
}
}
public static async Task GetUpdatesAsync(
ConsumerBase twitter, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) {
using (var httpClient = twitter.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 static async Task GetFavorites(ConsumerBase twitter, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) {
using (var httpClient = twitter.CreateHttpClient(accessToken)) {
using (HttpResponseMessage response = await httpClient.GetAsync(GetFavoritesEndpoint, cancellationToken)) {
response.EnsureSuccessStatusCode();
return XDocument.Parse(await response.Content.ReadAsStringAsync());
}
}
}
public static async Task UpdateProfileBackgroundImageAsync(ConsumerBase twitter, string 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 = twitter.CreateHttpClient(accessToken)) {
using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) {
response.EnsureSuccessStatusCode();
string responseString = await response.Content.ReadAsStringAsync();
return XDocument.Parse(responseString);
}
}
}
public static Task UpdateProfileImageAsync(ConsumerBase twitter, string accessToken, string pathToImage, CancellationToken cancellationToken = default(CancellationToken)) {
string contentType = "image/" + Path.GetExtension(pathToImage).Substring(1).ToLowerInvariant();
return UpdateProfileImageAsync(twitter, accessToken, File.OpenRead(pathToImage), contentType, cancellationToken);
}
public static async Task UpdateProfileImageAsync(ConsumerBase twitter, string 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 = twitter.CreateHttpClient(accessToken)) {
using (HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken)) {
response.EnsureSuccessStatusCode();
string responseString = await response.Content.ReadAsStringAsync();
return XDocument.Parse(responseString);
}
}
}
public static async Task VerifyCredentialsAsync(ConsumerBase twitter, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) {
using (var httpClient = twitter.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 static async Task GetUsername(ConsumerBase twitter, string accessToken, CancellationToken cancellationToken = default(CancellationToken)) {
XDocument xml = await VerifyCredentialsAsync(twitter, accessToken, cancellationToken);
XPathNavigator nav = xml.CreateNavigator();
return nav.SelectSingleNode("/user/screen_name").Value;
}
///
/// Prepares a redirect that will send the user to Twitter to sign in.
///
/// if set to true the user will be required to re-enter their Twitter credentials even if already logged in to Twitter.
/// The cancellation token.
///
/// The redirect message.
///
///
/// Call or
/// return StartSignInWithTwitter().AsActionResult()
/// to actually perform the redirect.
///
public static async Task StartSignInWithTwitterAsync(bool forceNewLogin = false, CancellationToken cancellationToken = default(CancellationToken)) {
var redirectParameters = new Dictionary();
if (forceNewLogin) {
redirectParameters["force_login"] = "true";
}
Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix("oauth_");
var request = await TwitterSignIn.PrepareRequestUserAuthorizationAsync(callback, null, redirectParameters, cancellationToken);
return await TwitterSignIn.Channel.PrepareResponseAsync(request, cancellationToken);
}
///
/// 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.
///
/// The cancellation token.
///
/// A tuple with the screen name and Twitter unique user ID if successful; otherwise null.
///
public static async Task> TryFinishSignInWithTwitterAsync(CancellationToken cancellationToken = default(CancellationToken)) {
var response = await TwitterSignIn.ProcessUserAuthorizationAsync(cancellationToken: cancellationToken);
if (response == null) {
return null;
}
string screenName = response.ExtraData["screen_name"];
int userId = int.Parse(response.ExtraData["user_id"]);
// If we were going to make this LOOK like OpenID even though it isn't,
// this seems like a reasonable, secure claimed id to allow the user to assume.
////OpenId.Identifier fake_claimed_id = string.Format(CultureInfo.InvariantCulture, "http://twitter.com/{0}#{1}", screenName, userId);
return Tuple.Create(screenName, userId);
}
}
}