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);
}
}