Reputation: 1
I'm sending a POST request using RestSharp in C# which requires digest authentication (using package RestSharp.Authenticators.Digest).
When I use
client.Authenticator = new DigestAuthenticator("username", "password");
the client.Execute(request);
method throws an exception
Header not found: qop at RestSharp.Authenticators.Digest.DictionaryHeaderExtension.GetHeader(IDictionary 2 header, String key) at RestSharp.Authenticators.Digest.DigestAuthenticatorManager.GetDigestDataFromException(WebException ex) at RestSharp.Authenticators.Digest.DigestAuthenticatorManager.GetDigestAuthHeader(String path, Method method) at RestSharp.Authenticators.Digest.DigestAuthenticator.Authenticate(IRestClient client, IRestRequest request) at RestSharp.RestClient.AuthenticateIfNeeded(IRestRequest request) at RestSharp.RestClient.Execute(IRestRequest request, String httpMethod, Func`3 getResponse) at RestSharp.RestClient.Execute(IRestRequest request)**
Upvotes: 0
Views: 1149
Reputation: 174
Use this authenticator instead. It won't work with everything but it is much faster and does not use any extra nuggets. I don't have much experience with Digest but this worked for me. I may fail if you need more than one cookie or if the digest uses extra parameters.
public class RESTDigestAuthenticator : IAuthenticator
{
readonly string _username;
readonly string _password;
private static string _realm;
private static string _nonce;
private static string _qop;
private static string _opaque;
private static string _cnonce;
private static DateTime _cnonceDate;
private static int _nc;
public RESTDigestAuthenticator(string username, string password)
{
_username = username;
_password = password;
}
void IAuthenticator.Authenticate(IRestClient client, IRestRequest request)
{
//Debug.WriteLine($"base={client.BaseUrl}, resource={request.Resource}");
// If we've got a recent Auth header, re-use it!
if (!string.IsNullOrEmpty(_cnonce) &&
DateTime.Now.Subtract(_cnonceDate).TotalHours < 1.0)
{
request.AddHeader("Authorization", GetDigestHeader(request.Method.ToString(), request.Resource));
}
else
{
try
{
var response = new RestClient(client.BaseUrl).Execute(request);
// Try to fix a 401 exception by adding a Authorization header
if (response == null)
{
throw new Exception($"No response from {client.BaseUrl}");
}
else if (response.StatusCode != System.Net.HttpStatusCode.Unauthorized)
{
throw new Exception($"Expected Unauthorized, got {response.StatusCode}");
}
string wwwAuthenticateHeader;
var wwwHead = response.Headers.FirstOrDefault(x => x.Name == "WWW-Authenticate");
if (wwwHead != null)
{
wwwAuthenticateHeader = wwwHead.Value.ToString();
}
else
{
throw new Exception("no wwwAuthHeader");
}
string cookie = null;
string cookieName = null;
string cookieVal = null;
var cook = response.Headers.FirstOrDefault(x => x.Name == "Set-Cookie");
if (cook != null)
{
cookie = cook.Value.ToString();
if (cookie.Contains(";"))
{
cookie = cookie.Split(new[] { ";" }, StringSplitOptions.None)[0];
if (cookie.Contains("="))
{
var kvp = cookie.Split(new[] { "=" }, StringSplitOptions.None);
cookieName = kvp[0];
cookieVal = kvp[1];
}
}
}
else
{
Debug.WriteLine("No Set-Cookie", Logger.LogLevel.Trace);
}
_realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
_nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
_qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
_opaque = GrabHeaderVar("opaque", wwwAuthenticateHeader);
_nc = 0;
_cnonce = new Random().Next(123400, 9999999).ToString();
_cnonceDate = DateTime.Now;
request.AddHeader("Authorization", GetDigestHeader(request.Method.ToString(), request.Resource));
if (!string.IsNullOrWhiteSpace(cookieName) && !string.IsNullOrWhiteSpace(cookieVal))
{
request.AddCookie(cookieName, cookieVal);
}
//request.Parameters.RemoveAll(x => x.Value.ToString().Contains("gzip, deflate"));
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
}
private string CalculateMd5Hash(
string input)
{
var inputBytes = Encoding.ASCII.GetBytes(input);
var hash = MD5.Create().ComputeHash(inputBytes);
var sb = new StringBuilder();
foreach (var b in hash)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
private string GrabHeaderVar(
string varName,
string header)
{
var regHeader = new Regex(string.Format(@"{0}=""([^""]*)""", varName));
var matchHeader = regHeader.Match(header);
if (matchHeader.Success)
{
return matchHeader.Groups[1].Value;
}
else
{
Debug.WriteLine($"Header {varName} not found");
return null;
}
}
private string GetDigestHeader(string method, string dir)
{
if (!dir.StartsWith("/"))
{
dir = "/" + dir;
}
_nc = _nc + 1;
var ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", _username, _realm, _password));
var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", method.ToUpper(), dir));
var digestResponse =
CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2));
string header = string.Format($"Digest username=\"{_username}\",realm=\"{_realm}\",nonce=\"{_nonce}\",uri=\"{dir}\"," + //removed spaces
$"algorithm=MD5,response=\"{digestResponse}\",qop={_qop},nc={_nc:00000000},cnonce=\"{_cnonce}\"");
if (!string.IsNullOrWhiteSpace(_opaque))
{
header += $",opaque=\"{_opaque}\"";
}
return header;
}
}
Upvotes: 0