feat(mgmt): /api/audit/{query,export} endpoints with permission gates (#23 M8)

This commit is contained in:
Joseph Doherty
2026-05-20 21:49:14 -04:00
parent 263884fa63
commit a1bdd94d4c
7 changed files with 968 additions and 4 deletions
@@ -86,14 +86,25 @@ public static class AuthorizationPolicies
/// Roles that satisfy <see cref="OperationalAudit"/>. Held in one place
/// so the seed/docs and the policy stay in lockstep.
/// </summary>
internal static readonly string[] OperationalAuditRoles = { "Admin", "Audit", "AuditReadOnly" };
/// <remarks>
/// Public so the ManagementService HTTP API (#23 M8) — which gates the
/// <c>/api/audit/*</c> routes with a manual Basic-Auth + LDAP role check
/// rather than the ASP.NET authorization-policy pipeline — can reuse the
/// exact same role set the <see cref="OperationalAudit"/> policy enforces.
/// </remarks>
public static readonly string[] OperationalAuditRoles = { "Admin", "Audit", "AuditReadOnly" };
/// <summary>
/// Roles that satisfy <see cref="AuditExport"/>. A strict subset of
/// <see cref="OperationalAuditRoles"/> — read access does NOT imply
/// export permission.
/// </summary>
internal static readonly string[] AuditExportRoles = { "Admin", "Audit" };
/// <remarks>
/// Public for the same reason as <see cref="OperationalAuditRoles"/> —
/// the ManagementService <c>/api/audit/export</c> route checks roles
/// against this set directly.
/// </remarks>
public static readonly string[] AuditExportRoles = { "Admin", "Audit" };
public static IServiceCollection AddScadaLinkAuthorization(this IServiceCollection services)
{
+3 -1
View File
@@ -15,7 +15,9 @@ public class LdapAuthService
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
// virtual: a test seam so HTTP-pipeline tests (e.g. the #23 M8 audit
// endpoints) can substitute the LDAP bind without standing up a directory.
public virtual async Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(username))
return new LdapAuthResult(false, null, null, null, "Username is required.");
+3 -1
View File
@@ -11,7 +11,9 @@ public class RoleMapper
_securityRepository = securityRepository ?? throw new ArgumentNullException(nameof(securityRepository));
}
public async Task<RoleMappingResult> MapGroupsToRolesAsync(
// virtual: a test seam so HTTP-pipeline tests (e.g. the #23 M8 audit
// endpoints) can substitute the LDAP-group→role resolution.
public virtual async Task<RoleMappingResult> MapGroupsToRolesAsync(
IReadOnlyList<string> ldapGroups,
CancellationToken ct = default)
{