New: Make Twitter NetStandard compatible

This commit is contained in:
ta264 2019-10-14 21:21:00 +01:00 committed by Mark McDowall
parent 0878f514aa
commit 1d02208316
10 changed files with 1186 additions and 55 deletions

View File

@ -0,0 +1,13 @@
OAuth (http://github.com/danielcrenna/oauth)
Written by Daniel Crenna
(http://danielcrenna.com)
This work is public domain.
"The person who associated a work with this document has
dedicated the work to the Commons by waiving all of his
or her rights to the work worldwide under copyright law
and all related or neighboring legal rights he or she
had in the work, to the extent allowable by law."
For more information, please visit:
http://creativecommons.org/publicdomain/zero/1.0/

View File

@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
namespace NzbDrone.Common.OAuth
{
/// <summary>
/// A request wrapper for the OAuth 1.0a specification.
/// </summary>
/// <seealso href="http://oauth.net/"/>
public class OAuthRequest
{
public virtual OAuthSignatureMethod SignatureMethod { get; set; }
public virtual OAuthSignatureTreatment SignatureTreatment { get; set; }
public virtual OAuthRequestType Type { get; set; }
public virtual string Method { get; set; }
public virtual string Realm { get; set; }
public virtual string ConsumerKey { get; set; }
public virtual string ConsumerSecret { get; set; }
public virtual string Token { get; set; }
public virtual string TokenSecret { get; set; }
public virtual string Verifier { get; set; }
public virtual string ClientUsername { get; set; }
public virtual string ClientPassword { get; set; }
public virtual string CallbackUrl { get; set; }
public virtual string Version { get; set; }
public virtual string SessionHandle { get; set; }
/// <seealso cref="http://oauth.net/core/1.0#request_urls"/>
public virtual string RequestUrl { get; set; }
#region Authorization Header
#if !WINRT
public string GetAuthorizationHeader(NameValueCollection parameters)
{
var collection = new WebParameterCollection(parameters);
return GetAuthorizationHeader(collection);
}
#endif
public string GetAuthorizationHeader(IDictionary<string, string> parameters)
{
var collection = new WebParameterCollection(parameters);
return GetAuthorizationHeader(collection);
}
public string GetAuthorizationHeader()
{
var collection = new WebParameterCollection(0);
return GetAuthorizationHeader(collection);
}
public string GetAuthorizationHeader(WebParameterCollection parameters)
{
switch (Type)
{
case OAuthRequestType.RequestToken:
ValidateRequestState();
return GetSignatureAuthorizationHeader(parameters);
case OAuthRequestType.AccessToken:
ValidateAccessRequestState();
return GetSignatureAuthorizationHeader(parameters);
case OAuthRequestType.ProtectedResource:
ValidateProtectedResourceState();
return GetSignatureAuthorizationHeader(parameters);
case OAuthRequestType.ClientAuthentication:
ValidateClientAuthAccessRequestState();
return GetClientSignatureAuthorizationHeader(parameters);
default:
throw new ArgumentOutOfRangeException();
}
}
private string GetSignatureAuthorizationHeader(WebParameterCollection parameters)
{
var signature = GetNewSignature(parameters);
parameters.Add("oauth_signature", signature);
return WriteAuthorizationHeader(parameters);
}
private string GetClientSignatureAuthorizationHeader(WebParameterCollection parameters)
{
var signature = GetNewSignatureXAuth(parameters);
parameters.Add("oauth_signature", signature);
return WriteAuthorizationHeader(parameters);
}
private string WriteAuthorizationHeader(WebParameterCollection parameters)
{
var sb = new StringBuilder("OAuth ");
if (!IsNullOrBlank(Realm))
{
sb.AppendFormat("realm=\"{0}\",", OAuthTools.UrlEncodeRelaxed(Realm));
}
parameters.Sort((l, r) => l.Name.CompareTo(r.Name));
foreach (var parameter in parameters.Where(parameter =>
!IsNullOrBlank(parameter.Name) &&
!IsNullOrBlank(parameter.Value) &&
(parameter.Name.StartsWith("oauth_") || parameter.Name.StartsWith("x_auth_"))))
{
sb.AppendFormat("{0}=\"{1}\",", parameter.Name, parameter.Value);
}
sb.Remove(sb.Length - 1, 1);
var authorization = sb.ToString();
return authorization;
}
#endregion
#region Authorization Query
#if !WINRT
public string GetAuthorizationQuery(NameValueCollection parameters)
{
var collection = new WebParameterCollection(parameters);
return GetAuthorizationQuery(collection);
}
#endif
public string GetAuthorizationQuery(IDictionary<string, string> parameters)
{
var collection = new WebParameterCollection(parameters);
return GetAuthorizationQuery(collection);
}
public string GetAuthorizationQuery()
{
var collection = new WebParameterCollection(0);
return GetAuthorizationQuery(collection);
}
private string GetAuthorizationQuery(WebParameterCollection parameters)
{
switch (Type)
{
case OAuthRequestType.RequestToken:
ValidateRequestState();
return GetSignatureAuthorizationQuery(parameters);
case OAuthRequestType.AccessToken:
ValidateAccessRequestState();
return GetSignatureAuthorizationQuery(parameters);
case OAuthRequestType.ProtectedResource:
ValidateProtectedResourceState();
return GetSignatureAuthorizationQuery(parameters);
case OAuthRequestType.ClientAuthentication:
ValidateClientAuthAccessRequestState();
return GetClientSignatureAuthorizationQuery(parameters);
default:
throw new ArgumentOutOfRangeException();
}
}
private string GetSignatureAuthorizationQuery(WebParameterCollection parameters)
{
var signature = GetNewSignature(parameters);
parameters.Add("oauth_signature", signature);
return WriteAuthorizationQuery(parameters);
}
private string GetClientSignatureAuthorizationQuery(WebParameterCollection parameters)
{
var signature = GetNewSignatureXAuth(parameters);
parameters.Add("oauth_signature", signature);
return WriteAuthorizationQuery(parameters);
}
private static string WriteAuthorizationQuery(WebParameterCollection parameters)
{
var sb = new StringBuilder();
parameters.Sort((l, r) => l.Name.CompareTo(r.Name));
var count = 0;
foreach (var parameter in parameters.Where(parameter =>
!IsNullOrBlank(parameter.Name) &&
!IsNullOrBlank(parameter.Value) &&
(parameter.Name.StartsWith("oauth_") || parameter.Name.StartsWith("x_auth_"))))
{
count++;
var format = count < parameters.Count ? "{0}={1}&" : "{0}={1}";
sb.AppendFormat(format, parameter.Name, parameter.Value);
}
var authorization = sb.ToString();
return authorization;
}
#endregion
private string GetNewSignature(WebParameterCollection parameters)
{
var timestamp = OAuthTools.GetTimestamp();
var nonce = OAuthTools.GetNonce();
AddAuthParameters(parameters, timestamp, nonce);
var signatureBase = OAuthTools.ConcatenateRequestElements(Method.ToUpperInvariant(), RequestUrl, parameters);
var signature = OAuthTools.GetSignature(SignatureMethod, SignatureTreatment, signatureBase, ConsumerSecret, TokenSecret);
return signature;
}
private string GetNewSignatureXAuth(WebParameterCollection parameters)
{
var timestamp = OAuthTools.GetTimestamp();
var nonce = OAuthTools.GetNonce();
AddXAuthParameters(parameters, timestamp, nonce);
var signatureBase = OAuthTools.ConcatenateRequestElements(Method.ToUpperInvariant(), RequestUrl, parameters);
var signature = OAuthTools.GetSignature(SignatureMethod, SignatureTreatment, signatureBase, ConsumerSecret, TokenSecret);
return signature;
}
#region Static Helpers
public static OAuthRequest ForRequestToken(string consumerKey, string consumerSecret)
{
var credentials = new OAuthRequest
{
Method = "GET",
Type = OAuthRequestType.RequestToken,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
SignatureTreatment = OAuthSignatureTreatment.Escaped,
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret
};
return credentials;
}
public static OAuthRequest ForRequestToken(string consumerKey, string consumerSecret, string callbackUrl)
{
var credentials = ForRequestToken(consumerKey, consumerSecret);
credentials.CallbackUrl = callbackUrl;
return credentials;
}
public static OAuthRequest ForAccessToken(string consumerKey, string consumerSecret, string requestToken, string requestTokenSecret)
{
var credentials = new OAuthRequest
{
Method = "GET",
Type = OAuthRequestType.AccessToken,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
SignatureTreatment = OAuthSignatureTreatment.Escaped,
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret,
Token = requestToken,
TokenSecret = requestTokenSecret
};
return credentials;
}
public static OAuthRequest ForAccessToken(string consumerKey, string consumerSecret, string requestToken, string requestTokenSecret, string verifier)
{
var credentials = ForAccessToken(consumerKey, consumerSecret, requestToken, requestTokenSecret);
credentials.Verifier = verifier;
return credentials;
}
public static OAuthRequest ForAccessTokenRefresh(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret, string sessionHandle)
{
var credentials = ForAccessToken(consumerKey, consumerSecret, accessToken, accessTokenSecret);
credentials.SessionHandle = sessionHandle;
return credentials;
}
public static OAuthRequest ForAccessTokenRefresh(string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret, string sessionHandle, string verifier)
{
var credentials = ForAccessToken(consumerKey, consumerSecret, accessToken, accessTokenSecret);
credentials.SessionHandle = sessionHandle;
credentials.Verifier = verifier;
return credentials;
}
public static OAuthRequest ForClientAuthentication(string consumerKey, string consumerSecret, string username, string password)
{
var credentials = new OAuthRequest
{
Method = "GET",
Type = OAuthRequestType.ClientAuthentication,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
SignatureTreatment = OAuthSignatureTreatment.Escaped,
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret,
ClientUsername = username,
ClientPassword = password
};
return credentials;
}
public static OAuthRequest ForProtectedResource(string method, string consumerKey, string consumerSecret, string accessToken, string accessTokenSecret)
{
var credentials = new OAuthRequest
{
Method = method ?? "GET",
Type = OAuthRequestType.ProtectedResource,
SignatureMethod = OAuthSignatureMethod.HmacSha1,
SignatureTreatment = OAuthSignatureTreatment.Escaped,
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret,
Token = accessToken,
TokenSecret = accessTokenSecret
};
return credentials;
}
#endregion
private void ValidateRequestState()
{
if (IsNullOrBlank(Method))
{
throw new ArgumentException("You must specify an HTTP method");
}
if (IsNullOrBlank(RequestUrl))
{
throw new ArgumentException("You must specify a request token URL");
}
if (IsNullOrBlank(ConsumerKey))
{
throw new ArgumentException("You must specify a consumer key");
}
if (IsNullOrBlank(ConsumerSecret))
{
throw new ArgumentException("You must specify a consumer secret");
}
}
private void ValidateAccessRequestState()
{
if (IsNullOrBlank(Method))
{
throw new ArgumentException("You must specify an HTTP method");
}
if (IsNullOrBlank(RequestUrl))
{
throw new ArgumentException("You must specify an access token URL");
}
if (IsNullOrBlank(ConsumerKey))
{
throw new ArgumentException("You must specify a consumer key");
}
if (IsNullOrBlank(ConsumerSecret))
{
throw new ArgumentException("You must specify a consumer secret");
}
if (IsNullOrBlank(Token))
{
throw new ArgumentException("You must specify a token");
}
}
private void ValidateClientAuthAccessRequestState()
{
if (IsNullOrBlank(Method))
{
throw new ArgumentException("You must specify an HTTP method");
}
if (IsNullOrBlank(RequestUrl))
{
throw new ArgumentException("You must specify an access token URL");
}
if (IsNullOrBlank(ConsumerKey))
{
throw new ArgumentException("You must specify a consumer key");
}
if (IsNullOrBlank(ConsumerSecret))
{
throw new ArgumentException("You must specify a consumer secret");
}
if (IsNullOrBlank(ClientUsername) || IsNullOrBlank(ClientPassword))
{
throw new ArgumentException("You must specify user credentials");
}
}
private void ValidateProtectedResourceState()
{
if (IsNullOrBlank(Method))
{
throw new ArgumentException("You must specify an HTTP method");
}
if (IsNullOrBlank(ConsumerKey))
{
throw new ArgumentException("You must specify a consumer key");
}
if (IsNullOrBlank(ConsumerSecret))
{
throw new ArgumentException("You must specify a consumer secret");
}
}
private void AddAuthParameters(ICollection<WebParameter> parameters, string timestamp, string nonce)
{
var authParameters = new WebParameterCollection
{
new WebParameter("oauth_consumer_key", ConsumerKey),
new WebParameter("oauth_nonce", nonce),
new WebParameter("oauth_signature_method", ToRequestValue(SignatureMethod)),
new WebParameter("oauth_timestamp", timestamp),
new WebParameter("oauth_version", Version ?? "1.0")
};
if (!IsNullOrBlank(Token))
{
authParameters.Add(new WebParameter("oauth_token", Token));
}
if (!IsNullOrBlank(CallbackUrl))
{
authParameters.Add(new WebParameter("oauth_callback", CallbackUrl));
}
if (!IsNullOrBlank(Verifier))
{
authParameters.Add(new WebParameter("oauth_verifier", Verifier));
}
if (!IsNullOrBlank(SessionHandle))
{
authParameters.Add(new WebParameter("oauth_session_handle", SessionHandle));
}
foreach (var authParameter in authParameters)
{
parameters.Add(authParameter);
}
}
private void AddXAuthParameters(ICollection<WebParameter> parameters, string timestamp, string nonce)
{
var authParameters = new WebParameterCollection
{
new WebParameter("x_auth_username", ClientUsername),
new WebParameter("x_auth_password", ClientPassword),
new WebParameter("x_auth_mode", "client_auth"),
new WebParameter("oauth_consumer_key", ConsumerKey),
new WebParameter("oauth_signature_method", ToRequestValue(SignatureMethod)),
new WebParameter("oauth_timestamp", timestamp),
new WebParameter("oauth_nonce", nonce),
new WebParameter("oauth_version", Version ?? "1.0")
};
foreach (var authParameter in authParameters)
{
parameters.Add(authParameter);
}
}
public static string ToRequestValue(OAuthSignatureMethod signatureMethod)
{
var value = signatureMethod.ToString().ToUpper();
var shaIndex = value.IndexOf("SHA1");
return shaIndex > -1 ? value.Insert(shaIndex, "-") : value;
}
private static bool IsNullOrBlank(string value)
{
return String.IsNullOrEmpty(value) || (!String.IsNullOrEmpty(value) && value.Trim() == String.Empty);
}
}
}

View File

@ -0,0 +1,14 @@
namespace NzbDrone.Common.OAuth
{
/// <summary>
/// The types of OAuth requests possible in a typical workflow.
/// Used for validation purposes and to build static helpers.
/// </summary>
public enum OAuthRequestType
{
RequestToken,
AccessToken,
ProtectedResource,
ClientAuthentication
}
}

View File

@ -0,0 +1,12 @@
namespace NzbDrone.Common.OAuth
{
/// <summary>
/// The encryption method to use when hashing a request signature.
/// </summary>
public enum OAuthSignatureMethod
{
HmacSha1,
PlainText,
RsaSha1
}
}

View File

@ -0,0 +1,13 @@
namespace NzbDrone.Common.OAuth
{
/// <summary>
/// Specifies whether the final signature value should be escaped during calculation.
/// This might be necessary for some OAuth implementations that do not obey the default
/// specification for signature escaping.
/// </summary>
public enum OAuthSignatureTreatment
{
Escaped,
Unescaped
}
}

View File

@ -0,0 +1,409 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if !WINRT
using System.Security.Cryptography;
#else
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
using System.Globalization;
#endif
namespace NzbDrone.Common.OAuth
{
/// <summary>
/// A general purpose toolset for creating components of an OAuth request.
/// </summary>
/// <seealso href="http://oauth.net/"/>
public static class OAuthTools
{
private const string AlphaNumeric = Upper + Lower + Digit;
private const string Digit = "1234567890";
private const string Lower = "abcdefghijklmnopqrstuvwxyz";
private const string Unreserved = AlphaNumeric + "-._~";
private const string Upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static readonly Random _random;
private static readonly object _randomLock = new object();
#if !SILVERLIGHT && !WINRT
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
#endif
static OAuthTools()
{
#if !SILVERLIGHT && !WINRT
var bytes = new byte[4];
_rng.GetNonZeroBytes(bytes);
_random = new Random(BitConverter.ToInt32(bytes, 0));
#else
_random = new Random();
#endif
}
/// <summary>
/// All text parameters are UTF-8 encoded (per section 5.1).
/// </summary>
/// <seealso href="http://www.hueniverse.com/hueniverse/2008/10/beginners-gui-1.html"/>
#if !WINRT
private static readonly Encoding _encoding = Encoding.UTF8;
#else
private static readonly BinaryStringEncoding _encoding = BinaryStringEncoding.Utf8;
#endif
/// <summary>
/// Generates a random 16-byte lowercase alphanumeric string.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#nonce"/>
/// <returns></returns>
public static string GetNonce()
{
const string chars = (Lower + Digit);
var nonce = new char[16];
lock (_randomLock)
{
for (var i = 0; i < nonce.Length; i++)
{
nonce[i] = chars[_random.Next(0, chars.Length)];
}
}
return new string(nonce);
}
/// <summary>
/// Generates a timestamp based on the current elapsed seconds since '01/01/1970 0000 GMT"
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#nonce"/>
/// <returns></returns>
public static string GetTimestamp()
{
return GetTimestamp(DateTime.UtcNow);
}
/// <summary>
/// Generates a timestamp based on the elapsed seconds of a given time since '01/01/1970 0000 GMT"
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#nonce"/>
/// <param name="dateTime">A specified point in time.</param>
/// <returns></returns>
public static string GetTimestamp(DateTime dateTime)
{
var timestamp = ToUnixTime(dateTime);
return timestamp.ToString();
}
private static long ToUnixTime(DateTime dateTime)
{
var timeSpan = (dateTime - new DateTime(1970, 1, 1));
var timestamp = (long)timeSpan.TotalSeconds;
return timestamp;
}
/// <summary>
/// URL encodes a string based on section 5.1 of the OAuth spec.
/// Namely, percent encoding with [RFC3986], avoiding unreserved characters,
/// upper-casing hexadecimal characters, and UTF-8 encoding for text value pairs.
/// </summary>
/// <param name="value"></param>
/// <seealso href="http://oauth.net/core/1.0#encoding_parameters" />
public static string UrlEncodeRelaxed(string value)
{
var escaped = Uri.EscapeDataString(value);
// LinkedIn users have problems because it requires escaping brackets
escaped = escaped.Replace("(", PercentEncode("("))
.Replace(")", PercentEncode(")"));
return escaped;
}
private static string PercentEncode(string s)
{
var bytes = Encoding.UTF8.GetBytes(s);
var sb = new StringBuilder();
foreach (var b in bytes)
{
// Supports proper encoding of special characters (\n\r\t\b)
if ((b > 7 && b < 11) || b == 13)
{
sb.Append(string.Format("%0{0:X}", b));
}
else
{
sb.Append(string.Format("%{0:X}", b));
}
}
return sb.ToString();
}
/// <summary>
/// URL encodes a string based on section 5.1 of the OAuth spec.
/// Namely, percent encoding with [RFC3986], avoiding unreserved characters,
/// upper-casing hexadecimal characters, and UTF-8 encoding for text value pairs.
/// </summary>
/// <param name="value"></param>
/// <seealso href="http://oauth.net/core/1.0#encoding_parameters" />
public static string UrlEncodeStrict(string value)
{
// [JD]: We need to escape the apostrophe as well or the signature will fail
var original = value;
var ret = original.OfType<char>().Where(
c => !Unreserved.OfType<char>().Contains(c) && c != '%').Aggregate(
value, (current, c) => current.Replace(
c.ToString(), PercentEncode(c.ToString())
));
return ret.Replace("%%", "%25%"); // Revisit to encode actual %'s
}
/// <summary>
/// Sorts a collection of key-value pairs by name, and then value if equal,
/// concatenating them into a single string. This string should be encoded
/// prior to, or after normalization is run.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.1.1"/>
/// <param name="parameters"></param>
/// <returns></returns>
public static string NormalizeRequestParameters(WebParameterCollection parameters)
{
var copy = SortParametersExcludingSignature(parameters);
var concatenated = Concatenate(copy, "=", "&");
return concatenated;
}
private static string Concatenate(ICollection<WebParameter> collection, string separator, string spacer)
{
var sb = new StringBuilder();
var total = collection.Count;
var count = 0;
foreach (var item in collection)
{
sb.Append(item.Name);
sb.Append(separator);
sb.Append(item.Value);
count++;
if (count < total)
{
sb.Append(spacer);
}
}
return sb.ToString();
}
/// <summary>
/// Sorts a <see cref="WebParameterCollection"/> by name, and then value if equal.
/// </summary>
/// <param name="parameters">A collection of parameters to sort</param>
/// <returns>A sorted parameter collection</returns>
public static WebParameterCollection SortParametersExcludingSignature(WebParameterCollection parameters)
{
var copy = new WebParameterCollection(parameters);
var exclusions = copy.Where(n => EqualsIgnoreCase(n.Name, "oauth_signature"));
copy.RemoveAll(exclusions);
foreach(var parameter in copy)
{
parameter.Value = UrlEncodeStrict(parameter.Value);
}
copy.Sort((x, y) => x.Name.Equals(y.Name) ? x.Value.CompareTo(y.Value) : x.Name.CompareTo(y.Name));
return copy;
}
private static bool EqualsIgnoreCase(string left, string right)
{
#if WINRT
return CultureInfo.InvariantCulture.CompareInfo.Compare(left, right, CompareOptions.IgnoreCase) == 0;
#else
return String.Compare(left, right, StringComparison.InvariantCultureIgnoreCase) == 0;
#endif
}
/// <summary>
/// Creates a request URL suitable for making OAuth requests.
/// Resulting URLs must exclude port 80 or port 443 when accompanied by HTTP and HTTPS, respectively.
/// Resulting URLs must be lower case.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.1.2"/>
/// <param name="url">The original request URL</param>
/// <returns></returns>
public static string ConstructRequestUrl(Uri url)
{
if (url == null)
{
throw new ArgumentNullException("url");
}
var sb = new StringBuilder();
var requestUrl = string.Format("{0}://{1}", url.Scheme, url.Host);
var qualified = string.Format(":{0}", url.Port);
var basic = url.Scheme == "http" && url.Port == 80;
var secure = url.Scheme == "https" && url.Port == 443;
sb.Append(requestUrl);
sb.Append(!basic && !secure ? qualified : "");
sb.Append(url.AbsolutePath);
return sb.ToString(); //.ToLower();
}
/// <summary>
/// Creates a request elements concatentation value to send with a request.
/// This is also known as the signature base.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.1.3"/>
/// <seealso href="http://oauth.net/core/1.0#sig_base_example"/>
/// <param name="method">The request's HTTP method type</param>
/// <param name="url">The request URL</param>
/// <param name="parameters">The request's parameters</param>
/// <returns>A signature base string</returns>
public static string ConcatenateRequestElements(string method, string url, WebParameterCollection parameters)
{
var sb = new StringBuilder();
// Separating &'s are not URL encoded
var requestMethod = string.Concat(method.ToUpper(), "&");
var requestUrl = string.Concat(UrlEncodeRelaxed(ConstructRequestUrl(new Uri(url))), "&");
var requestParameters = UrlEncodeRelaxed(NormalizeRequestParameters(parameters));
sb.Append(requestMethod);
sb.Append(requestUrl);
sb.Append(requestParameters);
return sb.ToString();
}
/// <summary>
/// Creates a signature value given a signature base and the consumer secret.
/// This method is used when the token secret is currently unknown.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.2"/>
/// <param name="signatureMethod">The hashing method</param>
/// <param name="signatureBase">The signature base</param>
/// <param name="consumerSecret">The consumer key</param>
/// <returns></returns>
public static string GetSignature(OAuthSignatureMethod signatureMethod,
string signatureBase,
string consumerSecret)
{
return GetSignature(signatureMethod, OAuthSignatureTreatment.Escaped, signatureBase, consumerSecret, null);
}
/// <summary>
/// Creates a signature value given a signature base and the consumer secret.
/// This method is used when the token secret is currently unknown.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.2"/>
/// <param name="signatureMethod">The hashing method</param>
/// <param name="signatureTreatment">The treatment to use on a signature value</param>
/// <param name="signatureBase">The signature base</param>
/// <param name="consumerSecret">The consumer key</param>
/// <returns></returns>
public static string GetSignature(OAuthSignatureMethod signatureMethod,
OAuthSignatureTreatment signatureTreatment,
string signatureBase,
string consumerSecret)
{
return GetSignature(signatureMethod, signatureTreatment, signatureBase, consumerSecret, null);
}
/// <summary>
/// Creates a signature value given a signature base and the consumer secret and a known token secret.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.2"/>
/// <param name="signatureMethod">The hashing method</param>
/// <param name="signatureBase">The signature base</param>
/// <param name="consumerSecret">The consumer secret</param>
/// <param name="tokenSecret">The token secret</param>
/// <returns></returns>
public static string GetSignature(OAuthSignatureMethod signatureMethod,
string signatureBase,
string consumerSecret,
string tokenSecret)
{
return GetSignature(signatureMethod, OAuthSignatureTreatment.Escaped, consumerSecret, tokenSecret);
}
/// <summary>
/// Creates a signature value given a signature base and the consumer secret and a known token secret.
/// </summary>
/// <seealso href="http://oauth.net/core/1.0#rfc.section.9.2"/>
/// <param name="signatureMethod">The hashing method</param>
/// <param name="signatureTreatment">The treatment to use on a signature value</param>
/// <param name="signatureBase">The signature base</param>
/// <param name="consumerSecret">The consumer secret</param>
/// <param name="tokenSecret">The token secret</param>
/// <returns></returns>
public static string GetSignature(OAuthSignatureMethod signatureMethod,
OAuthSignatureTreatment signatureTreatment,
string signatureBase,
string consumerSecret,
string tokenSecret)
{
if (IsNullOrBlank(tokenSecret))
{
tokenSecret = String.Empty;
}
consumerSecret = UrlEncodeRelaxed(consumerSecret);
tokenSecret = UrlEncodeRelaxed(tokenSecret);
string signature;
switch (signatureMethod)
{
case OAuthSignatureMethod.HmacSha1:
{
var key = string.Concat(consumerSecret, "&", tokenSecret);
#if WINRT
IBuffer keyMaterial = CryptographicBuffer.ConvertStringToBinary(key, _encoding);
MacAlgorithmProvider hmacSha1Provider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1);
CryptographicKey macKey = hmacSha1Provider.CreateKey(keyMaterial);
IBuffer dataToBeSigned = CryptographicBuffer.ConvertStringToBinary(signatureBase, _encoding);
IBuffer signatureBuffer = CryptographicEngine.Sign(macKey, dataToBeSigned);
signature = CryptographicBuffer.EncodeToBase64String(signatureBuffer);
#else
var crypto = new HMACSHA1();
crypto.Key = _encoding.GetBytes(key);
signature = HashWith(signatureBase, crypto);
#endif
break;
}
default:
throw new NotImplementedException("Only HMAC-SHA1 is currently supported.");
}
var result = signatureTreatment == OAuthSignatureTreatment.Escaped
? UrlEncodeRelaxed(signature)
: signature;
return result;
}
#if !WINRT
private static string HashWith(string input, HashAlgorithm algorithm)
{
var data = Encoding.UTF8.GetBytes(input);
var hash = algorithm.ComputeHash(data);
return Convert.ToBase64String(hash);
}
#endif
private static bool IsNullOrBlank(string value)
{
return String.IsNullOrEmpty(value) || (!String.IsNullOrEmpty(value) && value.Trim() == String.Empty);
}
}
}

View File

@ -0,0 +1,14 @@
namespace NzbDrone.Common.OAuth
{
public class WebParameter
{
public WebParameter(string name, string value)
{
Name = name;
Value = value;
}
public string Value { get; set; }
public string Name { get; private set; }
}
}

View File

@ -0,0 +1,202 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Specialized;
namespace NzbDrone.Common.OAuth
{
public class WebParameterCollection : IList<WebParameter>
{
private IList<WebParameter> _parameters;
public virtual WebParameter this[string name]
{
get
{
var parameters = this.Where(p => p.Name.Equals(name));
if(parameters.Count() == 0)
{
return null;
}
if(parameters.Count() == 1)
{
return parameters.Single();
}
var value = string.Join(",", parameters.Select(p => p.Value).ToArray());
return new WebParameter(name, value);
}
}
public virtual IEnumerable<string> Names
{
get { return _parameters.Select(p => p.Name); }
}
public virtual IEnumerable<string> Values
{
get { return _parameters.Select(p => p.Value); }
}
public WebParameterCollection(IEnumerable<WebParameter> parameters)
{
_parameters = new List<WebParameter>(parameters);
}
#if !WINRT
public WebParameterCollection(NameValueCollection collection) : this()
{
AddCollection(collection);
}
public virtual void AddRange(NameValueCollection collection)
{
AddCollection(collection);
}
private void AddCollection(NameValueCollection collection)
{
var parameters = collection.AllKeys.Select(key => new WebParameter(key, collection[key]));
foreach (var parameter in parameters)
{
_parameters.Add(parameter);
}
}
#endif
public WebParameterCollection(IDictionary<string, string> collection) : this()
{
AddCollection(collection);
}
public void AddCollection(IDictionary<string, string> collection)
{
foreach (var parameter in collection.Keys.Select(key => new WebParameter(key, collection[key])))
{
_parameters.Add(parameter);
}
}
public WebParameterCollection()
{
_parameters = new List<WebParameter>(0);
}
public WebParameterCollection(int capacity)
{
_parameters = new List<WebParameter>(capacity);
}
private void AddCollection(IEnumerable<WebParameter> collection)
{
foreach (var pair in collection.Select(parameter => new WebParameter(parameter.Name, parameter.Value)))
{
_parameters.Add(pair);
}
}
public virtual void AddRange(WebParameterCollection collection)
{
AddCollection(collection);
}
public virtual void AddRange(IEnumerable<WebParameter> collection)
{
AddCollection(collection);
}
public virtual void Sort(Comparison<WebParameter> comparison)
{
var sorted = new List<WebParameter>(_parameters);
sorted.Sort(comparison);
_parameters = sorted;
}
public virtual bool RemoveAll(IEnumerable<WebParameter> parameters)
{
var array = parameters.ToArray();
var success = array.Aggregate(true, (current, parameter) => current & _parameters.Remove(parameter));
return success && array.Length > 0;
}
public virtual void Add(string name, string value)
{
var pair = new WebParameter(name, value);
_parameters.Add(pair);
}
#region IList<WebParameter> Members
public virtual IEnumerator<WebParameter> GetEnumerator()
{
return _parameters.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public virtual void Add(WebParameter parameter)
{
_parameters.Add(parameter);
}
public virtual void Clear()
{
_parameters.Clear();
}
public virtual bool Contains(WebParameter parameter)
{
return _parameters.Contains(parameter);
}
public virtual void CopyTo(WebParameter[] parameters, int arrayIndex)
{
_parameters.CopyTo(parameters, arrayIndex);
}
public virtual bool Remove(WebParameter parameter)
{
return _parameters.Remove(parameter);
}
public virtual int Count
{
get { return _parameters.Count; }
}
public virtual bool IsReadOnly
{
get { return _parameters.IsReadOnly; }
}
public virtual int IndexOf(WebParameter parameter)
{
return _parameters.IndexOf(parameter);
}
public virtual void Insert(int index, WebParameter parameter)
{
_parameters.Insert(index, parameter);
}
public virtual void RemoveAt(int index)
{
_parameters.RemoveAt(index);
}
public virtual WebParameter this[int index]
{
get { return _parameters[index]; }
set { _parameters[index] = value; }
}
#endregion
}
}

View File

@ -1,13 +1,13 @@
using FluentValidation.Results;
using NLog;
using System;
using OAuth;
using System.Net;
using System.Collections.Specialized;
using System.IO;
using System.Web;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.OAuth;
namespace NzbDrone.Core.Notifications.Twitter
{

View File

@ -1,7 +1,6 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@ -60,59 +59,6 @@ namespace TinyTwitter
.Execute();
}
public IEnumerable<Tweet> GetHomeTimeline(long? sinceId = null, long? maxId = null, int? count = 20)
{
return GetTimeline("https://api.twitter.com/1.1/statuses/home_timeline.json", sinceId, maxId, count, "");
}
public IEnumerable<Tweet> GetMentions(long? sinceId = null, long? maxId = null, int? count = 20)
{
return GetTimeline("https://api.twitter.com/1.1/statuses/mentions.json", sinceId, maxId, count, "");
}
public IEnumerable<Tweet> GetUserTimeline(long? sinceId = null, long? maxId = null, int? count = 20, string screenName = "")
{
return GetTimeline("https://api.twitter.com/1.1/statuses/user_timeline.json", sinceId, maxId, count, screenName);
}
private IEnumerable<Tweet> GetTimeline(string url, long? sinceId, long? maxId, int? count, string screenName)
{
var builder = new RequestBuilder(oauth, "GET", url);
if (sinceId.HasValue)
builder.AddParameter("since_id", sinceId.Value.ToString());
if (maxId.HasValue)
builder.AddParameter("max_id", maxId.Value.ToString());
if (count.HasValue)
builder.AddParameter("count", count.Value.ToString());
if (screenName != "")
builder.AddParameter("screen_name", screenName);
var responseContent = builder.Execute();
var tweets = (object[])JsonConvert.DeserializeObject(responseContent);
return tweets.Cast<Dictionary<string, object>>().Select(tweet =>
{
var user = ((Dictionary<string, object>)tweet["user"]);
var date = DateTime.ParseExact(tweet["created_at"].ToString(),
"ddd MMM dd HH:mm:ss zz00 yyyy",
CultureInfo.InvariantCulture).ToLocalTime();
return new Tweet
{
Id = (long)tweet["id"],
CreatedAt = date,
Text = (string)tweet["text"],
UserName = (string)user["name"],
ScreenName = (string)user["screen_name"]
};
}).ToArray();
}
#region RequestBuilder
public class RequestBuilder