Skip to content

Commit 217c0bf

Browse files
committed
♻️ Changes to improve tests
1 parent 00a074f commit 217c0bf

4 files changed

Lines changed: 158 additions & 213 deletions

File tree

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,47 @@
1+
using System.Net;
2+
13
namespace RestSharp.Authenticators.Digest
24
{
3-
using System.Net;
4-
5-
using RestSharp.Authenticators;
6-
using RestSharp;
7-
85
/// <summary>
9-
/// Digest midleware for <see cref="IRestClient"/>.
6+
/// Digest middleware for <see cref="IRestClient" />.
107
/// </summary>
118
public class DigestAuthenticator : IAuthenticator
129
{
13-
/// <summary>
14-
/// The username.
15-
/// </summary>
16-
private readonly string username;
17-
18-
/// <summary>
19-
/// The password.
20-
/// </summary>
21-
private readonly string password;
10+
private const int DEFAULT_TIMEOUT = 100000;
11+
private readonly string _password;
2212

13+
private readonly string _username;
2314

2415
/// <summary>
25-
/// The timeout.
26-
/// </summary>
27-
public int Timeout { get; set; } = 100000; //default WebRequest timeout
28-
29-
/// <summary>
30-
/// Creates a new instance of <see cref="DigestAuthenticator"/> class.
16+
/// Creates a new instance of <see cref="DigestAuthenticator" /> class.
3117
/// </summary>
3218
/// <param name="username">The username.</param>
3319
/// <param name="password">The password.</param>
3420
public DigestAuthenticator(string username, string password)
3521
{
36-
this.username = username;
37-
this.password = password;
38-
} 
22+
_username = username;
23+
_password = password;
24+
Timeout = DEFAULT_TIMEOUT;
25+
}
26+
27+
/// <summary>
28+
/// The web request timeout (default 100000).
29+
/// </summary>
30+
public int Timeout { get; set; }
3931

40-
/// <inheritdoc cref="IAuthenticator" />.
32+
/// <inheritdoc cref="IAuthenticator" />
4133
public void Authenticate(IRestClient client, IRestRequest request)
4234
{
43-
request.Credentials = new NetworkCredential(username, this.password); 
35+
// TODO: We must check this to use the new way.
36+
#pragma warning disable CS0618
37+
request.Credentials = new NetworkCredential(_username, _password);
38+
#pragma warning restore CS0618
4439

4540
var uri = client.BuildUri(request);
46-
var manager = new DigestAuthenticatorManager(client.BaseUrl, this.username, this.password, Timeout);
41+
var manager = new DigestAuthenticatorManager(client.BaseUrl, _username, _password, Timeout);
4742
manager.GetDigestAuthHeader(uri.PathAndQuery, request.Method);
4843
var digestHeader = manager.GetDigestHeader(uri.PathAndQuery, request.Method);
4944
request.AddParameter("Authorization", digestHeader, ParameterType.HttpHeader);
5045
}
51-
} 
52-
}
46+
}
47+
}

src/DigestAuthenticator/DigestAuthenticator.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
<OutputType>Library</OutputType>
99
<IsPackable>true</IsPackable>
1010
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
11-
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
1211
</PropertyGroup>
1312
<PropertyGroup>
1413
<TargetFrameworks>netstandard2.0</TargetFrameworks>
Lines changed: 60 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,48 @@
1-
namespace RestSharp.Authenticators.Digest
1+
using System;
2+
using System.Diagnostics;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Security.Cryptography;
7+
using System.Text;
8+
9+
namespace RestSharp.Authenticators.Digest
210
{
3-
using System.Collections.Generic;
4-
using System.Globalization;
5-
using System.Linq;
6-
using System.Net;
7-
using System.Security.Cryptography;
8-
using System.Text;
9-
using System.Text.RegularExpressions;
10-
using System;
11-
12-
using RestSharp;
13-
1411
/// <summary>
15-
/// DigestAuthenticatorManager class.
12+
/// DigestAuthenticatorManager class.
1613
/// </summary>
1714
internal class DigestAuthenticatorManager
1815
{
19-
/// <summary>
20-
/// Header Realm.
21-
/// </summary>
22-
private const string DIGEST_REALM = "Digest realm";
23-
24-
/// <summary>
25-
/// Header Realm.
26-
/// </summary>
27-
private const string REALM = "realm";
28-
29-
/// <summary>
30-
/// Header nonce.
31-
/// </summary>
32-
private const string NONCE = "nonce";
33-
34-
/// <summary>
35-
/// Header qop.
36-
/// </summary>
37-
private const string QOP = "qop";
38-
39-
/// <summary>
40-
/// The host.
41-
/// </summary>
4216
private readonly Uri _host;
4317

44-
/// <summary>
45-
/// The user.
46-
/// </summary>
47-
private readonly string _username;
48-
49-
/// <summary>
50-
/// The password.
51-
/// </summary>
5218
private readonly string _password;
5319

54-
/// <summary>
55-
/// The timeout.
56-
/// </summary>
5720
private readonly int _timeout;
5821

22+
private readonly string _username;
23+
5924
/// <summary>
60-
/// The Realm that is returned by the first digest request (without the data).
25+
/// The cnounce that is generated randomly by the application.
6126
/// </summary>
62-
private string _realm;
27+
private string _cnonce;
6328

6429
/// <summary>
65-
/// The nonce that is returned by the first digest request (without the data).
30+
/// The nonce that is returned by the first digest request (without the data).
6631
/// </summary>
6732
private string _nonce;
6833

6934
/// <summary>
70-
/// The qop that is returned by the first digest request (without the data).
35+
/// The qop that is returned by the first digest request (without the data).
7136
/// </summary>
7237
private string _qop;
7338

7439
/// <summary>
75-
/// The cnounce that is generated randomly by the application.
40+
/// The Realm that is returned by the first digest request (without the data).
7641
/// </summary>
77-
private string _cnonce;
78-
79-
/// <summary>
80-
/// The nounce count (usually 000001)
81-
/// </summary>
82-
private const int NONCE_COUNT = 1;
42+
private string _realm;
8343

8444
/// <summary>
85-
/// Creates a new instance of <see cref="DigestAuthenticatorManager"/> class.
45+
/// Creates a new instance of <see cref="DigestAuthenticatorManager" /> class.
8646
/// </summary>
8747
/// <param name="host">The host.</param>
8848
/// <param name="username">The username.</param>
@@ -97,58 +57,22 @@ public DigestAuthenticatorManager(Uri host, string username, string password, in
9757
}
9858

9959
/// <summary>
100-
/// Generate the MD5 Hash.
101-
/// </summary>
102-
/// <param name="input">The input.</param>
103-
/// <returns>The MD5.</returns>
104-
private static string GenerateMD5(string input)
105-
{
106-
var inputBytes = Encoding.ASCII.GetBytes(input);
107-
var hash = MD5.Create().ComputeHash(inputBytes);
108-
var stringBuilder = new StringBuilder();
109-
hash.ToList().ForEach(b => stringBuilder.Append(b.ToString("x2")));
110-
return stringBuilder.ToString();
111-
}
112-
113-
/// <summary>
114-
/// Gets the digest header.
115-
/// </summary>
116-
/// <param name="digestUri">The digest uri.</param>
117-
/// <param name="method">The method.</param>
118-
/// <returns>The digest header.</returns>
119-
public string GetDigestHeader(string digestUri, Method method)
120-
{
121-
var hash1 = GenerateMD5($"{_username}:{_realm}:{_password}");
122-
var hash2 = GenerateMD5($"{method}:{digestUri}");
123-
var digestResponse = GenerateMD5($"{hash1}:{_nonce}:{NONCE_COUNT:00000000}:{_cnonce}:{_qop}:{hash2}");
124-
return $"Digest username=\"{_username}\"," +
125-
$"realm=\"{_realm}\"," +
126-
$"nonce=\"{_nonce}\"," +
127-
$"uri=\"{digestUri}\"," +
128-
"algorithm=MD5," +
129-
$"response=\"{digestResponse}\"," +
130-
$"qop={_qop}," +
131-
$"nc={NONCE_COUNT:00000000}," +
132-
$"cnonce=\"{_cnonce}\"";
133-
}
134-
135-
/// <summary>
136-
/// Gets the digest auth header.
60+
/// Gets the digest auth header.
13761
/// </summary>
13862
/// <param name="path">The request path.</param>
13963
/// <param name="method">The request method.</param>
14064
public void GetDigestAuthHeader(string path, Method method)
14165
{
14266
var uri = new Uri(_host, path);
143-
var request = (HttpWebRequest)WebRequest.Create(uri);
67+
var request = (HttpWebRequest) WebRequest.Create(uri);
14468
request.Method = method.ToString();
14569
request.ContentLength = 0;
14670
request.Timeout = _timeout;
14771

14872
try
14973
{
150-
var response = (HttpWebResponse)request.GetResponse();
151-
System.Diagnostics.Debug.WriteLine(response);
74+
var response = (HttpWebResponse) request.GetResponse();
75+
Debug.WriteLine(response);
15276
}
15377
catch (WebException ex)
15478
{
@@ -157,105 +81,58 @@ public void GetDigestAuthHeader(string path, Method method)
15781
}
15882

15983
/// <summary>
160-
/// Gets the digest data from exception.
84+
/// Gets the digest header.
16185
/// </summary>
162-
/// <param name="ex">The exception.</param>
163-
private void GetDigestDataFromException(WebException ex)
86+
/// <param name="digestUri">The digest uri.</param>
87+
/// <param name="method">The method.</param>
88+
/// <returns>The digest header.</returns>
89+
public string GetDigestHeader(string digestUri, Method method)
16490
{
165-
if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
166-
{
167-
throw ex;
168-
}
169-
170-
var wwwAuthenticateHeader = TransformHeaderToDictionary(
171-
ex.Response.Headers["WWW-Authenticate"]
172-
);
173-
174-
_cnonce = new Random()
175-
.Next(123400, 9999999)
176-
.ToString(CultureInfo.InvariantCulture);
177-
178-
_realm = wwwAuthenticateHeader.GetHeader(REALM);
179-
_nonce = wwwAuthenticateHeader.GetHeader(NONCE);
180-
_qop = wwwAuthenticateHeader.GetHeader(QOP);
91+
var hash1 = GenerateMD5($"{_username}:{_realm}:{_password}");
92+
var hash2 = GenerateMD5($"{method}:{digestUri}");
93+
var digestResponse =
94+
GenerateMD5($"{hash1}:{_nonce}:{DigestHeader.NONCE_COUNT:00000000}:{_cnonce}:{_qop}:{hash2}");
95+
return $"Digest username=\"{_username}\"," +
96+
$"realm=\"{_realm}\"," +
97+
$"nonce=\"{_nonce}\"," +
98+
$"uri=\"{digestUri}\"," +
99+
"algorithm=MD5," +
100+
$"response=\"{digestResponse}\"," +
101+
$"qop={_qop}," +
102+
$"nc={DigestHeader.NONCE_COUNT:00000000}," +
103+
$"cnonce=\"{_cnonce}\"";
181104
}
182105

183106
/// <summary>
184-
/// Transform the header to dictionary.
107+
/// Generate the MD5 Hash.
185108
/// </summary>
186-
/// <param name="wwwAuthenticateHeader">The header</param>
187-
/// <returns>A instance of <see cref="IDictionary{K,V}"/>.</returns>
188-
private static IDictionary<string, string> TransformHeaderToDictionary(string wwwAuthenticateHeader)
109+
/// <param name="input">The input.</param>
110+
/// <returns>The MD5.</returns>
111+
private static string GenerateMD5(string input)
189112
{
190-
var regex = new Regex("realm=\"(?<realm>.*?)\"|qop=\"(?<qop>.*?)\"|nonce=\"(?<nonce>.*?)\"|stale=\"(?<stale>.*?)\"|opaque=\"(?<opaque>.*?)\"|domain=\"(?<domain>.*?)\"", RegexOptions.IgnoreCase | RegexOptions.Compiled);
191-
var matches = regex.Matches(wwwAuthenticateHeader);
192-
193-
var dict = new Dictionary<string, string>();
194-
195-
// There should be 6 matches
196-
foreach (Match m in matches)
197-
{
198-
if (!m.Success)
199-
{
200-
continue;
201-
}
202-
203-
if (m.Groups[QOP].Success)
204-
{
205-
dict.Add(QOP, m.Groups[QOP].Value);
206-
}
207-
if (m.Groups[REALM].Success)
208-
{
209-
dict.Add(REALM, m.Groups[REALM].Value);
210-
}
211-
if (m.Groups[NONCE].Success)
212-
{
213-
dict.Add(NONCE, m.Groups[NONCE].Value);
214-
}
215-
//if (m.Groups[STALE].Success)
216-
//{
217-
// dict.Add(STALE, m.Groups[STALE].Value);
218-
//}
219-
//if (m.Groups[OPAQUE].Success)
220-
//{
221-
// dict.Add(OPAQUE, m.Groups[OPAQUE].Value);
222-
//}
223-
//if (m.Groups[DOMAIN].Success)
224-
//{
225-
// dict.Add(DOMAIN, m.Groups[DOMAIN].Value);
226-
//}
227-
}
228-
229-
return dict;
113+
var inputBytes = Encoding.ASCII.GetBytes(input);
114+
var hash = MD5.Create().ComputeHash(inputBytes);
115+
var stringBuilder = new StringBuilder();
116+
hash.ToList().ForEach(b => stringBuilder.Append(b.ToString("x2")));
117+
return stringBuilder.ToString();
230118
}
231-
}
232119

233-
/// <summary>
234-
/// Dictionary extension.
235-
/// </summary>
236-
internal static class DictionaryHeaderExtension
237-
{
238-
internal static string GetHeader(this IDictionary<string, string> header, string key)
120+
private void GetDigestDataFromException(WebException ex)
239121
{
240-
if (header.TryGetValue(key, out var value))
122+
if (ex.Response == null || ((HttpWebResponse) ex.Response).StatusCode != HttpStatusCode.Unauthorized)
241123
{
242-
return value;
124+
throw ex;
243125
}
244126

245-
throw new ApplicationException($"Header not found: {key}");
246-
}
127+
var digestHeader = new DigestHeader(ex.Response.Headers["WWW-Authenticate"]);
247128

248-
internal static string GetFirstHeader(this IDictionary<string, string> header, params string[] keys)
249-
{
250-
foreach (var key in keys)
251-
{
252-
if (header.TryGetValue(key, out var value))
253-
{
254-
return value;
255-
}
256-
}
129+
_cnonce = new Random()
130+
.Next(123400, 9999999)
131+
.ToString(CultureInfo.InvariantCulture);
257132

258-
throw new ApplicationException($"No Headers found with following keys: {string.Join(",", keys)}");
133+
_realm = digestHeader.Realm;
134+
_nonce = digestHeader.Nonce;
135+
_qop = digestHeader.Qop;
259136
}
260137
}
261138
}

0 commit comments

Comments
 (0)