comparison Agendas/trunk/src/Agendas.Twitter/oAuth.cs @ 90:d1688622fa88

Autenticando con twitter (falta emprolijar el código, pero autentica!)
author Nelo@Kenia.neluz.int
date Fri, 03 Jun 2011 21:35:59 -0300
parents
children
comparison
equal deleted inserted replaced
89:24e9488ac152 90:d1688622fa88
1 using System;
2 using System.Security.Cryptography;
3 using System.Collections.Generic;
4 using System.Text;
5 using System.Web;
6
7 namespace AltNetHispano.Agendas.Twitter
8 {
9 public class OAuthBase
10 {
11
12 /// <summary>
13 /// Provides a predefined set of algorithms that are supported officially by the protocol
14 /// </summary>
15 public enum SignatureTypes
16 {
17 HMACSHA1,
18 PLAINTEXT,
19 RSASHA1
20 }
21
22 /// <summary>
23 /// Provides an internal structure to sort the query parameter
24 /// </summary>
25 protected class QueryParameter
26 {
27 private string name = null;
28 private string value = null;
29
30 public QueryParameter(string name, string value)
31 {
32 this.name = name;
33 this.value = value;
34 }
35
36 public string Name
37 {
38 get { return name; }
39 }
40
41 public string Value
42 {
43 get { return value; }
44 }
45 }
46
47 /// <summary>
48 /// Comparer class used to perform the sorting of the query parameters
49 /// </summary>
50 protected class QueryParameterComparer : IComparer<QueryParameter>
51 {
52
53 #region IComparer<QueryParameter> Members
54
55 public int Compare(QueryParameter x, QueryParameter y)
56 {
57 if (x.Name == y.Name)
58 {
59 return string.Compare(x.Value, y.Value);
60 }
61 else
62 {
63 return string.Compare(x.Name, y.Name);
64 }
65 }
66
67 #endregion
68 }
69
70 protected const string OAuthVersion = "1.0";
71 protected const string OAuthParameterPrefix = "oauth_";
72
73 //
74 // List of know and used oauth parameters' names
75 //
76 protected const string OAuthConsumerKeyKey = "oauth_consumer_key";
77 protected const string OAuthCallbackKey = "oauth_callback";
78 protected const string OAuthVersionKey = "oauth_version";
79 protected const string OAuthSignatureMethodKey = "oauth_signature_method";
80 protected const string OAuthSignatureKey = "oauth_signature";
81 protected const string OAuthTimestampKey = "oauth_timestamp";
82 protected const string OAuthNonceKey = "oauth_nonce";
83 protected const string OAuthTokenKey = "oauth_token";
84 protected const string OAuthTokenSecretKey = "oauth_token_secret";
85 protected const string OAuthVerifierKey = "oauth_verifier";
86
87 protected const string HMACSHA1SignatureType = "HMAC-SHA1";
88 protected const string PlainTextSignatureType = "PLAINTEXT";
89 protected const string RSASHA1SignatureType = "RSA-SHA1";
90
91 protected Random random = new Random();
92
93 protected string unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
94
95 /// <summary>
96 /// Helper function to compute a hash value
97 /// </summary>
98 /// <param name="hashAlgorithm">The hashing algoirhtm used. If that algorithm needs some initialization, like HMAC and its derivatives, they should be initialized prior to passing it to this function</param>
99 /// <param name="data">The data to hash</param>
100 /// <returns>a Base64 string of the hash value</returns>
101 private string ComputeHash(HashAlgorithm hashAlgorithm, string data)
102 {
103 if (hashAlgorithm == null)
104 {
105 throw new ArgumentNullException("hashAlgorithm");
106 }
107
108 if (string.IsNullOrEmpty(data))
109 {
110 throw new ArgumentNullException("data");
111 }
112
113 byte[] dataBuffer = System.Text.Encoding.ASCII.GetBytes(data);
114 byte[] hashBytes = hashAlgorithm.ComputeHash(dataBuffer);
115
116 return Convert.ToBase64String(hashBytes);
117 }
118
119 /// <summary>
120 /// Internal function to cut out all non oauth query string parameters (all parameters not begining with "oauth_")
121 /// </summary>
122 /// <param name="parameters">The query string part of the Url</param>
123 /// <returns>A list of QueryParameter each containing the parameter name and value</returns>
124 private List<QueryParameter> GetQueryParameters(string parameters)
125 {
126 if (parameters.StartsWith("?"))
127 {
128 parameters = parameters.Remove(0, 1);
129 }
130
131 List<QueryParameter> result = new List<QueryParameter>();
132
133 if (!string.IsNullOrEmpty(parameters))
134 {
135 string[] p = parameters.Split('&');
136 foreach (string s in p)
137 {
138 if (!string.IsNullOrEmpty(s) && !s.StartsWith(OAuthParameterPrefix))
139 {
140 if (s.IndexOf('=') > -1)
141 {
142 string[] temp = s.Split('=');
143 result.Add(new QueryParameter(temp[0], temp[1]));
144 }
145 else
146 {
147 result.Add(new QueryParameter(s, string.Empty));
148 }
149 }
150 }
151 }
152
153 return result;
154 }
155
156 /// <summary>
157 /// This is a different Url Encode implementation since the default .NET one outputs the percent encoding in lower case.
158 /// While this is not a problem with the percent encoding spec, it is used in upper case throughout OAuth
159 /// </summary>
160 /// <param name="value">The value to Url encode</param>
161 /// <returns>Returns a Url encoded string</returns>
162 public string UrlEncode(string value)
163 {
164 StringBuilder result = new StringBuilder();
165
166 foreach (char symbol in value)
167 {
168 if (unreservedChars.IndexOf(symbol) != -1)
169 {
170 result.Append(symbol);
171 }
172 else
173 {
174 result.Append('%' + String.Format("{0:X2}", (int)symbol));
175 }
176 }
177
178 return result.ToString();
179 }
180
181 /// <summary>
182 /// Normalizes the request parameters according to the spec
183 /// </summary>
184 /// <param name="parameters">The list of parameters already sorted</param>
185 /// <returns>a string representing the normalized parameters</returns>
186 protected string NormalizeRequestParameters(IList<QueryParameter> parameters)
187 {
188 StringBuilder sb = new StringBuilder();
189 QueryParameter p = null;
190 for (int i = 0; i < parameters.Count; i++)
191 {
192 p = parameters[i];
193 sb.AppendFormat("{0}={1}", p.Name, p.Value);
194
195 if (i < parameters.Count - 1)
196 {
197 sb.Append("&");
198 }
199 }
200
201 return sb.ToString();
202 }
203
204 /// <summary>
205 /// Generate the signature base that is used to produce the signature
206 /// </summary>
207 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
208 /// <param name="consumerKey">The consumer key</param>
209 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
210 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
211 /// <param name="callBackUrl">The callback URL (for OAuth 1.0a).If your client cannot accept callbacks, the value MUST be 'oob' </param>
212 /// <param name="oauthVerifier">This value MUST be included when exchanging Request Tokens for Access Tokens. Otherwise pass a null or an empty string</param>
213 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
214 /// <param name="signatureType">The signature type. To use the default values use <see cref="OAuthBase.SignatureTypes">OAuthBase.SignatureTypes</see>.</param>
215 /// <returns>The signature base</returns>
216 public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string callBackUrl, string oauthVerifier, string httpMethod, string timeStamp, string nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters)
217 {
218 if (token == null)
219 {
220 token = string.Empty;
221 }
222
223 if (tokenSecret == null)
224 {
225 tokenSecret = string.Empty;
226 }
227
228 if (string.IsNullOrEmpty(consumerKey))
229 {
230 throw new ArgumentNullException("consumerKey");
231 }
232
233 if (string.IsNullOrEmpty(httpMethod))
234 {
235 throw new ArgumentNullException("httpMethod");
236 }
237
238 if (string.IsNullOrEmpty(signatureType))
239 {
240 throw new ArgumentNullException("signatureType");
241 }
242
243 normalizedUrl = null;
244 normalizedRequestParameters = null;
245
246 List<QueryParameter> parameters = GetQueryParameters(url.Query);
247 parameters.Add(new QueryParameter(OAuthVersionKey, OAuthVersion));
248 parameters.Add(new QueryParameter(OAuthNonceKey, nonce));
249 parameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp));
250 parameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType));
251 parameters.Add(new QueryParameter(OAuthConsumerKeyKey, consumerKey));
252
253 if (!string.IsNullOrEmpty(callBackUrl))
254 {
255 parameters.Add(new QueryParameter(OAuthCallbackKey, UrlEncode(callBackUrl)));
256 }
257
258
259 if (!string.IsNullOrEmpty(oauthVerifier))
260 {
261 parameters.Add(new QueryParameter(OAuthVerifierKey, oauthVerifier));
262 }
263
264 if (!string.IsNullOrEmpty(token))
265 {
266 parameters.Add(new QueryParameter(OAuthTokenKey, token));
267 }
268
269 parameters.Sort(new QueryParameterComparer());
270
271 normalizedUrl = string.Format("{0}://{1}", url.Scheme, url.Host);
272 if (!((url.Scheme == "http" && url.Port == 80) || (url.Scheme == "https" && url.Port == 443)))
273 {
274 normalizedUrl += ":" + url.Port;
275 }
276 normalizedUrl += url.AbsolutePath;
277 normalizedRequestParameters = NormalizeRequestParameters(parameters);
278
279 StringBuilder signatureBase = new StringBuilder();
280 signatureBase.AppendFormat("{0}&", httpMethod.ToUpper());
281 signatureBase.AppendFormat("{0}&", UrlEncode(normalizedUrl));
282 signatureBase.AppendFormat("{0}", UrlEncode(normalizedRequestParameters));
283
284 return signatureBase.ToString();
285 }
286
287 /// <summary>
288 /// Generate the signature value based on the given signature base and hash algorithm
289 /// </summary>
290 /// <param name="signatureBase">The signature based as produced by the GenerateSignatureBase method or by any other means</param>
291 /// <param name="hash">The hash algorithm used to perform the hashing. If the hashing algorithm requires initialization or a key it should be set prior to calling this method</param>
292 /// <returns>A base64 string of the hash value</returns>
293 public string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash)
294 {
295 return ComputeHash(hash, signatureBase);
296 }
297
298 /// <summary>
299 /// Generates a signature using the HMAC-SHA1 algorithm
300 /// </summary>
301 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
302 /// <param name="consumerKey">The consumer key</param>
303 /// <param name="consumerSecret">The consumer seceret</param>
304 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
305 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
306 /// <param name="callBackUrl">The callback URL (for OAuth 1.0a).If your client cannot accept callbacks, the value MUST be 'oob' </param>
307 /// <param name="oauthVerifier">This value MUST be included when exchanging Request Tokens for Access Tokens. Otherwise pass a null or an empty string</param>
308 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
309 /// <returns>A base64 string of the hash value</returns>
310 public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string callBackUrl, string oauthVerifier, string httpMethod, string timeStamp, string nonce, out string normalizedUrl, out string normalizedRequestParameters)
311 {
312 return GenerateSignature(url, consumerKey, consumerSecret, token, tokenSecret, callBackUrl, oauthVerifier, httpMethod, timeStamp, nonce, SignatureTypes.HMACSHA1, out normalizedUrl, out normalizedRequestParameters);
313 }
314
315 /// <summary>
316 /// Generates a signature using the specified signatureType
317 /// </summary>
318 /// <param name="url">The full url that needs to be signed including its non OAuth url parameters</param>
319 /// <param name="consumerKey">The consumer key</param>
320 /// <param name="consumerSecret">The consumer seceret</param>
321 /// <param name="token">The token, if available. If not available pass null or an empty string</param>
322 /// <param name="tokenSecret">The token secret, if available. If not available pass null or an empty string</param>
323 /// <param name="callBackUrl">The callback URL (for OAuth 1.0a).If your client cannot accept callbacks, the value MUST be 'oob' </param>
324 /// <param name="oauthVerifier">This value MUST be included when exchanging Request Tokens for Access Tokens. Otherwise pass a null or an empty string</param>
325 /// <param name="httpMethod">The http method used. Must be a valid HTTP method verb (POST,GET,PUT, etc)</param>
326 /// <param name="signatureType">The type of signature to use</param>
327 /// <returns>A base64 string of the hash value</returns>
328 public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string tokenSecret, string callBackUrl, string oauthVerifier, string httpMethod, string timeStamp, string nonce, SignatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters)
329 {
330 normalizedUrl = null;
331 normalizedRequestParameters = null;
332
333 switch (signatureType)
334 {
335 case SignatureTypes.PLAINTEXT:
336 return HttpUtility.UrlEncode(string.Format("{0}&{1}", consumerSecret, tokenSecret));
337 case SignatureTypes.HMACSHA1:
338 string signatureBase = GenerateSignatureBase(url, consumerKey, token, tokenSecret, callBackUrl, oauthVerifier, httpMethod, timeStamp, nonce, HMACSHA1SignatureType, out normalizedUrl, out normalizedRequestParameters);
339
340 HMACSHA1 hmacsha1 = new HMACSHA1();
341 hmacsha1.Key = Encoding.ASCII.GetBytes(string.Format("{0}&{1}", UrlEncode(consumerSecret), string.IsNullOrEmpty(tokenSecret) ? "" : UrlEncode(tokenSecret)));
342
343 return GenerateSignatureUsingHash(signatureBase, hmacsha1);
344 case SignatureTypes.RSASHA1:
345 throw new NotImplementedException();
346 default:
347 throw new ArgumentException("Unknown signature type", "signatureType");
348 }
349 }
350
351 /// <summary>
352 /// Generate the timestamp for the signature
353 /// </summary>
354 /// <returns></returns>
355 public virtual string GenerateTimeStamp()
356 {
357 // Default implementation of UNIX time of the current UTC time
358 TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
359 return Convert.ToInt64(ts.TotalSeconds).ToString();
360 }
361
362 /// <summary>
363 /// Generate a nonce
364 /// </summary>
365 /// <returns></returns>
366 public virtual string GenerateNonce()
367 {
368 // Just a simple implementation of a random number between 123400 and 9999999
369 return random.Next(123400, 9999999).ToString();
370 }
371
372 }
373 }