Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Creates OAuth request using Dino Chiesa's class. It then uses Json.net to deserialize the json from twitter in to a tweet object which is then printed to screen.
// TwitPic/OAuth.cs
//
// Code to do OAuth stuff, in support of a cropper plugin that sends
// a screen snap to TwitPic.com.
//
// There's one main class: OAuth.Manager. It handles interaction with the OAuth-
// enabled service, for requesting temporary tokens (aka request tokens), as well
// as access tokens. It also provides a convenient way to construct an oauth
// Authorization header for use in any Http transaction.
//
// The code has been tested with Twitter and TwitPic, from a desktop application.
//
// -------------------------------------------------------
// Dino Chiesa
// Tue, 14 Dec 2010 12:31
//
namespace OAuth
{
using System;
using System.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
//using CropperPlugins.Utils;
/// <summary>
/// A class to manage OAuth interactions. This works with
/// Twitter, not sure about other OAuth-enabled services.
/// </summary>
/// <remarks>
/// <para>
/// This class holds the relevant oauth parameters, and exposes
/// methods that do things, based on those params.
/// </para>
/// <para>
/// See http://dev.twitpic.com/docs/2/upload/ for an example of the
/// oauth parameters. The params include token, consumer_key,
/// timestamp, version, and so on. In the actual HTTP message, they
/// all include the oauth_ prefix, so .. oauth_token,
/// oauth_timestamp, and so on. You set these via a string indexer.
/// If the instance of the class is called oauth, then to set
/// the oauth_token parameter, you use oath["token"] in C#.
/// </para>
/// <para>
/// This class automatically sets many of the required oauth parameters;
/// this includes the timestamp, nonce, callback, and version parameters.
/// (The callback param is initialized to 'oob'). You can reset any of
/// these parameters as you see fit. In many cases you won't have to.
/// </para>
/// <para>
/// The public methods on the class include:
/// AcquireRequestToken, AcquireAccessToken,
/// GenerateCredsHeader, and GenerateAuthorizationHeader. The
/// first two are used only on the first run of an applicaiton,
/// or after a user has explicitly de-authorized an application
/// for use with OAuth. Normally, the GenerateXxxHeader methods
/// can be used repeatedly, when sending HTTP messages that
/// require an OAuth Authorization header.
/// </para>
/// <para>
/// The AcquireRequestToken and AcquireAccessToken methods
/// actually send out HTTP messages.
/// </para>
/// <para>
/// The GenerateXxxxHeaders are used when constructing and
/// sending your own HTTP messages.
/// </para>
/// </remarks>
public class Manager
{
/// <summary>
/// The default public constructor.
/// </summary>
/// <remarks>
/// <para>
/// Initializes various fields to default values.
/// </para>
/// </remarks>
public Manager()
{
_random = new Random();
_params = new Dictionary<String,String>();
_params["callback"] = "oob"; // presume "desktop" consumer
_params["consumer_key"] = "";
_params["consumer_secret"] = "";
_params["timestamp"] = GenerateTimeStamp();
_params["nonce"] = GenerateNonce();
_params["signature_method"] = "HMAC-SHA1";
_params["signature"] = "";
_params["token"] = "";
_params["token_secret"] = "";
_params["version"] = "1.0";
}
/// <summary>
/// The constructor to use when using OAuth when you already
/// have an OAuth access token.
/// </summary>
/// <remarks>
/// <para>
/// The parameters for this constructor all have the
/// meaning you would expect. The token and tokenSecret
/// are set in oauth_token, and oauth_token_secret.
/// These are *Access* tokens, obtained after a call
/// to AcquireAccessToken. The application can store
/// those tokens and re-use them on successive runs.
/// For twitter at least, the access tokens never expire.
/// </para>
/// </remarks>
public Manager(string consumerKey,
string consumerSecret,
string token,
string tokenSecret) : this()
{
_params["consumer_key"] = consumerKey;
_params["consumer_secret"] = consumerSecret;
_params["token"] = token;
_params["token_secret"] = tokenSecret;
}
/// <summary>
/// string indexer to get or set oauth parameter values.
/// </summary>
/// <remarks>
/// <para>
/// Use the parameter name *without* the oauth_ prefix.
/// If you want to set the value for the oauth_token parameter
/// field in an HTTP message, then use oauth["token"].
/// </para>
/// <para>
/// The set of oauth param names known by this indexer includes:
/// callback, consumer_key, consumer_secret, timestamp, nonce,
/// signature_method, signature, token, token_secret, and version.
/// </para>
/// <para>
/// If you try setting a parameter with a name that is not known,
/// the setter will throw. You cannot add new oauth parameters
/// using the setter on this indexer.
/// </para>
/// </remarks>
public string this[string ix]
{
get
{
if (_params.ContainsKey(ix))
return _params[ix];
throw new ArgumentException(ix);
}
set
{
if (!_params.ContainsKey(ix))
throw new ArgumentException(ix);
_params[ix] = value;
}
}
/// <summary>
/// Generate the timestamp for the signature.
/// </summary>
/// <returns>The timestamp, in string form.</returns>
private string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - _epoch;
return Convert.ToInt64(ts.TotalSeconds).ToString();
}
/// <summary>
/// Renews the nonce and timestamp on the oauth parameters.
/// </summary>
/// <remarks>
/// <para>
/// Each new request should get a new, current timestamp, and a
/// nonce. This helper method does both of those things. This gets
/// called before generating an authorization header, as for example
/// when the user of this class calls <see cref='AcquireRequestToken'>.
/// </para>
/// </remarks>
private void NewRequest()
{
_params["nonce"] = GenerateNonce();
_params["timestamp"] = GenerateTimeStamp();
}
/// <summary>
/// Generate an oauth nonce.
/// </summary>
/// <remarks>
/// <para>
/// According to RFC 5849, A nonce is a random string,
/// uniquely generated by the client to allow the server to
/// verify that a request has never been made before and
/// helps prevent replay attacks when requests are made over
/// a non-secure channel. The nonce value MUST be unique
/// across all requests with the same timestamp, client
/// credentials, and token combinations.
/// </para>
/// <para>
/// One way to implement the nonce is just to use a
/// monotonically-increasing integer value. It starts at zero and
/// increases by 1 for each new request or signature generated.
/// Keep in mind the nonce needs to be unique only for a given
/// timestamp! So if your app makes less than one request per
/// second, then using a static nonce of "0" will work.
/// </para>
/// <para>
/// Most oauth nonce generation routines are waaaaay over-engineered,
/// and this one is no exception.
/// </para>
/// </remarks>
/// <returns>the nonce</returns>
private string GenerateNonce()
{
var sb = new System.Text.StringBuilder();
for (int i=0; i < 8; i++)
{
int g = _random.Next(3);
switch(g)
{
case 0:
// lowercase alpha
sb.Append( (char)(_random.Next(26)+97), 1);
break;
default:
// numeric digits
sb.Append( (char)(_random.Next(10)+48), 1);
break;
}
}
return sb.ToString();
}
/// <summary>
/// Internal function to extract from a URL all query string
/// parameters that are not related to oauth - in other words all
/// parameters not begining with "oauth_".
/// </summary>
///
/// <remarks>
/// <para>
/// For example, given a url like http://foo?a=7&guff, the
/// returned value will be a Dictionary of string-to-string
/// relations. There will be 2 entries in the Dictionary: "a"=>7,
/// and "guff"=>"".
/// </para>
/// </remarks>
///
/// <param name="queryString">The query string part of the Url</param>
///
/// <returns>A Dictionary containing the set of
/// parameter names and associated values</returns>
private Dictionary<String,String> ExtractQueryParameters(string queryString)
{
if (queryString.StartsWith("?"))
queryString = queryString.Remove(0, 1);
var result = new Dictionary<String,String>();
if (string.IsNullOrEmpty(queryString))
return result;
foreach (string s in queryString.Split('&'))
{
if (!string.IsNullOrEmpty(s) && !s.StartsWith("oauth_"))
{
if (s.IndexOf('=') > -1)
{
string[] temp = s.Split('=');
result.Add(temp[0], temp[1]);
}
else
result.Add(s, string.Empty);
}
}
return result;
}
/// <summary>
/// This is an oauth-compliant Url Encoder. The default .NET
/// encoder outputs the percent encoding in lower case. While this
/// is not a problem with the percent encoding defined in RFC 3986,
/// OAuth (RFC 5849) requires that the characters be upper case
/// throughout OAuth.
/// </summary>
///
/// <param name="value">The value to encode</param>
///
/// <returns>the Url-encoded version of that string</returns>
public static string UrlEncode(string value)
{
var result = new System.Text.StringBuilder();
foreach (char symbol in value)
{
if (unreservedChars.IndexOf(symbol) != -1)
result.Append(symbol);
else
result.Append('%' + String.Format("{0:X2}", (int)symbol));
}
return result.ToString();
}
private static string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
/// <summary>
/// Formats the list of request parameters into string a according
/// to the requirements of oauth. The resulting string could be used
/// in the Authorization header of the request.
/// </summary>
///
/// <remarks>
/// <para>
/// See http://dev.twitter.com/pages/auth#intro for some
/// background. The output of this is not suitable for signing.
/// </para>
/// <para>
/// There are 2 formats for specifying the list of oauth
/// parameters in the oauth spec: one suitable for signing, and
/// the other suitable for use within Authorization HTTP Headers.
/// This method emits a string suitable for the latter.
/// </para>
/// </remarks>
///
/// <param name="parameters">The Dictionary of
/// parameters. It need not be sorted.</param>
///
/// <returns>a string representing the parameters</returns>
private static string EncodeRequestParameters(ICollection<KeyValuePair<String,String>> p)
{
var sb = new System.Text.StringBuilder();
foreach (KeyValuePair<String,String> item in p.OrderBy(x => x.Key))
{
if (!String.IsNullOrEmpty(item.Value) &&
!item.Key.EndsWith("secret"))
sb.AppendFormat("oauth_{0}=\"{1}\", ",
item.Key,
UrlEncode(item.Value));
}
return sb.ToString().TrimEnd(' ').TrimEnd(',');
}
/// <summary>
/// Acquire a request token, from the given URI, using the given
/// HTTP method.
/// </summary>
///
/// <remarks>
/// <para>
/// To use this method, first instantiate a new Oauth.Manager object,
/// then set the callback param (oauth["callback"]='oob'). After the
/// call returns, you should direct the user to open a browser window
/// to the authorization page for the OAuth-enabled service. Or,
/// you can automatically open that page yourself. Do this with
/// System.Diagnostics.Process.Start(), passing the URL of the page.
/// There should be one query param: oauth_token with the value
/// obtained from oauth["token"].
/// </para>
/// <para>
/// According to the OAuth spec, you need to do this only ONCE per
/// application. In other words, the first time the application
/// is run. The normal oauth workflow is: (1) get a request token,
/// (2) use that to acquire an access token (which requires explicit
/// user approval), then (3) using that access token, invoke
/// protected services. The first two steps need to be done only
/// once per application.
/// </para>
/// <para>
/// For Twitter, at least, you can cache the access tokens
/// indefinitely; Twitter says they never expire. However, other
/// oauth services may not do the same. Also: the user may at any
/// time revoke his authorization for your app, in which case you
/// need to perform the first 2 steps again.
/// </para>
/// </remarks>
///
/// <seealso cref='AcquireAccessToken'>
///
/// </example>
/// <returns>
/// a response object that contains the entire text of the response,
/// as well as extracted parameters. This method presumes the
/// response is query-param encoded. In other words,
/// poauth_token=foo&something_else=bar.
/// </returns>
public OAuthResponse AcquireRequestToken(string uri, string method)
{
NewRequest();
var authzHeader = GetAuthorizationHeader(uri, method);
// prepare the token request
var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
request.Headers.Add("Authorization", authzHeader);
request.Method = method;
using (var response = (System.Net.HttpWebResponse)request.GetResponse())
{
using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
{
var r = new OAuthResponse(reader.ReadToEnd());
this["token"] = r["oauth_token"];
// Sometimes the request_token URL gives us an access token,
// with no user interaction required. Eg, when prior approval
// has already been granted.
try
{
if (r["oauth_token_secret"] != null)
this["token_secret"] = r["oauth_token_secret"];
}
catch { }
return r;
}
}
}
/// <summary>
/// Acquire an access token, from the given URI, using the given
/// HTTP method.
/// </summary>
///
/// <remarks>
/// <para>
/// To use this method, you must first set the oauth_token to the value
/// of the request token. Eg, oauth["token"] = "whatever".
/// </para>
/// <para>
/// According to the OAuth spec, you need to do this only ONCE per
/// application. In other words, the first time the application
/// is run. The normal oauth workflow is: (1) get a request token,
/// (2) use that to acquire an access token (which requires explicit
/// user approval), then (3) using that access token, invoke
/// protected services. The first two steps need to be done only
/// once per application.
/// </para>
/// <para>
/// For Twitter, at least, you can cache the access tokens
/// indefinitely; Twitter says they never expire. However, other
/// oauth services may not do the same. Also: the user may at any
/// time revoke his authorization for your app, in which case you
/// need to perform the first 2 steps again.
/// </para>
/// </remarks>
///
/// <seealso cref='AcquireRequestToken'>
///
/// </example>
/// <returns>
/// a response object that contains the entire text of the response,
/// as well as extracted parameters. This method presumes the
/// response is query-param encoded. In other words,
/// poauth_token=foo&something_else=bar.
/// </returns>
public OAuthResponse AcquireAccessToken(string uri, string method, string pin)
{
NewRequest();
_params["verifier"] = pin;
var authzHeader = GetAuthorizationHeader(uri, method);
// prepare the token request
var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
request.Headers.Add("Authorization", authzHeader);
request.Method = method;
using (var response = (System.Net.HttpWebResponse)request.GetResponse())
{
using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
{
var r = new OAuthResponse(reader.ReadToEnd());
this["token"] = r["oauth_token"];
this["token_secret"] = r["oauth_token_secret"];
return r;
}
}
}
/// <summary>
/// Generate a string to be used in an Authorization header in
/// an HTTP request.
/// </summary>
/// <remarks>
/// <para>
/// This method assembles the available oauth_ parameters that
/// have been set in the Dictionary in this instance, produces
/// the signature base (As described by the OAuth spec, RFC 5849),
/// signs it, then re-formats the oauth_ parameters into the
/// appropriate form, including the oauth_signature value, and
/// returns the result.
/// </para>
/// <para>
/// If you pass in a non-null, non-empty realm, this method will
/// include the realm='foo' clause in the Authorization header.
/// </para>
/// </remarks>
///
/// <seealso cref='GenerateAuthzHeader'></seealso>
public string GenerateCredsHeader(string uri, string method, string realm)
{
NewRequest();
var authzHeader = GetAuthorizationHeader(uri, method, realm);
return authzHeader;
}
/// <summary>
/// Generate a string to be used in an Authorization header in
/// an HTTP request.
/// </summary>
/// <remarks>
/// <para>
/// This method assembles the available oauth_ parameters that
/// have been set in the Dictionary in this instance, produces
/// the signature base (As described by the OAuth spec, RFC 5849),
/// signs it, then re-formats the oauth_ parameters into the
/// appropriate form, including the oauth_signature value, and
/// returns the result.
/// </para>
/// </remarks>
///
/// <seealso cref='GenerateAuthzHeader'>
public string GenerateAuthzHeader(string uri, string method)
{
NewRequest();
var authzHeader = GetAuthorizationHeader(uri, method, null);
return authzHeader;
}
private string GetAuthorizationHeader(string uri, string method)
{
return GetAuthorizationHeader(uri, method, null);
}
private string GetAuthorizationHeader(string uri, string method, string realm)
{
if (string.IsNullOrEmpty(this._params["consumer_key"]))
throw new ArgumentNullException("consumer_key");
if (string.IsNullOrEmpty(this._params["signature_method"]))
throw new ArgumentNullException("signature_method");
Sign(uri, method);
var erp = EncodeRequestParameters(this._params);
//Tracing.Trace("erp = {0}", erp);
return (String.IsNullOrEmpty(realm))
? "OAuth " + erp
: String.Format("OAuth realm=\"{0}\", ", realm) + erp;
}
private void Sign(string uri, string method)
{
var signatureBase = GetSignatureBase(uri, method);
var hash = GetHash();
byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(signatureBase);
byte[] hashBytes = hash.ComputeHash(dataBuffer);
this["signature"] = Convert.ToBase64String(hashBytes);
}
/// <summary>
/// Formats the list of request parameters into "signature base" string as
/// defined by RFC 5849. This will then be MAC'd with a suitable hash.
/// </summary>
private string GetSignatureBase(string url, string method)
{
// normalize the URI
var uri = new Uri(url);
var normUrl = string.Format("{0}://{1}", uri.Scheme, uri.Host);
if (!((uri.Scheme == "http" && uri.Port == 80) ||
(uri.Scheme == "https" && uri.Port == 443)))
normUrl += ":" + uri.Port;
normUrl += uri.AbsolutePath;
// the sigbase starts with the method and the encoded URI
var sb = new System.Text.StringBuilder();
sb.Append(method)
.Append('&')
.Append(UrlEncode(normUrl))
.Append('&');
// the parameters follow - all oauth params plus any params on
// the uri
// each uri may have a distinct set of query params
var p = ExtractQueryParameters(uri.Query);
// add all non-empty params to the "current" params
foreach (var p1 in this._params)
{
// Exclude all oauth params that are secret or
// signatures; any secrets should be kept to ourselves,
// and any existing signature will be invalid.
if (!String.IsNullOrEmpty(this._params[p1.Key]) &&
!p1.Key.EndsWith("_secret") &&
!p1.Key.EndsWith("signature"))
p.Add("oauth_" + p1.Key, p1.Value);
}
// concat+format all those params
var sb1 = new System.Text.StringBuilder();
foreach (KeyValuePair<String,String> item in p.OrderBy(x => x.Key))
{
// even "empty" params need to be encoded this way.
sb1.AppendFormat("{0}={1}&", item.Key, item.Value);
}
// append the UrlEncoded version of that string to the sigbase
sb.Append(UrlEncode(sb1.ToString().TrimEnd('&')));
var result = sb.ToString();
//Tracing.Trace("Sigbase: '{0}'", result);
return result;
}
private HashAlgorithm GetHash()
{
if (this["signature_method"] != "HMAC-SHA1")
throw new NotImplementedException();
string keystring = string.Format("{0}&{1}",
UrlEncode(this["consumer_secret"]),
UrlEncode(this["token_secret"]));
//Tracing.Trace("keystring: '{0}'", keystring);
var hmacsha1 = new HMACSHA1
{
Key = System.Text.Encoding.ASCII.GetBytes(keystring)
};
return hmacsha1;
}
#if BROKEN
/// <summary>
/// Return the oauth string that can be used in an Authorization
/// header. All the oauth terms appear in the string, in alphabetical
/// order.
/// </summary>
public string GetOAuthHeader()
{
return EncodeRequestParameters(this._params);
}
#endif
private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
private Dictionary<String,String> _params;
private Random _random;
}
/// <summary>
/// A class to hold an OAuth response message.
/// </summary>
public class OAuthResponse
{
/// <summary>
/// All of the text in the response. This is useful if the app wants
/// to do its own parsing.
/// </summary>
public string AllText { get;set; }
private Dictionary<String,String> _params;
/// <summary>
/// a Dictionary of response parameters.
/// </summary>
public string this[string ix]
{
get
{
return _params[ix];
}
}
public OAuthResponse(string alltext)
{
AllText = alltext;
_params = new Dictionary<String,String>();
var kvpairs = alltext.Split('&');
foreach (var pair in kvpairs)
{
var kv = pair.Split('=');
_params.Add(kv[0],kv[1]);
}
// expected keys:
// oauth_token, oauth_token_secret, user_id, screen_name, etc
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using System.Security.Cryptography;
using OAuth;
using Newtonsoft.Json;
namespace Twitter.Net
{
class Program
{
public static readonly string
URL_REQUEST_TOKEN = "https://api.twitter.com/oauth/request_token",
URL_AUTHORIZE = "https://api.twitter.com/oauth/authorize?oauth_token=",
URL_ACCESS_TOKEN = "https://api.twitter.com/oauth/access_token",
URL_VERIFY_CREDS = "https://api.twitter.com/1/account/verify_credentials.json",
AUTHENTICATION_REALM = "http://api.twitter.com/";
static void Main(string[] args)
{
Manager client = new Manager("5yB2Uqodp9I4gJQg2vkqg","HCWFBBQ94wfnSQkkUiMeO3a2j2kq7yuBvldL6zek","","");
client.AcquireRequestToken(URL_REQUEST_TOKEN, "POST");
System.Diagnostics.Process.Start(URL_AUTHORIZE + client["token"]);
Console.WriteLine("Go get the pin\n");
Console.WriteLine("Pin?:");
string pin = Console.ReadLine();
OAuthResponse resp = client.AcquireAccessToken(URL_ACCESS_TOKEN, "POST", pin);
string authHeader = client.GenerateAuthzHeader("https://api.twitter.com/1.1/statuses/home_timeline.json", "GET");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.twitter.com/1.1/statuses/home_timeline.json");
request.Headers.Add("Authorization", authHeader);
request.Method = "GET";
request.ContentType = "application/x-www-form-urlencoded";
/*using (Stream stream = request.GetRequestStream())
{
byte[] content = ASCIIEncoding.ASCII.GetBytes(postBody);
stream.Write(content, 0, content.Length);
}*/
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream stream = response.GetResponseStream();
byte[] content = new byte[response.ContentLength];
StreamReader sr = new StreamReader(stream);
string text = sr.ReadToEnd();
List<Tweet> tweets = JsonConvert.DeserializeObject<List<Tweet>>(text);
foreach (Tweet t in tweets)
{
Console.WriteLine("({0}) : {1} \nBy {2} (@{3})\n", t.CreatedAt, t.Text, t.user.name, t.user.screen_name);
}
}
public class Tweet
{
public string Text
{
get;
set;
}
[JsonProperty("created_at")]
public string CreatedAt
{
get;
set;
}
public string Source
{
get;
set;
}
[JsonProperty("retweet_count")]
public int RetweetCount
{
get;
set;
}
public User user
{ get; set; }
}
public class User
{
public string name
{ get; set; }
public string screen_name
{ get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment