PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/src/BoxKite.Twitter/Authentication/TwitterAuthenticator.cs

https://github.com/ahmad2x4/BoxKite.Twitter
C# | 498 lines | 412 code | 68 blank | 18 comment | 61 complexity | aa338465c79b9c31b172fd95b44ea5d9 MD5 | raw file
  1. // (c) 2012-2014 Nick Hodge mailto:hodgenick@gmail.com & Brendan Forster
  2. // License: MS-PL
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics.Contracts;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Reactive.Linq;
  11. using System.Reactive.Threading.Tasks;
  12. using System.Text;
  13. using BoxKite.Twitter.Extensions;
  14. using BoxKite.Twitter.Models;
  15. using System.Threading.Tasks;
  16. using Newtonsoft.Json;
  17. using Newtonsoft.Json.Linq;
  18. namespace BoxKite.Twitter.Authentication
  19. {
  20. public static class TwitterAuthenticator
  21. {
  22. public static async Task<string> StartUserAuthentication(this IUserSession session)
  23. {
  24. if (string.IsNullOrWhiteSpace(session.clientID))
  25. throw new ArgumentException("ClientID must be specified", session.clientID);
  26. if (string.IsNullOrWhiteSpace(session.clientSecret))
  27. throw new ArgumentException("ClientSecret must be specified", session.clientSecret);
  28. if (session.PlatformAdaptor == null)
  29. throw new ArgumentException("Need a Platform Adaptor");
  30. var sinceEpoch = session.GenerateTimestamp();
  31. var nonce = session.GenerateNoonce();
  32. var sigBaseStringParams =
  33. string.Format(
  34. "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method=HMAC-SHA1&oauth_timestamp={2}&oauth_version=1.0",
  35. session.clientID,
  36. nonce,
  37. sinceEpoch);
  38. var sigBaseString = string.Format("POST&{0}&{1}", TwitterApi.RequestTokenUrl().UrlEncode(), sigBaseStringParams.UrlEncode());
  39. var signature = session.GenerateSignature(session.clientSecret, sigBaseString, null);
  40. var dataToPost = string.Format(
  41. "OAuth realm=\"\", oauth_nonce=\"{0}\", oauth_timestamp=\"{1}\", oauth_consumer_key=\"{2}\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_signature=\"{3}\"",
  42. nonce,
  43. sinceEpoch,
  44. session.clientID,
  45. signature.UrlEncode());
  46. var response = await PostData(TwitterApi.RequestTokenUrl(), dataToPost);
  47. if (string.IsNullOrWhiteSpace(response))
  48. return null;
  49. var oAuthToken = "";
  50. foreach (var splits in response.Split('&').Select(t => t.Split('=')))
  51. {
  52. switch (splits[0])
  53. {
  54. case "oauth_token": //these tokens are request tokens, first step before getting access tokens
  55. oAuthToken = splits[1];
  56. break;
  57. case "oauth_token_secret": // not used
  58. var OAuthTokenSecret = splits[1];
  59. break;
  60. case "oauth_callback_confirmed":
  61. break;
  62. }
  63. }
  64. if (!string.IsNullOrWhiteSpace(oAuthToken))
  65. session.PlatformAdaptor.DisplayAuthInBrowser(TwitterApi.AuthenticateUrl()+ oAuthToken);
  66. return oAuthToken;
  67. }
  68. public static async Task<TwitterCredentials> ConfirmPin(this IUserSession session, string pinAuthorizationCode, string oAuthToken)
  69. {
  70. if (string.IsNullOrWhiteSpace(pinAuthorizationCode))
  71. throw new ArgumentException("pin AuthorizationCode must be specified", pinAuthorizationCode);
  72. var sinceEpoch = session.GenerateTimestamp();
  73. var nonce = session.GenerateNoonce();
  74. var dataToPost = string.Format(
  75. "OAuth realm=\"\", oauth_nonce=\"{0}\", oauth_timestamp=\"{1}\", oauth_consumer_key=\"{2}\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_verifier=\"{3}\", oauth_token=\"{4}\"",
  76. nonce,
  77. sinceEpoch,
  78. session.clientID,
  79. pinAuthorizationCode,
  80. oAuthToken);
  81. var response = await PostData(TwitterApi.AuthorizeTokenUrl(), dataToPost);
  82. if (string.IsNullOrWhiteSpace(response))
  83. return TwitterCredentials.Null; //oops something wrong here
  84. var _accessToken = "";
  85. var _accessTokenSecret = "";
  86. var _userId = "";
  87. var _screenName = "";
  88. foreach (var splits in response.Split('&').Select(t => t.Split('=')))
  89. {
  90. switch (splits[0])
  91. {
  92. case "oauth_token": //these tokens are request tokens, first step before getting access tokens
  93. _accessToken = splits[1];
  94. break;
  95. case "oauth_token_secret":
  96. _accessTokenSecret = splits[1];
  97. break;
  98. case "user_id":
  99. _userId = splits[1];
  100. break;
  101. case "screen_name":
  102. _screenName = splits[1];
  103. break;
  104. }
  105. }
  106. if (_accessToken != null && _accessTokenSecret != null && _userId != null && _screenName != null)
  107. {
  108. if (await session.StartApplicationOnlyAuth())
  109. {
  110. var twitterCreds = new TwitterCredentials()
  111. {
  112. ConsumerKey = session.clientID,
  113. ConsumerSecret = session.clientSecret,
  114. BearerToken = session.bearerToken,
  115. ScreenName = _screenName,
  116. Token = _accessToken,
  117. TokenSecret = _accessTokenSecret,
  118. UserID = Int64.Parse(_userId),
  119. Valid = true
  120. };
  121. session.IsActive = true;
  122. return twitterCreds;
  123. }
  124. }
  125. return TwitterCredentials.Null;
  126. }
  127. #if (WIN8RT)
  128. public static async Task<TwitterCredentials> Authentication(this IUserSession session, string _callbackuri)
  129. {
  130. if (string.IsNullOrWhiteSpace(session.clientID))
  131. throw new ArgumentException("ClientID must be specified", session.clientID);
  132. if (string.IsNullOrWhiteSpace(session.clientSecret))
  133. throw new ArgumentException("ClientSecret must be specified", session.clientSecret);
  134. var sinceEpoch = session.GenerateTimestamp();
  135. var nonce = session.GenerateNoonce();
  136. var sigBaseStringParams =
  137. string.Format(
  138. "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method=HMAC-SHA1&oauth_timestamp={2}&oauth_version=1.0",
  139. session.clientID,
  140. nonce,
  141. sinceEpoch);
  142. var sigBaseString = string.Format("POST&{0}&{1}", TwitterApi.RequestTokenUrl().UrlEncode(), sigBaseStringParams.UrlEncode());
  143. var signature = session.GenerateSignature(session.clientSecret, sigBaseString, null);
  144. var dataToPost = string.Format(
  145. "OAuth realm=\"\", oauth_nonce=\"{0}\", oauth_timestamp=\"{1}\", oauth_consumer_key=\"{2}\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_signature=\"{3}\"",
  146. nonce,
  147. sinceEpoch,
  148. session.clientID,
  149. signature.UrlEncode());
  150. var response = await PostData(TwitterApi.RequestTokenUrl(), dataToPost);
  151. if (string.IsNullOrWhiteSpace(response))
  152. return TwitterCredentials.Null;
  153. var oauthCallbackConfirmed = false;
  154. var oAuthToken = "";
  155. foreach (var splits in response.Split('&').Select(t => t.Split('=')))
  156. {
  157. switch (splits[0])
  158. {
  159. case "oauth_token": //these tokens are request tokens, first step before getting access tokens
  160. oAuthToken = splits[1];
  161. break;
  162. case "oauth_token_secret":
  163. var OAuthTokenSecret = splits[1];
  164. break;
  165. case "oauth_callback_confirmed":
  166. if (splits[1].ToLower() == "true") oauthCallbackConfirmed = true;
  167. break;
  168. }
  169. }
  170. if (oauthCallbackConfirmed && !string.IsNullOrWhiteSpace(oAuthToken))
  171. {
  172. var authresponse = await session.PlatformAdaptor.AuthWithBroker(TwitterApi.AuthenticateUrl() + oAuthToken, _callbackuri);
  173. if (!string.IsNullOrWhiteSpace(authresponse))
  174. {
  175. var responseData = authresponse.Substring(authresponse.IndexOf("oauth_token"));
  176. string request_token = null;
  177. string oauth_verifier = null;
  178. String[] keyValPairs = responseData.Split('&');
  179. foreach (var t in keyValPairs)
  180. {
  181. var splits = t.Split('=');
  182. switch (splits[0])
  183. {
  184. case "oauth_token":
  185. request_token = splits[1];
  186. break;
  187. case "oauth_verifier":
  188. oauth_verifier = splits[1];
  189. break;
  190. }
  191. }
  192. sinceEpoch = session.GenerateTimestamp();
  193. nonce = session.GenerateNoonce();
  194. sigBaseStringParams = string.Format(
  195. "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method=HMAC-SHA1&oauth_timestamp={2}&oauth_token={3}&oauth_version=1.0",
  196. session.clientID,
  197. nonce,
  198. sinceEpoch,
  199. request_token);
  200. sigBaseString = string.Format("POST&{0}&{1}", TwitterApi.AuthorizeTokenUrl().UrlEncode(), sigBaseStringParams.UrlEncode());
  201. signature = session.GenerateSignature(session.clientSecret, sigBaseString, null);
  202. var httpContent = String.Format("oauth_verifier={0}", oauth_verifier);
  203. dataToPost = string.Format(
  204. "OAuth realm=\"\", oauth_nonce=\"{0}\", oauth_timestamp=\"{1}\", oauth_consumer_key=\"{2}\", oauth_signature_method=\"HMAC-SHA1\", oauth_version=\"1.0\", oauth_token=\"{3}\", oauth_signature=\"{4}\"",
  205. nonce,
  206. sinceEpoch,
  207. session.clientID,
  208. request_token,
  209. signature.UrlEncode());
  210. response = await PostData(TwitterApi.AuthorizeTokenUrl(), dataToPost, httpContent);
  211. if (string.IsNullOrWhiteSpace(response))
  212. return TwitterCredentials.Null; //oops something wrong here
  213. var _accessToken = "";
  214. var _accessTokenSecret = "";
  215. var _userId = "";
  216. var _screenName = "";
  217. foreach (var splits in response.Split('&').Select(t => t.Split('=')))
  218. {
  219. switch (splits[0])
  220. {
  221. case "oauth_token": //these tokens are request tokens, first step before getting access tokens
  222. _accessToken = splits[1];
  223. break;
  224. case "oauth_token_secret":
  225. _accessTokenSecret = splits[1];
  226. break;
  227. case "user_id":
  228. _userId = splits[1];
  229. break;
  230. case "screen_name":
  231. _screenName = splits[1];
  232. break;
  233. }
  234. }
  235. if (_accessToken != null && _accessTokenSecret != null && _userId != null && _screenName != null)
  236. {
  237. return new TwitterCredentials()
  238. {
  239. ConsumerKey = session.clientID,
  240. ConsumerSecret = session.clientSecret,
  241. ScreenName = _screenName,
  242. Token = _accessToken,
  243. TokenSecret = _accessTokenSecret,
  244. UserID = Int64.Parse(_userId),
  245. Valid = true
  246. };
  247. }
  248. return TwitterCredentials.Null;
  249. }
  250. }
  251. return TwitterCredentials.Null;
  252. }
  253. #endif
  254. /// <summary>
  255. /// XAuth for Twitter: Note: this hasn't been tested in production and is implemented from documentation only!
  256. /// Please contact @NickHodgeMSFT if you need to debugged worked out!
  257. /// </summary>
  258. public static async Task<TwitterCredentials> XAuthentication(this IUserSession session, string xauthusername,
  259. string xauthpassword)
  260. {
  261. if (string.IsNullOrWhiteSpace(session.clientID))
  262. throw new ArgumentException("ClientID must be specified", session.clientID);
  263. if (string.IsNullOrWhiteSpace(session.clientSecret))
  264. throw new ArgumentException("ClientSecret must be specified", session.clientSecret);
  265. var sinceEpoch = session.GenerateTimestamp();
  266. var nonce = session.GenerateNoonce();
  267. var sigBaseStringParams =
  268. string.Format(
  269. "oauth_consumer_key={0}&oauth_nonce={1}&oauth_signature_method=HMAC-SHA1&oauth_timestamp={2}&oauth_version=1.0",
  270. session.clientID,
  271. nonce,
  272. sinceEpoch);
  273. var sigBaseString = string.Format("POST&{0}&{1}", TwitterApi.XAuthorizeTokenUrl().UrlEncode(),
  274. sigBaseStringParams.UrlEncode());
  275. var signature = session.GenerateSignature(session.clientSecret, sigBaseString, null);
  276. var dataToPost = string.Format(
  277. "OAuth oauth_consumer_key=\"{2}\",oauth_nonce=\"{0}\",oauth_signature=\"{3}\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"{1}\",oauth_version=\"1.0\"",
  278. nonce,
  279. sinceEpoch,
  280. session.clientID,
  281. signature.UrlEncode());
  282. var contentToPost = string.Format(
  283. "x_auth_username={0}&x_auth_password={1}&x_auth_mode=client_auth",
  284. xauthusername.UrlEncode(),
  285. xauthpassword.UrlEncode());
  286. var authresponse = await PostData(TwitterApi.XAuthorizeTokenUrl(), dataToPost, contentToPost);
  287. var _accessToken = "";
  288. var _accessTokenSecret = "";
  289. var _userId = "";
  290. var _screenName = "";
  291. foreach (var splits in authresponse.Split('&').Select(t => t.Split('=')))
  292. {
  293. switch (splits[0])
  294. {
  295. case "oauth_token": //these tokens are request tokens, first step before getting access tokens
  296. _accessToken = splits[1];
  297. break;
  298. case "oauth_token_secret":
  299. _accessTokenSecret = splits[1];
  300. break;
  301. case "user_id":
  302. _userId = splits[1];
  303. break;
  304. case "screen_name":
  305. _screenName = splits[1];
  306. break;
  307. }
  308. }
  309. if (_accessToken != null && _accessTokenSecret != null && _userId != null && _screenName != null)
  310. {
  311. return new TwitterCredentials()
  312. {
  313. ConsumerKey = session.clientID,
  314. ConsumerSecret = session.clientSecret,
  315. ScreenName = _screenName,
  316. Token = _accessToken,
  317. TokenSecret = _accessTokenSecret,
  318. UserID = Int64.Parse(_userId),
  319. Valid = true
  320. };
  321. }
  322. return TwitterCredentials.Null;
  323. }
  324. /// <summary>
  325. /// Using client(consumer) id and key, start a 'readonly' Application-auth'd session
  326. /// </summary>
  327. public static async Task<bool> StartApplicationOnlyAuth(this IApplicationSession appsession)
  328. {
  329. if (string.IsNullOrEmpty(appsession.clientID))
  330. throw new ArgumentException("Twitter Consumer Key is required for Application only Auth");
  331. if (string.IsNullOrEmpty(appsession.clientSecret))
  332. throw new ArgumentException("Twitter Consumer Secret is required for Application only Auth");
  333. // ref: https://dev.twitter.com/docs/auth/application-only-auth
  334. // and ref: http://tools.ietf.org/html/rfc6749#section-4.4
  335. var oAuth2TokenUrlPostRequestRfc6749 = new SortedDictionary<string, string>
  336. {
  337. {"grant_type", "client_credentials"}
  338. };
  339. var result = await appsession.PostAsync(TwitterApi.OAuth2TokenUrl(), oAuth2TokenUrlPostRequestRfc6749, forInitialAuth: true);
  340. if (!result.IsSuccessStatusCode)
  341. return false;
  342. var content = await result.Content.ReadAsStringAsync();
  343. var jresponse = JObject.Parse(content);
  344. appsession.bearerToken = (string) jresponse["access_token"];
  345. appsession.IsActive = true;
  346. return true;
  347. }
  348. /// <summary>
  349. /// Using client(consumer) id and key, start a 'readonly' Application-auth'd session
  350. /// </summary>
  351. /// <remarks>ref: https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token </remarks>
  352. public static async Task<bool> StopApplicationOnlyAuth(this IApplicationSession appsession)
  353. {
  354. if (string.IsNullOrEmpty(appsession.clientID))
  355. throw new ArgumentException("Twitter Consumer Key is required for Application only Auth");
  356. if (string.IsNullOrEmpty(appsession.clientSecret))
  357. throw new ArgumentException("Twitter Consumer Secret is required for Application only Auth");
  358. // ref: https://dev.twitter.com/docs/auth/application-only-auth
  359. // and ref: http://tools.ietf.org/html/rfc6749#section-4.4
  360. // and ref: https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token
  361. var oAuth2TokenUrlPostRequestRfc6749 = new SortedDictionary<string, string>
  362. {
  363. {"access_token", appsession.bearerToken}
  364. };
  365. var result = await appsession.PostAsync(TwitterApi.OAuth2TokenRevokeUrl(), oAuth2TokenUrlPostRequestRfc6749, forInitialAuth: true);
  366. if (!result.IsSuccessStatusCode)
  367. return false;
  368. var content = await result.Content.ReadAsStringAsync();
  369. var jresponse = JObject.Parse(content);
  370. appsession.bearerToken = (string)jresponse["access_token"];
  371. appsession.IsActive = false;
  372. return true;
  373. }
  374. private static string GenerateSignature(this IUserSession session, string signingKey, string baseString, string tokenSecret)
  375. {
  376. session.PlatformAdaptor.AssignKey(Encoding.UTF8.GetBytes(string.Format("{0}&{1}", OAuthUrlEncode(signingKey),
  377. string.IsNullOrEmpty(tokenSecret)
  378. ? ""
  379. : OAuthUrlEncode(tokenSecret))));
  380. var dataBuffer = Encoding.UTF8.GetBytes(baseString);
  381. var hashBytes = session.PlatformAdaptor.ComputeHash(dataBuffer);
  382. var signatureString = Convert.ToBase64String(hashBytes);
  383. return signatureString;
  384. }
  385. private static string OAuthUrlEncode(string value)
  386. {
  387. var result = new StringBuilder();
  388. foreach (var symbol in value)
  389. {
  390. if (TwitterApi.SafeURLEncodeChars().IndexOf((char) symbol) != -1)
  391. {
  392. result.Append(symbol);
  393. }
  394. else
  395. {
  396. result.Append('%' + String.Format("{0:X2}", (int) symbol));
  397. }
  398. }
  399. return result.ToString();
  400. }
  401. private static async Task<string> PostData(string url, string authdata, string content = null)
  402. {
  403. try
  404. {
  405. var handler = new HttpClientHandler();
  406. if (handler.SupportsAutomaticDecompression)
  407. {
  408. handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
  409. }
  410. var client = new HttpClient(handler);
  411. var request = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
  412. request.Headers.Add("Accept-Encoding", "identity");
  413. request.Headers.Add("User-Agent", TwitterApi.UserAgent());
  414. request.Headers.Add("Authorization", authdata);
  415. if (content != null)
  416. {
  417. request.Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded");
  418. }
  419. var response = await client.SendAsync(request);
  420. var clientresponse =
  421. response.Content.ReadAsStringAsync().ToObservable().Timeout(TimeSpan.FromSeconds(30));
  422. return await clientresponse;
  423. }
  424. catch (Exception e)
  425. {
  426. return e.Message;
  427. }
  428. }
  429. }
  430. }