feat(application): PermisosOverride record + PermisoResolver static helper [UDT-009]

This commit is contained in:
2026-04-15 21:25:09 -03:00
parent be86c2fac9
commit da1eb83ac1
4 changed files with 339 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
namespace SIGCM2.Application.Common;
/// <summary>
/// UDT-009: Resolves effective permissions as (rolPermisos grant) \ deny.
/// Static helper — no dependencies, pure algorithm, freely testable.
/// </summary>
public static class PermisoResolver
{
/// <summary>
/// Returns the effective permission set for a user.
/// Algorithm: start with role permissions, add grant, remove deny.
/// Deny always wins over grant (last operation). Idempotent on duplicates.
/// Never throws.
/// </summary>
public static IReadOnlySet<string> Resolve(
IEnumerable<string> rolPermisos,
PermisosOverride overrides)
{
var set = new HashSet<string>(rolPermisos, StringComparer.Ordinal);
foreach (var g in overrides.Grant)
set.Add(g);
foreach (var d in overrides.Deny)
set.Remove(d);
return set;
}
}

View File

@@ -0,0 +1,60 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SIGCM2.Application.Common;
/// <summary>
/// UDT-009: Overrides explícitos sobre permisos heredados del rol.
/// Shape: { "grant": [...], "deny": [...] }
/// </summary>
public sealed record PermisosOverride(
[property: JsonPropertyName("grant")] IReadOnlyList<string> Grant,
[property: JsonPropertyName("deny")] IReadOnlyList<string> Deny)
{
/// <summary>No overrides — empty grant and deny.</summary>
public static readonly PermisosOverride Empty =
new(Array.Empty<string>(), Array.Empty<string>());
private static readonly JsonSerializerOptions Options = new()
{
PropertyNameCaseInsensitive = true,
};
/// <summary>
/// Parses <paramref name="json"/> tolerantly:
/// - null / "" / whitespace → Empty
/// - starts with '[' (legacy '[]' or '["*"]') → Empty (backward compat)
/// - valid JSON object with grant/deny → parsed record
/// - malformed or wrong-shape JSON → Empty (tolerant in runtime)
/// </summary>
public static PermisosOverride FromJson(string? json)
{
if (string.IsNullOrWhiteSpace(json))
return Empty;
var trimmed = json.Trim();
// Legacy: '[]' or '["*"]' — array shape, treat as no overrides
if (trimmed.StartsWith('['))
return Empty;
try
{
var parsed = JsonSerializer.Deserialize<PermisosOverride>(trimmed, Options);
if (parsed is null)
return Empty;
return new PermisosOverride(
parsed.Grant ?? Array.Empty<string>(),
parsed.Deny ?? Array.Empty<string>());
}
catch (JsonException)
{
// Tolerant: malformed JSON → Empty (protects authorization handler)
return Empty;
}
}
/// <summary>Serializes to canonical JSON shape.</summary>
public string ToJson() => JsonSerializer.Serialize(this, Options);
}