Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@

## v2.0.0

- Changes the compatibility to the RestSharp version `111.2.0` or greater.
- 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
8 changes: 5 additions & 3 deletions src/DigestAuthenticator/DigestAuthenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class DigestAuthenticator : IAuthenticator

private readonly string _username;
private readonly TimeSpan _timeout;
private readonly RestClientOptions? _handshakeClientOptions;

/// <summary>
/// Creates a new instance of <see cref="DigestAuthenticator" /> class.
Expand All @@ -24,7 +25,7 @@ public class DigestAuthenticator : IAuthenticator
/// <param name="password">The password.</param>
/// <param name="logger">The optional logger.</param>
/// <param name="timeout">The request timeout.</param>
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))
{
Expand All @@ -45,15 +46,16 @@ public DigestAuthenticator(string username, string password, int timeout = DEFAU
_password = password;
_timeout = TimeSpan.FromMilliseconds(timeout);
_logger = logger ?? NullLogger.Instance;
_handshakeClientOptions = restClientOptions;
}

/// <inheritdoc cref="IAuthenticator" />
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);
Expand Down
21 changes: 16 additions & 5 deletions src/DigestAuthenticator/DigestAuthenticatorManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,6 +48,7 @@ internal class DigestAuthenticatorManager
/// </summary>
private string? _realm;

private readonly RestClientOptions? _handshakeClientOptions;
/// <summary>
/// The opaque that is returned by the first digest request (without the data).
/// </summary>
Expand All @@ -65,7 +67,7 @@ static DigestAuthenticatorManager()
/// <param name="password">The password.</param>
/// <param name="timeout">The timeout.</param>
/// <param name="logger"></param>
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))
{
Expand All @@ -87,6 +89,7 @@ public DigestAuthenticatorManager(Uri host, string username, string password, Ti
_password = password;
_timeout = timeout;
_logger = logger;
_handshakeClientOptions = handshakeClientOptions;
}

/// <summary>
Expand All @@ -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);
Expand All @@ -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");
}
Expand Down
27 changes: 27 additions & 0 deletions test/DigestAuthenticator.Tests/DigestIntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ILogger>();
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.");

}
}
15 changes: 13 additions & 2 deletions test/DigestAuthenticator.Tests/Fixtures/DigestServerStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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}.");
}
Expand All @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions test/DigestAuthenticator.Tests/Fixtures/TestProxy.cs
Original file line number Diff line number Diff line change
@@ -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; }
}