Public Access
63 lines
2.5 KiB
C#
63 lines
2.5 KiB
C#
using DodoSSH.Api.Authorization;
|
|
using DodoSSH.Domain.Authorization;
|
|
|
|
namespace DodoSSH.Api.Features.Relay;
|
|
|
|
/// <summary>
|
|
/// Ends a user's relay sessions to vaults they can no longer reach, when their access changes.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// A relay session is authorised once, when its ticket is issued, and used to run on for as long as it
|
|
/// carried bytes — up to the maximum lifetime, twelve hours by default. Removing somebody from a team took
|
|
/// their keys and their vault list away at once, and left any shell they had open through the relay running
|
|
/// into the team's machines until it went idle.
|
|
/// </para>
|
|
/// <para>
|
|
/// Told by the same announcement that tells their event socket to re-read its vaults — see
|
|
/// <see cref="Events.IVaultEventPublisher.VaultAccessChanged"/> — and it asks the same question the ticket
|
|
/// endpoint asked, against the database as it now stands. Off the announcing request, because that request
|
|
/// has already committed and answered, and a slow re-check must not hold it.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal sealed class RelayAccessWatch(
|
|
RelaySessionRegistry sessions,
|
|
IServiceScopeFactory scopes,
|
|
ILogger<RelayAccessWatch> logger)
|
|
{
|
|
/// <summary>Re-checks every session the user has open, in the background.</summary>
|
|
/// <returns>The re-check, for a caller that wants to wait for it; the announcement does not.</returns>
|
|
internal Task AccessChangedAsync(Guid userId)
|
|
{
|
|
var open = sessions.SessionsOf(userId);
|
|
|
|
return open.Count == 0 ? Task.CompletedTask : Task.Run(() => RecheckAsync(userId, open));
|
|
}
|
|
|
|
private async Task RecheckAsync(Guid userId, IReadOnlyList<RelaySessionRegistry.Lease> open)
|
|
{
|
|
try
|
|
{
|
|
var scope = scopes.CreateAsyncScope();
|
|
await using var _ = scope.ConfigureAwait(false);
|
|
|
|
var access = scope.ServiceProvider.GetRequiredService<IVaultAccessService>();
|
|
|
|
foreach (var lease in open)
|
|
{
|
|
var resolved = await access.ResolveAsync(userId, lease.VaultId, CancellationToken.None)
|
|
.ConfigureAwait(false);
|
|
|
|
if (!resolved.Granted || !resolved.Permissions.HasFlag(PermissionFlags.Connect))
|
|
{
|
|
lease.Revoke();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exception) when (exception is not OutOfMemoryException)
|
|
{
|
|
RelayLog.AccessRecheckFailed(logger, userId, exception);
|
|
}
|
|
}
|
|
}
|