fix(management): elide ExternalSystem AuthConfiguration from responses and audit (arch-review C3)
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -1661,16 +1661,36 @@ public class ManagementActor : ReceiveActor
|
||||
// External System handlers
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Secret-elided ExternalSystemDefinition projection (arch-review C3): the
|
||||
/// opaque <c>AuthConfiguration</c> blob holds API keys / Basic credentials and
|
||||
/// must never leave this boundary via responses or audit afterState. Presence
|
||||
/// is surfaced as <c>HasAuthConfiguration</c>; every non-secret field is
|
||||
/// projected verbatim. Mirrors <see cref="SmtpConfigPublicShape"/>.
|
||||
/// </summary>
|
||||
private static object ExternalSystemPublicShape(ExternalSystemDefinition d) => new
|
||||
{
|
||||
d.Id,
|
||||
d.Name,
|
||||
d.EndpointUrl,
|
||||
d.AuthType,
|
||||
d.MaxRetries,
|
||||
d.RetryDelay,
|
||||
d.TimeoutSeconds,
|
||||
HasAuthConfiguration = !string.IsNullOrEmpty(d.AuthConfiguration),
|
||||
};
|
||||
|
||||
private static async Task<object?> HandleListExternalSystems(IServiceProvider sp)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
return await repo.GetAllExternalSystemsAsync();
|
||||
return (await repo.GetAllExternalSystemsAsync()).Select(ExternalSystemPublicShape).ToList();
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleGetExternalSystem(IServiceProvider sp, GetExternalSystemCommand cmd)
|
||||
{
|
||||
var repo = sp.GetRequiredService<IExternalSystemRepository>();
|
||||
return await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId);
|
||||
var def = await repo.GetExternalSystemByIdAsync(cmd.ExternalSystemId);
|
||||
return def is null ? null : ExternalSystemPublicShape(def);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleCreateExternalSystem(IServiceProvider sp, CreateExternalSystemCommand cmd, string user)
|
||||
@@ -1683,8 +1703,9 @@ public class ManagementActor : ReceiveActor
|
||||
};
|
||||
await repo.AddExternalSystemAsync(def);
|
||||
await repo.SaveChangesAsync();
|
||||
await AuditAsync(sp, user, "Create", "ExternalSystem", def.Id.ToString(), def.Name, def);
|
||||
return def;
|
||||
var publicShape = ExternalSystemPublicShape(def);
|
||||
await AuditAsync(sp, user, "Create", "ExternalSystem", def.Id.ToString(), def.Name, publicShape);
|
||||
return publicShape;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleUpdateExternalSystem(IServiceProvider sp, UpdateExternalSystemCommand cmd, string user)
|
||||
@@ -1695,12 +1716,15 @@ public class ManagementActor : ReceiveActor
|
||||
def.Name = cmd.Name;
|
||||
def.EndpointUrl = cmd.EndpointUrl;
|
||||
def.AuthType = cmd.AuthType;
|
||||
def.AuthConfiguration = cmd.AuthConfiguration;
|
||||
// Preserve-if-null: an update that omits AuthConfiguration leaves the stored
|
||||
// secret intact (mirrors the SMTP/SMS credential preserve-if-null rule).
|
||||
if (cmd.AuthConfiguration is not null) def.AuthConfiguration = cmd.AuthConfiguration;
|
||||
if (cmd.TimeoutSeconds is { } ts) def.TimeoutSeconds = ts;
|
||||
await repo.UpdateExternalSystemAsync(def);
|
||||
await repo.SaveChangesAsync();
|
||||
await AuditAsync(sp, user, "Update", "ExternalSystem", def.Id.ToString(), def.Name, def);
|
||||
return def;
|
||||
var publicShape = ExternalSystemPublicShape(def);
|
||||
await AuditAsync(sp, user, "Update", "ExternalSystem", def.Id.ToString(), def.Name, publicShape);
|
||||
return publicShape;
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleDeleteExternalSystem(IServiceProvider sp, DeleteExternalSystemCommand cmd, string user)
|
||||
|
||||
Reference in New Issue
Block a user