diff --git a/RELEASES.md b/RELEASES.md index 49d72b2..166598f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -18,4 +18,8 @@ ## v2.0.0 -- Changes the compatibility to the RestSharp version `111.2.0` or greater. \ No newline at end of file +- Changes the compatibility to the RestSharp version `111.2.0` or greater. + +## v2.0.1 + +- Add ability to change client options from digest RestClient object \ No newline at end of file diff --git a/src/DigestAuthenticator/DigestAuthenticator.cs b/src/DigestAuthenticator/DigestAuthenticator.cs index 90a4e9e..c1ec638 100644 --- a/src/DigestAuthenticator/DigestAuthenticator.cs +++ b/src/DigestAuthenticator/DigestAuthenticator.cs @@ -16,6 +16,7 @@ public class DigestAuthenticator : IAuthenticator private readonly string _username; private readonly TimeSpan _timeout; + private readonly RestClientOptions? _handshakeClientOptions; /// /// Creates a new instance of class. @@ -24,7 +25,7 @@ public class DigestAuthenticator : IAuthenticator /// The password. /// The optional logger. /// The request timeout. - public DigestAuthenticator(string username, string password, int timeout = DEFAULT_TIMEOUT, ILogger? logger = null) + public DigestAuthenticator(string username, string password, int timeout = DEFAULT_TIMEOUT, ILogger? logger = null, RestClientOptions? restClientOptions=null) { if (string.IsNullOrWhiteSpace(username)) { @@ -45,6 +46,7 @@ public DigestAuthenticator(string username, string password, int timeout = DEFAU _password = password; _timeout = TimeSpan.FromMilliseconds(timeout); _logger = logger ?? NullLogger.Instance; + _handshakeClientOptions = restClientOptions; } /// @@ -52,8 +54,8 @@ public async ValueTask Authenticate(IRestClient client, RestRequest request) { _logger.LogDebug("Initiate Digest authentication"); var uri = client.BuildUri(request); - var manager = new DigestAuthenticatorManager(client.BuildUri(new RestRequest()), _username, _password, _timeout, _logger); - await manager.GetDigestAuthHeader(uri.PathAndQuery, request.Method,client.Options.Proxy).ConfigureAwait(false); + var manager = new DigestAuthenticatorManager(client.BuildUri(new RestRequest()), _username, _password, _timeout, _handshakeClientOptions, _logger); + await manager.GetDigestAuthHeader(uri.PathAndQuery, request.Method, client.Options.Proxy).ConfigureAwait(false); var digestHeader = manager.GetDigestHeader(uri.PathAndQuery, request.Method); request.AddOrUpdateHeader("Connection", "Keep-Alive"); request.AddOrUpdateHeader(KnownHeaders.Authorization, digestHeader); diff --git a/src/DigestAuthenticator/DigestAuthenticatorManager.cs b/src/DigestAuthenticator/DigestAuthenticatorManager.cs index 2115b5e..71a4236 100644 --- a/src/DigestAuthenticator/DigestAuthenticatorManager.cs +++ b/src/DigestAuthenticator/DigestAuthenticatorManager.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using System.Net; +using System.Net.Security; using System.Reflection; using System.Security.Authentication; using System.Security.Cryptography; @@ -47,6 +48,7 @@ internal class DigestAuthenticatorManager /// private string? _realm; + private readonly RestClientOptions? _handshakeClientOptions; /// /// The opaque that is returned by the first digest request (without the data). /// @@ -65,7 +67,7 @@ static DigestAuthenticatorManager() /// The password. /// The timeout. /// - public DigestAuthenticatorManager(Uri host, string username, string password, TimeSpan timeout, ILogger logger) + public DigestAuthenticatorManager(Uri host, string username, string password, TimeSpan timeout, RestClientOptions? handshakeClientOptions, ILogger logger) { if (string.IsNullOrWhiteSpace(username)) { @@ -87,6 +89,7 @@ public DigestAuthenticatorManager(Uri host, string username, string password, Ti _password = password; _timeout = timeout; _logger = logger; + _handshakeClientOptions = handshakeClientOptions; } /// @@ -98,7 +101,7 @@ public DigestAuthenticatorManager(Uri host, string username, string password, Ti public async Task GetDigestAuthHeader( string path, Method method, - IWebProxy? proxy = default) + IWebProxy? proxy = null) { _logger.LogDebug("Initiating GetDigestAuthHeader"); var uri = new Uri(_host, path); @@ -108,11 +111,19 @@ public async Task GetDigestAuthHeader( request.AddOrUpdateHeader("User-Agent", $"RestSharp.Authenticators.Digest/{_assemblyVersion}"); request.AddOrUpdateHeader("Accept-Encoding", "gzip, deflate, br"); request.Timeout = _timeout; - using var client = new RestClient(new RestClientOptions() + + RestClient client; + if (_handshakeClientOptions != null) + { + client = new RestClient(_handshakeClientOptions); + } + else { - Proxy = proxy - }); + client = new RestClient(new RestClientOptions() { Proxy = proxy }); + } + var response = await client.ExecuteAsync(request).ConfigureAwait(false); + client.Dispose(); GetDigestDataFromFailResponse(response); _logger.LogDebug("GetDigestAuthHeader completed"); } diff --git a/test/DigestAuthenticator.Tests/DigestIntegrationTest.cs b/test/DigestAuthenticator.Tests/DigestIntegrationTest.cs index d4470b9..582c8aa 100644 --- a/test/DigestAuthenticator.Tests/DigestIntegrationTest.cs +++ b/test/DigestAuthenticator.Tests/DigestIntegrationTest.cs @@ -37,4 +37,31 @@ public async Task Given_ADigestAuthEndpoint_When_ITryToGetInfo_Then_TheAuthMustB response.StatusCode.Should().Be(HttpStatusCode.OK); loggerMock.ReceivedWithAnyArgs().LogDebug("NONONO"); } + + + [Fact] + public async Task Given_ADigestAuthEndpoint_When_ITryToInjectOwnClient_Then_TheAuthMustBeResolved() + { + bool proxyCalled = false; + + var loggerMock = Substitute.For(); + loggerMock.BeginScope("DigestServerStub"); + + var request = new RestRequest("values"); + request.AddHeader("Content-Type", "application/json"); + + RestClientOptions options = new RestClientOptions() + { + Proxy = new TestProxy(() => proxyCalled = true) + }; + + var client = _fixture.CreateInjectedOptionClient(loggerMock, options); + var response = await client.ExecuteAsync(request); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + loggerMock.ReceivedWithAnyArgs().LogDebug("NONONO"); + + Assert.True(proxyCalled, "Injected RestClientOptions.Proxy should be used in digest handshake."); + + } } diff --git a/test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs b/test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs index fbd207d..1ebb73f 100644 --- a/test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs +++ b/test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs @@ -15,7 +15,7 @@ public class DigestServerStub : IAsyncDisposable { private readonly CancellationTokenSource _cancellationTokenSource; private readonly Task _serverTask; - + private const string REALM = "test-realm"; private const string USERNAME = "test-user"; private const string PASSWORD = "test-password"; @@ -26,7 +26,7 @@ public DigestServerStub() var nonce = GenerateNonce(); _cancellationTokenSource = new CancellationTokenSource(); - + _serverTask = StartServer(REALM, USERNAME, PASSWORD, nonce, PORT); Console.WriteLine($"Server started! port: {PORT}."); } @@ -41,6 +41,17 @@ public IRestClient CreateClient(ILogger logger) return new RestClient(restOptions); } + public IRestClient CreateInjectedOptionClient(ILogger logger, RestClientOptions clientOptions ) + { + + var restOptions = new RestClientOptions($"http://localhost:{PORT}") + { + Authenticator = new DigestAuthenticator(USERNAME, PASSWORD, logger: logger, restClientOptions: clientOptions) + }; + + return new RestClient(restOptions); + } + public async ValueTask DisposeAsync() { GC.SuppressFinalize(this); diff --git a/test/DigestAuthenticator.Tests/Fixtures/TestProxy.cs b/test/DigestAuthenticator.Tests/Fixtures/TestProxy.cs new file mode 100644 index 0000000..035419c --- /dev/null +++ b/test/DigestAuthenticator.Tests/Fixtures/TestProxy.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace RestSharp.Authenticators.Digest.Tests.Fixtures; +internal class TestProxy : IWebProxy +{ + private readonly Action _onProxyCalled; + + public TestProxy(Action onProxyCalled) + { + _onProxyCalled = onProxyCalled; + } + + public Uri GetProxy(Uri destination) + { + _onProxyCalled(); + return destination; + } + + public bool IsBypassed(Uri host) => false; + + public ICredentials? Credentials { get; set; } +}