tenant fixes

This commit is contained in:
master
2026-02-23 23:44:50 +02:00
parent bdb1438654
commit 4f947a8b61
159 changed files with 1064 additions and 556 deletions

View File

@@ -10,6 +10,7 @@ using StellaOps.AdvisoryAI.Attestation;
using StellaOps.AdvisoryAI.Attestation.Models;
using StellaOps.AdvisoryAI.Attestation.Storage;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.AdvisoryAI.WebService.Endpoints;
@@ -34,7 +35,8 @@ public static class AttestationEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/advisory-ai/runs/{runId}/claims
app.MapGet("/v1/advisory-ai/runs/{runId}/claims", HandleGetRunClaims)
@@ -46,7 +48,8 @@ public static class AttestationEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/advisory-ai/attestations/recent
app.MapGet("/v1/advisory-ai/attestations/recent", HandleListRecentAttestations)
@@ -57,7 +60,8 @@ public static class AttestationEndpoints
.Produces<RecentAttestationsResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// POST /v1/advisory-ai/attestations/verify
app.MapPost("/v1/advisory-ai/attestations/verify", HandleVerifyAttestation)
@@ -69,7 +73,8 @@ public static class AttestationEndpoints
.Produces<AttestationVerificationResponse>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
}
private static async Task<IResult> HandleGetRunAttestation(

View File

@@ -18,6 +18,7 @@ using StellaOps.AdvisoryAI.Chat.Services;
using StellaOps.AdvisoryAI.Chat.Settings;
using StellaOps.AdvisoryAI.WebService.Contracts;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Text.Json;
@@ -45,7 +46,8 @@ public static class ChatEndpoints
{
var group = builder.MapGroup("/api/v1/chat")
.WithTags("Advisory Chat")
.RequireAuthorization(AdvisoryAIPolicies.OperatePolicy);
.RequireAuthorization(AdvisoryAIPolicies.OperatePolicy)
.RequireTenant();
// Single query endpoint (non-streaming)
group.MapPost("/query", ProcessQueryAsync)

View File

@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Determinism;
using StellaOps.Evidence.Pack;
using StellaOps.Evidence.Pack.Models;
@@ -35,7 +36,8 @@ public static class EvidencePackEndpoints
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.OperatePolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/evidence-packs/{packId} - Get Evidence Pack
app.MapGet("/v1/evidence-packs/{packId}", HandleGetEvidencePack)
@@ -47,7 +49,8 @@ public static class EvidencePackEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// POST /v1/evidence-packs/{packId}/sign - Sign Evidence Pack
app.MapPost("/v1/evidence-packs/{packId}/sign", HandleSignEvidencePack)
@@ -59,7 +62,8 @@ public static class EvidencePackEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.OperatePolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// POST /v1/evidence-packs/{packId}/verify - Verify Evidence Pack
app.MapPost("/v1/evidence-packs/{packId}/verify", HandleVerifyEvidencePack)
@@ -71,7 +75,8 @@ public static class EvidencePackEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/evidence-packs/{packId}/export - Export Evidence Pack
app.MapGet("/v1/evidence-packs/{packId}/export", HandleExportEvidencePack)
@@ -83,7 +88,8 @@ public static class EvidencePackEndpoints
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/runs/{runId}/evidence-packs - List Evidence Packs for Run
app.MapGet("/v1/runs/{runId}/evidence-packs", HandleListRunEvidencePacks)
@@ -94,7 +100,8 @@ public static class EvidencePackEndpoints
.Produces<EvidencePackListResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
// GET /v1/evidence-packs - List Evidence Packs
app.MapGet("/v1/evidence-packs", HandleListEvidencePacks)
@@ -105,7 +112,8 @@ public static class EvidencePackEndpoints
.Produces<EvidencePackListResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireRateLimiting("advisory-ai");
.RequireRateLimiting("advisory-ai")
.RequireTenant();
}
private static async Task<IResult> HandleCreateEvidencePack(

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.KnowledgeSearch;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.AdvisoryAI.WebService.Endpoints;
@@ -19,7 +20,8 @@ public static class KnowledgeSearchEndpoints
{
var group = builder.MapGroup("/v1/advisory-ai")
.WithTags("Advisory AI - Knowledge Search")
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy);
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireTenant();
group.MapPost("/search", SearchAsync)
.WithName("AdvisoryAiKnowledgeSearch")

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.Inference.LlmProviders;
using StellaOps.AdvisoryAI.Plugin.Unified;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Plugin.Abstractions.Capabilities;
using System.Security.Cryptography;
using System.Text;
@@ -29,7 +30,8 @@ public static class LlmAdapterEndpoints
{
var group = builder.MapGroup("/v1/advisory-ai/adapters")
.WithTags("Advisory AI - LLM Adapters")
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy);
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireTenant();
group.MapGet("/llm/providers", ListProvidersAsync)
.WithName("ListLlmProviders")

View File

@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.AdvisoryAI.Runs;
using StellaOps.AdvisoryAI.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Determinism;
using System.Collections.Immutable;
@@ -29,7 +30,8 @@ public static class RunEndpoints
{
var group = builder.MapGroup("/api/v1/runs")
.WithTags("Runs")
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy);
.RequireAuthorization(AdvisoryAIPolicies.ViewPolicy)
.RequireTenant();
group.MapPost("/", CreateRunAsync)
.WithName("CreateRun")

View File

@@ -29,6 +29,7 @@ using StellaOps.AdvisoryAI.WebService.Services;
using StellaOps.Auth.Abstractions;
using StellaOps.Evidence.Pack;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Router.AspNet;
using System.Collections.Immutable;
using System.Diagnostics;
@@ -103,6 +104,7 @@ var routerEnabled = builder.Services.AddRouterMicroservice(
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
routerOptionsSection: "Router");
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddRateLimiter(options =>
@@ -145,6 +147,7 @@ if (app.Environment.IsDevelopment())
app.UseStellaOpsCors();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.UseRateLimiter();
app.TryUseStellaRouter(routerEnabled);

View File

@@ -41,6 +41,7 @@ public sealed class CompanionExplainEndpointTests
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Client", "companion-tests");
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:companion");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new CompanionExplainRequest
{
@@ -84,6 +85,7 @@ public sealed class CompanionExplainEndpointTests
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Client", "companion-tests");
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:companion");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new CompanionExplainRequest
{

View File

@@ -49,6 +49,7 @@ public sealed class KnowledgeSearchEndpointsIntegrationTests : IDisposable
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:search");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var response = await client.PostAsJsonAsync("/v1/advisory-ai/search", new AdvisoryKnowledgeSearchRequest
{
@@ -75,6 +76,7 @@ public sealed class KnowledgeSearchEndpointsIntegrationTests : IDisposable
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:index:write");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var response = await client.PostAsync("/v1/advisory-ai/index/rebuild", content: null);
response.StatusCode.Should().Be(HttpStatusCode.OK);

View File

@@ -40,6 +40,7 @@ public sealed class LlmAdapterEndpointsIntegrationTests
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:adapter:read");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var response = await client.GetAsync("/v1/advisory-ai/adapters/llm/providers");
response.StatusCode.Should().Be(HttpStatusCode.OK);
@@ -57,6 +58,7 @@ public sealed class LlmAdapterEndpointsIntegrationTests
{
using var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Scopes", "advisory:adapter:invoke");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new OpenAiChatCompletionRequest
{

View File

@@ -6,6 +6,7 @@ using StellaOps.AirGap.Time.Models;
using StellaOps.AirGap.Time.Services;
using StellaOps.Auth.Abstractions;
using System.Security.Claims;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.AirGap.Controller.Endpoints;
@@ -14,7 +15,8 @@ internal static class AirGapEndpoints
public static RouteGroupBuilder MapAirGapEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/system/airgap")
.RequireAuthorization(AirGapPolicies.StatusRead);
.RequireAuthorization(AirGapPolicies.StatusRead)
.RequireTenant();
group.MapGet("/status", HandleStatus)
.RequireAuthorization(AirGapPolicies.StatusRead)

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Authentication;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.AirGap.Controller.Auth;
using StellaOps.AirGap.Controller.DependencyInjection;
using StellaOps.AirGap.Controller.Endpoints;
@@ -29,6 +30,7 @@ builder.Services.AddSingleton<TimeProvider>(TimeProvider.System);
builder.Services.AddAirGapController(builder.Configuration);
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -44,6 +46,7 @@ app.LogStellaOpsLocalHostname("airgap-controller");
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
app.MapAirGapEndpoints();

View File

@@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using StellaOps.Attestor.TileProxy.Services;
using System.Text.Json;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.Attestor.TileProxy.Endpoints;
@@ -48,7 +49,8 @@ public static class TileEndpoints
.Produces(StatusCodes.Status502BadGateway);
// Admin endpoints
var admin = endpoints.MapGroup("/_admin");
var admin = endpoints.MapGroup("/_admin")
.RequireTenant();
admin.MapGet("/cache/stats", GetCacheStats)
.WithName("GetCacheStats")

View File

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Attestor.Persistence.Entities;
using StellaOps.Attestor.Services;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.Attestor.WebService.Endpoints;
@@ -25,7 +26,8 @@ public static class VerdictEndpoints
{
var group = app.MapGroup("/api/v1/verdicts")
.WithTags("Verdicts")
.WithOpenApi();
.WithOpenApi()
.RequireTenant();
group.MapPost("/", CreateVerdict)
.WithName("CreateVerdict")

View File

@@ -26,6 +26,7 @@ public class ProofsApiContractTests : IClassFixture<AttestorTestWebApplicationFa
public ProofsApiContractTests(AttestorTestWebApplicationFactory factory)
{
_client = factory.CreateClient();
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
#region POST /proofs/{entry}/spine Contract Tests
@@ -302,6 +303,7 @@ public class AnchorsApiContractTests : IClassFixture<AttestorTestWebApplicationF
public AnchorsApiContractTests(AttestorTestWebApplicationFactory factory)
{
_client = factory.CreateClient();
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
[Fact]
@@ -346,6 +348,7 @@ public class VerifyApiContractTests : IClassFixture<AttestorTestWebApplicationFa
public VerifyApiContractTests(AttestorTestWebApplicationFactory factory)
{
_client = factory.CreateClient();
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
[Fact]

View File

@@ -177,6 +177,7 @@ public sealed class AttestationBundleEndpointsTests
private static void AttachAuth(HttpClient client)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
}

View File

@@ -46,6 +46,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
// Act
var response = await client.GetAsync("/swagger/v1/swagger.json");
@@ -71,6 +72,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
// Act
var response = await client.GetAsync("/swagger/v1/swagger.json");
@@ -112,6 +114,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = new
{
@@ -143,6 +146,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var invalidEntryId = "invalid-entry-format";
var request = new
{
@@ -171,6 +175,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Act
@@ -197,6 +202,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new
{
envelope = new
@@ -232,6 +238,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new { }; // Missing envelope
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/verify")
@@ -260,6 +267,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var digestId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e";
// Act
@@ -286,6 +294,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var digest = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e";
// Act
@@ -306,6 +315,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var endpoints = new[]
{
"/health",
@@ -348,6 +358,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/verify")
{
Content = new StringContent("<xml/>", Encoding.UTF8, "application/xml")
@@ -370,6 +381,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/verify")
{
Content = new StringContent("{}", Encoding.UTF8, "application/json")
@@ -399,6 +411,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/proofs/invalid-entry/spine")
{
Content = JsonContent.Create(new { })
@@ -437,6 +450,7 @@ public sealed class AttestorContractSnapshotTests : IClassFixture<AttestorTestWe
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
// Act
var response = await client.GetAsync("/health");

View File

@@ -50,6 +50,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/proofs/{Uri.EscapeDataString(entryId)}/spine")
@@ -77,6 +78,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = new
@@ -117,6 +119,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/proofs/{Uri.EscapeDataString(entryId)}/spine")
@@ -140,6 +143,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/proofs/{Uri.EscapeDataString(entryId)}/spine")
@@ -161,6 +165,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Missing evidenceIds, reasoningId, vexVerdictId
@@ -189,6 +194,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = $"{invalidDigest}:pkg:npm/example@1.0.0";
var request = new
@@ -218,6 +224,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Array with null values
@@ -247,6 +254,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Create a very large array of evidence IDs (>10MB)
@@ -287,6 +295,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
// Actual implementation may use circuit breaker or graceful degradation
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Act
@@ -320,6 +329,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
// The system should either fail gracefully or continue without transparency logging
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = new
@@ -354,6 +364,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = new
{
@@ -383,6 +394,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"/proofs/{Uri.EscapeDataString(entryId)}/spine")
@@ -433,6 +445,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Request with multiple invalid fields
@@ -485,6 +498,7 @@ public sealed class AttestorNegativeTests : IClassFixture<AttestorTestWebApplica
{
// Arrange
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var invalidRequest = new { invalid = true };

View File

@@ -62,6 +62,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -92,6 +93,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -147,6 +149,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var request = CreateValidVerifyRequest();
// Act
@@ -188,6 +191,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -224,6 +228,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -264,6 +269,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
// Invalid request (missing required fields)
@@ -305,6 +311,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var nonExistentId = "sha256:0000000000000000000000000000000000000000000000000000000000000000:pkg:npm/nonexistent@1.0.0";
// Act
@@ -338,6 +345,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -379,6 +387,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();
@@ -427,6 +436,7 @@ public sealed class AttestorOTelTraceTests : IClassFixture<AttestorTestWebApplic
ActivitySource.AddActivityListener(listener);
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
var entryId = "sha256:4d5f6e7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e:pkg:npm/example@1.0.0";
var request = CreateValidSpineRequest();

View File

@@ -72,5 +72,6 @@ public sealed class WebServiceFeatureGateTests
private static void AttachAuth(HttpClient client)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
}

View File

@@ -26,6 +26,7 @@ using StellaOps.Attestor.Watchlist;
using StellaOps.Attestor.WebService.Endpoints;
using StellaOps.Attestor.WebService.Options;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Configuration;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Determinism;
@@ -267,6 +268,7 @@ internal static class AttestorWebServiceComposition
configureOptions: options => { options.TimeProvider ??= TimeProvider.System; });
}
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("attestor:write", policy =>
@@ -411,6 +413,7 @@ internal static class AttestorWebServiceComposition
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
app.MapHealthChecks("/health/ready");

View File

@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using StellaOps.Attestor.Persistence.Repositories;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.Attestor.WebService.Endpoints;
@@ -24,7 +25,8 @@ public static class PredicateRegistryEndpoints
public static void MapPredicateRegistryEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/v1/attestor/predicates")
.WithTags("Predicate Registry");
.WithTags("Predicate Registry")
.RequireTenant();
group.MapGet("/", ListPredicateTypes)
.WithName("ListPredicateTypes")

View File

@@ -12,6 +12,7 @@ using StellaOps.BinaryIndex.VexBridge;
using StellaOps.BinaryIndex.WebService.Middleware;
using StellaOps.BinaryIndex.WebService.Services;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.BinaryIndex.WebService.Telemetry;
using StellaOps.Router.AspNet;
@@ -65,6 +66,7 @@ builder.Services.AddResolutionRateLimiting(options =>
builder.Services.AddHealthChecks()
.AddRedis(redisConnectionString, name: "redis");
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -88,6 +90,7 @@ app.UseStellaOpsCors();
// HTTPS redirection removed — the gateway handles TLS termination.
app.UseResolutionRateLimiting();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
app.MapControllers();
app.MapHealthChecks("/health");

View File

@@ -3,6 +3,7 @@ using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Cartographer.Options;
using StellaOps.Router.AspNet;
@@ -71,6 +72,7 @@ if (authorityOptions.Enabled)
builder.Services.AddHealthChecks()
.AddCheck("cartographer_ready", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -99,6 +101,7 @@ if (authorityOptions.Enabled)
app.UseAuthorization();
app.TryUseStellaRouter(routerEnabled);
}
app.UseStellaOpsTenantMiddleware();
app.MapHealthChecks("/healthz").AllowAnonymous();
app.MapHealthChecks("/readyz", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions

View File

@@ -100,6 +100,7 @@ public sealed class EvidenceAuditEndpointsTests : IDisposable
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(EvidenceLockerTestAuthHandler.SchemeName);
client.DefaultRequestHeaders.Add("X-Test-Tenant", tenantId);
client.DefaultRequestHeaders.Add("X-Test-Scopes", scopes);
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
}
public void Dispose()

View File

@@ -412,6 +412,7 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
client.DefaultRequestHeaders.Add("X-Tenant-Id", tenantId);
client.DefaultRequestHeaders.Add("X-Scopes", scopes);
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
}
public void Dispose()

View File

@@ -467,6 +467,7 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
client.DefaultRequestHeaders.Add("X-Tenant-Id", tenantId);
client.DefaultRequestHeaders.Add("X-Scopes", scopes);
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
}
public void Dispose()

View File

@@ -308,6 +308,7 @@ public sealed class EvidenceReindexIntegrationTests : IDisposable
client.DefaultRequestHeaders.Add("X-Tenant-Id", tenantId);
client.DefaultRequestHeaders.Add("X-Test-Subject", "test-user@example.com");
client.DefaultRequestHeaders.Add("X-Scopes", scopes);
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", tenantId);
}
private static string ComputeSha256(string input)

View File

@@ -32,6 +32,8 @@ public sealed class ExportEndpointsTests : IClassFixture<ExportEndpointsTests.Ex
{
_fixture = fixture;
_client = fixture.DerivedFactory.CreateClient();
_client.DefaultRequestHeaders.Add("Authorization", "Bearer test-token");
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
[Fact]

View File

@@ -0,0 +1,112 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.Core.Persistence;
/// <summary>
/// Repository for managing export distributions.
/// </summary>
public interface IExportDistributionRepository
{
/// <summary>
/// Gets a distribution by ID.
/// </summary>
Task<ExportDistribution?> GetByIdAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets a distribution by idempotency key.
/// </summary>
Task<ExportDistribution?> GetByIdempotencyKeyAsync(
Guid tenantId,
string idempotencyKey,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions for a run.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions by status.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListByStatusAsync(
Guid tenantId,
ExportDistributionStatus status,
int limit = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions due for retention deletion.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListExpiredAsync(
DateTimeOffset asOf,
int limit = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new distribution record.
/// </summary>
Task<ExportDistribution> CreateAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates a distribution record.
/// Returns the updated record, or null if not found.
/// </summary>
Task<ExportDistribution?> UpdateAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Performs an idempotent upsert based on idempotency key.
/// Returns existing distribution if key matches, otherwise creates new.
/// </summary>
Task<(ExportDistribution Distribution, bool WasCreated)> UpsertByIdempotencyKeyAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Marks a distribution for deletion.
/// </summary>
Task<bool> MarkForDeletionAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes a distribution record and returns whether it existed.
/// </summary>
Task<bool> DeleteAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets distribution statistics for a run.
/// </summary>
Task<ExportDistributionStats> GetStatsAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Statistics for distributions of a run.
/// </summary>
public sealed record ExportDistributionStats
{
public int Total { get; init; }
public int Pending { get; init; }
public int Distributing { get; init; }
public int Distributed { get; init; }
public int Verified { get; init; }
public int Failed { get; init; }
public int Cancelled { get; init; }
public long TotalSizeBytes { get; init; }
}

View File

@@ -0,0 +1,66 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.Core.Persistence;
/// <summary>
/// Repository for managing export profiles.
/// </summary>
public interface IExportProfileRepository
{
/// <summary>
/// Gets a profile by ID for a tenant.
/// </summary>
Task<ExportProfile?> GetByIdAsync(
Guid tenantId,
Guid profileId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists profiles for a tenant with optional filtering.
/// </summary>
Task<(IReadOnlyList<ExportProfile> Items, int TotalCount)> ListAsync(
Guid tenantId,
ExportProfileStatus? status = null,
ExportProfileKind? kind = null,
string? search = null,
int offset = 0,
int limit = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new profile.
/// </summary>
Task<ExportProfile> CreateAsync(
ExportProfile profile,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing profile.
/// </summary>
Task<ExportProfile?> UpdateAsync(
ExportProfile profile,
CancellationToken cancellationToken = default);
/// <summary>
/// Archives a profile (soft delete).
/// </summary>
Task<bool> ArchiveAsync(
Guid tenantId,
Guid profileId,
CancellationToken cancellationToken = default);
/// <summary>
/// Checks if a profile name is unique within a tenant.
/// </summary>
Task<bool> IsNameUniqueAsync(
Guid tenantId,
string name,
Guid? excludeProfileId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets active scheduled profiles for processing.
/// </summary>
Task<IReadOnlyList<ExportProfile>> GetScheduledProfilesAsync(
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,133 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.Core.Persistence;
/// <summary>
/// Repository for managing export runs.
/// </summary>
public interface IExportRunRepository
{
/// <summary>
/// Gets a run by ID for a tenant.
/// </summary>
Task<ExportRun?> GetByIdAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists runs for a tenant with optional filtering.
/// </summary>
Task<(IReadOnlyList<ExportRun> Items, int TotalCount)> ListAsync(
Guid tenantId,
Guid? profileId = null,
ExportRunStatus? status = null,
ExportRunTrigger? trigger = null,
DateTimeOffset? createdAfter = null,
DateTimeOffset? createdBefore = null,
string? correlationId = null,
int offset = 0,
int limit = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new run.
/// </summary>
Task<ExportRun> CreateAsync(
ExportRun run,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates run status and progress.
/// </summary>
Task<ExportRun?> UpdateAsync(
ExportRun run,
CancellationToken cancellationToken = default);
/// <summary>
/// Cancels a run if it's in a cancellable state.
/// </summary>
Task<bool> CancelAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets active runs count for concurrency checks.
/// </summary>
Task<int> GetActiveRunsCountAsync(
Guid tenantId,
Guid? profileId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets queued runs count.
/// </summary>
Task<int> GetQueuedRunsCountAsync(
Guid tenantId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets the next queued run to execute.
/// </summary>
Task<ExportRun?> DequeueNextRunAsync(
Guid tenantId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Repository for managing export artifacts.
/// </summary>
public interface IExportArtifactRepository
{
/// <summary>
/// Gets an artifact by ID.
/// </summary>
Task<ExportArtifact?> GetByIdAsync(
Guid tenantId,
Guid artifactId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists artifacts for a run.
/// </summary>
Task<IReadOnlyList<ExportArtifact>> ListByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new artifact record.
/// </summary>
Task<ExportArtifact> CreateAsync(
ExportArtifact artifact,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes artifacts for a run.
/// </summary>
Task<int> DeleteByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Represents an export artifact.
/// </summary>
public sealed record ExportArtifact
{
public required Guid ArtifactId { get; init; }
public required Guid RunId { get; init; }
public required Guid TenantId { get; init; }
public required string Name { get; init; }
public required string Kind { get; init; }
public required string Path { get; init; }
public long SizeBytes { get; init; }
public string? ContentType { get; init; }
public required string Checksum { get; init; }
public string ChecksumAlgorithm { get; init; } = "SHA-256";
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset? ExpiresAt { get; init; }
}

View File

@@ -4,7 +4,7 @@ using Npgsql;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Infrastructure.Db;
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
using StellaOps.ExportCenter.WebService.Distribution;
using StellaOps.ExportCenter.Core.Persistence;
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;

View File

@@ -4,7 +4,7 @@ using Npgsql;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Infrastructure.Db;
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
using StellaOps.ExportCenter.WebService.Api;
using StellaOps.ExportCenter.Core.Persistence;
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;

View File

@@ -4,7 +4,7 @@ using Npgsql;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Infrastructure.Db;
using StellaOps.ExportCenter.Infrastructure.EfCore.Models;
using StellaOps.ExportCenter.WebService.Api;
using StellaOps.ExportCenter.Core.Persistence;
namespace StellaOps.ExportCenter.Infrastructure.Postgres.Repositories;

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
using StellaOps.ExportCenter.WebService.Api;
using StellaOps.TestKit;

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.ExportCenter.Core.Persistence;
using StellaOps.ExportCenter.WebService.Api;
using Xunit;

View File

@@ -6,7 +6,9 @@ using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Determinism;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
using StellaOps.ExportCenter.Core.Planner;
using IExportProfileRepository = StellaOps.ExportCenter.Core.Persistence.IExportProfileRepository;
using StellaOps.ExportCenter.WebService.Telemetry;
using System.Runtime.CompilerServices;
using System.Security.Claims;

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Determinism;
using StellaOps.ExportCenter.Core.Persistence;
namespace StellaOps.ExportCenter.WebService.Api;

View File

@@ -1,66 +1,3 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.WebService.Api;
/// <summary>
/// Repository for managing export profiles.
/// </summary>
public interface IExportProfileRepository
{
/// <summary>
/// Gets a profile by ID for a tenant.
/// </summary>
Task<ExportProfile?> GetByIdAsync(
Guid tenantId,
Guid profileId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists profiles for a tenant with optional filtering.
/// </summary>
Task<(IReadOnlyList<ExportProfile> Items, int TotalCount)> ListAsync(
Guid tenantId,
ExportProfileStatus? status = null,
ExportProfileKind? kind = null,
string? search = null,
int offset = 0,
int limit = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new profile.
/// </summary>
Task<ExportProfile> CreateAsync(
ExportProfile profile,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates an existing profile.
/// </summary>
Task<ExportProfile?> UpdateAsync(
ExportProfile profile,
CancellationToken cancellationToken = default);
/// <summary>
/// Archives a profile (soft delete).
/// </summary>
Task<bool> ArchiveAsync(
Guid tenantId,
Guid profileId,
CancellationToken cancellationToken = default);
/// <summary>
/// Checks if a profile name is unique within a tenant.
/// </summary>
Task<bool> IsNameUniqueAsync(
Guid tenantId,
string name,
Guid? excludeProfileId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets active scheduled profiles for processing.
/// </summary>
Task<IReadOnlyList<ExportProfile>> GetScheduledProfilesAsync(
CancellationToken cancellationToken = default);
}
// This interface has been moved to StellaOps.ExportCenter.Core.Persistence.
// Import that namespace instead of StellaOps.ExportCenter.WebService.Api for IExportProfileRepository.
// This file is kept for reference only.

View File

@@ -1,133 +1,4 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.WebService.Api;
/// <summary>
/// Repository for managing export runs.
/// </summary>
public interface IExportRunRepository
{
/// <summary>
/// Gets a run by ID for a tenant.
/// </summary>
Task<ExportRun?> GetByIdAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists runs for a tenant with optional filtering.
/// </summary>
Task<(IReadOnlyList<ExportRun> Items, int TotalCount)> ListAsync(
Guid tenantId,
Guid? profileId = null,
ExportRunStatus? status = null,
ExportRunTrigger? trigger = null,
DateTimeOffset? createdAfter = null,
DateTimeOffset? createdBefore = null,
string? correlationId = null,
int offset = 0,
int limit = 50,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new run.
/// </summary>
Task<ExportRun> CreateAsync(
ExportRun run,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates run status and progress.
/// </summary>
Task<ExportRun?> UpdateAsync(
ExportRun run,
CancellationToken cancellationToken = default);
/// <summary>
/// Cancels a run if it's in a cancellable state.
/// </summary>
Task<bool> CancelAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets active runs count for concurrency checks.
/// </summary>
Task<int> GetActiveRunsCountAsync(
Guid tenantId,
Guid? profileId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets queued runs count.
/// </summary>
Task<int> GetQueuedRunsCountAsync(
Guid tenantId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets the next queued run to execute.
/// </summary>
Task<ExportRun?> DequeueNextRunAsync(
Guid tenantId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Repository for managing export artifacts.
/// </summary>
public interface IExportArtifactRepository
{
/// <summary>
/// Gets an artifact by ID.
/// </summary>
Task<ExportArtifact?> GetByIdAsync(
Guid tenantId,
Guid artifactId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists artifacts for a run.
/// </summary>
Task<IReadOnlyList<ExportArtifact>> ListByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new artifact record.
/// </summary>
Task<ExportArtifact> CreateAsync(
ExportArtifact artifact,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes artifacts for a run.
/// </summary>
Task<int> DeleteByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Represents an export artifact.
/// </summary>
public sealed record ExportArtifact
{
public required Guid ArtifactId { get; init; }
public required Guid RunId { get; init; }
public required Guid TenantId { get; init; }
public required string Name { get; init; }
public required string Kind { get; init; }
public required string Path { get; init; }
public long SizeBytes { get; init; }
public string? ContentType { get; init; }
public required string Checksum { get; init; }
public string ChecksumAlgorithm { get; init; } = "SHA-256";
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
public DateTimeOffset CreatedAt { get; init; }
public DateTimeOffset? ExpiresAt { get; init; }
}
// The IExportRunRepository, IExportArtifactRepository interfaces and ExportArtifact record
// have been moved to StellaOps.ExportCenter.Core.Persistence.
// Import that namespace instead of StellaOps.ExportCenter.WebService.Api.
// This file is kept for reference only.

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
using System.Collections.Concurrent;
namespace StellaOps.ExportCenter.WebService.Api;

View File

@@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;
using StellaOps.Determinism;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
using System.Globalization;
using System.Text.Json;

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Determinism;
using StellaOps.ExportCenter.Core.Persistence;
using StellaOps.ExportCenter.WebService.Distribution.Oci;
namespace StellaOps.ExportCenter.WebService.Distribution;

View File

@@ -1,4 +1,5 @@
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
namespace StellaOps.ExportCenter.WebService.Distribution;

View File

@@ -1,112 +1,4 @@
using StellaOps.ExportCenter.Core.Domain;
namespace StellaOps.ExportCenter.WebService.Distribution;
/// <summary>
/// Repository for managing export distributions.
/// </summary>
public interface IExportDistributionRepository
{
/// <summary>
/// Gets a distribution by ID.
/// </summary>
Task<ExportDistribution?> GetByIdAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets a distribution by idempotency key.
/// </summary>
Task<ExportDistribution?> GetByIdempotencyKeyAsync(
Guid tenantId,
string idempotencyKey,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions for a run.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListByRunAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions by status.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListByStatusAsync(
Guid tenantId,
ExportDistributionStatus status,
int limit = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// Lists distributions due for retention deletion.
/// </summary>
Task<IReadOnlyList<ExportDistribution>> ListExpiredAsync(
DateTimeOffset asOf,
int limit = 100,
CancellationToken cancellationToken = default);
/// <summary>
/// Creates a new distribution record.
/// </summary>
Task<ExportDistribution> CreateAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Updates a distribution record.
/// Returns the updated record, or null if not found.
/// </summary>
Task<ExportDistribution?> UpdateAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Performs an idempotent upsert based on idempotency key.
/// Returns existing distribution if key matches, otherwise creates new.
/// </summary>
Task<(ExportDistribution Distribution, bool WasCreated)> UpsertByIdempotencyKeyAsync(
ExportDistribution distribution,
CancellationToken cancellationToken = default);
/// <summary>
/// Marks a distribution for deletion.
/// </summary>
Task<bool> MarkForDeletionAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Deletes a distribution record and returns whether it existed.
/// </summary>
Task<bool> DeleteAsync(
Guid tenantId,
Guid distributionId,
CancellationToken cancellationToken = default);
/// <summary>
/// Gets distribution statistics for a run.
/// </summary>
Task<ExportDistributionStats> GetStatsAsync(
Guid tenantId,
Guid runId,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Statistics for distributions of a run.
/// </summary>
public sealed record ExportDistributionStats
{
public int Total { get; init; }
public int Pending { get; init; }
public int Distributing { get; init; }
public int Distributed { get; init; }
public int Verified { get; init; }
public int Failed { get; init; }
public int Cancelled { get; init; }
public long TotalSizeBytes { get; init; }
}
// The IExportDistributionRepository interface and ExportDistributionStats record
// have been moved to StellaOps.ExportCenter.Core.Persistence.
// Import that namespace instead of StellaOps.ExportCenter.WebService.Distribution.
// This file is kept for reference only.

View File

@@ -1,6 +1,7 @@
using Microsoft.Extensions.Options;
using StellaOps.ExportCenter.Core.Domain;
using StellaOps.ExportCenter.Core.Persistence;
using System.Collections.Concurrent;
namespace StellaOps.ExportCenter.WebService.Distribution;

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using StellaOps.AirGap.Policy;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.ExportCenter.WebService;
using StellaOps.ExportCenter.WebService.Api;
using StellaOps.ExportCenter.WebService.Attestation;
@@ -104,6 +105,7 @@ builder.Services.AddExportApiServices(options =>
builder.Services.AddOpenApi();
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -125,6 +127,7 @@ if (app.Environment.IsDevelopment())
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
// OpenAPI discovery endpoints (anonymous)
@@ -162,19 +165,22 @@ app.MapGet("/exports", () => Results.Ok(Array.Empty<object>()))
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportViewer)
.WithDeprecation(DeprecatedEndpointsRegistry.ListExports)
.WithSummary("List exports (DEPRECATED)")
.WithDescription("This endpoint is deprecated. Use GET /v1/exports/profiles instead.");
.WithDescription("This endpoint is deprecated. Use GET /v1/exports/profiles instead.")
.RequireTenant();
app.MapPost("/exports", () => Results.Accepted("/exports", new { status = "scheduled" }))
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportOperator)
.WithDeprecation(DeprecatedEndpointsRegistry.CreateExport)
.WithSummary("Create export (DEPRECATED)")
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/evidence or /v1/exports/attestations instead.");
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/evidence or /v1/exports/attestations instead.")
.RequireTenant();
app.MapDelete("/exports/{id}", (string id) => Results.NoContent())
.RequireAuthorization(StellaOpsResourceServerPolicies.ExportAdmin)
.WithDeprecation(DeprecatedEndpointsRegistry.DeleteExport)
.WithSummary("Delete export (DEPRECATED)")
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/runs/{id}/cancel instead.");
.WithDescription("This endpoint is deprecated. Use POST /v1/exports/runs/{id}/cancel instead.")
.RequireTenant();
// Refresh Router endpoint cache
app.TryRefreshStellaRouterEndpoints(routerEnabled);

View File

@@ -5,6 +5,7 @@
// =============================================================================
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -27,6 +28,8 @@ public sealed class EvidenceDecisionApiIntegrationTests : IClassFixture<Findings
{
AllowAutoRedirect = false
});
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
[Fact(DisplayName = "GET /v1/alerts returns paginated list")]

View File

@@ -6,6 +6,7 @@
// =============================================================================
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -28,6 +29,8 @@ public sealed class ScoringEndpointsIntegrationTests : IClassFixture<FindingsLed
{
AllowAutoRedirect = false
});
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
#region Task 8 - Single Score Endpoint Tests

View File

@@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -30,6 +31,8 @@ public sealed class ScoringObservabilityTests : IClassFixture<FindingsLedgerWebA
{
AllowAutoRedirect = false
});
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
#region Trace Context Tests

View File

@@ -6,6 +6,7 @@
// =============================================================================
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -28,6 +29,8 @@ public sealed class WebhookEndpointsIntegrationTests : IClassFixture<FindingsLed
{
AllowAutoRedirect = false
});
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token");
_client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "test-tenant");
}
#region Registration Tests

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Graph.Api.Contracts;
using StellaOps.Graph.Api.Security;
using StellaOps.Graph.Api.Services;
@@ -55,6 +56,7 @@ builder.Services.AddAuthorization(options =>
GraphScopeClaimReader.HasAnyScope(context.User, GraphPolicies.ExportScopes));
});
});
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -73,6 +75,7 @@ app.UseRouting();
app.TryUseStellaRouter(routerEnabled);
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.MapPost("/graph/search", async (HttpContext context, GraphSearchRequest request, IGraphSearchService service, CancellationToken ct) =>
{
@@ -109,7 +112,8 @@ app.MapPost("/graph/search", async (HttpContext context, GraphSearchRequest requ
LogAudit(context, "/graph/search", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Empty;
});
})
.RequireTenant();
app.MapPost("/graph/query", async (HttpContext context, GraphQueryRequest request, IGraphQueryService service, CancellationToken ct) =>
{
@@ -146,7 +150,8 @@ app.MapPost("/graph/query", async (HttpContext context, GraphQueryRequest reques
LogAudit(context, "/graph/query", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Empty;
});
})
.RequireTenant();
app.MapPost("/graph/paths", async (HttpContext context, GraphPathRequest request, IGraphPathService service, CancellationToken ct) =>
{
@@ -183,7 +188,8 @@ app.MapPost("/graph/paths", async (HttpContext context, GraphPathRequest request
LogAudit(context, "/graph/paths", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Empty;
});
})
.RequireTenant();
app.MapPost("/graph/diff", async (HttpContext context, GraphDiffRequest request, IGraphDiffService service, CancellationToken ct) =>
{
@@ -220,7 +226,8 @@ app.MapPost("/graph/diff", async (HttpContext context, GraphDiffRequest request,
LogAudit(context, "/graph/diff", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Empty;
});
})
.RequireTenant();
app.MapPost("/graph/lineage", async (HttpContext context, GraphLineageRequest request, IGraphLineageService service, CancellationToken ct) =>
{
@@ -249,7 +256,8 @@ app.MapPost("/graph/lineage", async (HttpContext context, GraphLineageRequest re
var response = await service.GetLineageAsync(tenantId, request, ct);
LogAudit(context, "/graph/lineage", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(response);
});
})
.RequireTenant();
app.MapPost("/graph/export", async (HttpContext context, GraphExportRequest request, IGraphExportService service, CancellationToken ct) =>
{
@@ -288,7 +296,8 @@ app.MapPost("/graph/export", async (HttpContext context, GraphExportRequest requ
};
LogAudit(context, "/graph/export", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(manifest);
});
})
.RequireTenant();
app.MapGet("/graph/export/{jobId}", async (string jobId, HttpContext context, IGraphExportService service, CancellationToken ct) =>
{
@@ -316,11 +325,12 @@ app.MapGet("/graph/export/{jobId}", async (string jobId, HttpContext context, IG
context.Response.Headers["X-Content-SHA256"] = job.Sha256;
LogAudit(context, "/graph/export/download", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.File(job.Payload, job.ContentType, $"graph-export-{job.JobId}.{job.Format}");
});
})
.RequireTenant();
// â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â
// â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"
// Edge Metadata API
// â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â€â
// â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"
app.MapPost("/graph/edges/metadata", async (EdgeMetadataRequest request, HttpContext context, IEdgeMetadataService service, CancellationToken ct) =>
{
@@ -340,7 +350,8 @@ app.MapPost("/graph/edges/metadata", async (EdgeMetadataRequest request, HttpCon
var response = await service.GetEdgeMetadataAsync(auth.TenantId!, request, ct);
LogAudit(context, "/graph/edges/metadata", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(response);
});
})
.RequireTenant();
app.MapGet("/graph/edges/{edgeId}/metadata", async (string edgeId, HttpContext context, IEdgeMetadataService service, CancellationToken ct) =>
{
@@ -366,7 +377,8 @@ app.MapGet("/graph/edges/{edgeId}/metadata", async (string edgeId, HttpContext c
LogAudit(context, "/graph/edges/metadata", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(result);
});
})
.RequireTenant();
app.MapGet("/graph/edges/path/{sourceNodeId}/{targetNodeId}", async (string sourceNodeId, string targetNodeId, HttpContext context, IEdgeMetadataService service, CancellationToken ct) =>
{
@@ -386,7 +398,8 @@ app.MapGet("/graph/edges/path/{sourceNodeId}/{targetNodeId}", async (string sour
var edges = await service.GetPathEdgesWithMetadataAsync(auth.TenantId!, sourceNodeId, targetNodeId, ct);
LogAudit(context, "/graph/edges/path", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(new { sourceNodeId, targetNodeId, edges = edges.ToList() });
});
})
.RequireTenant();
app.MapGet("/graph/edges/by-reason/{reason}", async (string reason, int? limit, string? cursor, HttpContext context, IEdgeMetadataService service, CancellationToken ct) =>
{
@@ -412,7 +425,8 @@ app.MapGet("/graph/edges/by-reason/{reason}", async (string reason, int? limit,
var response = await service.QueryByReasonAsync(auth.TenantId!, edgeReason, limit ?? 100, cursor, ct);
LogAudit(context, "/graph/edges/by-reason", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(response);
});
})
.RequireTenant();
app.MapGet("/graph/edges/by-evidence", async (string evidenceType, string evidenceRef, HttpContext context, IEdgeMetadataService service, CancellationToken ct) =>
{
@@ -432,7 +446,8 @@ app.MapGet("/graph/edges/by-evidence", async (string evidenceType, string eviden
var edges = await service.QueryByEvidenceAsync(auth.TenantId!, evidenceType, evidenceRef, ct);
LogAudit(context, "/graph/edges/by-evidence", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
return Results.Ok(edges);
});
})
.RequireTenant();
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));

View File

@@ -6,6 +6,7 @@ using StellaOps.IssuerDirectory.WebService.Contracts;
using StellaOps.IssuerDirectory.WebService.Options;
using StellaOps.IssuerDirectory.WebService.Security;
using StellaOps.IssuerDirectory.WebService.Services;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.IssuerDirectory.WebService.Endpoints;
@@ -14,7 +15,8 @@ public static class IssuerEndpoints
public static RouteGroupBuilder MapIssuerEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/issuer-directory/issuers")
.WithTags("Issuer Directory");
.WithTags("Issuer Directory")
.RequireTenant();
group.MapGet(string.Empty, ListIssuers)
.RequireAuthorization(IssuerDirectoryPolicies.Reader)

View File

@@ -5,6 +5,7 @@ using StellaOps.IssuerDirectory.WebService.Constants;
using StellaOps.IssuerDirectory.WebService.Contracts;
using StellaOps.IssuerDirectory.WebService.Security;
using StellaOps.IssuerDirectory.WebService.Services;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.IssuerDirectory.WebService.Endpoints;
@@ -12,7 +13,8 @@ internal static class IssuerKeyEndpoints
{
public static void MapIssuerKeyEndpoints(this RouteGroupBuilder group)
{
var keysGroup = group.MapGroup("{issuerId}/keys");
var keysGroup = group.MapGroup("{issuerId}/keys")
.RequireTenant();
keysGroup.MapGet(string.Empty, ListKeys)
.RequireAuthorization(IssuerDirectoryPolicies.Reader)

View File

@@ -4,6 +4,7 @@ using StellaOps.IssuerDirectory.WebService.Constants;
using StellaOps.IssuerDirectory.WebService.Contracts;
using StellaOps.IssuerDirectory.WebService.Security;
using StellaOps.IssuerDirectory.WebService.Services;
using StellaOps.Auth.ServerIntegration.Tenancy;
namespace StellaOps.IssuerDirectory.WebService.Endpoints;
@@ -11,7 +12,8 @@ internal static class IssuerTrustEndpoints
{
public static void MapIssuerTrustEndpoints(this RouteGroupBuilder group)
{
var trustGroup = group.MapGroup("{issuerId}/trust");
var trustGroup = group.MapGroup("{issuerId}/trust")
.RequireTenant();
trustGroup.MapGet(string.Empty, GetTrust)
.RequireAuthorization(IssuerDirectoryPolicies.Reader)

View File

@@ -11,6 +11,7 @@ using Serilog;
using Serilog.Events;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Configuration;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.IssuerDirectory.Core.Services;
@@ -100,6 +101,7 @@ builder.Services.AddOpenTelemetry()
.AddRuntimeInstrumentation())
.WithTracing(tracing => tracing.AddAspNetCoreInstrumentation().AddHttpClientInstrumentation());
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -117,6 +119,7 @@ app.UseSerilogRequestLogging();
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
var issuerGroup = app.MapIssuerEndpoints();

View File

@@ -398,8 +398,8 @@ static void ConfigureEndpoints(WebApplication app)
var options = app.Services.GetRequiredService<IOptions<NotifyWebServiceOptions>>().Value;
var tenantHeader = options.Api.TenantHeader;
var apiBasePath = options.Api.BasePath.TrimEnd('/');
var apiGroup = app.MapGroup(options.Api.BasePath);
var internalGroup = app.MapGroup(options.Api.InternalBasePath);
var apiGroup = app.MapGroup(options.Api.BasePath).RequireTenant();
var internalGroup = app.MapGroup(options.Api.InternalBasePath).RequireTenant();
internalGroup.MapPost("/rules/normalize", (JsonNode? body, NotifySchemaMigrationService service) => Normalize(body, service.UpgradeRule))
.WithName("notify.rules.normalize")

View File

@@ -8,6 +8,7 @@ using StellaOps.OpsMemory.Models;
using StellaOps.OpsMemory.Playbook;
using StellaOps.OpsMemory.Storage;
using StellaOps.OpsMemory.WebService.Security;
using StellaOps.Auth.ServerIntegration.Tenancy;
using System.Collections.Immutable;
namespace StellaOps.OpsMemory.WebService.Endpoints;
@@ -25,7 +26,8 @@ public static class OpsMemoryEndpoints
{
var group = app.MapGroup("/api/v1/opsmemory")
.WithTags("OpsMemory")
.RequireAuthorization(OpsMemoryPolicies.Read);
.RequireAuthorization(OpsMemoryPolicies.Read)
.RequireTenant();
group.MapPost("/decisions", RecordDecisionAsync)
.WithName("RecordDecision")

View File

@@ -2,6 +2,7 @@
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using Npgsql;
using StellaOps.Determinism;
using StellaOps.OpsMemory.Playbook;
@@ -47,6 +48,7 @@ builder.Services.AddAuthorization(options =>
options.AddStellaOpsScopePolicy(OpsMemoryPolicies.Write, StellaOpsScopes.OpsMemoryWrite);
});
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -69,6 +71,7 @@ if (app.Environment.IsDevelopment())
app.UseStellaOpsCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
app.TryUseStellaRouter(routerEnabled);
// Map endpoints

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -25,7 +26,8 @@ public static class ApprovalEndpoints
{
var group = app.MapGroup(prefix)
.WithTags("Approvals")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
var list = group.MapGet(string.Empty, ListApprovals)
.WithDescription("Return a list of release approval requests for the calling tenant, optionally filtered by status (Pending, Approved, Rejected), urgency level, and target environment. Each record includes the associated release, requester identity, SLA deadline, and policy gate context.");

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class AuditEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/audit")
.WithTags("Orchestrator Audit")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// List and get operations
group.MapGet(string.Empty, ListAuditEntries)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Core.Services;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class CircuitBreakerEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/circuit-breakers")
.WithTags("Orchestrator Circuit Breakers")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// List circuit breakers
group.MapGet(string.Empty, ListCircuitBreakers)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Scheduling;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class DagEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/dag")
.WithTags("Orchestrator DAG")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet("run/{runId:guid}", GetRunDag)
.WithName("Orchestrator_GetRunDag")

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.DeadLetter;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.WebService.Services;
@@ -22,7 +23,8 @@ public static class DeadLetterEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/deadletter")
.WithTags("Orchestrator Dead-Letter")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// Entry management
group.MapGet(string.Empty, ListEntries)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http.HttpResults;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Core.Domain.Export;
using StellaOps.Orchestrator.Core.Services;
@@ -18,7 +19,8 @@ public static class ExportJobEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/export")
.WithTags("Export Jobs")
.RequireAuthorization(OrchestratorPolicies.ExportViewer);
.RequireAuthorization(OrchestratorPolicies.ExportViewer)
.RequireTenant();
group.MapPost("jobs", CreateExportJob)
.WithName("Orchestrator_CreateExportJob")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Services;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -14,7 +15,8 @@ public static class FirstSignalEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/runs")
.WithTags("Orchestrator Runs")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet("{runId:guid}/first-signal", GetFirstSignal)
.WithName("Orchestrator_GetFirstSignal")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -17,7 +18,8 @@ public static class JobEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/jobs")
.WithTags("Orchestrator Jobs")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet(string.Empty, ListJobs)
.WithName("Orchestrator_ListJobs")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Metrics.Kpi;
namespace StellaOps.Orchestrator.WebService.Endpoints;
@@ -16,7 +17,8 @@ public static class KpiEndpoints
{
var group = app.MapGroup("/api/v1/metrics/kpis")
.WithTags("Quality KPIs")
.RequireAuthorization(OrchestratorPolicies.ObservabilityRead);
.RequireAuthorization(OrchestratorPolicies.ObservabilityRead)
.RequireTenant();
// GET /api/v1/metrics/kpis
group.MapGet("/", GetQualityKpis)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class LedgerEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/ledger")
.WithTags("Orchestrator Ledger")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// Ledger entry operations
group.MapGet(string.Empty, ListLedgerEntries)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -22,7 +23,8 @@ public static class PackRegistryEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/registry/packs")
.WithTags("Orchestrator Pack Registry")
.RequireAuthorization(OrchestratorPolicies.PacksRead);
.RequireAuthorization(OrchestratorPolicies.PacksRead)
.RequireTenant();
// Pack CRUD endpoints
group.MapPost("", CreatePack)

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using PackLogLevel = StellaOps.Orchestrator.Core.Domain.LogLevel;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Cryptography;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Core.Domain.Events;
@@ -38,7 +39,8 @@ public static class PackRunEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/pack-runs")
.WithTags("Orchestrator Pack Runs")
.RequireAuthorization(OrchestratorPolicies.PacksRead);
.RequireAuthorization(OrchestratorPolicies.PacksRead)
.RequireTenant();
// Scheduling endpoints
group.MapPost("", SchedulePackRun)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Infrastructure.Postgres;
using StellaOps.Orchestrator.Infrastructure.Repositories;
@@ -19,7 +20,8 @@ public static class QuotaEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/quotas")
.WithTags("Orchestrator Quotas")
.RequireAuthorization(OrchestratorPolicies.Quota);
.RequireAuthorization(OrchestratorPolicies.Quota)
.RequireTenant();
// Quota CRUD operations
group.MapGet(string.Empty, ListQuotas)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Core.Services;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class QuotaGovernanceEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/quota-governance")
.WithTags("Orchestrator Quota Governance")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// Policy management
group.MapGet("policies", ListPolicies)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -21,7 +22,8 @@ public static class ReleaseControlV2Endpoints
{
var approvals = app.MapGroup("/api/v1/approvals")
.WithTags("Approvals v2")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
approvals.MapGet(string.Empty, ListApprovals)
.WithName("ApprovalsV2_List")
@@ -73,13 +75,15 @@ public static class ReleaseControlV2Endpoints
var apiRuns = app.MapGroup("/api/v1/runs")
.WithTags("Runs v2")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
MapRunGroup(apiRuns);
apiRuns.WithGroupName("runs-v2");
var legacyV1Runs = app.MapGroup("/v1/runs")
.WithTags("Runs v2")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
MapRunGroup(legacyV1Runs);
legacyV1Runs.WithGroupName("runs-v1-compat");
}
@@ -88,7 +92,8 @@ public static class ReleaseControlV2Endpoints
{
var environments = app.MapGroup("/api/v1/environments")
.WithTags("Environments v2")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
environments.MapGet("/{id}", GetEnvironmentDetail)
.WithName("EnvironmentsV2_Get")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.WebService.Services;
namespace StellaOps.Orchestrator.WebService.Endpoints;
@@ -19,7 +20,8 @@ public static class ReleaseDashboardEndpoints
{
var group = app.MapGroup(prefix)
.WithTags("ReleaseDashboard")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
var dashboard = group.MapGet("/dashboard", GetDashboard)
.WithDescription("Return a consolidated release dashboard snapshot for the Console control plane, including pending approvals, active promotions, recent deployments, and environment health indicators. Used by the UI to populate the main release management view.");

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.WebService.Services;
namespace StellaOps.Orchestrator.WebService.Endpoints;
@@ -27,7 +28,8 @@ public static class ReleaseEndpoints
{
var group = app.MapGroup(prefix)
.WithTags("Releases")
.RequireAuthorization(OrchestratorPolicies.ReleaseRead);
.RequireAuthorization(OrchestratorPolicies.ReleaseRead)
.RequireTenant();
var list = group.MapGet(string.Empty, ListReleases)
.WithDescription("Return a paginated list of releases for the calling tenant, optionally filtered by status, environment, project, and creation time window. Each release record includes its name, version, current status, component count, and lifecycle timestamps.");

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -18,7 +19,8 @@ public static class RunEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/runs")
.WithTags("Orchestrator Runs")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet(string.Empty, ListRuns)
.WithName("Orchestrator_ListRuns")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Core.SloManagement;
using StellaOps.Orchestrator.WebService.Contracts;
@@ -18,7 +19,8 @@ public static class SloEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/slos")
.WithTags("Orchestrator SLOs")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
// SLO CRUD operations
group.MapGet(string.Empty, ListSlos)

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Contracts;
using StellaOps.Orchestrator.WebService.Services;
@@ -17,7 +18,8 @@ public static class SourceEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/sources")
.WithTags("Orchestrator Sources")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet(string.Empty, ListSources)
.WithName("Orchestrator_ListSources")

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Infrastructure.Repositories;
using StellaOps.Orchestrator.WebService.Services;
using StellaOps.Orchestrator.WebService.Streaming;
@@ -18,7 +19,8 @@ public static class StreamEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/stream")
.WithTags("Orchestrator Streams")
.RequireAuthorization(OrchestratorPolicies.Read);
.RequireAuthorization(OrchestratorPolicies.Read)
.RequireTenant();
group.MapGet("jobs/{jobId:guid}", StreamJob)
.WithName("Orchestrator_StreamJob")

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Orchestrator.Core.Domain;
using StellaOps.Orchestrator.Infrastructure;
using StellaOps.Orchestrator.Infrastructure.Repositories;
@@ -24,7 +25,8 @@ public static class WorkerEndpoints
{
var group = app.MapGroup("/api/v1/orchestrator/worker")
.WithTags("Orchestrator Workers")
.RequireAuthorization(OrchestratorPolicies.Operate);
.RequireAuthorization(OrchestratorPolicies.Operate)
.RequireTenant();
group.MapPost("claim", ClaimJob)
.WithName("Orchestrator_ClaimJob")

View File

@@ -9,11 +9,13 @@ using StellaOps.Orchestrator.WebService.Endpoints;
using StellaOps.Orchestrator.WebService.Services;
using StellaOps.Orchestrator.WebService.Streaming;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Router.AspNet;
using StellaOps.Telemetry.Core;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddRouting(options => options.LowercaseUrls = true);
builder.Services.AddEndpointsApiExplorer();
@@ -139,6 +141,7 @@ if (app.Environment.IsDevelopment())
}
app.UseStellaOpsCors();
app.UseStellaOpsTenantMiddleware();
// Enable telemetry context propagation (extracts tenant/actor/correlation from headers)
// Per ORCH-OBS-50-001

View File

@@ -9,6 +9,7 @@ using StellaOps.PacksRegistry.WebService;
using StellaOps.PacksRegistry.WebService.Contracts;
using StellaOps.PacksRegistry.WebService.Options;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Router.AspNet;
using System.Text.Json.Serialization;
@@ -57,6 +58,7 @@ builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddHealthChecks();
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
// Stella Router integration
@@ -76,6 +78,7 @@ if (app.Environment.IsDevelopment())
}
app.UseStellaOpsCors();
app.UseStellaOpsTenantMiddleware();
app.MapHealthChecks("/healthz");
app.TryUseStellaRouter(routerEnabled);
@@ -160,7 +163,8 @@ app.MapPost("/api/v1/packs", async (PackUploadRequest request, PackService servi
.WithDescription("Uploads a new policy pack as base64-encoded content with optional signature and provenance attachment. Returns 201 Created with the registered pack record and assigned pack ID. Requires the X-StellaOps-Tenant header or a tenantId body field.")
.Produces<PackResponse>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized);
.Produces(StatusCodes.Status401Unauthorized)
.RequireTenant();
app.MapGet("/api/v1/packs", async (string? tenant, bool? includeDeprecated, PackService service, LifecycleService lifecycleService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -197,7 +201,8 @@ app.MapGet("/api/v1/packs", async (string? tenant, bool? includeDeprecated, Pack
.WithName("ListPacks")
.WithDescription("Returns the list of policy packs for the specified tenant, optionally excluding deprecated packs. When tenant allowlists are configured, a tenant query parameter or X-StellaOps-Tenant header is required.")
.Produces<IEnumerable<PackResponse>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized);
.Produces(StatusCodes.Status401Unauthorized)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}", async (string packId, PackService service, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -226,7 +231,8 @@ app.MapGet("/api/v1/packs/{packId}", async (string packId, PackService service,
.Produces<PackResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/content", async (string packId, PackService service, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -262,7 +268,8 @@ app.MapGet("/api/v1/packs/{packId}/content", async (string packId, PackService s
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/provenance", async (string packId, PackService service, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -302,7 +309,8 @@ app.MapGet("/api/v1/packs/{packId}/provenance", async (string packId, PackServic
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/manifest", async (string packId, PackService service, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -344,7 +352,8 @@ app.MapGet("/api/v1/packs/{packId}/manifest", async (string packId, PackService
.Produces<PackManifestResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapPost("/api/v1/packs/{packId}/signature", async (string packId, RotateSignatureRequest request, PackService service, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -391,7 +400,8 @@ app.MapPost("/api/v1/packs/{packId}/signature", async (string packId, RotateSign
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapPost("/api/v1/packs/{packId}/attestations", async (string packId, AttestationUploadRequest request, AttestationService attestationService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -433,7 +443,8 @@ app.MapPost("/api/v1/packs/{packId}/attestations", async (string packId, Attesta
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/attestations", async (string packId, AttestationService attestationService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -461,7 +472,8 @@ app.MapGet("/api/v1/packs/{packId}/attestations", async (string packId, Attestat
.Produces<IEnumerable<AttestationResponse>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/attestations/{type}", async (string packId, string type, AttestationService attestationService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -496,7 +508,8 @@ app.MapGet("/api/v1/packs/{packId}/attestations/{type}", async (string packId, s
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/parity", async (string packId, ParityService parityService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -525,7 +538,8 @@ app.MapGet("/api/v1/packs/{packId}/parity", async (string packId, ParityService
.Produces<ParityResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/packs/{packId}/lifecycle", async (string packId, LifecycleService lifecycleService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -554,7 +568,8 @@ app.MapGet("/api/v1/packs/{packId}/lifecycle", async (string packId, LifecycleSe
.Produces<LifecycleResponse>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapPost("/api/v1/packs/{packId}/lifecycle", async (string packId, LifecycleRequest request, LifecycleService lifecycleService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -596,7 +611,8 @@ app.MapPost("/api/v1/packs/{packId}/lifecycle", async (string packId, LifecycleR
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapPost("/api/v1/packs/{packId}/parity", async (string packId, ParityRequest request, ParityService parityService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -638,7 +654,8 @@ app.MapPost("/api/v1/packs/{packId}/parity", async (string packId, ParityRequest
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapPost("/api/v1/export/offline-seed", async (OfflineSeedRequest request, ExportService exportService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -667,7 +684,8 @@ app.MapPost("/api/v1/export/offline-seed", async (OfflineSeedRequest request, Ex
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden);
.Produces(StatusCodes.Status403Forbidden)
.RequireTenant();
app.MapPost("/api/v1/mirrors", async (MirrorRequest request, MirrorService mirrorService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -700,7 +718,8 @@ app.MapPost("/api/v1/mirrors", async (MirrorRequest request, MirrorService mirro
.Produces<MirrorResponse>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden);
.Produces(StatusCodes.Status403Forbidden)
.RequireTenant();
app.MapGet("/api/v1/mirrors", async (string? tenant, MirrorService mirrorService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -723,7 +742,8 @@ app.MapGet("/api/v1/mirrors", async (string? tenant, MirrorService mirrorService
.WithDescription("Returns all mirror registrations for the specified tenant, or all mirrors if no tenant filter is applied. Returns 403 if the caller's tenant allowlist excludes the requested tenant.")
.Produces<IEnumerable<MirrorResponse>>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden);
.Produces(StatusCodes.Status403Forbidden)
.RequireTenant();
app.MapPost("/api/v1/mirrors/{id}/sync", async (string id, MirrorSyncRequest request, MirrorService mirrorService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -752,7 +772,8 @@ app.MapPost("/api/v1/mirrors/{id}/sync", async (string id, MirrorSyncRequest req
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
.Produces(StatusCodes.Status404NotFound)
.RequireTenant();
app.MapGet("/api/v1/compliance/summary", async (string? tenant, ComplianceService complianceService, HttpContext context, AuthOptions auth, CancellationToken cancellationToken) =>
{
@@ -775,7 +796,8 @@ app.MapGet("/api/v1/compliance/summary", async (string? tenant, ComplianceServic
.WithDescription("Returns a compliance summary for the specified tenant's pack collection including signed pack count, unsigned count, packs with attestations, deprecated packs, and mirror sync status breakdown. Returns 403 if the tenant is not allowed.")
.Produces<ComplianceSummary>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden);
.Produces(StatusCodes.Status403Forbidden)
.RequireTenant();
// Refresh Router endpoint cache
app.TryRefreshStellaRouterEndpoints(routerEnabled);

View File

@@ -2,6 +2,7 @@ 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;
@@ -20,7 +21,8 @@ public static class AdministrationTrustSigningMutationEndpoints
{
var group = app.MapGroup("/api/v1/administration/trust-signing")
.WithTags("Administration")
.RequireAuthorization(PlatformPolicies.TrustRead);
.RequireAuthorization(PlatformPolicies.TrustRead)
.RequireTenant();
group.MapGet("/keys", async Task<IResult>(
HttpContext context,

View File

@@ -3,6 +3,7 @@ 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;
@@ -18,7 +19,8 @@ public static class AnalyticsEndpoints
{
var analytics = app.MapGroup("/api/analytics")
.WithTags("Analytics")
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
.RequireAuthorization(PlatformPolicies.AnalyticsRead)
.RequireTenant();
analytics.MapGet("/suppliers", async Task<IResult> (
HttpContext context,

View File

@@ -2,6 +2,7 @@ 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;
@@ -16,7 +17,8 @@ public static class ContextEndpoints
{
var context = app.MapGroup("/api/v2/context")
.WithTags("Platform Context")
.RequireAuthorization(PlatformPolicies.ContextRead);
.RequireAuthorization(PlatformPolicies.ContextRead)
.RequireTenant();
context.MapGet("/regions", async Task<IResult>(
HttpContext httpContext,

View File

@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Services;
@@ -19,7 +20,8 @@ public static class EnvironmentSettingsAdminEndpoints
{
var group = app.MapGroup("/platform/envsettings/db")
.WithTags("Environment Settings Admin")
.RequireAuthorization(PlatformPolicies.SetupRead);
.RequireAuthorization(PlatformPolicies.SetupRead)
.RequireTenant();
group.MapGet("/", async (IEnvironmentSettingsStore store, CancellationToken ct) =>
{

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Options;
using StellaOps.Platform.WebService.Services;

View File

@@ -7,6 +7,7 @@ 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.Services;
using StellaOps.ReleaseOrchestrator.EvidenceThread.Export;
@@ -33,7 +34,8 @@ public static class EvidenceThreadEndpoints
{
var evidence = app.MapGroup("/api/v1/evidence")
.WithTags("Evidence Thread")
.RequireAuthorization(PlatformPolicies.ContextRead);
.RequireAuthorization(PlatformPolicies.ContextRead)
.RequireTenant();
// GET /api/v1/evidence/{artifactDigest} - Get evidence thread for artifact
evidence.MapGet("/{artifactDigest}", GetEvidenceThread)

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Services;
@@ -21,7 +22,8 @@ public static class FederationTelemetryEndpoints
{
var group = app.MapGroup("/api/v1/telemetry/federation")
.WithTags("Federated Telemetry")
.RequireAuthorization(PlatformPolicies.FederationRead);
.RequireAuthorization(PlatformPolicies.FederationRead)
.RequireTenant();
// GET /consent — get consent state
group.MapGet("/consent", async Task<IResult>(

View File

@@ -7,6 +7,7 @@ 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;
@@ -25,7 +26,8 @@ public static class FunctionMapEndpoints
{
var maps = app.MapGroup("/api/v1/function-maps")
.WithTags("Function Maps")
.RequireAuthorization(PlatformPolicies.FunctionMapRead);
.RequireAuthorization(PlatformPolicies.FunctionMapRead)
.RequireTenant();
MapCrudEndpoints(maps);
MapVerifyEndpoints(maps);

View File

@@ -2,6 +2,7 @@ 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;
@@ -16,7 +17,8 @@ public static class IntegrationReadModelEndpoints
{
var integrations = app.MapGroup("/api/v2/integrations")
.WithTags("Integrations V2")
.RequireAuthorization(PlatformPolicies.IntegrationsRead);
.RequireAuthorization(PlatformPolicies.IntegrationsRead)
.RequireTenant();
integrations.MapGet("/feeds", async Task<IResult>(
HttpContext context,

View File

@@ -2,6 +2,7 @@ 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;
@@ -17,7 +18,8 @@ public static class LegacyAliasEndpoints
{
var legacy = app.MapGroup("/api/v1")
.WithTags("Pack22 Legacy Aliases")
.RequireAuthorization(PlatformPolicies.ContextRead);
.RequireAuthorization(PlatformPolicies.ContextRead)
.RequireTenant();
legacy.MapGet("/context/regions", async Task<IResult>(
HttpContext context,

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Services;
@@ -44,7 +45,8 @@ public static class PackAdapterEndpoints
var platform = app.MapGroup("/api/v1/platform")
.WithTags("Platform Ops")
.RequireAuthorization(PlatformPolicies.HealthRead);
.RequireAuthorization(PlatformPolicies.HealthRead)
.RequireTenant();
platform.MapGet("/data-integrity/summary", (
HttpContext context,
@@ -158,7 +160,8 @@ public static class PackAdapterEndpoints
.RequireAuthorization(PlatformPolicies.HealthRead);
var administration = app.MapGroup("/api/v1/administration")
.WithTags("Administration");
.WithTags("Administration")
.RequireTenant();
administration.MapGet("/summary", (
HttpContext context,

View File

@@ -8,6 +8,7 @@ using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Services;
using System;
using System.Linq;
using StellaOps.Auth.ServerIntegration.Tenancy;
using System.Threading;
using System.Threading.Tasks;
@@ -19,7 +20,8 @@ public static class PlatformEndpoints
{
var platform = app.MapGroup("/api/v1/platform")
.WithTags("Platform")
.RequireAuthorization(PlatformPolicies.HealthRead);
.RequireAuthorization(PlatformPolicies.HealthRead)
.RequireTenant();
MapHealthEndpoints(platform);
MapQuotaEndpoints(platform);
@@ -478,7 +480,8 @@ public static class PlatformEndpoints
{
var quotas = app.MapGroup("/api/v1/authority/quotas")
.WithTags("Platform Quotas Compatibility")
.RequireAuthorization(PlatformPolicies.QuotaRead);
.RequireAuthorization(PlatformPolicies.QuotaRead)
.RequireTenant();
quotas.MapGet(string.Empty, async Task<IResult> (
HttpContext context,
@@ -715,7 +718,8 @@ public static class PlatformEndpoints
var rateLimits = app.MapGroup("/api/v1/gateway/rate-limits")
.WithTags("Platform Gateway Compatibility")
.RequireAuthorization(PlatformPolicies.QuotaRead);
.RequireAuthorization(PlatformPolicies.QuotaRead)
.RequireTenant();
rateLimits.MapGet(string.Empty, (HttpContext context, PlatformRequestContextResolver resolver) =>
{

View File

@@ -7,6 +7,7 @@ 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;
@@ -26,7 +27,8 @@ public static class PolicyInteropEndpoints
{
var interop = app.MapGroup("/api/v1/policy/interop")
.WithTags("PolicyInterop")
.RequireAuthorization(PlatformPolicies.PolicyRead);
.RequireAuthorization(PlatformPolicies.PolicyRead)
.RequireTenant();
MapExportEndpoint(interop);
MapImportEndpoint(interop);

View File

@@ -2,6 +2,7 @@ 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;
@@ -20,7 +21,8 @@ public static class ReleaseControlEndpoints
{
var bundles = app.MapGroup("/api/v1/release-control/bundles")
.WithTags("Release Control")
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
.RequireAuthorization(PlatformPolicies.ReleaseControlRead)
.RequireTenant();
bundles.MapGet(string.Empty, async Task<IResult>(
HttpContext context,

View File

@@ -2,6 +2,7 @@ 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;
@@ -17,7 +18,8 @@ public static class ReleaseReadModelEndpoints
{
var releases = app.MapGroup("/api/v2/releases")
.WithTags("Releases V2")
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
.RequireAuthorization(PlatformPolicies.ReleaseControlRead)
.RequireTenant();
releases.MapGet(string.Empty, async Task<IResult>(
HttpContext context,

View File

@@ -8,6 +8,7 @@ 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;
@@ -26,7 +27,8 @@ public static class ScoreEndpoints
{
var score = app.MapGroup("/api/v1/score")
.WithTags("Score")
.RequireAuthorization(PlatformPolicies.ScoreRead);
.RequireAuthorization(PlatformPolicies.ScoreRead)
.RequireTenant();
MapEvaluateEndpoints(score);
MapHistoryEndpoints(score);

Some files were not shown because too many files have changed in this diff Show More