feat(thalos-service): propagate provider metadata through grpc flow
This commit is contained in:
parent
654a808c54
commit
f960f0656d
@ -7,6 +7,7 @@ Constrain thalos-service to orchestration responsibilities after thalos-domain e
|
|||||||
- Coordinate identity use-case flow
|
- Coordinate identity use-case flow
|
||||||
- Delegate policy/token decisions to thalos-domain abstractions
|
- Delegate policy/token decisions to thalos-domain abstractions
|
||||||
- Adapt transport contracts
|
- Adapt transport contracts
|
||||||
|
- Route provider metadata (`InternalJwt`, `AzureAd`, `Google`) between edge/service/dal boundaries
|
||||||
|
|
||||||
## Prohibited Responsibilities
|
## Prohibited Responsibilities
|
||||||
- Owning identity decision policies
|
- Owning identity decision policies
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Thalos.Service.Application.Grpc;
|
using Thalos.Service.Application.Grpc;
|
||||||
|
using BuildingBlock.Identity.Contracts.Conventions;
|
||||||
using BuildingBlock.Identity.Contracts.Requests;
|
using BuildingBlock.Identity.Contracts.Requests;
|
||||||
|
|
||||||
namespace Thalos.Service.Application.Adapters;
|
namespace Thalos.Service.Application.Adapters;
|
||||||
@ -11,12 +12,24 @@ public sealed class IdentityPolicyGrpcContractAdapter : IIdentityPolicyGrpcContr
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public EvaluateIdentityPolicyGrpcContract ToGrpc(EvaluateIdentityPolicyRequest request)
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public EvaluateIdentityPolicyRequest FromGrpc(EvaluateIdentityPolicyGrpcContract contract)
|
public EvaluateIdentityPolicyRequest FromGrpc(EvaluateIdentityPolicyGrpcContract contract)
|
||||||
{
|
{
|
||||||
return new EvaluateIdentityPolicyRequest(contract.SubjectId, contract.TenantId, contract.PermissionCode);
|
var provider = Enum.TryParse<IdentityAuthProvider>(contract.Provider, true, out var parsedProvider)
|
||||||
|
? parsedProvider
|
||||||
|
: IdentityAuthProvider.InternalJwt;
|
||||||
|
|
||||||
|
return new EvaluateIdentityPolicyRequest(
|
||||||
|
contract.SubjectId,
|
||||||
|
contract.TenantId,
|
||||||
|
contract.PermissionCode,
|
||||||
|
provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,4 +6,9 @@ namespace Thalos.Service.Application.Grpc;
|
|||||||
/// <param name="SubjectId">Identity subject identifier.</param>
|
/// <param name="SubjectId">Identity subject identifier.</param>
|
||||||
/// <param name="TenantId">Tenant scope identifier.</param>
|
/// <param name="TenantId">Tenant scope identifier.</param>
|
||||||
/// <param name="PermissionCode">Permission code to evaluate.</param>
|
/// <param name="PermissionCode">Permission code to evaluate.</param>
|
||||||
public sealed record EvaluateIdentityPolicyGrpcContract(string SubjectId, string TenantId, string PermissionCode);
|
/// <param name="Provider">Auth provider.</param>
|
||||||
|
public sealed record EvaluateIdentityPolicyGrpcContract(
|
||||||
|
string SubjectId,
|
||||||
|
string TenantId,
|
||||||
|
string PermissionCode,
|
||||||
|
string Provider = "InternalJwt");
|
||||||
|
|||||||
@ -20,7 +20,8 @@ public sealed class IdentityPolicyContextReadPortDalAdapter(
|
|||||||
CreateEnvelope(),
|
CreateEnvelope(),
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.TenantId,
|
request.TenantId,
|
||||||
request.PermissionCode);
|
request.PermissionCode,
|
||||||
|
request.Provider);
|
||||||
|
|
||||||
var policyRecord = await identityRepository.ReadIdentityPolicyAsync(policyLookupRequest);
|
var policyRecord = await identityRepository.ReadIdentityPolicyAsync(policyLookupRequest);
|
||||||
if (policyRecord is null)
|
if (policyRecord is null)
|
||||||
@ -28,6 +29,7 @@ public sealed class IdentityPolicyContextReadPortDalAdapter(
|
|||||||
return new IdentityPolicyContextData(
|
return new IdentityPolicyContextData(
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.PermissionCode,
|
request.PermissionCode,
|
||||||
|
request.Provider,
|
||||||
false,
|
false,
|
||||||
[]);
|
[]);
|
||||||
}
|
}
|
||||||
@ -35,7 +37,8 @@ public sealed class IdentityPolicyContextReadPortDalAdapter(
|
|||||||
var permissionSetRequest = new IdentityPermissionSetLookupRequest(
|
var permissionSetRequest = new IdentityPermissionSetLookupRequest(
|
||||||
policyLookupRequest.Envelope,
|
policyLookupRequest.Envelope,
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.TenantId);
|
request.TenantId,
|
||||||
|
request.Provider);
|
||||||
|
|
||||||
var permissions = await identityRepository.ReadPermissionSetAsync(permissionSetRequest);
|
var permissions = await identityRepository.ReadPermissionSetAsync(permissionSetRequest);
|
||||||
var grantedPermissions = permissions
|
var grantedPermissions = permissions
|
||||||
@ -45,6 +48,7 @@ public sealed class IdentityPolicyContextReadPortDalAdapter(
|
|||||||
return new IdentityPolicyContextData(
|
return new IdentityPolicyContextData(
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.PermissionCode,
|
request.PermissionCode,
|
||||||
|
policyRecord.Provider,
|
||||||
policyRecord.ContextSatisfied,
|
policyRecord.ContextSatisfied,
|
||||||
grantedPermissions);
|
grantedPermissions);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,15 +19,17 @@ public sealed class IdentityTokenReadPortDalAdapter(
|
|||||||
var lookupRequest = new IdentityTokenLookupRequest(
|
var lookupRequest = new IdentityTokenLookupRequest(
|
||||||
CreateEnvelope(),
|
CreateEnvelope(),
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.TenantId);
|
request.TenantId,
|
||||||
|
request.Provider,
|
||||||
|
request.ExternalToken);
|
||||||
|
|
||||||
var tokenRecord = await identityRepository.ReadIdentityTokenAsync(lookupRequest);
|
var tokenRecord = await identityRepository.ReadIdentityTokenAsync(lookupRequest);
|
||||||
if (tokenRecord is null)
|
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()
|
private IdentityContractEnvelope CreateEnvelope()
|
||||||
|
|||||||
@ -12,6 +12,8 @@ service IdentityRuntime {
|
|||||||
message IssueIdentityTokenGrpcRequest {
|
message IssueIdentityTokenGrpcRequest {
|
||||||
string subject_id = 1;
|
string subject_id = 1;
|
||||||
string tenant_id = 2;
|
string tenant_id = 2;
|
||||||
|
string provider = 3;
|
||||||
|
string external_token = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IssueIdentityTokenGrpcResponse {
|
message IssueIdentityTokenGrpcResponse {
|
||||||
@ -23,6 +25,7 @@ message EvaluateIdentityPolicyGrpcRequest {
|
|||||||
string subject_id = 1;
|
string subject_id = 1;
|
||||||
string tenant_id = 2;
|
string tenant_id = 2;
|
||||||
string permission_code = 3;
|
string permission_code = 3;
|
||||||
|
string provider = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EvaluateIdentityPolicyGrpcResponse {
|
message EvaluateIdentityPolicyGrpcResponse {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
using BuildingBlock.Identity.Contracts.Conventions;
|
||||||
using Thalos.Service.Application.Adapters;
|
using Thalos.Service.Application.Adapters;
|
||||||
using Thalos.Service.Application.Grpc;
|
using Thalos.Service.Application.Grpc;
|
||||||
using Thalos.Service.Application.UseCases;
|
using Thalos.Service.Application.UseCases;
|
||||||
@ -24,7 +25,11 @@ public sealed class IdentityRuntimeGrpcService(
|
|||||||
IssueIdentityTokenGrpcRequest request,
|
IssueIdentityTokenGrpcRequest request,
|
||||||
ServerCallContext context)
|
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);
|
var useCaseResponse = await issueIdentityTokenUseCase.HandleAsync(useCaseRequest);
|
||||||
|
|
||||||
return new IssueIdentityTokenGrpcResponse
|
return new IssueIdentityTokenGrpcResponse
|
||||||
@ -47,7 +52,8 @@ public sealed class IdentityRuntimeGrpcService(
|
|||||||
var grpcContract = new EvaluateIdentityPolicyGrpcContract(
|
var grpcContract = new EvaluateIdentityPolicyGrpcContract(
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.TenantId,
|
request.TenantId,
|
||||||
request.PermissionCode);
|
request.PermissionCode,
|
||||||
|
request.Provider);
|
||||||
|
|
||||||
var useCaseRequest = grpcContractAdapter.FromGrpc(grpcContract);
|
var useCaseRequest = grpcContractAdapter.FromGrpc(grpcContract);
|
||||||
var useCaseResponse = await evaluateIdentityPolicyUseCase.HandleAsync(useCaseRequest);
|
var useCaseResponse = await evaluateIdentityPolicyUseCase.HandleAsync(useCaseRequest);
|
||||||
@ -59,4 +65,11 @@ public sealed class IdentityRuntimeGrpcService(
|
|||||||
IsAllowed = useCaseResponse.IsAllowed
|
IsAllowed = useCaseResponse.IsAllowed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IdentityAuthProvider ParseProvider(string provider)
|
||||||
|
{
|
||||||
|
return Enum.TryParse<IdentityAuthProvider>(provider, true, out var parsedProvider)
|
||||||
|
? parsedProvider
|
||||||
|
: IdentityAuthProvider.InternalJwt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using BuildingBlock.Identity.Contracts.Requests;
|
using BuildingBlock.Identity.Contracts.Requests;
|
||||||
using BuildingBlock.Identity.Contracts.Responses;
|
using BuildingBlock.Identity.Contracts.Responses;
|
||||||
|
using BuildingBlock.Identity.Contracts.Conventions;
|
||||||
using Thalos.Service.Application.Ports;
|
using Thalos.Service.Application.Ports;
|
||||||
using Thalos.Service.Application.UseCases;
|
using Thalos.Service.Application.UseCases;
|
||||||
using Thalos.Domain.Contracts;
|
using Thalos.Domain.Contracts;
|
||||||
@ -27,7 +28,11 @@ public class EvaluateIdentityPolicyUseCaseTests
|
|||||||
{
|
{
|
||||||
public IdentityPolicyContextRequest BuildPolicyContextRequest(EvaluateIdentityPolicyRequest request)
|
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(
|
public EvaluateIdentityPolicyResponse Evaluate(
|
||||||
@ -48,6 +53,7 @@ public class EvaluateIdentityPolicyUseCaseTests
|
|||||||
return Task.FromResult(new IdentityPolicyContextData(
|
return Task.FromResult(new IdentityPolicyContextData(
|
||||||
request.SubjectId,
|
request.SubjectId,
|
||||||
request.PermissionCode,
|
request.PermissionCode,
|
||||||
|
IdentityAuthProvider.InternalJwt,
|
||||||
true,
|
true,
|
||||||
[request.PermissionCode]));
|
[request.PermissionCode]));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using BuildingBlock.Identity.Contracts.Requests;
|
using BuildingBlock.Identity.Contracts.Requests;
|
||||||
|
using BuildingBlock.Identity.Contracts.Conventions;
|
||||||
using Thalos.Service.Application.Ports;
|
using Thalos.Service.Application.Ports;
|
||||||
using Thalos.Service.Application.UseCases;
|
using Thalos.Service.Application.UseCases;
|
||||||
using Thalos.Domain.Contracts;
|
using Thalos.Domain.Contracts;
|
||||||
@ -25,7 +26,7 @@ public class IssueIdentityTokenUseCaseTests
|
|||||||
{
|
{
|
||||||
public Task<IdentityTokenData> ReadTokenAsync(IssueIdentityTokenRequest request)
|
public Task<IdentityTokenData> ReadTokenAsync(IssueIdentityTokenRequest request)
|
||||||
{
|
{
|
||||||
return Task.FromResult(new IdentityTokenData("token-123", 3600));
|
return Task.FromResult(new IdentityTokenData("token-123", 3600, IdentityAuthProvider.InternalJwt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using BuildingBlock.Identity.Contracts.Conventions;
|
||||||
using BuildingBlock.Identity.Contracts.Requests;
|
using BuildingBlock.Identity.Contracts.Requests;
|
||||||
using Thalos.Service.Application.Adapters;
|
using Thalos.Service.Application.Adapters;
|
||||||
using Thalos.Service.Application.DependencyInjection;
|
using Thalos.Service.Application.DependencyInjection;
|
||||||
@ -44,6 +45,26 @@ public class RuntimeWiringTests
|
|||||||
Assert.Equal(0, tokenResponse.ExpiresInSeconds);
|
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<IIssueIdentityTokenUseCase>();
|
||||||
|
|
||||||
|
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]
|
[Fact]
|
||||||
public void IdentityPolicyGrpcContractAdapter_WhenMapped_PreservesValues()
|
public void IdentityPolicyGrpcContractAdapter_WhenMapped_PreservesValues()
|
||||||
{
|
{
|
||||||
@ -62,12 +83,17 @@ public class RuntimeWiringTests
|
|||||||
public void IdentityPolicyGrpcContractAdapter_WhenFromGrpc_UsesExpectedContractShape()
|
public void IdentityPolicyGrpcContractAdapter_WhenFromGrpc_UsesExpectedContractShape()
|
||||||
{
|
{
|
||||||
var adapter = new IdentityPolicyGrpcContractAdapter();
|
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);
|
var request = adapter.FromGrpc(contract);
|
||||||
|
|
||||||
Assert.Equal("subject-9", request.SubjectId);
|
Assert.Equal("subject-9", request.SubjectId);
|
||||||
Assert.Equal("tenant-9", request.TenantId);
|
Assert.Equal("tenant-9", request.TenantId);
|
||||||
Assert.Equal("identity.token.issue", request.PermissionCode);
|
Assert.Equal("identity.token.issue", request.PermissionCode);
|
||||||
|
Assert.Equal(IdentityAuthProvider.Google, request.Provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user