fix(templateengine): audit projected template/script summaries instead of the tracked graph and script bodies (review 2026-10-21 08/G1)
This commit is contained in:
@@ -78,7 +78,7 @@ public class SharedScriptService
|
||||
// SaveChangesAsync ran.
|
||||
await _repository.AddSharedScriptAsync(script, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "SharedScript", script.Id.ToString(), name, script, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "SharedScript", script.Id.ToString(), name, TemplateAuditShapes.SharedScript(script), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<SharedScript>.Success(script);
|
||||
@@ -123,7 +123,7 @@ public class SharedScriptService
|
||||
script.ReturnDefinition = returnDefinition;
|
||||
|
||||
await _repository.UpdateSharedScriptAsync(script, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "SharedScript", scriptId.ToString(), script.Name, script, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "SharedScript", scriptId.ToString(), script.Name, TemplateAuditShapes.SharedScript(script), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<SharedScript>.Success(script);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Compact after-state shapes for the configuration audit log (review 2026-10-21
|
||||
/// 08/G1). <c>IAuditService.LogAsync</c> serialises whatever it is handed into
|
||||
/// <c>AuditLogEntries.AfterStateJson</c> — an indefinitely-retained table — so
|
||||
/// handing it a tracked <see cref="Template"/> wrote the whole loaded graph,
|
||||
/// every attribute, alarm and script body included, on each create / rename / move.
|
||||
/// These shapes keep the identity, the scalar fields, per-collection member counts
|
||||
/// and the fields that changed. Script bodies are never written: a body is
|
||||
/// represented by its length and SHA-256, which still lets an auditor tell whether
|
||||
/// (and to what) a body changed without storing it again on every edit.
|
||||
/// </summary>
|
||||
internal static class TemplateAuditShapes
|
||||
{
|
||||
/// <summary>Summary of a template: identity, scalars and member counts — no member rows.</summary>
|
||||
/// <param name="template">The template.</param>
|
||||
/// <param name="changedFields">Names of the fields this operation changed, if known.</param>
|
||||
/// <returns>The audit after-state object.</returns>
|
||||
internal static object Template(Template template, IReadOnlyList<string>? changedFields = null) => new
|
||||
{
|
||||
template.Id,
|
||||
template.Name,
|
||||
template.Description,
|
||||
template.ParentTemplateId,
|
||||
template.FolderId,
|
||||
template.IsDerived,
|
||||
template.OwnerCompositionId,
|
||||
AttributeCount = template.Attributes.Count,
|
||||
AlarmCount = template.Alarms.Count,
|
||||
ScriptCount = template.Scripts.Count,
|
||||
CompositionCount = template.Compositions.Count,
|
||||
NativeAlarmSourceCount = template.NativeAlarmSources.Count,
|
||||
ChangedFields = changedFields,
|
||||
};
|
||||
|
||||
/// <summary>A template script without its body (length + SHA-256 instead).</summary>
|
||||
/// <param name="script">The script.</param>
|
||||
/// <returns>The audit after-state object.</returns>
|
||||
internal static object Script(TemplateScript script) => new
|
||||
{
|
||||
script.Id,
|
||||
script.TemplateId,
|
||||
script.Name,
|
||||
script.IsLocked,
|
||||
script.LockedInDerived,
|
||||
script.IsInherited,
|
||||
script.TriggerType,
|
||||
script.TriggerConfiguration,
|
||||
script.ParameterDefinitions,
|
||||
script.ReturnDefinition,
|
||||
script.MinTimeBetweenRuns,
|
||||
script.ExecutionTimeoutSeconds,
|
||||
CodeLength = script.Code?.Length ?? 0,
|
||||
CodeSha256 = HashCode(script.Code),
|
||||
};
|
||||
|
||||
/// <summary>A shared script without its body (length + SHA-256 instead).</summary>
|
||||
/// <param name="script">The shared script.</param>
|
||||
/// <returns>The audit after-state object.</returns>
|
||||
internal static object SharedScript(SharedScript script) => new
|
||||
{
|
||||
script.Id,
|
||||
script.Name,
|
||||
script.ParameterDefinitions,
|
||||
script.ReturnDefinition,
|
||||
CodeLength = script.Code?.Length ?? 0,
|
||||
CodeSha256 = HashCode(script.Code),
|
||||
};
|
||||
|
||||
private static string? HashCode(string? code) =>
|
||||
code == null ? null : Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(code)));
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class TemplateService
|
||||
// to the row it described.
|
||||
await _repository.AddTemplateAsync(template, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "Template", template.Id.ToString(), name, template, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "Template", template.Id.ToString(), name, TemplateAuditShapes.Template(template), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<Template>.Success(template);
|
||||
@@ -130,6 +130,12 @@ public class TemplateService
|
||||
"Parent template cannot be changed after creation.");
|
||||
}
|
||||
|
||||
var changedFields = new List<string>();
|
||||
if (!string.Equals(template.Name, name, StringComparison.Ordinal))
|
||||
changedFields.Add(nameof(Template.Name));
|
||||
if (description != null && !string.Equals(template.Description, description, StringComparison.Ordinal))
|
||||
changedFields.Add(nameof(Template.Description));
|
||||
|
||||
template.Name = name;
|
||||
// Leave-unchanged semantics: only overwrite the description when a value is
|
||||
// supplied. null = not provided (keep existing); "" = explicit clear.
|
||||
@@ -142,7 +148,10 @@ public class TemplateService
|
||||
return Result<Template>.Failure(collisionResult);
|
||||
|
||||
await _repository.UpdateTemplateAsync(template, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "Template", templateId.ToString(), name, template, cancellationToken);
|
||||
// Audit a projected summary, never the tracked graph with every script body
|
||||
// (review 2026-10-21 08/G1).
|
||||
await _auditService.LogAsync(user, "Update", "Template", templateId.ToString(), name,
|
||||
TemplateAuditShapes.Template(template, changedFields), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<Template>.Success(template);
|
||||
@@ -234,7 +243,8 @@ public class TemplateService
|
||||
|
||||
template.FolderId = newFolderId;
|
||||
await _repository.UpdateTemplateAsync(template, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Move", "Template", template.Id.ToString(), template.Name, template, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Move", "Template", template.Id.ToString(), template.Name,
|
||||
TemplateAuditShapes.Template(template, [nameof(Template.FolderId)]), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<Template>.Success(template);
|
||||
@@ -723,7 +733,7 @@ public class TemplateService
|
||||
// real script Id rather than a literal "0".
|
||||
await _repository.AddTemplateScriptAsync(script, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "TemplateScript", script.Id.ToString(), script.Name, script, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Create", "TemplateScript", script.Id.ToString(), script.Name, TemplateAuditShapes.Script(script), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Propagate the new member to derived descendants.
|
||||
@@ -809,7 +819,7 @@ public class TemplateService
|
||||
// Name is NOT updated (fixed)
|
||||
|
||||
await _repository.UpdateTemplateScriptAsync(existing, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "TemplateScript", scriptId.ToString(), existing.Name, existing, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "TemplateScript", scriptId.ToString(), existing.Name, TemplateAuditShapes.Script(existing), cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// Propagate the changed effective value to derived descendants.
|
||||
|
||||
Reference in New Issue
Block a user