stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -1,22 +1,12 @@
using StellaOps.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.Audit.ReplayToken;
/// <summary>
/// Generates replay tokens using SHA-256 hashing with deterministic canonicalization.
/// </summary>
public sealed class Sha256ReplayTokenGenerator : IReplayTokenGenerator
public sealed partial class Sha256ReplayTokenGenerator : IReplayTokenGenerator
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
private readonly ICryptoHash _cryptoHash;
private readonly TimeProvider _timeProvider;
@@ -31,7 +21,6 @@ public sealed class Sha256ReplayTokenGenerator : IReplayTokenGenerator
ArgumentNullException.ThrowIfNull(request);
var hashHex = ComputeTokenValue(request, ReplayToken.DefaultVersion);
return new ReplayToken(hashHex, _timeProvider.GetUtcNow());
}
@@ -46,7 +35,6 @@ public sealed class Sha256ReplayTokenGenerator : IReplayTokenGenerator
}
var hashHex = ComputeTokenValue(request, ReplayToken.VersionWithExpiration);
var now = _timeProvider.GetUtcNow();
var expiresAt = now + effectiveExpiration;
@@ -67,14 +55,12 @@ public sealed class Sha256ReplayTokenGenerator : IReplayTokenGenerator
ArgumentNullException.ThrowIfNull(token);
ArgumentNullException.ThrowIfNull(request);
// Check hash first
var computed = ComputeTokenValue(request, token.Version);
if (!string.Equals(token.Value, computed, StringComparison.OrdinalIgnoreCase))
{
return ReplayTokenVerificationResult.Invalid;
}
// Check expiration
if (token.IsExpired(_timeProvider.GetUtcNow()))
{
return ReplayTokenVerificationResult.Expired;
@@ -82,111 +68,4 @@ public sealed class Sha256ReplayTokenGenerator : IReplayTokenGenerator
return ReplayTokenVerificationResult.Valid;
}
private string ComputeHash(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
return _cryptoHash.ComputeHashHex(bytes, HashAlgorithms.Sha256);
}
private string ComputeTokenValue(ReplayTokenRequest request, string version)
{
var canonical = Canonicalize(request, version);
return ComputeHash(canonical);
}
private static string? NormalizeValue(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Trim();
}
private static List<string> NormalizeSortedList(IReadOnlyList<string>? values)
{
if (values is null || values.Count == 0)
{
return new List<string>();
}
var normalized = values
.Where(static x => !string.IsNullOrWhiteSpace(x))
.Select(static x => x.Trim())
.OrderBy(static x => x, StringComparer.Ordinal)
.ToList();
return normalized;
}
private static Dictionary<string, string> NormalizeSortedDictionary(IReadOnlyDictionary<string, string>? values)
{
if (values is null || values.Count == 0)
{
return new Dictionary<string, string>();
}
var normalized = new List<KeyValuePair<string, string>>(values.Count);
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var kvp in values)
{
if (string.IsNullOrWhiteSpace(kvp.Key))
{
continue;
}
var key = kvp.Key.Trim();
if (!seen.Add(key))
{
throw new ArgumentException($"AdditionalContext contains duplicate key after normalization: '{key}'.", nameof(values));
}
normalized.Add(new KeyValuePair<string, string>(key, kvp.Value?.Trim() ?? string.Empty));
}
var ordered = normalized
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
.ToDictionary(static kvp => kvp.Key, static kvp => kvp.Value, StringComparer.Ordinal);
return ordered;
}
/// <summary>
/// Produces deterministic canonical representation of inputs.
/// </summary>
private static string Canonicalize(ReplayTokenRequest request, string version)
{
var canonical = new CanonicalReplayInput
{
Version = version,
FeedManifests = NormalizeSortedList(request.FeedManifests),
RulesVersion = NormalizeValue(request.RulesVersion),
RulesHash = NormalizeValue(request.RulesHash),
LatticePolicyVersion = NormalizeValue(request.LatticePolicyVersion),
LatticePolicyHash = NormalizeValue(request.LatticePolicyHash),
InputHashes = NormalizeSortedList(request.InputHashes),
ScoringConfigVersion = NormalizeValue(request.ScoringConfigVersion),
EvidenceHashes = NormalizeSortedList(request.EvidenceHashes),
AdditionalContext = NormalizeSortedDictionary(request.AdditionalContext)
};
return JsonSerializer.Serialize(canonical, JsonOptions);
}
private sealed class CanonicalReplayInput
{
public required string Version { get; init; }
public required List<string> FeedManifests { get; init; }
public string? RulesVersion { get; init; }
public string? RulesHash { get; init; }
public string? LatticePolicyVersion { get; init; }
public string? LatticePolicyHash { get; init; }
public required List<string> InputHashes { get; init; }
public string? ScoringConfigVersion { get; init; }
public required List<string> EvidenceHashes { get; init; }
public required Dictionary<string, string> AdditionalContext { get; init; }
}
}