/Application/Core/OAuth.cs

http://yet-another-music-application.googlecode.com/ · C# · 778 lines · 280 code · 60 blank · 438 comment · 26 complexity · 4625f7a450be939a2eba2ffa8d506dc3 MD5 · raw file

  1. /**
  2. * OAuth.cs
  3. *
  4. * Code to do OAuth stuff, in support of a cropper plugin that
  5. * sends a screen snap to TwitPic.com.
  6. *
  7. * There's one main class: OAuth.Manager. It handles interaction
  8. * with the OAuth-enabled service, for requesting temporary tokens
  9. * (aka request tokens), as well as access tokens. It also provides
  10. * a convenient way to construct an oauth Authorization header for
  11. * use in any Http transaction.
  12. *
  13. * The code has been tested with Twitter and TwitPic, from a
  14. * desktop application.
  15. *
  16. * * * * * * * * *
  17. *
  18. * Copyright 2010 Dino Chiesa
  19. *
  20. * This code was taken from TwitPic (www.twitpic.com) with
  21. * permission and is part of the Stoffi Music Player Project.
  22. * Visit our website at: stoffiplayer.com
  23. *
  24. * This program is free software; you can redistribute it and/or
  25. * modify it under the terms of the GNU General Public License
  26. * as published by the Free Software Foundation; either version
  27. * 3 of the License, or (at your option) any later version.
  28. *
  29. * See stoffiplayer.com/license for more information.
  30. **/
  31. namespace OAuth
  32. {
  33. using System;
  34. using System.Linq;
  35. using System.Collections.Generic;
  36. using System.Security.Cryptography;
  37. /// <summary>
  38. /// A class to manage OAuth interactions. This works with
  39. /// Twitter, not sure about other OAuth-enabled services.
  40. /// </summary>
  41. /// <remarks>
  42. /// <para>
  43. /// This class holds the relevant oauth parameters, and exposes
  44. /// methods that do things, based on those params.
  45. /// </para>
  46. /// <para>
  47. /// See http://dev.twitpic.com/docs/2/upload/ for an example of the
  48. /// oauth parameters. The params include token, consumer_key,
  49. /// timestamp, version, and so on. In the actual HTTP message, they
  50. /// all include the oauth_ prefix, so .. oauth_token,
  51. /// oauth_timestamp, and so on. You set these via a string indexer.
  52. /// If the instance of the class is called oauth, then to set
  53. /// the oauth_token parameter, you use oath["token"] in C#.
  54. /// </para>
  55. /// <para>
  56. /// This class automatically sets many of the required oauth parameters;
  57. /// this includes the timestamp, nonce, callback, and version parameters.
  58. /// (The callback param is initialized to 'oob'). You can reset any of
  59. /// these parameters as you see fit. In many cases you won't have to.
  60. /// </para>
  61. /// <para>
  62. /// The public methods on the class include:
  63. /// AcquireRequestToken, AcquireAccessToken,
  64. /// GenerateCredsHeader, and GenerateAuthzHeader. The
  65. /// first two are used only on the first run of an applicaiton,
  66. /// or after a user has explicitly de-authorized an application
  67. /// for use with OAuth. Normally, the GenerateXxxHeader methods
  68. /// can be used repeatedly, when sending HTTP messages that
  69. /// require an OAuth Authorization header.
  70. /// </para>
  71. /// <para>
  72. /// The AcquireRequestToken and AcquireAccessToken methods
  73. /// actually send out HTTP messages.
  74. /// </para>
  75. /// <para>
  76. /// The GenerateXxxxHeaders are used when constructing and
  77. /// sending your own HTTP messages.
  78. /// </para>
  79. /// </remarks>
  80. public class Manager
  81. {
  82. /// <summary>
  83. /// The default public constructor.
  84. /// </summary>
  85. /// <remarks>
  86. /// <para>
  87. /// Initializes various fields to default values.
  88. /// </para>
  89. /// </remarks>
  90. public Manager()
  91. {
  92. _random = new Random();
  93. _params = new Dictionary<String, String>();
  94. _params["callback"] = "oob"; // presume "desktop" consumer
  95. _params["consumer_key"] = "";
  96. _params["consumer_secret"] = "";
  97. _params["timestamp"] = GenerateTimeStamp();
  98. _params["nonce"] = GenerateNonce();
  99. _params["signature_method"] = "HMAC-SHA1";
  100. _params["signature"] = "";
  101. _params["token"] = "";
  102. _params["token_secret"] = "";
  103. _params["version"] = "1.0";
  104. }
  105. /// <summary>
  106. /// The constructor to use when using OAuth when you already
  107. /// have an OAuth access token.
  108. /// </summary>
  109. /// <remarks>
  110. /// <para>
  111. /// The parameters for this constructor all have the
  112. /// meaning you would expect. The token and tokenSecret
  113. /// are set in oauth_token, and oauth_token_secret.
  114. /// These are *Access* tokens, obtained after a call
  115. /// to AcquireAccessToken. The application can store
  116. /// those tokens and re-use them on successive runs.
  117. /// For twitter at least, the access tokens never expire.
  118. /// </para>
  119. /// </remarks>
  120. public Manager(string consumerKey,
  121. string consumerSecret,
  122. string token,
  123. string tokenSecret)
  124. : this()
  125. {
  126. _params["consumer_key"] = consumerKey;
  127. _params["consumer_secret"] = consumerSecret;
  128. _params["token"] = token;
  129. _params["token_secret"] = tokenSecret;
  130. }
  131. /// <summary>
  132. /// string indexer to get or set oauth parameter values.
  133. /// </summary>
  134. /// <remarks>
  135. /// <para>
  136. /// Use the parameter name *without* the oauth_ prefix.
  137. /// If you want to set the value for the oauth_token parameter
  138. /// field in an HTTP message, then use oauth["token"].
  139. /// </para>
  140. /// <para>
  141. /// The set of oauth param names known by this indexer includes:
  142. /// callback, consumer_key, consumer_secret, timestamp, nonce,
  143. /// signature_method, signature, token, token_secret, and version.
  144. /// </para>
  145. /// <para>
  146. /// If you try setting a parameter with a name that is not known,
  147. /// the setter will throw. You cannot add new oauth parameters
  148. /// using the setter on this indexer.
  149. /// </para>
  150. /// </remarks>
  151. public string this[string ix]
  152. {
  153. get
  154. {
  155. if (_params.ContainsKey(ix))
  156. return _params[ix];
  157. throw new ArgumentException(ix);
  158. }
  159. set
  160. {
  161. if (!_params.ContainsKey(ix))
  162. throw new ArgumentException(ix);
  163. _params[ix] = value;
  164. }
  165. }
  166. /// <summary>
  167. /// Generate the timestamp for the signature.
  168. /// </summary>
  169. /// <returns>The timestamp, in string form.</returns>
  170. private string GenerateTimeStamp()
  171. {
  172. TimeSpan ts = DateTime.UtcNow - _epoch;
  173. return Convert.ToInt64(ts.TotalSeconds).ToString();
  174. }
  175. /// <summary>
  176. /// Renews the nonce and timestamp on the oauth parameters.
  177. /// </summary>
  178. /// <remarks>
  179. /// <para>
  180. /// Each new request should get a new, current timestamp, and a
  181. /// nonce. This helper method does both of those things. This gets
  182. /// called before generating an authorization header, as for example
  183. /// when the user of this class calls <see cref='AcquireRequestToken'>.
  184. /// </para>
  185. /// </remarks>
  186. private void NewRequest()
  187. {
  188. _params["nonce"] = GenerateNonce();
  189. _params["timestamp"] = GenerateTimeStamp();
  190. }
  191. /// <summary>
  192. /// Generate an oauth nonce.
  193. /// </summary>
  194. /// <remarks>
  195. /// <para>
  196. /// According to RFC 5849, A nonce is a random string,
  197. /// uniquely generated by the client to allow the server to
  198. /// verify that a request has never been made before and
  199. /// helps prevent replay attacks when requests are made over
  200. /// a non-secure channel. The nonce value MUST be unique
  201. /// across all requests with the same timestamp, client
  202. /// credentials, and token combinations.
  203. /// </para>
  204. /// <para>
  205. /// One way to implement the nonce is just to use a
  206. /// monotonically-increasing integer value. It starts at zero and
  207. /// increases by 1 for each new request or signature generated.
  208. /// Keep in mind the nonce needs to be unique only for a given
  209. /// timestamp! So if your app makes less than one request per
  210. /// second, then using a static nonce of "0" will work.
  211. /// </para>
  212. /// <para>
  213. /// Most oauth nonce generation routines are waaaaay over-engineered,
  214. /// and this one is no exception.
  215. /// </para>
  216. /// </remarks>
  217. /// <returns>the nonce</returns>
  218. private string GenerateNonce()
  219. {
  220. var sb = new System.Text.StringBuilder();
  221. for (int i = 0; i < 8; i++)
  222. {
  223. int g = _random.Next(3);
  224. switch (g)
  225. {
  226. case 0:
  227. // lowercase alpha
  228. sb.Append((char)(_random.Next(26) + 97), 1);
  229. break;
  230. default:
  231. // numeric digits
  232. sb.Append((char)(_random.Next(10) + 48), 1);
  233. break;
  234. }
  235. }
  236. return sb.ToString();
  237. }
  238. /// <summary>
  239. /// Internal function to extract from a URL all query string
  240. /// parameters that are not related to oauth - in other words all
  241. /// parameters not begining with "oauth_".
  242. /// </summary>
  243. ///
  244. /// <remarks>
  245. /// <para>
  246. /// For example, given a url like http://foo?a=7&guff, the
  247. /// returned value will be a Dictionary of string-to-string
  248. /// relations. There will be 2 entries in the Dictionary: "a"=>7,
  249. /// and "guff"=>"".
  250. /// </para>
  251. /// </remarks>
  252. ///
  253. /// <param name="queryString">The query string part of the Url</param>
  254. ///
  255. /// <returns>A Dictionary containing the set of
  256. /// parameter names and associated values</returns>
  257. private Dictionary<String, String> ExtractQueryParameters(string queryString)
  258. {
  259. if (queryString.StartsWith("?"))
  260. queryString = queryString.Remove(0, 1);
  261. var result = new Dictionary<String, String>();
  262. if (string.IsNullOrEmpty(queryString))
  263. return result;
  264. foreach (string s in queryString.Split('&'))
  265. {
  266. if (!string.IsNullOrEmpty(s) && !s.StartsWith("oauth_"))
  267. {
  268. if (s.IndexOf('=') > -1)
  269. {
  270. string[] temp = s.Split('=');
  271. result.Add(temp[0], temp[1]);
  272. }
  273. else
  274. result.Add(s, string.Empty);
  275. }
  276. }
  277. return result;
  278. }
  279. /// <summary>
  280. /// This is an oauth-compliant Url Encoder. The default .NET
  281. /// encoder outputs the percent encoding in lower case. While this
  282. /// is not a problem with the percent encoding defined in RFC 3986,
  283. /// OAuth (RFC 5849) requires that the characters be upper case
  284. /// throughout OAuth.
  285. /// </summary>
  286. ///
  287. /// <param name="value">The value to encode</param>
  288. ///
  289. /// <returns>the Url-encoded version of that string</returns>
  290. public static string UrlEncode(string value)
  291. {
  292. var result = new System.Text.StringBuilder();
  293. foreach (char symbol in value)
  294. {
  295. if (unreservedChars.IndexOf(symbol) != -1)
  296. result.Append(symbol);
  297. else
  298. result.Append('%' + String.Format("{0:X2}", (int)symbol));
  299. }
  300. return result.ToString();
  301. }
  302. private static string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
  303. /// <summary>
  304. /// Formats the list of request parameters into string a according
  305. /// to the requirements of oauth. The resulting string could be used
  306. /// in the Authorization header of the request.
  307. /// </summary>
  308. ///
  309. /// <remarks>
  310. /// <para>
  311. /// See http://dev.twitter.com/pages/auth#intro for some
  312. /// background. The output of this is not suitable for signing.
  313. /// </para>
  314. /// <para>
  315. /// There are 2 formats for specifying the list of oauth
  316. /// parameters in the oauth spec: one suitable for signing, and
  317. /// the other suitable for use within Authorization HTTP Headers.
  318. /// This method emits a string suitable for the latter.
  319. /// </para>
  320. /// </remarks>
  321. ///
  322. /// <param name="parameters">The Dictionary of
  323. /// parameters. It need not be sorted.</param>
  324. ///
  325. /// <returns>a string representing the parameters</returns>
  326. private static string EncodeRequestParameters(ICollection<KeyValuePair<String, String>> p)
  327. {
  328. var sb = new System.Text.StringBuilder();
  329. foreach (KeyValuePair<String, String> item in p.OrderBy(x => x.Key))
  330. {
  331. if (!String.IsNullOrEmpty(item.Value) &&
  332. !item.Key.EndsWith("secret"))
  333. sb.AppendFormat("oauth_{0}=\"{1}\", ",
  334. item.Key,
  335. UrlEncode(item.Value));
  336. }
  337. return sb.ToString().TrimEnd(' ').TrimEnd(',');
  338. }
  339. /// <summary>
  340. /// Acquire a request token, from the given URI, using the given
  341. /// HTTP method.
  342. /// </summary>
  343. ///
  344. /// <remarks>
  345. /// <para>
  346. /// To use this method, first instantiate a new Oauth.Manager object,
  347. /// then set the callback param (oauth["callback"]='oob'). After the
  348. /// call returns, you should direct the user to open a browser window
  349. /// to the authorization page for the OAuth-enabled service. Or,
  350. /// you can automatically open that page yourself. Do this with
  351. /// System.Diagnostics.Process.Start(), passing the URL of the page.
  352. /// There should be one query param: oauth_token with the value
  353. /// obtained from oauth["token"].
  354. /// </para>
  355. /// <para>
  356. /// According to the OAuth spec, you need to do this only ONCE per
  357. /// application. In other words, the first time the application
  358. /// is run. The normal oauth workflow is: (1) get a request token,
  359. /// (2) use that to acquire an access token (which requires explicit
  360. /// user approval), then (3) using that access token, invoke
  361. /// protected services. The first two steps need to be done only
  362. /// once per application.
  363. /// </para>
  364. /// <para>
  365. /// For Twitter, at least, you can cache the access tokens
  366. /// indefinitely; Twitter says they never expire. However, other
  367. /// oauth services may not do the same. Also: the user may at any
  368. /// time revoke his authorization for your app, in which case you
  369. /// need to perform the first 2 steps again.
  370. /// </para>
  371. /// </remarks>
  372. ///
  373. /// <seealso cref='AcquireAccessToken'>
  374. ///
  375. ///
  376. /// <example>
  377. /// <para>
  378. /// This example shows how to request an access token and key
  379. /// from Twitter. It presumes you've already obtained a
  380. /// consumer key and secret via app registration. Requesting
  381. /// an access token is necessary only the first time you
  382. /// contact the service. You can cache the access key and
  383. /// token for subsequent runs, later.
  384. /// </para>
  385. /// <code>
  386. /// // the URL to obtain a temporary "request token"
  387. /// var rtUrl = "https://api.twitter.com/oauth/request_token";
  388. /// var oauth = new OAuth.Manager();
  389. /// // The consumer_{key,secret} are obtained via registration
  390. /// oauth["consumer_key"] = "~~~CONSUMER_KEY~~~~";
  391. /// oauth["consumer_secret"] = "~~~CONSUMER_SECRET~~~";
  392. /// oauth.AcquireRequestToken(rtUrl, "POST");
  393. /// var authzUrl = "https://api.twitter.com/oauth/authorize?oauth_token=" + oauth["token"];
  394. /// System.Diagnostics.Process.Start(authzUrl);
  395. /// // instruct the user to type in the PIN from that browser window
  396. /// var pin = "...";
  397. /// var atUrl = "https://api.twitter.com/oauth/access_token";
  398. /// oauth.AcquireAccessToken(atUrl, "POST", pin);
  399. ///
  400. /// // now, update twitter status using that access token
  401. /// var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
  402. /// var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
  403. /// var request = (HttpWebRequest)WebRequest.Create(appUrl);
  404. /// request.Method = "POST";
  405. /// request.PreAuthenticate = true;
  406. /// request.AllowWriteStreamBuffering = true;
  407. /// request.Headers.Add("Authorization", authzHeader);
  408. ///
  409. /// using (var response = (HttpWebResponse)request.GetResponse())
  410. /// {
  411. /// if (response.StatusCode != HttpStatusCode.OK)
  412. /// MessageBox.Show("There's been a problem trying to tweet:" +
  413. /// Environment.NewLine +
  414. /// response.StatusDescription);
  415. /// }
  416. /// </code>
  417. /// </example>
  418. ///
  419. /// <returns>
  420. /// a response object that contains the entire text of the response,
  421. /// as well as extracted parameters. This method presumes the
  422. /// response is query-param encoded. In other words,
  423. /// poauth_token=foo&something_else=bar.
  424. /// </returns>
  425. public OAuthResponse AcquireRequestToken(string uri, string method)
  426. {
  427. NewRequest();
  428. var authzHeader = GetAuthorizationHeader(uri, method);
  429. // prepare the token request
  430. var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
  431. request.Headers.Add("Authorization", authzHeader);
  432. request.Method = method;
  433. using (var response = (System.Net.HttpWebResponse)request.GetResponse())
  434. {
  435. using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
  436. {
  437. var r = new OAuthResponse(reader.ReadToEnd());
  438. this["token"] = r["oauth_token"];
  439. // Sometimes the request_token URL gives us an access token,
  440. // with no user interaction required. Eg, when prior approval
  441. // has already been granted.
  442. try
  443. {
  444. if (r["oauth_token_secret"] != null)
  445. this["token_secret"] = r["oauth_token_secret"];
  446. }
  447. catch { }
  448. return r;
  449. }
  450. }
  451. }
  452. /// <summary>
  453. /// Acquire an access token, from the given URI, using the given
  454. /// HTTP method.
  455. /// </summary>
  456. ///
  457. /// <remarks>
  458. /// <para>
  459. /// To use this method, you must first set the oauth_token to the value
  460. /// of the request token. Eg, oauth["token"] = "whatever".
  461. /// </para>
  462. /// <para>
  463. /// According to the OAuth spec, you need to do this only ONCE per
  464. /// application. In other words, the first time the application
  465. /// is run. The normal oauth workflow is: (1) get a request token,
  466. /// (2) use that to acquire an access token (which requires explicit
  467. /// user approval), then (3) using that access token, invoke
  468. /// protected services. The first two steps need to be done only
  469. /// once per application.
  470. /// </para>
  471. /// <para>
  472. /// For Twitter, at least, you can cache the access tokens
  473. /// indefinitely; Twitter says they never expire. However, other
  474. /// oauth services may not do the same. Also: the user may at any
  475. /// time revoke his authorization for your app, in which case you
  476. /// need to perform the first 2 steps again.
  477. /// </para>
  478. /// </remarks>
  479. ///
  480. /// <seealso cref='AcquireRequestToken'>
  481. ///
  482. /// <returns>
  483. /// a response object that contains the entire text of the response,
  484. /// as well as extracted parameters. This method presumes the
  485. /// response is query-param encoded. In other words,
  486. /// poauth_token=foo&something_else=bar.
  487. /// </returns>
  488. public OAuthResponse AcquireAccessToken(string uri, string method, string pin)
  489. {
  490. NewRequest();
  491. _params["verifier"] = pin;
  492. var authzHeader = GetAuthorizationHeader(uri, method);
  493. // prepare the token request
  494. var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
  495. request.Headers.Add("Authorization", authzHeader);
  496. request.Method = method;
  497. using (var response = (System.Net.HttpWebResponse)request.GetResponse())
  498. {
  499. using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
  500. {
  501. var r = new OAuthResponse(reader.ReadToEnd());
  502. this["token"] = r["oauth_token"];
  503. this["token_secret"] = r["oauth_token_secret"];
  504. return r;
  505. }
  506. }
  507. }
  508. /// <summary>
  509. /// Generate a string to be used in an Authorization header in
  510. /// an HTTP request.
  511. /// </summary>
  512. /// <remarks>
  513. /// <para>
  514. /// This method assembles the available oauth_ parameters that
  515. /// have been set in the Dictionary in this instance, produces
  516. /// the signature base (As described by the OAuth spec, RFC 5849),
  517. /// signs it, then re-formats the oauth_ parameters into the
  518. /// appropriate form, including the oauth_signature value, and
  519. /// returns the result.
  520. /// </para>
  521. /// <para>
  522. /// If you pass in a non-null, non-empty realm, this method will
  523. /// include the realm='foo' clause in the Authorization header.
  524. /// </para>
  525. /// </remarks>
  526. ///
  527. /// <seealso cref='GenerateAuthzHeader'/>
  528. public string GenerateCredsHeader(string uri, string method, string realm)
  529. {
  530. NewRequest();
  531. var authzHeader = GetAuthorizationHeader(uri, method, realm);
  532. return authzHeader;
  533. }
  534. /// <summary>
  535. /// Generate a string to be used in an Authorization header in
  536. /// an HTTP request.
  537. /// </summary>
  538. /// <remarks>
  539. /// <para>
  540. /// This method assembles the available oauth_ parameters that
  541. /// have been set in the Dictionary in this instance, produces
  542. /// the signature base (As described by the OAuth spec, RFC 5849),
  543. /// signs it, then re-formats the oauth_ parameters into the
  544. /// appropriate form, including the oauth_signature value, and
  545. /// returns the result.
  546. /// </para>
  547. /// </remarks>
  548. ///
  549. /// <example>
  550. /// <para>
  551. /// This example shows how to update the Twitter status
  552. /// using the stored consumer key and secret, and a previously
  553. /// obtained access token and secret.
  554. /// </para>
  555. /// <code>
  556. /// var oauth = new OAuth.Manager();
  557. /// oauth["consumer_key"] = "~~ your stored consumer key ~~";
  558. /// oauth["consumer_secret"] = "~~ your stored consumer secret ~~";
  559. /// oauth["token"] = "~~ your stored access token ~~";
  560. /// oauth["token_secret"] = "~~ your stored access secret ~~";
  561. /// var appUrl = "http://api.twitter.com/1/statuses/update.xml?status=Hello";
  562. /// var authzHeader = oauth.GenerateAuthzHeader(appUrl, "POST");
  563. /// var request = (HttpWebRequest)WebRequest.Create(appUrl);
  564. /// request.Method = "POST";
  565. /// request.PreAuthenticate = true;
  566. /// request.AllowWriteStreamBuffering = true;
  567. /// request.Headers.Add("Authorization", authzHeader);
  568. ///
  569. /// using (var response = (HttpWebResponse)request.GetResponse())
  570. /// {
  571. /// if (response.StatusCode != HttpStatusCode.OK)
  572. /// MessageBox.Show("There's been a problem trying to tweet:" +
  573. /// Environment.NewLine +
  574. /// response.StatusDescription);
  575. /// }
  576. /// </code>
  577. /// </example>
  578. /// <seealso cref='GenerateCredsHeader'/>
  579. public string GenerateAuthzHeader(string uri, string method)
  580. {
  581. NewRequest();
  582. var authzHeader = GetAuthorizationHeader(uri, method, null);
  583. return authzHeader;
  584. }
  585. private string GetAuthorizationHeader(string uri, string method)
  586. {
  587. return GetAuthorizationHeader(uri, method, null);
  588. }
  589. private string GetAuthorizationHeader(string uri, string method, string realm)
  590. {
  591. if (string.IsNullOrEmpty(this._params["consumer_key"]))
  592. throw new ArgumentNullException("consumer_key");
  593. if (string.IsNullOrEmpty(this._params["signature_method"]))
  594. throw new ArgumentNullException("signature_method");
  595. Sign(uri, method);
  596. var erp = EncodeRequestParameters(this._params);
  597. return (String.IsNullOrEmpty(realm))
  598. ? "OAuth " + erp
  599. : String.Format("OAuth realm=\"{0}\", ", realm) + erp;
  600. }
  601. private void Sign(string uri, string method)
  602. {
  603. var signatureBase = GetSignatureBase(uri, method);
  604. var hash = GetHash();
  605. byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(signatureBase);
  606. byte[] hashBytes = hash.ComputeHash(dataBuffer);
  607. this["signature"] = Convert.ToBase64String(hashBytes);
  608. }
  609. /// <summary>
  610. /// Formats the list of request parameters into "signature base" string as
  611. /// defined by RFC 5849. This will then be MAC'd with a suitable hash.
  612. /// </summary>
  613. private string GetSignatureBase(string url, string method)
  614. {
  615. // normalize the URI
  616. var uri = new Uri(url);
  617. var normUrl = string.Format("{0}://{1}", uri.Scheme, uri.Host);
  618. if (!((uri.Scheme == "http" && uri.Port == 80) ||
  619. (uri.Scheme == "https" && uri.Port == 443)))
  620. normUrl += ":" + uri.Port;
  621. normUrl += uri.AbsolutePath;
  622. // the sigbase starts with the method and the encoded URI
  623. var sb = new System.Text.StringBuilder();
  624. sb.Append(method)
  625. .Append('&')
  626. .Append(UrlEncode(normUrl))
  627. .Append('&');
  628. // the parameters follow - all oauth params plus any params on
  629. // the uri
  630. // each uri may have a distinct set of query params
  631. var p = ExtractQueryParameters(uri.Query);
  632. // add all non-empty params to the "current" params
  633. foreach (var p1 in this._params)
  634. {
  635. // Exclude all oauth params that are secret or
  636. // signatures; any secrets should be kept to ourselves,
  637. // and any existing signature will be invalid.
  638. if (!String.IsNullOrEmpty(this._params[p1.Key]) &&
  639. !p1.Key.EndsWith("_secret") &&
  640. !p1.Key.EndsWith("signature"))
  641. p.Add("oauth_" + p1.Key, p1.Value);
  642. }
  643. // concat+format all those params
  644. var sb1 = new System.Text.StringBuilder();
  645. foreach (KeyValuePair<String, String> item in p.OrderBy(x => x.Key))
  646. {
  647. // even "empty" params need to be encoded this way.
  648. sb1.AppendFormat("{0}={1}&", item.Key, item.Value);
  649. }
  650. // append the UrlEncoded version of that string to the sigbase
  651. sb.Append(UrlEncode(sb1.ToString().TrimEnd('&')));
  652. var result = sb.ToString();
  653. return result;
  654. }
  655. private HashAlgorithm GetHash()
  656. {
  657. if (this["signature_method"] != "HMAC-SHA1")
  658. throw new NotImplementedException();
  659. string keystring = string.Format("{0}&{1}",
  660. UrlEncode(this["consumer_secret"]),
  661. UrlEncode(this["token_secret"]));
  662. var hmacsha1 = new HMACSHA1
  663. {
  664. Key = System.Text.Encoding.ASCII.GetBytes(keystring)
  665. };
  666. return hmacsha1;
  667. }
  668. #if BROKEN
  669. /// <summary>
  670. /// Return the oauth string that can be used in an Authorization
  671. /// header. All the oauth terms appear in the string, in alphabetical
  672. /// order.
  673. /// </summary>
  674. public string GetOAuthHeader()
  675. {
  676. return EncodeRequestParameters(this._params);
  677. }
  678. #endif
  679. private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);
  680. private Dictionary<String, String> _params;
  681. private Random _random;
  682. }
  683. /// <summary>
  684. /// A class to hold an OAuth response message.
  685. /// </summary>
  686. public class OAuthResponse
  687. {
  688. /// <summary>
  689. /// All of the text in the response. This is useful if the app wants
  690. /// to do its own parsing.
  691. /// </summary>
  692. public string AllText { get; set; }
  693. private Dictionary<String, String> _params;
  694. /// <summary>
  695. /// a Dictionary of response parameters.
  696. /// </summary>
  697. public string this[string ix]
  698. {
  699. get
  700. {
  701. return _params[ix];
  702. }
  703. }
  704. public OAuthResponse(string alltext)
  705. {
  706. AllText = alltext;
  707. _params = new Dictionary<String, String>();
  708. var kvpairs = alltext.Split('&');
  709. foreach (var pair in kvpairs)
  710. {
  711. var kv = pair.Split('=');
  712. _params.Add(kv[0], kv[1]);
  713. }
  714. // expected keys:
  715. // oauth_token, oauth_token_secret, user_id, screen_name, etc
  716. }
  717. }
  718. }