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;
///
/// Trust and signing owner mutation endpoints backing Administration A6.
///
public static class AdministrationTrustSigningMutationEndpoints
{
private const int DefaultLimit = 50;
private const int MaxLimit = 200;
public static IEndpointRouteBuilder MapAdministrationTrustSigningMutationEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/v1/administration/trust-signing")
.WithTags("Administration")
.RequireAuthorization(PlatformPolicies.TrustRead)
.RequireTenant();
group.MapGet("/keys", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
TimeProvider timeProvider,
[FromQuery] int? limit,
[FromQuery] int? offset,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var normalizedLimit = NormalizeLimit(limit);
var normalizedOffset = NormalizeOffset(offset);
var items = await store.ListKeysAsync(
requestContext!.TenantId,
normalizedLimit,
normalizedOffset,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse(
requestContext.TenantId,
requestContext.ActorId,
timeProvider.GetUtcNow(),
Cached: false,
CacheTtlSeconds: 0,
items,
items.Count,
normalizedLimit,
normalizedOffset));
})
.WithName("ListAdministrationTrustKeys")
.WithSummary("List trust signing keys")
.RequireAuthorization(PlatformPolicies.TrustRead);
group.MapPost("/keys", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
CreateAdministrationTrustKeyRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var created = await store.CreateKeyAsync(
requestContext!.TenantId,
requestContext.ActorId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Created($"/api/v1/administration/trust-signing/keys/{created.KeyId}", created);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("CreateAdministrationTrustKey")
.WithSummary("Create trust signing key")
.RequireAuthorization(PlatformPolicies.TrustWrite);
group.MapPost("/keys/{keyId:guid}/rotate", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
Guid keyId,
RotateAdministrationTrustKeyRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var updated = await store.RotateKeyAsync(
requestContext!.TenantId,
requestContext.ActorId,
keyId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(updated);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId, certificateId: null);
}
})
.WithName("RotateAdministrationTrustKey")
.WithSummary("Rotate trust signing key")
.RequireAuthorization(PlatformPolicies.TrustWrite);
group.MapPost("/keys/{keyId:guid}/revoke", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
Guid keyId,
RevokeAdministrationTrustKeyRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var updated = await store.RevokeKeyAsync(
requestContext!.TenantId,
requestContext.ActorId,
keyId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(updated);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId, certificateId: null);
}
})
.WithName("RevokeAdministrationTrustKey")
.WithSummary("Revoke trust signing key")
.RequireAuthorization(PlatformPolicies.TrustAdmin);
group.MapGet("/issuers", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
TimeProvider timeProvider,
[FromQuery] int? limit,
[FromQuery] int? offset,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var normalizedLimit = NormalizeLimit(limit);
var normalizedOffset = NormalizeOffset(offset);
var items = await store.ListIssuersAsync(
requestContext!.TenantId,
normalizedLimit,
normalizedOffset,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse(
requestContext.TenantId,
requestContext.ActorId,
timeProvider.GetUtcNow(),
Cached: false,
CacheTtlSeconds: 0,
items,
items.Count,
normalizedLimit,
normalizedOffset));
})
.WithName("ListAdministrationTrustIssuers")
.WithSummary("List trust issuers")
.RequireAuthorization(PlatformPolicies.TrustRead);
group.MapPost("/issuers", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
RegisterAdministrationTrustIssuerRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var created = await store.RegisterIssuerAsync(
requestContext!.TenantId,
requestContext.ActorId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Created($"/api/v1/administration/trust-signing/issuers/{created.IssuerId}", created);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("RegisterAdministrationTrustIssuer")
.WithSummary("Register trust issuer")
.RequireAuthorization(PlatformPolicies.TrustWrite);
group.MapPost("/issuers/{issuerId:guid}/block", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
Guid issuerId,
BlockAdministrationTrustIssuerRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var updated = await store.BlockIssuerAsync(
requestContext!.TenantId,
requestContext.ActorId,
issuerId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(updated);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("BlockAdministrationTrustIssuer")
.WithSummary("Block trust issuer")
.RequireAuthorization(PlatformPolicies.TrustAdmin);
group.MapPost("/issuers/{issuerId:guid}/unblock", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
Guid issuerId,
UnblockAdministrationTrustIssuerRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var updated = await store.UnblockIssuerAsync(
requestContext!.TenantId,
requestContext.ActorId,
issuerId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(updated);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("UnblockAdministrationTrustIssuer")
.WithSummary("Unblock trust issuer")
.RequireAuthorization(PlatformPolicies.TrustAdmin);
group.MapGet("/certificates", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
TimeProvider timeProvider,
[FromQuery] int? limit,
[FromQuery] int? offset,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var normalizedLimit = NormalizeLimit(limit);
var normalizedOffset = NormalizeOffset(offset);
var items = await store.ListCertificatesAsync(
requestContext!.TenantId,
normalizedLimit,
normalizedOffset,
cancellationToken).ConfigureAwait(false);
return Results.Ok(new PlatformListResponse(
requestContext.TenantId,
requestContext.ActorId,
timeProvider.GetUtcNow(),
Cached: false,
CacheTtlSeconds: 0,
items,
items.Count,
normalizedLimit,
normalizedOffset));
})
.WithName("ListAdministrationTrustCertificates")
.WithSummary("List trust certificates")
.RequireAuthorization(PlatformPolicies.TrustRead);
group.MapPost("/certificates", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
RegisterAdministrationTrustCertificateRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var created = await store.RegisterCertificateAsync(
requestContext!.TenantId,
requestContext.ActorId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Created($"/api/v1/administration/trust-signing/certificates/{created.CertificateId}", created);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("RegisterAdministrationTrustCertificate")
.WithSummary("Register trust certificate")
.RequireAuthorization(PlatformPolicies.TrustWrite);
group.MapPost("/certificates/{certificateId:guid}/revoke", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
Guid certificateId,
RevokeAdministrationTrustCertificateRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var updated = await store.RevokeCertificateAsync(
requestContext!.TenantId,
requestContext.ActorId,
certificateId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(updated);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId);
}
})
.WithName("RevokeAdministrationTrustCertificate")
.WithSummary("Revoke trust certificate")
.RequireAuthorization(PlatformPolicies.TrustAdmin);
group.MapGet("/transparency-log", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var config = await store.GetTransparencyLogConfigAsync(
requestContext!.TenantId,
cancellationToken).ConfigureAwait(false);
if (config is null)
{
return Results.NotFound(new { error = "transparency_log_not_configured" });
}
return Results.Ok(new PlatformItemResponse(
requestContext.TenantId,
requestContext.ActorId,
timeProvider.GetUtcNow(),
Cached: false,
CacheTtlSeconds: 0,
config));
})
.WithName("GetAdministrationTrustTransparencyLog")
.WithSummary("Get trust transparency log configuration")
.RequireAuthorization(PlatformPolicies.TrustRead);
group.MapPut("/transparency-log", async Task(
HttpContext context,
PlatformRequestContextResolver resolver,
IAdministrationTrustSigningStore store,
ConfigureAdministrationTransparencyLogRequest request,
CancellationToken cancellationToken) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var config = await store.ConfigureTransparencyLogAsync(
requestContext!.TenantId,
requestContext.ActorId,
request,
cancellationToken).ConfigureAwait(false);
return Results.Ok(config);
}
catch (InvalidOperationException ex)
{
return MapStoreError(ex, keyId: null, certificateId: null);
}
})
.WithName("ConfigureAdministrationTrustTransparencyLog")
.WithSummary("Configure trust transparency log")
.RequireAuthorization(PlatformPolicies.TrustAdmin);
return app;
}
private static IResult MapStoreError(InvalidOperationException exception, Guid? keyId, Guid? certificateId)
{
return exception.Message switch
{
"request_required" => Results.BadRequest(new { error = "request_required" }),
"tenant_required" => Results.BadRequest(new { error = "tenant_required" }),
"tenant_id_invalid" => Results.BadRequest(new { error = "tenant_id_invalid" }),
"reason_required" => Results.BadRequest(new { error = "reason_required" }),
"key_alias_required" => Results.BadRequest(new { error = "key_alias_required" }),
"key_algorithm_required" => Results.BadRequest(new { error = "key_algorithm_required" }),
"key_alias_exists" => Results.Conflict(new { error = "key_alias_exists" }),
"key_not_found" => Results.NotFound(new { error = "key_not_found", keyId }),
"key_revoked" => Results.Conflict(new { error = "key_revoked", keyId }),
"issuer_name_required" => Results.BadRequest(new { error = "issuer_name_required" }),
"issuer_uri_required" => Results.BadRequest(new { error = "issuer_uri_required" }),
"issuer_uri_invalid" => Results.BadRequest(new { error = "issuer_uri_invalid" }),
"issuer_trust_level_required" => Results.BadRequest(new { error = "issuer_trust_level_required" }),
"issuer_uri_exists" => Results.Conflict(new { error = "issuer_uri_exists" }),
"issuer_not_found" => Results.NotFound(new { error = "issuer_not_found" }),
"certificate_serial_required" => Results.BadRequest(new { error = "certificate_serial_required" }),
"certificate_validity_invalid" => Results.BadRequest(new { error = "certificate_validity_invalid" }),
"certificate_serial_exists" => Results.Conflict(new { error = "certificate_serial_exists" }),
"certificate_not_found" => Results.NotFound(new { error = "certificate_not_found", certificateId }),
"transparency_log_url_required" => Results.BadRequest(new { error = "transparency_log_url_required" }),
"transparency_log_url_invalid" => Results.BadRequest(new { error = "transparency_log_url_invalid" }),
"transparency_witness_url_invalid" => Results.BadRequest(new { error = "transparency_witness_url_invalid" }),
_ => Results.BadRequest(new { error = exception.Message })
};
}
private static int NormalizeLimit(int? value)
{
return value switch
{
null => DefaultLimit,
< 1 => 1,
> MaxLimit => MaxLimit,
_ => value.Value
};
}
private static int NormalizeOffset(int? value) => value is null or < 0 ? 0 : value.Value;
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;
}
}