Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Endpoints/OverrideEndpoints.cs
StellaOps Bot 3b96b2e3ea
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
up
2025-11-27 23:45:09 +02:00

361 lines
12 KiB
C#

using System.Security.Claims;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.RiskProfile.Overrides;
namespace StellaOps.Policy.Engine.Endpoints;
internal static class OverrideEndpoints
{
public static IEndpointRouteBuilder MapOverrides(this IEndpointRouteBuilder endpoints)
{
var group = endpoints.MapGroup("/api/risk/overrides")
.RequireAuthorization()
.WithTags("Risk Overrides");
group.MapPost("/", CreateOverride)
.WithName("CreateOverride")
.WithSummary("Create a new override with audit metadata.")
.Produces<OverrideResponse>(StatusCodes.Status201Created)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest);
group.MapGet("/{overrideId}", GetOverride)
.WithName("GetOverride")
.WithSummary("Get an override by ID.")
.Produces<OverrideResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapDelete("/{overrideId}", DeleteOverride)
.WithName("DeleteOverride")
.WithSummary("Delete an override.")
.Produces(StatusCodes.Status204NoContent)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/profile/{profileId}", ListProfileOverrides)
.WithName("ListProfileOverrides")
.WithSummary("List all overrides for a risk profile.")
.Produces<OverrideListResponse>(StatusCodes.Status200OK);
group.MapPost("/validate", ValidateOverride)
.WithName("ValidateOverride")
.WithSummary("Validate an override for conflicts before creating.")
.Produces<OverrideValidationResponse>(StatusCodes.Status200OK);
group.MapPost("/{overrideId}:approve", ApproveOverride)
.WithName("ApproveOverride")
.WithSummary("Approve an override that requires review.")
.Produces<OverrideResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status400BadRequest)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapPost("/{overrideId}:disable", DisableOverride)
.WithName("DisableOverride")
.WithSummary("Disable an active override.")
.Produces<OverrideResponse>(StatusCodes.Status200OK)
.Produces<ProblemHttpResult>(StatusCodes.Status404NotFound);
group.MapGet("/{overrideId}/history", GetOverrideHistory)
.WithName("GetOverrideHistory")
.WithSummary("Get application history for an override.")
.Produces<OverrideHistoryResponse>(StatusCodes.Status200OK);
return endpoints;
}
private static IResult CreateOverride(
HttpContext context,
[FromBody] CreateOverrideRequest request,
OverrideService overrideService,
RiskProfileConfigurationService profileService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
if (scopeResult is not null)
{
return scopeResult;
}
if (request == null || string.IsNullOrWhiteSpace(request.ProfileId))
{
return Results.BadRequest(new ProblemDetails
{
Title = "Invalid request",
Detail = "ProfileId is required.",
Status = StatusCodes.Status400BadRequest
});
}
if (string.IsNullOrWhiteSpace(request.Reason))
{
return Results.BadRequest(new ProblemDetails
{
Title = "Invalid request",
Detail = "Reason is required for audit purposes.",
Status = StatusCodes.Status400BadRequest
});
}
// Verify profile exists
var profile = profileService.GetProfile(request.ProfileId);
if (profile == null)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Profile not found",
Detail = $"Risk profile '{request.ProfileId}' was not found.",
Status = StatusCodes.Status400BadRequest
});
}
// Validate for conflicts
var validation = overrideService.ValidateConflicts(request);
if (validation.HasConflicts)
{
var conflictDetails = string.Join("; ", validation.Conflicts.Select(c => c.Description));
return Results.BadRequest(new ProblemDetails
{
Title = "Override conflicts detected",
Detail = conflictDetails,
Status = StatusCodes.Status400BadRequest,
Extensions = { ["conflicts"] = validation.Conflicts }
});
}
var actorId = ResolveActorId(context);
try
{
var auditedOverride = overrideService.Create(request, actorId);
return Results.Created(
$"/api/risk/overrides/{auditedOverride.OverrideId}",
new OverrideResponse(auditedOverride, validation.Warnings));
}
catch (ArgumentException ex)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Invalid request",
Detail = ex.Message,
Status = StatusCodes.Status400BadRequest
});
}
}
private static IResult GetOverride(
HttpContext context,
[FromRoute] string overrideId,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
if (scopeResult is not null)
{
return scopeResult;
}
var auditedOverride = overrideService.Get(overrideId);
if (auditedOverride == null)
{
return Results.NotFound(new ProblemDetails
{
Title = "Override not found",
Detail = $"Override '{overrideId}' was not found.",
Status = StatusCodes.Status404NotFound
});
}
return Results.Ok(new OverrideResponse(auditedOverride, null));
}
private static IResult DeleteOverride(
HttpContext context,
[FromRoute] string overrideId,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
if (scopeResult is not null)
{
return scopeResult;
}
if (!overrideService.Delete(overrideId))
{
return Results.NotFound(new ProblemDetails
{
Title = "Override not found",
Detail = $"Override '{overrideId}' was not found.",
Status = StatusCodes.Status404NotFound
});
}
return Results.NoContent();
}
private static IResult ListProfileOverrides(
HttpContext context,
[FromRoute] string profileId,
[FromQuery] bool includeInactive,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
if (scopeResult is not null)
{
return scopeResult;
}
var overrides = overrideService.ListByProfile(profileId, includeInactive);
return Results.Ok(new OverrideListResponse(profileId, overrides));
}
private static IResult ValidateOverride(
HttpContext context,
[FromBody] CreateOverrideRequest request,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
if (scopeResult is not null)
{
return scopeResult;
}
if (request == null)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Invalid request",
Detail = "Request body is required.",
Status = StatusCodes.Status400BadRequest
});
}
var validation = overrideService.ValidateConflicts(request);
return Results.Ok(new OverrideValidationResponse(validation));
}
private static IResult ApproveOverride(
HttpContext context,
[FromRoute] string overrideId,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyActivate);
if (scopeResult is not null)
{
return scopeResult;
}
var actorId = ResolveActorId(context);
try
{
var auditedOverride = overrideService.Approve(overrideId, actorId ?? "system");
if (auditedOverride == null)
{
return Results.NotFound(new ProblemDetails
{
Title = "Override not found",
Detail = $"Override '{overrideId}' was not found.",
Status = StatusCodes.Status404NotFound
});
}
return Results.Ok(new OverrideResponse(auditedOverride, null));
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(new ProblemDetails
{
Title = "Approval failed",
Detail = ex.Message,
Status = StatusCodes.Status400BadRequest
});
}
}
private static IResult DisableOverride(
HttpContext context,
[FromRoute] string overrideId,
[FromQuery] string? reason,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyEdit);
if (scopeResult is not null)
{
return scopeResult;
}
var actorId = ResolveActorId(context);
var auditedOverride = overrideService.Disable(overrideId, actorId ?? "system", reason);
if (auditedOverride == null)
{
return Results.NotFound(new ProblemDetails
{
Title = "Override not found",
Detail = $"Override '{overrideId}' was not found.",
Status = StatusCodes.Status404NotFound
});
}
return Results.Ok(new OverrideResponse(auditedOverride, null));
}
private static IResult GetOverrideHistory(
HttpContext context,
[FromRoute] string overrideId,
[FromQuery] int limit,
OverrideService overrideService)
{
var scopeResult = ScopeAuthorization.RequireScope(context, StellaOpsScopes.PolicyRead);
if (scopeResult is not null)
{
return scopeResult;
}
var effectiveLimit = limit > 0 ? limit : 100;
var history = overrideService.GetApplicationHistory(overrideId, effectiveLimit);
return Results.Ok(new OverrideHistoryResponse(overrideId, history));
}
private static string? ResolveActorId(HttpContext context)
{
var user = context.User;
var actor = user?.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? user?.FindFirst(ClaimTypes.Upn)?.Value
?? user?.FindFirst("sub")?.Value;
if (!string.IsNullOrWhiteSpace(actor))
{
return actor;
}
if (context.Request.Headers.TryGetValue("X-StellaOps-Actor", out var header) && !string.IsNullOrWhiteSpace(header))
{
return header.ToString();
}
return null;
}
}
#region Response DTOs
internal sealed record OverrideResponse(
AuditedOverride Override,
IReadOnlyList<string>? Warnings);
internal sealed record OverrideListResponse(
string ProfileId,
IReadOnlyList<AuditedOverride> Overrides);
internal sealed record OverrideValidationResponse(OverrideConflictValidation Validation);
internal sealed record OverrideHistoryResponse(
string OverrideId,
IReadOnlyList<OverrideApplicationRecord> History);
#endregion