finish secrets finding work and audit remarks work save
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SecretDetectionSettingsEndpoints.cs
|
||||
// Sprint: SPRINT_20260104_006_BE (Secret Detection Configuration API)
|
||||
// Task: SDC-005 - Create Settings CRUD API endpoints
|
||||
// Description: HTTP endpoints for secret detection configuration.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Security;
|
||||
using StellaOps.Scanner.WebService.Services;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Endpoints for secret detection configuration.
|
||||
/// Per SPRINT_20260104_006_BE.
|
||||
/// </summary>
|
||||
internal static class SecretDetectionSettingsEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps secret detection settings endpoints.
|
||||
/// </summary>
|
||||
public static void MapSecretDetectionSettingsEndpoints(this RouteGroupBuilder apiGroup, string prefix = "/secrets/config")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(apiGroup);
|
||||
|
||||
var settings = apiGroup.MapGroup($"{prefix}/settings")
|
||||
.WithTags("Secret Detection Settings");
|
||||
|
||||
var exceptions = apiGroup.MapGroup($"{prefix}/exceptions")
|
||||
.WithTags("Secret Detection Exceptions");
|
||||
|
||||
var rules = apiGroup.MapGroup($"{prefix}/rules")
|
||||
.WithTags("Secret Detection Rules");
|
||||
|
||||
// ====================================================================
|
||||
// Settings Endpoints
|
||||
// ====================================================================
|
||||
|
||||
// GET /v1/secrets/config/settings/{tenantId} - Get settings
|
||||
settings.MapGet("/{tenantId:guid}", HandleGetSettingsAsync)
|
||||
.WithName("scanner.secrets.settings.get")
|
||||
.WithDescription("Get secret detection settings for a tenant.")
|
||||
.Produces<SecretDetectionSettingsResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.SecretSettingsRead);
|
||||
|
||||
// POST /v1/secrets/config/settings/{tenantId} - Create default settings
|
||||
settings.MapPost("/{tenantId:guid}", HandleCreateSettingsAsync)
|
||||
.WithName("scanner.secrets.settings.create")
|
||||
.WithDescription("Create default secret detection settings for a tenant.")
|
||||
.Produces<SecretDetectionSettingsResponseDto>(StatusCodes.Status201Created)
|
||||
.Produces(StatusCodes.Status409Conflict)
|
||||
.RequireAuthorization(ScannerPolicies.SecretSettingsWrite);
|
||||
|
||||
// PUT /v1/secrets/config/settings/{tenantId} - Update settings
|
||||
settings.MapPut("/{tenantId:guid}", HandleUpdateSettingsAsync)
|
||||
.WithName("scanner.secrets.settings.update")
|
||||
.WithDescription("Update secret detection settings for a tenant.")
|
||||
.Produces<SecretDetectionSettingsResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict)
|
||||
.RequireAuthorization(ScannerPolicies.SecretSettingsWrite);
|
||||
|
||||
// ====================================================================
|
||||
// Exception Pattern Endpoints
|
||||
// ====================================================================
|
||||
|
||||
// GET /v1/secrets/config/exceptions/{tenantId} - List exception patterns
|
||||
exceptions.MapGet("/{tenantId:guid}", HandleListExceptionsAsync)
|
||||
.WithName("scanner.secrets.exceptions.list")
|
||||
.WithDescription("List secret exception patterns for a tenant.")
|
||||
.Produces<SecretExceptionPatternListResponseDto>(StatusCodes.Status200OK)
|
||||
.RequireAuthorization(ScannerPolicies.SecretExceptionsRead);
|
||||
|
||||
// GET /v1/secrets/config/exceptions/{tenantId}/{exceptionId} - Get exception pattern
|
||||
exceptions.MapGet("/{tenantId:guid}/{exceptionId:guid}", HandleGetExceptionAsync)
|
||||
.WithName("scanner.secrets.exceptions.get")
|
||||
.WithDescription("Get a specific secret exception pattern.")
|
||||
.Produces<SecretExceptionPatternResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.SecretExceptionsRead);
|
||||
|
||||
// POST /v1/secrets/config/exceptions/{tenantId} - Create exception pattern
|
||||
exceptions.MapPost("/{tenantId:guid}", HandleCreateExceptionAsync)
|
||||
.WithName("scanner.secrets.exceptions.create")
|
||||
.WithDescription("Create a new secret exception pattern.")
|
||||
.Produces<SecretExceptionPatternResponseDto>(StatusCodes.Status201Created)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization(ScannerPolicies.SecretExceptionsWrite);
|
||||
|
||||
// PUT /v1/secrets/config/exceptions/{tenantId}/{exceptionId} - Update exception pattern
|
||||
exceptions.MapPut("/{tenantId:guid}/{exceptionId:guid}", HandleUpdateExceptionAsync)
|
||||
.WithName("scanner.secrets.exceptions.update")
|
||||
.WithDescription("Update a secret exception pattern.")
|
||||
.Produces<SecretExceptionPatternResponseDto>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.SecretExceptionsWrite);
|
||||
|
||||
// DELETE /v1/secrets/config/exceptions/{tenantId}/{exceptionId} - Delete exception pattern
|
||||
exceptions.MapDelete("/{tenantId:guid}/{exceptionId:guid}", HandleDeleteExceptionAsync)
|
||||
.WithName("scanner.secrets.exceptions.delete")
|
||||
.WithDescription("Delete a secret exception pattern.")
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.RequireAuthorization(ScannerPolicies.SecretExceptionsWrite);
|
||||
|
||||
// ====================================================================
|
||||
// Rule Catalog Endpoints
|
||||
// ====================================================================
|
||||
|
||||
// GET /v1/secrets/config/rules/categories - Get available rule categories
|
||||
rules.MapGet("/categories", HandleGetRuleCategoriesAsync)
|
||||
.WithName("scanner.secrets.rules.categories")
|
||||
.WithDescription("Get available secret detection rule categories.")
|
||||
.Produces<RuleCategoriesResponseDto>(StatusCodes.Status200OK)
|
||||
.RequireAuthorization(ScannerPolicies.SecretSettingsRead);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Settings Handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<IResult> HandleGetSettingsAsync(
|
||||
Guid tenantId,
|
||||
ISecretDetectionSettingsService service,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await service.GetSettingsAsync(tenantId, cancellationToken);
|
||||
|
||||
if (settings is null)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Settings not found",
|
||||
detail = $"No secret detection settings found for tenant '{tenantId}'."
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(settings);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleCreateSettingsAsync(
|
||||
Guid tenantId,
|
||||
ISecretDetectionSettingsService service,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Check if settings already exist
|
||||
var existing = await service.GetSettingsAsync(tenantId, cancellationToken);
|
||||
if (existing is not null)
|
||||
{
|
||||
return Results.Conflict(new
|
||||
{
|
||||
type = "conflict",
|
||||
title = "Settings already exist",
|
||||
detail = $"Secret detection settings already exist for tenant '{tenantId}'."
|
||||
});
|
||||
}
|
||||
|
||||
var username = context.User.Identity?.Name ?? "system";
|
||||
var settings = await service.CreateSettingsAsync(tenantId, username, cancellationToken);
|
||||
|
||||
return Results.Created($"/v1/secrets/config/settings/{tenantId}", settings);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleUpdateSettingsAsync(
|
||||
Guid tenantId,
|
||||
UpdateSecretDetectionSettingsRequestDto request,
|
||||
ISecretDetectionSettingsService service,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var username = context.User.Identity?.Name ?? "system";
|
||||
var (success, settings, error) = await service.UpdateSettingsAsync(
|
||||
tenantId,
|
||||
request.Settings,
|
||||
request.ExpectedVersion,
|
||||
username,
|
||||
cancellationToken);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (error?.Contains("not found", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Settings not found",
|
||||
detail = error
|
||||
});
|
||||
}
|
||||
|
||||
if (error?.Contains("conflict", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
return Results.Conflict(new
|
||||
{
|
||||
type = "conflict",
|
||||
title = "Version conflict",
|
||||
detail = error
|
||||
});
|
||||
}
|
||||
|
||||
return Results.BadRequest(new
|
||||
{
|
||||
type = "validation-error",
|
||||
title = "Validation failed",
|
||||
detail = error
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(settings);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Exception Pattern Handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<IResult> HandleListExceptionsAsync(
|
||||
Guid tenantId,
|
||||
ISecretExceptionPatternService service,
|
||||
bool includeInactive = false,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var patterns = await service.GetPatternsAsync(tenantId, includeInactive, cancellationToken);
|
||||
return Results.Ok(patterns);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleGetExceptionAsync(
|
||||
Guid tenantId,
|
||||
Guid exceptionId,
|
||||
ISecretExceptionPatternService service,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var pattern = await service.GetPatternAsync(exceptionId, cancellationToken);
|
||||
|
||||
if (pattern is null || pattern.TenantId != tenantId)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Exception pattern not found",
|
||||
detail = $"No exception pattern found with ID '{exceptionId}'."
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(pattern);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleCreateExceptionAsync(
|
||||
Guid tenantId,
|
||||
SecretExceptionPatternDto request,
|
||||
ISecretExceptionPatternService service,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var username = context.User.Identity?.Name ?? "system";
|
||||
var (pattern, errors) = await service.CreatePatternAsync(tenantId, request, username, cancellationToken);
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return Results.BadRequest(new
|
||||
{
|
||||
type = "validation-error",
|
||||
title = "Validation failed",
|
||||
detail = string.Join("; ", errors),
|
||||
errors
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Created($"/v1/secrets/config/exceptions/{tenantId}/{pattern!.Id}", pattern);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleUpdateExceptionAsync(
|
||||
Guid tenantId,
|
||||
Guid exceptionId,
|
||||
SecretExceptionPatternDto request,
|
||||
ISecretExceptionPatternService service,
|
||||
HttpContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Verify pattern belongs to tenant
|
||||
var existing = await service.GetPatternAsync(exceptionId, cancellationToken);
|
||||
if (existing is null || existing.TenantId != tenantId)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Exception pattern not found",
|
||||
detail = $"No exception pattern found with ID '{exceptionId}'."
|
||||
});
|
||||
}
|
||||
|
||||
var username = context.User.Identity?.Name ?? "system";
|
||||
var (success, pattern, errors) = await service.UpdatePatternAsync(
|
||||
exceptionId,
|
||||
request,
|
||||
username,
|
||||
cancellationToken);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (errors.Count > 0 && errors[0].Contains("not found", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Exception pattern not found",
|
||||
detail = errors[0]
|
||||
});
|
||||
}
|
||||
|
||||
return Results.BadRequest(new
|
||||
{
|
||||
type = "validation-error",
|
||||
title = "Validation failed",
|
||||
detail = string.Join("; ", errors),
|
||||
errors
|
||||
});
|
||||
}
|
||||
|
||||
return Results.Ok(pattern);
|
||||
}
|
||||
|
||||
private static async Task<IResult> HandleDeleteExceptionAsync(
|
||||
Guid tenantId,
|
||||
Guid exceptionId,
|
||||
ISecretExceptionPatternService service,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Verify pattern belongs to tenant
|
||||
var existing = await service.GetPatternAsync(exceptionId, cancellationToken);
|
||||
if (existing is null || existing.TenantId != tenantId)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Exception pattern not found",
|
||||
detail = $"No exception pattern found with ID '{exceptionId}'."
|
||||
});
|
||||
}
|
||||
|
||||
var deleted = await service.DeletePatternAsync(exceptionId, cancellationToken);
|
||||
if (!deleted)
|
||||
{
|
||||
return Results.NotFound(new
|
||||
{
|
||||
type = "not-found",
|
||||
title = "Exception pattern not found",
|
||||
detail = $"No exception pattern found with ID '{exceptionId}'."
|
||||
});
|
||||
}
|
||||
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Rule Catalog Handlers
|
||||
// ========================================================================
|
||||
|
||||
private static async Task<IResult> HandleGetRuleCategoriesAsync(
|
||||
ISecretDetectionSettingsService service,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var categories = await service.GetRuleCategoriesAsync(cancellationToken);
|
||||
return Results.Ok(categories);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user