Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

created form of DigestAuthenticator.cs #2120

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
191 changes: 191 additions & 0 deletions src/RestSharp/Authenticators/DigestAuthenticator.cs
@@ -0,0 +1,191 @@
// Copyright (c) .NET Foundation and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using RestSharp.Authenticators;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace RestSharp.Authenticators;

/// <summary>
/// Allows "digest access authentication" for HTTP requests.
/// </summary>
/// <remarks>
/// Encoding can be specified depending on what your server expect (see https://stackoverflow.com/a/7243567).
/// UTF-8 is used by default but some servers might expect ISO-8859-1 encoding.
/// </remarks>
[PublicAPI]
public class DigestAuthenticator : IAuthenticator
{
private string _username;
private string _password;
private string _realm;
private string _nonce;
private string _qop;
private string _opaque;
public DigestAuthenticator(string username, string password)

Check warning on line 43 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable field '_realm' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 43 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable field '_nonce' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 43 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable field '_qop' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 43 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Non-nullable field '_opaque' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
_username = username;
_password = password;
}
public ValueTask Authenticate(IRestClient client, RestRequest request)
{
if (string.IsNullOrEmpty(_realm) || string.IsNullOrEmpty(_nonce) || string.IsNullOrEmpty(_qop))
{
try
{
FetchAuthenticationInfo(client, request);
}
catch (Exception ex)
{
Console.WriteLine("Error: {0}", ex.Message);
if (client.Options.ThrowOnAnyError)
throw ex;
return new ValueTask(Task.FromResult(new HeaderParameter(KnownHeaders.Authorization, "")));
}
}
string authorizationHeader = GenerateDigestAuthorization(request.Method.ToString(), client.Options.BaseUrl.ToString(), _username, _password, _realm, _nonce, _opaque);

Check warning on line 64 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
_realm = "";
_nonce = "";
_qop = "";
return new ValueTask(Task.FromResult(request.AddOrUpdateParameter(new HeaderParameter(KnownHeaders.Authorization, authorizationHeader))));
}
private void FetchAuthenticationInfo(IRestClient client, RestRequest request)
{
RestClient headClient = new RestClient(new RestClientOptions(client.Options.BaseUrl.ToString())

Check warning on line 72 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
{
MaxTimeout = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds)
});
RestRequest headRequest = new RestRequest(client.BuildUri(request).PathAndQuery, request.Method);
headRequest.Timeout = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds);
RestResponse headResponse = headClient.Execute(headRequest);

Check failure on line 78 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

The type arguments for method 'RestClientExtensions.Execute<T>(IRestClient, RestRequest)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Check failure on line 78 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

The type arguments for method 'RestClientExtensions.Execute<T>(IRestClient, RestRequest)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Check failure on line 78 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

The type arguments for method 'RestClientExtensions.Execute<T>(IRestClient, RestRequest)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Check failure on line 78 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

The type arguments for method 'RestClientExtensions.Execute<T>(IRestClient, RestRequest)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
if (headResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var wwwAuthenticateHeader = headResponse.Headers.FirstOrDefault(h => h.Name == "WWW-Authenticate")?.Value.ToString();

Check warning on line 81 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
if (!string.IsNullOrEmpty(wwwAuthenticateHeader))
ParseAuthenticationInfo(wwwAuthenticateHeader);

Check warning on line 83 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference argument for parameter 'wwwAuthenticateHeader' in 'void DigestAuthenticator.ParseAuthenticationInfo(string wwwAuthenticateHeader)'.
}
else
{
if (headResponse.StatusCode == HttpStatusCode.RequestTimeout)
throw new Exception("The request timed out.");
else
throw new Exception("The server did not respond with a valid WWW-Authenticate in the header.");
}
}
private void ParseAuthenticationInfo(string wwwAuthenticateHeader)
{
// Parse the WWW-Authenticate header and extract the necessary information (realm, nonce, qop, etc.)
// You'll need to implement the parsing logic according to the structure of the header.
// This will depend on the server's implementation of Digest authentication.
// Example parsing code for demonstration purposes:
// The parsing logic here assumes a specific format for the header. You should adjust this to match your server's format.
var valores = ExtrairValoresPropriedades(wwwAuthenticateHeader);
valores.TryGetValue("realm", out _realm);
valores.TryGetValue("nonce", out _nonce);
valores.TryGetValue("qop", out _qop);
valores.TryGetValue("opaque", out _opaque);
}
public static Dictionary<string, string> ExtrairValoresPropriedades(string texto)
{
// Define o padrão da regex para encontrar os valores entre as aspas
string padraoRegex = @"(\w+)=""([^""]+)""";
var regex = new Regex(padraoRegex);
// Cria um dicionário para armazenar os pares chave-valor
var valores = new Dictionary<string, string>();
// Encontra todas as correspondências na string
var correspondencias = regex.Matches(texto);
// Itera sobre as correspondências e adiciona ao dicionário
foreach (Match correspondencia in correspondencias)
{
if (correspondencia.Success)
{
string chave = correspondencia.Groups[1].Value;
string valor = correspondencia.Groups[2].Value;
valores[chave] = valor;
}
}
return valores;
}
private string GetValueFromHeader(string header, string key)
{
string searchKey = key + "=";
int startIndex = header.IndexOf(searchKey, StringComparison.OrdinalIgnoreCase);
if (startIndex == -1)
return null;

Check warning on line 132 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference return.
startIndex += searchKey.Length;
int endIndex = header.IndexOf('"', startIndex);
if (endIndex == -1)
return null;

Check warning on line 136 in src/RestSharp/Authenticators/DigestAuthenticator.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference return.
return header.Substring(startIndex, endIndex - startIndex);
}
private string GenerateDigestAuthorization(
string method,
string uri,
string username,
string password,
string realm,
string nonce,
string opaque,
string qop = "auth",
string algorithm = "MD5")
{
string cnonce = Guid.NewGuid().ToString("N").Substring(0, 8).ToLower();
string A1 = $"{username}:{realm}:{password}";
string H_A1 = CalculateMD5Hash(A1);
string A2 = $"{method.ToUpper()}:{uri}";
string H_A2 = CalculateMD5Hash(A2);
string request_digest = gerarRequestDigest(H_A1, nonce, "00000001", cnonce, qop, H_A2);
string authorizationHeader = $"Digest username=\"{username}\", " +
$"realm=\"{realm}\", " +
$"nonce=\"{nonce}\", " +
$"uri=\"{uri}\", " +
$"algorithm=\"{algorithm}\", " +
$"qop={qop}, " +
$"nc=00000001, " +
$"cnonce=\"{cnonce}\", " +
$"response=\"{request_digest}\", " +
$"opaque=\"{opaque}\"";
return authorizationHeader;
}
private string CalculateMD5Hash(string input)
{
using (MD5 md5 = MD5.Create())
{
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("x2"));
}
return sb.ToString();
}
}
private string gerarRequestDigest(string H_A1, string nonce, string nc, string cnonce, string qop, string H_A2)
{
string combined = "";
if (qop == "auth")
combined = $"{H_A1}:{nonce}:{nc}:{cnonce}:{qop}:{H_A2}";
else
combined = $"{H_A1}:{nonce}:{H_A2}";
return CalculateMD5Hash(combined);
}
}