- Aktualisiert um AuthClient zu den Diensten als Hosted Service hinzuzufügen. - Der zugehörige Unit-Test wurde aktualisiert, um IHost anstelle von IServiceProvider zu verwenden, um gehostete Dienste testen zu können.
278 lines
8.3 KiB
C#
278 lines
8.3 KiB
C#
using DigitalData.Auth.Abstractions;
|
|
using DigitalData.Auth.API.Hubs;
|
|
using DigitalData.Auth.Client;
|
|
using DigitalData.Core.Abstractions.Security;
|
|
using DigitalData.Core.Security;
|
|
using DigitalData.Core.Security.Config;
|
|
using DigitalData.Core.Security.RSAKey;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace DigitalData.Auth.Tests.Client;
|
|
|
|
// TODO: The test checks if the services are working. Performance measurement is ignored. Update it to measure performance as well.
|
|
[TestFixture]
|
|
public class AuthClientTests
|
|
{
|
|
private string _hubUrl;
|
|
|
|
private Func<Action<ClientParams>, IHost> Build;
|
|
|
|
private WebApplication? _app;
|
|
|
|
private int _port;
|
|
|
|
private readonly Queue<IAsyncDisposable> _disposableAsync = new();
|
|
|
|
private static int AvailablePort
|
|
{
|
|
get
|
|
{
|
|
using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0);
|
|
listener.Start();
|
|
int port = ((System.Net.IPEndPoint)listener.LocalEndpoint).Port;
|
|
listener.Stop();
|
|
return port;
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<RSATokenDescriptor> CreatetokenDescriptors()
|
|
{
|
|
return [
|
|
new()
|
|
{
|
|
Issuer = "Foo",
|
|
Audience = "Bar",
|
|
Lifetime = new TimeSpan(1, 0, 0),
|
|
Content = Instance.RSAFactory.CreatePrivateKeyPem()
|
|
}
|
|
];
|
|
}
|
|
|
|
private WebApplication CreateWebApplication(int port)
|
|
{
|
|
// Create builder and add SignalR service
|
|
var builder = WebApplication.CreateBuilder();
|
|
builder.Services.AddSignalR();
|
|
builder.Services.AddCryptoFactory(new CryptoFactoryParams()
|
|
{
|
|
PemDirectory = "/",
|
|
Decryptors = [new RSADecryptor()],
|
|
TokenDescriptors = CreatetokenDescriptors()
|
|
});
|
|
builder.Services.AddMemoryCache();
|
|
|
|
// Listen AvailablePort and map hub
|
|
var app = builder.Build();
|
|
var url = $"http://localhost:{port}";
|
|
var hubRoute = "/auth-hub";
|
|
_hubUrl = url + hubRoute;
|
|
app.Urls.Add(url);
|
|
app.MapHub<AuthHub>(hubRoute);
|
|
app.Start();
|
|
_disposableAsync.Enqueue(app);
|
|
return app;
|
|
}
|
|
|
|
private static CryptoFactoryParams GetCryptoFactoryParamsOf(WebApplication application) => application
|
|
.Services.GetRequiredService<IOptions<CryptoFactoryParams>>().Value;
|
|
|
|
[SetUp]
|
|
public void Setup()
|
|
{
|
|
Build = options =>
|
|
{
|
|
var host = Host.CreateDefaultBuilder()
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddAuthHubClient(options: options)
|
|
.BuildServiceProvider();
|
|
})
|
|
.Build();
|
|
|
|
if(host is IAsyncDisposable disposable)
|
|
_disposableAsync.Enqueue(disposable);
|
|
|
|
return host;
|
|
};
|
|
|
|
// Create and run test server
|
|
_port = AvailablePort;
|
|
_app = CreateWebApplication(_port);
|
|
}
|
|
|
|
[TearDown]
|
|
public async Task TearDown()
|
|
{
|
|
// Stop test server
|
|
if (_app is not null)
|
|
{
|
|
await _app.StopAsync();
|
|
await _app.DisposeAsync();
|
|
Console.WriteLine("Test server stopped.");
|
|
}
|
|
|
|
while (_disposableAsync.Count > 0)
|
|
{
|
|
var disposable = _disposableAsync.Dequeue();
|
|
if (disposable is IHost host)
|
|
await host.StopAsync();
|
|
await disposable.DisposeAsync();
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
[TestCase(true, false, true, TestName = "ShouldStart_WhenHostStartsEvenIfClientDoesNot")]
|
|
[TestCase(false, true, true, TestName = "ShouldStart_WhenClientStartsEvenIfHostDoesNot")]
|
|
public async Task StartAsync_ShouldConnectSuccessfully(bool startHost, bool startClient, bool expectedIsConnected)
|
|
{
|
|
// Arrange
|
|
var host = Build(opt => opt.Url = _hubUrl);
|
|
var client = host.Services.GetRequiredService<IAuthClient>();
|
|
|
|
// Act
|
|
if (startHost)
|
|
await host.StartAsync();
|
|
|
|
if (startClient)
|
|
await client.StartAsync();
|
|
|
|
// Assert
|
|
Assert.That(client.IsConnected, Is.EqualTo(expectedIsConnected));
|
|
}
|
|
|
|
[Test]
|
|
public async Task ReceiveMessage_ShouldCallOnMessageReceived()
|
|
{
|
|
// Arrange
|
|
string rcv_issuer = string.Empty;
|
|
string rcv_audience = string.Empty;
|
|
string rcv_key = string.Empty;
|
|
|
|
// Sender client
|
|
var sender_host = Build(opt => opt.Url = _hubUrl);
|
|
var sender_client = sender_host.Services.GetRequiredService<IAuthClient>();
|
|
await sender_client.StartAsync();
|
|
|
|
// Receiver client
|
|
var receiver_host = Build(opt =>
|
|
{
|
|
opt.Url = _hubUrl;
|
|
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) =>
|
|
{
|
|
rcv_issuer = issuer;
|
|
rcv_audience = audience;
|
|
rcv_key = key;
|
|
};
|
|
});
|
|
var client_receiver = receiver_host.Services.GetRequiredService<IAuthClient>();
|
|
await client_receiver.StartAsync();
|
|
|
|
string issuer = "issuer";
|
|
string audience = "audience";
|
|
string key = "key";
|
|
|
|
// Act
|
|
await sender_client.SendPublicKeyAsync(issuer, audience, key);
|
|
|
|
// delay fort getting answer
|
|
await Task.Delay(2000);
|
|
|
|
// Assert
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(rcv_issuer, Is.EqualTo(issuer));
|
|
Assert.That(rcv_audience, Is.EqualTo(audience));
|
|
Assert.That(rcv_key, Is.EqualTo(key));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetPublicKey_ShouldReturnExpectedPublicKey()
|
|
{
|
|
// Arrange
|
|
string? publicKey = null;
|
|
var host = Build(opt =>
|
|
{
|
|
opt.Url = _hubUrl;
|
|
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) => publicKey = key;
|
|
});
|
|
var client = host.Services.GetRequiredService<IAuthClient>();
|
|
await client.StartAsync();
|
|
|
|
|
|
var expectedPublicKey = GetCryptoFactoryParamsOf(_app).TokenDescriptors.Get("Foo", "Bar").PublicKey.Content;
|
|
|
|
// Act
|
|
await client.GetPublicKeyAsync("Foo", "Bar");
|
|
|
|
// wait for network
|
|
await Task.Delay(2000);
|
|
|
|
// Assert
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(publicKey, Is.Not.Null);
|
|
Assert.That(publicKey, Is.EqualTo(expectedPublicKey));
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task StartAsync_ShouldUpdateAllPublicKey()
|
|
{
|
|
// Arrange
|
|
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
|
|
var host = Build(opt =>
|
|
{
|
|
opt.Url = _hubUrl;
|
|
opt.PublicKeys.Add(publicKey);
|
|
});
|
|
var client = host.Services.GetRequiredService<IAuthClient>();
|
|
await client.StartAsync();
|
|
|
|
// Act
|
|
var expectedPublicKey = GetCryptoFactoryParamsOf(_app).TokenDescriptors.Get("Foo", "Bar").PublicKey;
|
|
|
|
// wait for network
|
|
await Task.Delay(2000);
|
|
|
|
// Assert
|
|
Assert.That(publicKey.Content, Is.EqualTo(expectedPublicKey.Content));
|
|
}
|
|
|
|
[Test]
|
|
public async Task Reconnected_ShouldUpdateAllPublicKey()
|
|
{
|
|
// Arrange
|
|
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
|
|
var host = Build(opt =>
|
|
{
|
|
opt.Url = _hubUrl;
|
|
opt.PublicKeys.Add(publicKey);
|
|
opt.RetryDelay = new TimeSpan(0, 0, 1);
|
|
});
|
|
var client = host.Services.GetRequiredService<IAuthClient>();
|
|
await client.StartAsync();
|
|
|
|
// Act
|
|
CancellationToken cancellationToken = default;
|
|
await _app!.StopAsync(cancellationToken);
|
|
_app = null;
|
|
|
|
var newApp = CreateWebApplication(_port);
|
|
|
|
var expectedPublicKey = GetCryptoFactoryParamsOf(newApp).TokenDescriptors.Get("Foo", "Bar").PublicKey;
|
|
|
|
// wait for network
|
|
await Task.Delay(5000);
|
|
|
|
// Assert
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(cancellationToken.IsCancellationRequested, Is.False);
|
|
Assert.That(publicKey.Content, Is.EqualTo(expectedPublicKey.Content));
|
|
});
|
|
}
|
|
} |