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(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest); group.MapGet("/{overrideId}", GetOverride) .WithName("GetOverride") .WithSummary("Get an override by ID.") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound); group.MapDelete("/{overrideId}", DeleteOverride) .WithName("DeleteOverride") .WithSummary("Delete an override.") .Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status404NotFound); group.MapGet("/profile/{profileId}", ListProfileOverrides) .WithName("ListProfileOverrides") .WithSummary("List all overrides for a risk profile.") .Produces(StatusCodes.Status200OK); group.MapPost("/validate", ValidateOverride) .WithName("ValidateOverride") .WithSummary("Validate an override for conflicts before creating.") .Produces(StatusCodes.Status200OK); group.MapPost("/{overrideId}:approve", ApproveOverride) .WithName("ApproveOverride") .WithSummary("Approve an override that requires review.") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound); group.MapPost("/{overrideId}:disable", DisableOverride) .WithName("DisableOverride") .WithSummary("Disable an active override.") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound); group.MapGet("/{overrideId}/history", GetOverrideHistory) .WithName("GetOverrideHistory") .WithSummary("Get application history for an override.") .Produces(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? Warnings); internal sealed record OverrideListResponse( string ProfileId, IReadOnlyList Overrides); internal sealed record OverrideValidationResponse(OverrideConflictValidation Validation); internal sealed record OverrideHistoryResponse( string OverrideId, IReadOnlyList History); #endregion