82 lines
3.5 KiB
C#
82 lines
3.5 KiB
C#
using System.Security.Cryptography;
|
|
using Charac.Server.Authentication;
|
|
using Charac.Server.Connections;
|
|
using Charac.Client;
|
|
|
|
namespace Charac.Server.Tests;
|
|
|
|
public sealed class ActiveClientDevicesTests
|
|
{
|
|
private static readonly ExternalIdentity Alice = new("https://auth.example/", "alice");
|
|
private static readonly ExternalIdentity Bob = new("https://auth.example/", "bob");
|
|
|
|
[Fact]
|
|
public void SameInstallationCanHoldSeveralConnectionsWhileAnotherIsRejected()
|
|
{
|
|
var devices = new ActiveClientDevices();
|
|
var first = Credential();
|
|
var second = Credential();
|
|
Assert.Equal(ClientDeviceStatus.Invalid, devices.Check(Alice, "spoofed"));
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Alice, first, out var one));
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Alice, first, out var two));
|
|
Assert.Equal(ClientDeviceStatus.Occupied, devices.Check(Alice, second));
|
|
Assert.Equal(ClientDeviceStatus.Occupied, devices.Acquire(Alice, second, out var rejected));
|
|
Assert.Null(rejected);
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Bob, second, out var bob));
|
|
one!.Dispose();
|
|
Assert.Equal(ClientDeviceStatus.Occupied, devices.Check(Alice, second));
|
|
two!.Dispose();
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Alice, second, out var replacement));
|
|
replacement!.Dispose();
|
|
bob!.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public void OperatorDisconnectReleasesDeviceAndStaleLeaseCannotReleaseReplacement()
|
|
{
|
|
var devices = new ActiveClientDevices();
|
|
var first = Credential();
|
|
var second = Credential();
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Alice, first, out var old));
|
|
using (devices.BeginDisconnect(Alice))
|
|
{
|
|
Assert.Equal(ClientDeviceStatus.Occupied, devices.Check(Alice, second));
|
|
Assert.False(devices.TryUse(old!, () => new object(), out _));
|
|
}
|
|
Assert.Equal(ClientDeviceStatus.Allowed, devices.Acquire(Alice, second, out var current));
|
|
old!.Dispose();
|
|
Assert.Equal(ClientDeviceStatus.Occupied, devices.Check(Alice, first));
|
|
current!.Dispose();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InstallationCredentialPersistsAndIsPrivate()
|
|
{
|
|
var directory = Path.Combine(Path.GetTempPath(), "rac-device-" + Guid.NewGuid().ToString("N"));
|
|
try
|
|
{
|
|
var concurrent = await Task.WhenAll(Enumerable.Range(0, 8)
|
|
.Select(_ => Task.Run(() => ClientDeviceCredential.LoadOrCreate(directory))));
|
|
var first = concurrent[0];
|
|
Assert.Equal(43, first.Length);
|
|
Assert.Equal(first, ClientDeviceCredential.LoadOrCreate(directory));
|
|
Assert.All(concurrent, value => Assert.Equal(first, value));
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
var mode = File.GetUnixFileMode(Path.Combine(directory, "device-key"));
|
|
Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, mode &
|
|
(UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead |
|
|
UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(directory))
|
|
Directory.Delete(directory, recursive: true);
|
|
}
|
|
}
|
|
|
|
private static string Credential() => Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
|
|
.TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
|
}
|