From f960f0656da2fe0a156f1a1beedaf3f27075d1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ren=C3=A9=20White=20Enciso?= Date: Wed, 25 Feb 2026 13:13:56 -0600 Subject: [PATCH] feat(thalos-service): propagate provider metadata through grpc flow --- .../service-orchestration-boundary.md | 1 + .../IdentityPolicyGrpcContractAdapter.cs | 17 +++++++++-- .../EvaluateIdentityPolicyGrpcContract.cs | 7 ++++- ...IdentityPolicyContextReadPortDalAdapter.cs | 8 ++++-- .../Ports/IdentityTokenReadPortDalAdapter.cs | 8 ++++-- .../Protos/identity_runtime.proto | 3 ++ .../Services/IdentityRuntimeGrpcService.cs | 17 +++++++++-- .../EvaluateIdentityPolicyUseCaseTests.cs | 8 +++++- .../IssueIdentityTokenUseCaseTests.cs | 3 +- .../RuntimeWiringTests.cs | 28 ++++++++++++++++++- 10 files changed, 87 insertions(+), 13 deletions(-) diff --git a/docs/architecture/service-orchestration-boundary.md b/docs/architecture/service-orchestration-boundary.md index 5fb9d1d..bc5b098 100644 --- a/docs/architecture/service-orchestration-boundary.md +++ b/docs/architecture/service-orchestration-boundary.md @@ -7,6 +7,7 @@ Constrain thalos-service to orchestration responsibilities after thalos-domain e - Coordinate identity use-case flow - Delegate policy/token decisions to thalos-domain abstractions - Adapt transport contracts +- Route provider metadata (`InternalJwt`, `AzureAd`, `Google`) between edge/service/dal boundaries ## Prohibited Responsibilities - Owning identity decision policies diff --git a/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs b/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs index ebfde41..25cfde3 100644 --- a/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs +++ b/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs @@ -1,4 +1,5 @@ using Thalos.Service.Application.Grpc; +using BuildingBlock.Identity.Contracts.Conventions; using BuildingBlock.Identity.Contracts.Requests; namespace Thalos.Service.Application.Adapters; @@ -11,12 +12,24 @@ public sealed class IdentityPolicyGrpcContractAdapter : IIdentityPolicyGrpcContr /// public EvaluateIdentityPolicyGrpcContract ToGrpc(EvaluateIdentityPolicyRequest request) { - return new EvaluateIdentityPolicyGrpcContract(request.SubjectId, request.TenantId, request.PermissionCode); + return new EvaluateIdentityPolicyGrpcContract( + request.SubjectId, + request.TenantId, + request.PermissionCode, + request.Provider.ToString()); } /// public EvaluateIdentityPolicyRequest FromGrpc(EvaluateIdentityPolicyGrpcContract contract) { - return new EvaluateIdentityPolicyRequest(contract.SubjectId, contract.TenantId, contract.PermissionCode); + var provider = Enum.TryParse(contract.Provider, true, out var parsedProvider) + ? parsedProvider + : IdentityAuthProvider.InternalJwt; + + return new EvaluateIdentityPolicyRequest( + contract.SubjectId, + contract.TenantId, + contract.PermissionCode, + provider); } } diff --git a/src/Thalos.Service.Application/Grpc/EvaluateIdentityPolicyGrpcContract.cs b/src/Thalos.Service.Application/Grpc/EvaluateIdentityPolicyGrpcContract.cs index 8fd286d..2dfacea 100644 --- a/src/Thalos.Service.Application/Grpc/EvaluateIdentityPolicyGrpcContract.cs +++ b/src/Thalos.Service.Application/Grpc/EvaluateIdentityPolicyGrpcContract.cs @@ -6,4 +6,9 @@ namespace Thalos.Service.Application.Grpc; /// Identity subject identifier. /// Tenant scope identifier. /// Permission code to evaluate. -public sealed record EvaluateIdentityPolicyGrpcContract(string SubjectId, string TenantId, string PermissionCode); +/// Auth provider. +public sealed record EvaluateIdentityPolicyGrpcContract( + string SubjectId, + string TenantId, + string PermissionCode, + string Provider = "InternalJwt"); diff --git a/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs b/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs index d66caa9..e3f7825 100644 --- a/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs +++ b/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs @@ -20,7 +20,8 @@ public sealed class IdentityPolicyContextReadPortDalAdapter( CreateEnvelope(), request.SubjectId, request.TenantId, - request.PermissionCode); + request.PermissionCode, + request.Provider); var policyRecord = await identityRepository.ReadIdentityPolicyAsync(policyLookupRequest); if (policyRecord is null) @@ -28,6 +29,7 @@ public sealed class IdentityPolicyContextReadPortDalAdapter( return new IdentityPolicyContextData( request.SubjectId, request.PermissionCode, + request.Provider, false, []); } @@ -35,7 +37,8 @@ public sealed class IdentityPolicyContextReadPortDalAdapter( var permissionSetRequest = new IdentityPermissionSetLookupRequest( policyLookupRequest.Envelope, request.SubjectId, - request.TenantId); + request.TenantId, + request.Provider); var permissions = await identityRepository.ReadPermissionSetAsync(permissionSetRequest); var grantedPermissions = permissions @@ -45,6 +48,7 @@ public sealed class IdentityPolicyContextReadPortDalAdapter( return new IdentityPolicyContextData( request.SubjectId, request.PermissionCode, + policyRecord.Provider, policyRecord.ContextSatisfied, grantedPermissions); } diff --git a/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs b/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs index 834ec48..1b40dcc 100644 --- a/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs +++ b/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs @@ -19,15 +19,17 @@ public sealed class IdentityTokenReadPortDalAdapter( var lookupRequest = new IdentityTokenLookupRequest( CreateEnvelope(), request.SubjectId, - request.TenantId); + request.TenantId, + request.Provider, + request.ExternalToken); var tokenRecord = await identityRepository.ReadIdentityTokenAsync(lookupRequest); if (tokenRecord is null) { - return new IdentityTokenData(null, null); + return new IdentityTokenData(null, null, request.Provider); } - return new IdentityTokenData(tokenRecord.Token, tokenRecord.ExpiresInSeconds); + return new IdentityTokenData(tokenRecord.Token, tokenRecord.ExpiresInSeconds, tokenRecord.Provider); } private IdentityContractEnvelope CreateEnvelope() diff --git a/src/Thalos.Service.Grpc/Protos/identity_runtime.proto b/src/Thalos.Service.Grpc/Protos/identity_runtime.proto index 80fcbce..02b620b 100644 --- a/src/Thalos.Service.Grpc/Protos/identity_runtime.proto +++ b/src/Thalos.Service.Grpc/Protos/identity_runtime.proto @@ -12,6 +12,8 @@ service IdentityRuntime { message IssueIdentityTokenGrpcRequest { string subject_id = 1; string tenant_id = 2; + string provider = 3; + string external_token = 4; } message IssueIdentityTokenGrpcResponse { @@ -23,6 +25,7 @@ message EvaluateIdentityPolicyGrpcRequest { string subject_id = 1; string tenant_id = 2; string permission_code = 3; + string provider = 4; } message EvaluateIdentityPolicyGrpcResponse { diff --git a/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs b/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs index 84c5991..43677db 100644 --- a/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs +++ b/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs @@ -1,4 +1,5 @@ using Grpc.Core; +using BuildingBlock.Identity.Contracts.Conventions; using Thalos.Service.Application.Adapters; using Thalos.Service.Application.Grpc; using Thalos.Service.Application.UseCases; @@ -24,7 +25,11 @@ public sealed class IdentityRuntimeGrpcService( IssueIdentityTokenGrpcRequest request, ServerCallContext context) { - var useCaseRequest = new IssueIdentityTokenRequest(request.SubjectId, request.TenantId); + var useCaseRequest = new IssueIdentityTokenRequest( + request.SubjectId, + request.TenantId, + ParseProvider(request.Provider), + request.ExternalToken); var useCaseResponse = await issueIdentityTokenUseCase.HandleAsync(useCaseRequest); return new IssueIdentityTokenGrpcResponse @@ -47,7 +52,8 @@ public sealed class IdentityRuntimeGrpcService( var grpcContract = new EvaluateIdentityPolicyGrpcContract( request.SubjectId, request.TenantId, - request.PermissionCode); + request.PermissionCode, + request.Provider); var useCaseRequest = grpcContractAdapter.FromGrpc(grpcContract); var useCaseResponse = await evaluateIdentityPolicyUseCase.HandleAsync(useCaseRequest); @@ -59,4 +65,11 @@ public sealed class IdentityRuntimeGrpcService( IsAllowed = useCaseResponse.IsAllowed }; } + + private static IdentityAuthProvider ParseProvider(string provider) + { + return Enum.TryParse(provider, true, out var parsedProvider) + ? parsedProvider + : IdentityAuthProvider.InternalJwt; + } } diff --git a/tests/Thalos.Service.Application.UnitTests/EvaluateIdentityPolicyUseCaseTests.cs b/tests/Thalos.Service.Application.UnitTests/EvaluateIdentityPolicyUseCaseTests.cs index b9f7697..4660373 100644 --- a/tests/Thalos.Service.Application.UnitTests/EvaluateIdentityPolicyUseCaseTests.cs +++ b/tests/Thalos.Service.Application.UnitTests/EvaluateIdentityPolicyUseCaseTests.cs @@ -1,5 +1,6 @@ using BuildingBlock.Identity.Contracts.Requests; using BuildingBlock.Identity.Contracts.Responses; +using BuildingBlock.Identity.Contracts.Conventions; using Thalos.Service.Application.Ports; using Thalos.Service.Application.UseCases; using Thalos.Domain.Contracts; @@ -27,7 +28,11 @@ public class EvaluateIdentityPolicyUseCaseTests { public IdentityPolicyContextRequest BuildPolicyContextRequest(EvaluateIdentityPolicyRequest request) { - return new IdentityPolicyContextRequest(request.SubjectId, request.TenantId, request.PermissionCode); + return new IdentityPolicyContextRequest( + request.SubjectId, + request.TenantId, + request.PermissionCode, + request.Provider); } public EvaluateIdentityPolicyResponse Evaluate( @@ -48,6 +53,7 @@ public class EvaluateIdentityPolicyUseCaseTests return Task.FromResult(new IdentityPolicyContextData( request.SubjectId, request.PermissionCode, + IdentityAuthProvider.InternalJwt, true, [request.PermissionCode])); } diff --git a/tests/Thalos.Service.Application.UnitTests/IssueIdentityTokenUseCaseTests.cs b/tests/Thalos.Service.Application.UnitTests/IssueIdentityTokenUseCaseTests.cs index 49282b3..888ee25 100644 --- a/tests/Thalos.Service.Application.UnitTests/IssueIdentityTokenUseCaseTests.cs +++ b/tests/Thalos.Service.Application.UnitTests/IssueIdentityTokenUseCaseTests.cs @@ -1,4 +1,5 @@ using BuildingBlock.Identity.Contracts.Requests; +using BuildingBlock.Identity.Contracts.Conventions; using Thalos.Service.Application.Ports; using Thalos.Service.Application.UseCases; using Thalos.Domain.Contracts; @@ -25,7 +26,7 @@ public class IssueIdentityTokenUseCaseTests { public Task ReadTokenAsync(IssueIdentityTokenRequest request) { - return Task.FromResult(new IdentityTokenData("token-123", 3600)); + return Task.FromResult(new IdentityTokenData("token-123", 3600, IdentityAuthProvider.InternalJwt)); } } } diff --git a/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs b/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs index ca99bce..60c2aa3 100644 --- a/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs +++ b/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using BuildingBlock.Identity.Contracts.Conventions; using BuildingBlock.Identity.Contracts.Requests; using Thalos.Service.Application.Adapters; using Thalos.Service.Application.DependencyInjection; @@ -44,6 +45,26 @@ public class RuntimeWiringTests Assert.Equal(0, tokenResponse.ExpiresInSeconds); } + [Fact] + public async Task AddThalosServiceRuntime_WhenAzureProviderUsed_IssuesProviderToken() + { + var services = new ServiceCollection(); + services.AddThalosServiceRuntime(); + + using var provider = services.BuildServiceProvider(); + var issueTokenUseCase = provider.GetRequiredService(); + + var tokenResponse = await issueTokenUseCase.HandleAsync( + new IssueIdentityTokenRequest( + string.Empty, + "tenant-2", + IdentityAuthProvider.AzureAd, + "azure-id-token")); + + Assert.StartsWith("azure:", tokenResponse.Token); + Assert.True(tokenResponse.ExpiresInSeconds > 0); + } + [Fact] public void IdentityPolicyGrpcContractAdapter_WhenMapped_PreservesValues() { @@ -62,12 +83,17 @@ public class RuntimeWiringTests public void IdentityPolicyGrpcContractAdapter_WhenFromGrpc_UsesExpectedContractShape() { var adapter = new IdentityPolicyGrpcContractAdapter(); - var contract = new EvaluateIdentityPolicyGrpcContract("subject-9", "tenant-9", "identity.token.issue"); + var contract = new EvaluateIdentityPolicyGrpcContract( + "subject-9", + "tenant-9", + "identity.token.issue", + IdentityAuthProvider.Google.ToString()); var request = adapter.FromGrpc(contract); Assert.Equal("subject-9", request.SubjectId); Assert.Equal("tenant-9", request.TenantId); Assert.Equal("identity.token.issue", request.PermissionCode); + Assert.Equal(IdentityAuthProvider.Google, request.Provider); } }