// SPDX-License-Identifier: BUSL-1.1 // Copyright (c) 2025 StellaOps // Sprint: SPRINT_20260122_041_Policy_interop_import_export_rego // Task: TASK-07 - Platform API Endpoints using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Constants; using StellaOps.Platform.WebService.Contracts; using StellaOps.Platform.WebService.Services; namespace StellaOps.Platform.WebService.Endpoints; /// /// Policy import/export interop API endpoints. /// Provides bidirectional policy exchange between JSON (PolicyPack v2) and OPA/Rego formats. /// public static class PolicyInteropEndpoints { /// /// Maps policy interop endpoints under /api/v1/policy/interop. /// public static IEndpointRouteBuilder MapPolicyInteropEndpoints(this IEndpointRouteBuilder app) { var interop = app.MapGroup("/api/v1/policy/interop") .WithTags("PolicyInterop") .RequireAuthorization(PlatformPolicies.PolicyRead) .RequireTenant(); MapExportEndpoint(interop); MapImportEndpoint(interop); MapValidateEndpoint(interop); MapEvaluateEndpoint(interop); MapFormatsEndpoint(interop); return app; } private static void MapExportEndpoint(IEndpointRouteBuilder group) { // POST /api/v1/policy/interop/export group.MapPost("/export", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IPolicyInteropService service, [FromBody] PolicyExportApiRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.ExportAsync( requestContext!, request, cancellationToken).ConfigureAwait(false); if (!result.Success) { return Results.BadRequest(new { error = "export_failed", diagnostics = result.Diagnostics }); } return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, DateTimeOffset.UtcNow, false, 0, result)); }) .WithName("ExportPolicy") .WithSummary("Export policy to format") .WithDescription("Exports a PolicyPack v2 document to JSON or OPA/Rego format with optional environment-specific thresholds and remediation hints.") .RequireAuthorization(PlatformPolicies.PolicyRead); // POST /api/v1/policy/interop/import } private static void MapImportEndpoint(IEndpointRouteBuilder group) { group.MapPost("/import", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IPolicyInteropService service, [FromBody] PolicyImportApiRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.ImportAsync( requestContext!, request, cancellationToken).ConfigureAwait(false); if (!result.Success) { return Results.BadRequest(new { error = "import_failed", diagnostics = result.Diagnostics }); } return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, DateTimeOffset.UtcNow, false, 0, result)); }) .WithName("ImportPolicy") .WithSummary("Import policy from format") .WithDescription("Imports a policy from JSON or OPA/Rego format into the native PolicyPack v2 model. Unknown Rego patterns are preserved for OPA evaluation.") .RequireAuthorization(PlatformPolicies.PolicyWrite); } private static void MapValidateEndpoint(IEndpointRouteBuilder group) { // POST /api/v1/policy/interop/validate group.MapPost("/validate", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IPolicyInteropService service, [FromBody] PolicyValidateApiRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.ValidateAsync( requestContext!, request, cancellationToken).ConfigureAwait(false); return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, DateTimeOffset.UtcNow, false, 0, result)); }) .WithName("ValidatePolicy") .WithSummary("Validate policy document") .WithDescription("Validates a policy document against the PolicyPack v2 schema or checks Rego syntax via embedded OPA.") .RequireAuthorization(PlatformPolicies.PolicyRead); } private static void MapEvaluateEndpoint(IEndpointRouteBuilder group) { // POST /api/v1/policy/interop/evaluate group.MapPost("/evaluate", async Task ( HttpContext context, PlatformRequestContextResolver resolver, IPolicyInteropService service, [FromBody] PolicyEvaluateApiRequest request, CancellationToken cancellationToken) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var result = await service.EvaluateAsync( requestContext!, request, cancellationToken).ConfigureAwait(false); var statusCode = result.Decision switch { "allow" => StatusCodes.Status200OK, "warn" => StatusCodes.Status200OK, "block" => StatusCodes.Status200OK, _ => StatusCodes.Status200OK }; return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, DateTimeOffset.UtcNow, false, 0, result)); }) .WithName("EvaluatePolicy") .WithSummary("Evaluate policy against input") .WithDescription("Evaluates a policy (JSON or Rego) against evidence input and returns allow/warn/block decision with remediation hints.") .RequireAuthorization(PlatformPolicies.PolicyEvaluate); } private static void MapFormatsEndpoint(IEndpointRouteBuilder group) { // GET /api/v1/policy/interop/formats group.MapGet("/formats", ( HttpContext context, PlatformRequestContextResolver resolver) => { if (!TryResolveContext(context, resolver, out var requestContext, out var failure)) { return failure!; } var formats = new PolicyFormatsApiResponse { Formats = [ new PolicyFormatInfo("json", "PolicyPack v2 (JSON)", "policy.stellaops.io/v2", true, true), new PolicyFormatInfo("rego", "OPA/Rego", "package stella.release", true, true) ] }; return Results.Ok(new PlatformItemResponse( requestContext!.TenantId, requestContext.ActorId, DateTimeOffset.UtcNow, true, 3600, formats)); }) .WithName("ListPolicyFormats") .WithSummary("List supported policy formats") .WithDescription("Returns the list of supported policy import/export formats.") .RequireAuthorization(PlatformPolicies.PolicyRead); } private static bool TryResolveContext( HttpContext context, PlatformRequestContextResolver resolver, out PlatformRequestContext? requestContext, out IResult? failure) { if (resolver.TryResolve(context, out requestContext, out var error)) { failure = null; return true; } failure = Results.BadRequest(new { error = error ?? "tenant_missing" }); return false; } }