Skip to content

Commit adde47e

Browse files
committed
feat: Allow custom RestClientOptions injection for DigestAuthenticator
This commit enables injecting custom RestClientOptions into DigestAuthenticator, allowing full control over the internal RestClient used during the digest handshake. It provides flexibility to configure SSL validation, proxies, and other client-specific settings without creating authentication loops. - Added optional RestClientOptions parameter to DigestAuthenticator - Passed RestClientOptions through DigestAuthenticatorManager - Ensured backward compatibility (default behavior remains unchanged)"
1 parent 627197f commit adde47e

5 files changed

Lines changed: 62 additions & 11 deletions

File tree

RELEASES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818

1919
## v2.0.0
2020

21-
- Changes the compatibility to the RestSharp version `111.2.0` or greater.
21+
- Changes the compatibility to the RestSharp version `111.2.0` or greater.
22+
23+
## v2.0.1
24+
25+
- Add ability to change client options from digest RestClient object

src/DigestAuthenticator/DigestAuthenticator.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class DigestAuthenticator : IAuthenticator
1616

1717
private readonly string _username;
1818
private readonly TimeSpan _timeout;
19+
private readonly RestClientOptions? _handshakeClientOptions;
1920

2021
/// <summary>
2122
/// Creates a new instance of <see cref="DigestAuthenticator" /> class.
@@ -24,7 +25,7 @@ public class DigestAuthenticator : IAuthenticator
2425
/// <param name="password">The password.</param>
2526
/// <param name="logger">The optional logger.</param>
2627
/// <param name="timeout">The request timeout.</param>
27-
public DigestAuthenticator(string username, string password, int timeout = DEFAULT_TIMEOUT, ILogger? logger = null)
28+
public DigestAuthenticator(string username, string password, int timeout = DEFAULT_TIMEOUT, ILogger? logger = null, RestClientOptions? restClientOptions=null)
2829
{
2930
if (string.IsNullOrWhiteSpace(username))
3031
{
@@ -45,15 +46,16 @@ public DigestAuthenticator(string username, string password, int timeout = DEFAU
4546
_password = password;
4647
_timeout = TimeSpan.FromMilliseconds(timeout);
4748
_logger = logger ?? NullLogger.Instance;
49+
_handshakeClientOptions = restClientOptions;
4850
}
4951

5052
/// <inheritdoc cref="IAuthenticator" />
5153
public async ValueTask Authenticate(IRestClient client, RestRequest request)
5254
{
5355
_logger.LogDebug("Initiate Digest authentication");
5456
var uri = client.BuildUri(request);
55-
var manager = new DigestAuthenticatorManager(client.BuildUri(new RestRequest()), _username, _password, _timeout, _logger);
56-
await manager.GetDigestAuthHeader(uri.PathAndQuery, request.Method,client.Options.Proxy).ConfigureAwait(false);
57+
var manager = new DigestAuthenticatorManager(client.BuildUri(new RestRequest()), _username, _password, _timeout, _handshakeClientOptions, _logger);
58+
await manager.GetDigestAuthHeader(uri.PathAndQuery, request.Method, client.Options.Proxy).ConfigureAwait(false);
5759
var digestHeader = manager.GetDigestHeader(uri.PathAndQuery, request.Method);
5860
request.AddOrUpdateHeader("Connection", "Keep-Alive");
5961
request.AddOrUpdateHeader(KnownHeaders.Authorization, digestHeader);

src/DigestAuthenticator/DigestAuthenticatorManager.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Globalization;
33
using System.Linq;
44
using System.Net;
5+
using System.Net.Security;
56
using System.Reflection;
67
using System.Security.Authentication;
78
using System.Security.Cryptography;
@@ -47,6 +48,8 @@ internal class DigestAuthenticatorManager
4748
/// </summary>
4849
private string? _realm;
4950

51+
private readonly RestClientOptions? _handshakeClientOptions;
52+
5053
static DigestAuthenticatorManager()
5154
{
5255
_assemblyVersion = Assembly.GetAssembly(typeof(DigestAuthenticatorManager)).GetName().Version;
@@ -60,7 +63,7 @@ static DigestAuthenticatorManager()
6063
/// <param name="password">The password.</param>
6164
/// <param name="timeout">The timeout.</param>
6265
/// <param name="logger"></param>
63-
public DigestAuthenticatorManager(Uri host, string username, string password, TimeSpan timeout, ILogger logger)
66+
public DigestAuthenticatorManager(Uri host, string username, string password, TimeSpan timeout, RestClientOptions? handshakeClientOptions, ILogger logger)
6467
{
6568
if (string.IsNullOrWhiteSpace(username))
6669
{
@@ -82,6 +85,7 @@ public DigestAuthenticatorManager(Uri host, string username, string password, Ti
8285
_password = password;
8386
_timeout = timeout;
8487
_logger = logger;
88+
_handshakeClientOptions = handshakeClientOptions;
8589
}
8690

8791
/// <summary>
@@ -93,7 +97,7 @@ public DigestAuthenticatorManager(Uri host, string username, string password, Ti
9397
public async Task GetDigestAuthHeader(
9498
string path,
9599
Method method,
96-
IWebProxy? proxy = default)
100+
IWebProxy? proxy = null)
97101
{
98102
_logger.LogDebug("Initiating GetDigestAuthHeader");
99103
var uri = new Uri(_host, path);
@@ -103,11 +107,19 @@ public async Task GetDigestAuthHeader(
103107
request.AddOrUpdateHeader("User-Agent", $"RestSharp.Authenticators.Digest/{_assemblyVersion}");
104108
request.AddOrUpdateHeader("Accept-Encoding", "gzip, deflate, br");
105109
request.Timeout = _timeout;
106-
using var client = new RestClient(new RestClientOptions()
110+
111+
RestClient client;
112+
if (_handshakeClientOptions != null)
113+
{
114+
client = new RestClient(_handshakeClientOptions);
115+
}
116+
else
107117
{
108-
Proxy = proxy
109-
});
118+
client = new RestClient(new RestClientOptions() { Proxy = proxy });
119+
}
120+
110121
var response = await client.ExecuteAsync(request).ConfigureAwait(false);
122+
client.Dispose();
111123
GetDigestDataFromFailResponse(response);
112124
_logger.LogDebug("GetDigestAuthHeader completed");
113125
}

test/DigestAuthenticator.Tests/DigestIntegrationTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,26 @@ public async Task Given_ADigestAuthEndpoint_When_ITryToGetInfo_Then_TheAuthMustB
3737
response.StatusCode.Should().Be(HttpStatusCode.OK);
3838
loggerMock.ReceivedWithAnyArgs().LogDebug("NONONO");
3939
}
40+
41+
42+
[Fact]
43+
public async Task Given_ADigestAuthEndpoint_When_ITryToInjectOwnClient_Then_TheAuthMustBeResolved()
44+
{
45+
var loggerMock = Substitute.For<ILogger>();
46+
loggerMock.BeginScope("DigestServerStub");
47+
48+
var request = new RestRequest("values");
49+
request.AddHeader("Content-Type", "application/json");
50+
51+
RestClientOptions options = new RestClientOptions()
52+
{
53+
MaxRedirects = 2
54+
};
55+
56+
var client = _fixture.CreateInjectedOptionClient(loggerMock, options);
57+
var response = await client.ExecuteAsync(request);
58+
59+
response.StatusCode.Should().Be(HttpStatusCode.OK);
60+
loggerMock.ReceivedWithAnyArgs().LogDebug("NONONO");
61+
}
4062
}

test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class DigestServerStub : IAsyncDisposable
1515
{
1616
private readonly CancellationTokenSource _cancellationTokenSource;
1717
private readonly Task _serverTask;
18-
18+
1919
private const string REALM = "test-realm";
2020
private const string USERNAME = "test-user";
2121
private const string PASSWORD = "test-password";
@@ -26,7 +26,7 @@ public DigestServerStub()
2626
var nonce = GenerateNonce();
2727

2828
_cancellationTokenSource = new CancellationTokenSource();
29-
29+
3030
_serverTask = StartServer(REALM, USERNAME, PASSWORD, nonce, PORT);
3131
Console.WriteLine($"Server started! port: {PORT}.");
3232
}
@@ -41,6 +41,17 @@ public IRestClient CreateClient(ILogger logger)
4141
return new RestClient(restOptions);
4242
}
4343

44+
public IRestClient CreateInjectedOptionClient(ILogger logger, RestClientOptions clientOptions )
45+
{
46+
47+
var restOptions = new RestClientOptions($"http://localhost:{PORT}")
48+
{
49+
Authenticator = new DigestAuthenticator(USERNAME, PASSWORD, logger: logger, restClientOptions: clientOptions)
50+
};
51+
52+
return new RestClient(restOptions);
53+
}
54+
4455
public async ValueTask DisposeAsync()
4556
{
4657
GC.SuppressFinalize(this);

0 commit comments

Comments
 (0)