feat: Document completed tasks for KMS, Cryptography, and Plugin Libraries

- Added detailed task completion records for KMS interface implementation and CLI support for file-based keys.
- Documented security enhancements including Argon2id password hashing, audit event contracts, and rate limiting configurations.
- Included scoped service support and integration updates for the Plugin platform, ensuring proper DI handling and testing coverage.
This commit is contained in:
master
2025-10-31 14:37:45 +02:00
parent 240e8ff25d
commit 15b4a1de6a
312 changed files with 6399 additions and 3319 deletions

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using HttpResults = Microsoft.AspNetCore.Http.Results;
using StellaOps.Aoc;
namespace StellaOps.Aoc.AspNetCore.Results;
/// <summary>
/// Helpers for emitting Aggregation-Only Contract error responses.
/// </summary>
public static class AocHttpResults
{
private const string DefaultProblemType = "https://stella-ops.org/problems/aoc-violation";
/// <summary>
/// Converts an <see cref="AocGuardException"/> into a RFC 7807 problem response.
/// </summary>
/// <param name="httpContext">The current HTTP context.</param>
/// <param name="exception">The guard exception.</param>
/// <param name="title">Optional problem title.</param>
/// <param name="detail">Optional problem detail.</param>
/// <param name="type">Optional problem type URI.</param>
/// <param name="status">Optional HTTP status code override.</param>
/// <param name="extensions">Optional extension payload merged with guard details.</param>
/// <returns>An HTTP result representing the problem response.</returns>
public static IResult Problem(
HttpContext httpContext,
AocGuardException exception,
string? title = null,
string? detail = null,
string? type = null,
int? status = null,
IDictionary<string, object?>? extensions = null)
{
if (httpContext is null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (exception is null)
{
throw new ArgumentNullException(nameof(exception));
}
var primaryCode = exception.Result.Violations.IsDefaultOrEmpty
? "ERR_AOC_000"
: exception.Result.Violations[0].ErrorCode;
var violationPayload = exception.Result.Violations
.Select(v => new Dictionary<string, object?>(StringComparer.Ordinal)
{
["code"] = v.ErrorCode,
["path"] = v.Path,
["message"] = v.Message
})
.ToArray();
var extensionPayload = new Dictionary<string, object?>(StringComparer.Ordinal)
{
["code"] = primaryCode,
["violations"] = violationPayload
};
if (extensions is not null)
{
foreach (var kvp in extensions)
{
extensionPayload[kvp.Key] = kvp.Value;
}
}
var statusCode = status ?? MapErrorCodeToStatus(primaryCode);
var problemType = type ?? DefaultProblemType;
var problemDetail = detail ?? $"AOC guard rejected the request with {primaryCode}.";
var problemTitle = title ?? "Aggregation-Only Contract violation";
return HttpResults.Problem(
statusCode: statusCode,
title: problemTitle,
detail: problemDetail,
type: problemType,
extensions: extensionPayload);
}
private static int MapErrorCodeToStatus(string errorCode) => errorCode switch
{
"ERR_AOC_003" => StatusCodes.Status409Conflict,
"ERR_AOC_004" => StatusCodes.Status422UnprocessableEntity,
"ERR_AOC_005" => StatusCodes.Status422UnprocessableEntity,
"ERR_AOC_006" => StatusCodes.Status403Forbidden,
_ => StatusCodes.Status400BadRequest,
};
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StellaOps.Aoc;
namespace StellaOps.Aoc.AspNetCore.Routing;
public sealed class AocGuardEndpointFilter<TRequest> : IEndpointFilter
{
private readonly Func<TRequest, IEnumerable<object?>> _payloadSelector;
private readonly JsonSerializerOptions _serializerOptions;
private readonly AocGuardOptions? _guardOptions;
public AocGuardEndpointFilter(
Func<TRequest, IEnumerable<object?>> payloadSelector,
JsonSerializerOptions? serializerOptions,
AocGuardOptions? guardOptions)
{
_payloadSelector = payloadSelector ?? throw new ArgumentNullException(nameof(payloadSelector));
_serializerOptions = serializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
_guardOptions = guardOptions;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (TryGetArgument(context, out var request))
{
var payloads = _payloadSelector(request);
if (payloads is not null)
{
var guard = context.HttpContext.RequestServices.GetRequiredService<IAocGuard>();
var options = ResolveOptions(context.HttpContext.RequestServices);
foreach (var payload in payloads)
{
if (payload is null)
{
continue;
}
JsonElement element = payload switch
{
JsonElement jsonElement => jsonElement,
JsonDocument jsonDocument => jsonDocument.RootElement,
_ => JsonSerializer.SerializeToElement(payload, _serializerOptions)
};
guard.ValidateOrThrow(element, options);
}
}
}
return await next(context).ConfigureAwait(false);
}
private AocGuardOptions ResolveOptions(IServiceProvider services)
{
if (_guardOptions is not null)
{
return _guardOptions;
}
var options = services.GetService<IOptions<AocGuardOptions>>();
return options?.Value ?? AocGuardOptions.Default;
}
private static bool TryGetArgument(EndpointFilterInvocationContext context, out TRequest argument)
{
for (var i = 0; i < context.Arguments.Count; i++)
{
if (context.Arguments[i] is TRequest typedArgument)
{
argument = typedArgument;
return true;
}
}
argument = default!;
return false;
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Aoc\StellaOps.Aoc.csproj" />
</ItemGroup>
</Project>