//-----------------------------------------------------------------------
//
// 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 class TwitterConsumer : Consumer {
///
/// 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(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/authorize",
"https://api.twitter.com/oauth/access_token");
///
/// The description of Twitter's OAuth protocol URIs for use with their "Sign in with Twitter" feature.
///
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");
///
/// 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");
///
/// Initializes a new instance of the class.
///
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();
}
///
/// Initializes a new instance of the class.
///
/// The consumer key.
/// The consumer secret.
public TwitterConsumer(string consumerKey, string consumerSecret)
: this() {
this.ConsumerKey = consumerKey;
this.ConsumerSecret = consumerSecret;
}
///
/// 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"]);
}
}
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.");
}
}
///
/// 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.
///
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 consumer = CreateConsumer();
consumer.ServiceProvider = SignInWithTwitterServiceDescription;
Uri redirectUrl = await consumer.RequestUserAuthorizationAsync(callback, cancellationToken: cancellationToken);
return redirectUrl;
}
///
/// 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 URL that came back from the service provider to complete the authorization.
/// The cancellation token.
///
/// A tuple with the screen name and Twitter unique user ID if successful; otherwise null.
///
public static async Task> 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 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 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 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 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 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 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 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;
}
}
}
}