* Evernote provider is now available. Based on Evernote SDK for .NET and the obsolete doc (ie. https://dev.evernote.com/doc/articles/authentication.php) (Step 3 is POST, not GET) * Fix SyncrhonizationContext deadlock caused by ASP.NET site * Evernote provider now working trought Xamarin OAuthAuthenticator and Identity Server 3 * Add claims for notestoreuri and accesstoken * Evernote OK, before cleanup * Cleanup * Remove my credentials in demo project. * Change the default URL to lower case
178 lines
8.2 KiB
C#
178 lines
8.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Web;
|
|
|
|
namespace Owin.Security.Providers.Evernote
|
|
{
|
|
internal sealed class OAuthBase
|
|
{
|
|
private readonly Random _random = new Random();
|
|
private readonly string _unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
|
|
private const string OAuthConsumerKey = "oauth_consumer_key";
|
|
private const string OAuthCallbackKey = "oauth_callback";
|
|
private const string OAuthSignatureMethodKey = "oauth_signature_method";
|
|
private const string OAuthTimestampKey = "oauth_timestamp";
|
|
private const string OAuthNonceKey = "oauth_nonce";
|
|
private const string OAuthTokenKey = "oauth_token";
|
|
private const string OAuthVerifierKey = "oauth_verifier";
|
|
private const string Hmacsha1SignatureType = "HMAC-SHA1";
|
|
private const string PlainTextSignatureType = "PLAINTEXT";
|
|
|
|
private string ComputeHash(HashAlgorithm hashAlgorithm, string data)
|
|
{
|
|
if (hashAlgorithm == null)
|
|
throw new ArgumentNullException("hashAlgorithm");
|
|
if (string.IsNullOrEmpty(data))
|
|
throw new ArgumentNullException("data");
|
|
byte[] bytes = Encoding.ASCII.GetBytes(data);
|
|
return Convert.ToBase64String(hashAlgorithm.ComputeHash(bytes));
|
|
}
|
|
|
|
private List<QueryParameter> GetQueryParameters(string parameters)
|
|
{
|
|
if (parameters.StartsWith("?"))
|
|
parameters = parameters.Remove(0, 1);
|
|
List<QueryParameter> queryParameterList = new List<QueryParameter>();
|
|
if (!string.IsNullOrEmpty(parameters))
|
|
{
|
|
string str = parameters;
|
|
char[] chArray = { '&' };
|
|
foreach (string name in str.Split(chArray))
|
|
{
|
|
if (!string.IsNullOrEmpty(name) && !name.StartsWith("oauth_"))
|
|
{
|
|
if (name.IndexOf('=') > -1)
|
|
{
|
|
string[] strArray = name.Split('=');
|
|
queryParameterList.Add(new QueryParameter(strArray[0], strArray[1]));
|
|
}
|
|
else
|
|
queryParameterList.Add(new QueryParameter(name, string.Empty));
|
|
}
|
|
}
|
|
}
|
|
return queryParameterList;
|
|
}
|
|
|
|
private string UrlEncode(string value)
|
|
{
|
|
var stringBuilder = new StringBuilder();
|
|
foreach (char ch in value)
|
|
{
|
|
if (_unreservedChars.IndexOf(ch) != -1)
|
|
stringBuilder.Append(ch);
|
|
else
|
|
stringBuilder.Append(37 + $"{(int) ch:X2}");
|
|
}
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private string NormalizeRequestParameters(IList<QueryParameter> parameters)
|
|
{
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
for (int index = 0; index < parameters.Count; ++index)
|
|
{
|
|
QueryParameter queryParameter = parameters[index];
|
|
stringBuilder.AppendFormat("{0}={1}", queryParameter.Name, queryParameter.Value);
|
|
if (index < parameters.Count - 1)
|
|
stringBuilder.Append("&");
|
|
}
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private string GenerateSignatureBase(Uri url, string consumerKey, string token, string verifier, string httpMethod, string timeStamp, string nonce, string callback, string signatureType, out string normalizedUrl, out string normalizedRequestParameters)
|
|
{
|
|
if (token == null)
|
|
token = string.Empty;
|
|
if (string.IsNullOrEmpty(consumerKey))
|
|
throw new ArgumentNullException("consumerKey");
|
|
if (string.IsNullOrEmpty(httpMethod))
|
|
throw new ArgumentNullException("httpMethod");
|
|
if (string.IsNullOrEmpty(signatureType))
|
|
throw new ArgumentNullException("signatureType");
|
|
|
|
List<QueryParameter> queryParameters = GetQueryParameters(url.Query);
|
|
queryParameters.Add(new QueryParameter(OAuthConsumerKey, consumerKey));
|
|
queryParameters.Add(new QueryParameter(OAuthSignatureMethodKey, signatureType));
|
|
queryParameters.Add(new QueryParameter(OAuthTimestampKey, timeStamp));
|
|
queryParameters.Add(new QueryParameter(OAuthNonceKey, nonce));
|
|
|
|
if (!string.IsNullOrEmpty(callback))
|
|
queryParameters.Add(new QueryParameter(OAuthCallbackKey, callback));
|
|
if (!string.IsNullOrEmpty(token))
|
|
queryParameters.Add(new QueryParameter(OAuthTokenKey, token));
|
|
if (!string.IsNullOrEmpty(verifier))
|
|
queryParameters.Add(new QueryParameter(OAuthVerifierKey, verifier));
|
|
|
|
normalizedUrl = $"{url.Scheme}://{url.Host}";
|
|
normalizedUrl += url.AbsolutePath;
|
|
normalizedRequestParameters = NormalizeRequestParameters(queryParameters);
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
stringBuilder.AppendFormat("{0}&", httpMethod.ToUpperInvariant());
|
|
stringBuilder.AppendFormat("{0}&", UrlEncode(normalizedUrl));
|
|
stringBuilder.AppendFormat("{0}", UrlEncode(normalizedRequestParameters));
|
|
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
private string GenerateSignatureUsingHash(string signatureBase, HashAlgorithm hash)
|
|
{
|
|
return ComputeHash(hash, signatureBase);
|
|
}
|
|
|
|
public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string verifier, string httpMethod, string timeStamp, string nonce, string callback, out string normalizedUrl, out string normalizedRequestParameters)
|
|
{
|
|
return GenerateSignature(url, consumerKey, consumerSecret, token, verifier, httpMethod, timeStamp, nonce, callback, SignatureTypes.Plaintext, out normalizedUrl, out normalizedRequestParameters);
|
|
}
|
|
|
|
public string GenerateSignature(Uri url, string consumerKey, string consumerSecret, string token, string verifier, string httpMethod, string timeStamp, string nonce, string callback, SignatureTypes signatureType, out string normalizedUrl, out string normalizedRequestParameters)
|
|
{
|
|
normalizedUrl = null;
|
|
normalizedRequestParameters = null;
|
|
switch (signatureType)
|
|
{
|
|
case SignatureTypes.Hmacsha1:
|
|
var signatureBase = GenerateSignatureBase(url, consumerKey, token, verifier, httpMethod, timeStamp, nonce, callback, Hmacsha1SignatureType, out normalizedUrl, out normalizedRequestParameters);
|
|
var hmacshA1 = new HMACSHA1
|
|
{
|
|
Key = Encoding.ASCII.GetBytes($"{UrlEncode(consumerSecret)}&{UrlEncode(verifier)}")
|
|
};
|
|
return GenerateSignatureUsingHash(signatureBase, hmacshA1);
|
|
case SignatureTypes.Plaintext:
|
|
GenerateSignatureBase(url, consumerKey, token, verifier, httpMethod, timeStamp, nonce, callback, PlainTextSignatureType, out normalizedUrl, out normalizedRequestParameters);
|
|
return HttpUtility.UrlEncode($"{consumerSecret}&{verifier}");
|
|
case SignatureTypes.Rsasha1:
|
|
throw new NotImplementedException();
|
|
default:
|
|
throw new ArgumentException(@"Unknown signature type", nameof(signatureType));
|
|
}
|
|
}
|
|
|
|
public string GenerateTimeStamp()
|
|
{
|
|
return Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds).ToString();
|
|
}
|
|
|
|
public string GenerateNonce()
|
|
{
|
|
return _random.Next(123400, 9999999).ToString();
|
|
}
|
|
|
|
private class QueryParameter
|
|
{
|
|
public string Name { get; }
|
|
|
|
public string Value { get; }
|
|
|
|
public QueryParameter(string name, string value)
|
|
{
|
|
Name = name;
|
|
Value = value;
|
|
}
|
|
}
|
|
}
|
|
}
|