Files
Nitload 0ea679932e
Verify / verify (ubuntu-latest, linux-bionic-arm64) (push) Canceled after 0s
Verify / verify (ubuntu-latest, linux-x64) (push) Canceled after 0s
Verify / verify (windows-latest, win-x64) (push) Canceled after 0s
Verify / postgres (push) Canceled after 0s
Build Charac server and client with OIDC, SSH sessions, and packages
2026-10-05 14:02:50 +08:00

76 lines
3.6 KiB
C#

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
using Charac.Server.Authentication;
using Charac.Server.Connections;
using Charac.Server.Hosting;
using Charac.Server.Ssh;
namespace Charac.Server.Tests;
public sealed class ClientResourceTests
{
[Fact]
public void SnapshotFiltersOwnerIssuerRevokedTargetAndChangedAccount()
{
var owner = new ExternalIdentity("https://auth.example/", "alice");
var target = new SshTargetResource(Guid.NewGuid(), "Fedora", "alice");
var now = DateTimeOffset.UtcNow;
var detached = new SshWorkspaceSnapshot(Guid.NewGuid(), target.Id, owner, "alice", now, "detached");
var attached = detached with { Id = Guid.NewGuid(), CreatedAt = now.AddMinutes(-1), State = "attached" };
var snapshots = new[]
{
detached, attached,
detached with { Owner = new(owner.Issuer, "bob") },
detached with { Owner = new("https://another.example/", owner.Subject) },
detached with { TargetId = Guid.NewGuid() },
detached with { Account = "previous-account" }
};
var resources = ClientResourceRoutes.CreateSnapshot(owner, [target], snapshots);
Assert.Equal(new[] { attached.Id, detached.Id }, resources.Sessions.Select(x => x.Id));
Assert.All(resources.Sessions, session => Assert.Equal("Fedora", session.TargetName));
Assert.Empty(ClientResourceRoutes.CreateSnapshot(owner, [], snapshots).Sessions);
Assert.Empty(ClientResourceRoutes.CreateSnapshot(owner, [target with { Account = "new-account" }], snapshots).Sessions);
using var json = JsonDocument.Parse(JsonSerializer.Serialize(resources, JsonSerializerOptions.Web));
var sessionProperties = json.RootElement.GetProperty("sessions")[0].EnumerateObject().Select(x => x.Name);
Assert.Equal(new[] { "id", "targetId", "targetName", "account", "createdAt", "state" }, sessionProperties);
Assert.Equal(new[] { "id", "name", "account" },
json.RootElement.GetProperty("targets")[0].EnumerateObject().Select(x => x.Name));
}
[Fact]
public async Task AnonymousResourceRequestIsRejectedBeforeReadingDatabase()
{
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
timeout.CancelAfter(TimeSpan.FromSeconds(15));
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseUrls("http://127.0.0.1:0");
builder.Logging.ClearProviders();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddScoped<SshAccessResolver>(_ => throw new InvalidOperationException("Database must not be resolved."));
builder.Services.AddSingleton<SshWorkspaceManager>();
builder.Services.AddSingleton<ActiveClientDevices>();
await using var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
ClientResourceRoutes.Map(app);
await app.StartAsync(timeout.Token);
try
{
using var http = new HttpClient();
using var response = await http.GetAsync(new Uri(new Uri(app.Urls.Single()), "/resources"), timeout.Token);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
finally
{
await app.StopAsync(CancellationToken.None);
}
}
}