c5e7479ee4
Add IDashboardSessionAdminService (Admin-role gate, friendly errors, audit logging) wrapping a new ISessionManager.KillWorkerAsync that skips graceful shutdown and cleans up registry/metrics. Sessions, Workers, and SessionDetails pages render Close / Kill buttons only when CanManage; the service re-checks the role on every call so forged clicks return Unauthenticated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
137 lines
4.8 KiB
C#
137 lines
4.8 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
|
|
public sealed class DashboardSessionAdminService(
|
|
ISessionManager sessionManager,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
ILogger<DashboardSessionAdminService>? logger = null) : IDashboardSessionAdminService
|
|
{
|
|
private const string UnauthorizedMessage = "Sign in as an Admin to close sessions or kill workers.";
|
|
private const string KillReason = "dashboard-admin-kill";
|
|
|
|
private readonly ILogger<DashboardSessionAdminService> _logger =
|
|
logger ?? NullLogger<DashboardSessionAdminService>.Instance;
|
|
|
|
public bool CanManage(ClaimsPrincipal user)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(user);
|
|
|
|
return user.Identity?.IsAuthenticated == true
|
|
&& user.IsInRole(DashboardRoles.Admin);
|
|
}
|
|
|
|
public async Task<DashboardSessionAdminResult> CloseSessionAsync(
|
|
ClaimsPrincipal user,
|
|
string sessionId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (!CanManage(user))
|
|
{
|
|
return DashboardSessionAdminResult.Fail(UnauthorizedMessage);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return DashboardSessionAdminResult.Fail("Session id is required.");
|
|
}
|
|
|
|
string actor = ResolveActor(user);
|
|
try
|
|
{
|
|
SessionCloseResult result = await sessionManager
|
|
.CloseSessionAsync(sessionId, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
_logger.LogInformation(
|
|
"Dashboard admin {Actor} closed session {SessionId} from {RemoteAddress}; alreadyClosed={AlreadyClosed}.",
|
|
actor,
|
|
sessionId,
|
|
ResolveRemoteAddress(),
|
|
result.AlreadyClosed);
|
|
|
|
return DashboardSessionAdminResult.Success(
|
|
result.AlreadyClosed
|
|
? $"Session {sessionId} was already closed."
|
|
: $"Session {sessionId} closed.");
|
|
}
|
|
catch (SessionManagerException exception) when (exception.ErrorCode == SessionManagerErrorCode.SessionNotFound)
|
|
{
|
|
return DashboardSessionAdminResult.Fail($"Session {sessionId} was not found.");
|
|
}
|
|
catch (SessionManagerException exception)
|
|
{
|
|
_logger.LogWarning(
|
|
exception,
|
|
"Dashboard admin {Actor} close failed for session {SessionId}.",
|
|
actor,
|
|
sessionId);
|
|
return DashboardSessionAdminResult.Fail(
|
|
$"Close failed: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<DashboardSessionAdminResult> KillWorkerAsync(
|
|
ClaimsPrincipal user,
|
|
string sessionId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (!CanManage(user))
|
|
{
|
|
return DashboardSessionAdminResult.Fail(UnauthorizedMessage);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return DashboardSessionAdminResult.Fail("Session id is required.");
|
|
}
|
|
|
|
string actor = ResolveActor(user);
|
|
try
|
|
{
|
|
SessionCloseResult result = await sessionManager
|
|
.KillWorkerAsync(sessionId, KillReason, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
_logger.LogInformation(
|
|
"Dashboard admin {Actor} killed worker for session {SessionId} from {RemoteAddress}; alreadyClosed={AlreadyClosed}.",
|
|
actor,
|
|
sessionId,
|
|
ResolveRemoteAddress(),
|
|
result.AlreadyClosed);
|
|
|
|
return DashboardSessionAdminResult.Success(
|
|
result.AlreadyClosed
|
|
? $"Session {sessionId} was already closed."
|
|
: $"Worker for session {sessionId} killed.");
|
|
}
|
|
catch (SessionManagerException exception) when (exception.ErrorCode == SessionManagerErrorCode.SessionNotFound)
|
|
{
|
|
return DashboardSessionAdminResult.Fail($"Session {sessionId} was not found.");
|
|
}
|
|
catch (SessionManagerException exception)
|
|
{
|
|
_logger.LogWarning(
|
|
exception,
|
|
"Dashboard admin {Actor} kill failed for session {SessionId}.",
|
|
actor,
|
|
sessionId);
|
|
return DashboardSessionAdminResult.Fail(
|
|
$"Kill failed: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
private static string ResolveActor(ClaimsPrincipal user)
|
|
{
|
|
return user.Identity?.Name ?? "<unknown>";
|
|
}
|
|
|
|
private string? ResolveRemoteAddress()
|
|
{
|
|
return httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
|
|
}
|
|
}
|