merge(identity): integrate identity contracts baseline

This commit is contained in:
José René White Enciso 2026-02-25 14:15:53 -06:00
commit 3bb0ce47ef
23 changed files with 364 additions and 0 deletions

6
.gitignore vendored
View File

@ -1,2 +1,8 @@
.tasks/
.agile/
bin/
obj/
TestResults/
.vs/
*.user
*.suo

View File

@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/BuildingBlock.Identity.Contracts/BuildingBlock.Identity.Contracts.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/BuildingBlock.Identity.Contracts.UnitTests/BuildingBlock.Identity.Contracts.UnitTests.csproj" />
</Folder>
</Solution>

View File

@ -8,6 +8,7 @@
| thalos-service policy contracts | Contracts/Policies | Preserve policy semantics and required fields |
| thalos-service policy context contracts | Contracts/Context | Keep field naming stable for compatibility window |
| thalos-bff refresh session contracts | Contracts/Sessions | Candidate for shared capability standardization |
| provider flow metadata (JWT/Azure/Google) | Contracts/Conventions | Provider metadata stays transport-neutral and additive |
## Namespace Strategy
- Current Thalos namespaces are mapped to `BuildingBlock.Identity.Contracts.*`.
@ -18,3 +19,4 @@
2. Add compatibility bridge in Thalos consumers.
3. Migrate service consumers first, then BFF consumers.
4. Deprecate old namespace usage after compatibility window.
5. Keep provider enum and provider-specific fields additive to avoid breaking consumers.

View File

@ -0,0 +1,8 @@
namespace BuildingBlock.Identity.Contracts.Abstractions;
/// <summary>
/// Marker contract for identity capability request/response definitions.
/// </summary>
public interface IIdentityCapabilityContract
{
}

View File

@ -0,0 +1,20 @@
using BuildingBlock.Identity.Contracts.Grpc;
using BuildingBlock.Identity.Contracts.Requests;
namespace BuildingBlock.Identity.Contracts.Adapters;
/// <summary>
/// Defines gRPC mapping boundary for identity capability requests.
/// </summary>
public interface IIdentityGrpcContractAdapter
{
/// <summary>
/// Maps request into gRPC contract shape.
/// </summary>
IdentityPolicyGrpcContract ToGrpc(EvaluateIdentityPolicyRequest request);
/// <summary>
/// Maps gRPC contract shape into request.
/// </summary>
EvaluateIdentityPolicyRequest FromGrpc(IdentityPolicyGrpcContract contract);
}

View File

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\blueprint-platform\src\Core.Blueprint.Common\Core.Blueprint.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,12 @@
namespace BuildingBlock.Identity.Contracts.Conventions;
/// <summary>
/// Defines shared envelope metadata for identity capability requests.
/// </summary>
public interface IIdentityContractRequest
{
/// <summary>
/// Gets request envelope metadata.
/// </summary>
IdentityContractEnvelope Envelope { get; }
}

View File

@ -0,0 +1,22 @@
namespace BuildingBlock.Identity.Contracts.Conventions;
/// <summary>
/// Supported identity authentication providers.
/// </summary>
public enum IdentityAuthProvider
{
/// <summary>
/// AgileWebs-issued internal JWT flow.
/// </summary>
InternalJwt = 0,
/// <summary>
/// Microsoft Entra ID / Azure AD OAuth/OIDC flow.
/// </summary>
AzureAd = 1,
/// <summary>
/// Google OAuth/OIDC flow.
/// </summary>
Google = 2
}

View File

@ -0,0 +1,8 @@
namespace BuildingBlock.Identity.Contracts.Conventions;
/// <summary>
/// Defines transport-neutral envelope metadata for identity contract messages.
/// </summary>
/// <param name="ContractVersion">Contract schema version.</param>
/// <param name="CorrelationId">Correlation identifier for cross-service tracing.</param>
public sealed record IdentityContractEnvelope(string ContractVersion, string CorrelationId);

View File

@ -0,0 +1,8 @@
namespace BuildingBlock.Identity.Contracts.Conventions;
/// <summary>
/// Marker type for identity capability package discovery.
/// </summary>
public sealed class IdentityPackageContract
{
}

View File

@ -0,0 +1,14 @@
namespace BuildingBlock.Identity.Contracts.Grpc;
/// <summary>
/// Defines transport-neutral shape for policy evaluation gRPC contract mapping.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="PermissionCode">Permission code.</param>
/// <param name="Provider">Auth provider.</param>
public sealed record IdentityPolicyGrpcContract(
string SubjectId,
string TenantId,
string PermissionCode,
string Provider = "InternalJwt");

View File

@ -0,0 +1,18 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
/// <summary>
/// Requests identity policy evaluation for a subject and permission.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="PermissionCode">Permission code to evaluate.</param>
/// <param name="Provider">Auth provider used for the request.</param>
public sealed record EvaluateIdentityPolicyRequest(
string SubjectId,
string TenantId,
string PermissionCode,
IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,17 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
/// <summary>
/// Requests identity provider token exchange for a subject token.
/// </summary>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="Provider">External identity provider.</param>
/// <param name="ExternalToken">Provider-issued token (id/access token).</param>
/// <param name="CorrelationId">Correlation identifier.</param>
public sealed record ExchangeIdentityProviderTokenRequest(
string TenantId,
IdentityAuthProvider Provider,
string ExternalToken,
string CorrelationId = "") : IIdentityCapabilityContract;

View File

@ -0,0 +1,18 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
/// <summary>
/// Requests identity policy context lookup.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="PermissionCode">Permission code to evaluate.</param>
/// <param name="Provider">Auth provider used for the request.</param>
public sealed record IdentityPolicyContextRequest(
string SubjectId,
string TenantId,
string PermissionCode,
IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,17 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
/// <summary>
/// Requests identity token issuance.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="Provider">Auth provider used for the request.</param>
/// <param name="ExternalToken">External provider token when applicable.</param>
public sealed record IssueIdentityTokenRequest(
string SubjectId,
string TenantId,
IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt,
string ExternalToken = "") : IIdentityCapabilityContract;

View File

@ -0,0 +1,16 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
/// <summary>
/// Requests identity session refresh.
/// </summary>
/// <param name="RefreshToken">Refresh token value.</param>
/// <param name="CorrelationId">Correlation identifier.</param>
/// <param name="Provider">Auth provider used for the request.</param>
public sealed record RefreshIdentitySessionRequest(
string RefreshToken,
string CorrelationId,
IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,12 @@
using BuildingBlock.Identity.Contracts.Abstractions;
namespace BuildingBlock.Identity.Contracts.Responses;
/// <summary>
/// Returns identity policy evaluation result.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="PermissionCode">Permission code evaluated.</param>
/// <param name="IsAllowed">Whether access is granted.</param>
public sealed record EvaluateIdentityPolicyResponse(string SubjectId, string PermissionCode, bool IsAllowed)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,17 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Responses;
/// <summary>
/// Returns token exchange result from an external provider token.
/// </summary>
/// <param name="SubjectId">Resolved identity subject identifier.</param>
/// <param name="TenantId">Tenant identifier.</param>
/// <param name="Provider">External identity provider.</param>
/// <param name="IsAuthenticated">Whether provider token was accepted.</param>
public sealed record ExchangeIdentityProviderTokenResponse(
string SubjectId,
string TenantId,
IdentityAuthProvider Provider,
bool IsAuthenticated) : IIdentityCapabilityContract;

View File

@ -0,0 +1,12 @@
using BuildingBlock.Identity.Contracts.Abstractions;
namespace BuildingBlock.Identity.Contracts.Responses;
/// <summary>
/// Returns policy context lookup result.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="PermissionCode">Permission code evaluated.</param>
/// <param name="ContextSatisfied">Whether contextual policy requirements were satisfied.</param>
public sealed record IdentityPolicyContextResponse(string SubjectId, string PermissionCode, bool ContextSatisfied)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,10 @@
using BuildingBlock.Identity.Contracts.Abstractions;
namespace BuildingBlock.Identity.Contracts.Responses;
/// <summary>
/// Returns issued identity token payload.
/// </summary>
/// <param name="Token">Issued token value.</param>
/// <param name="ExpiresInSeconds">Token lifetime in seconds.</param>
public sealed record IssueIdentityTokenResponse(string Token, int ExpiresInSeconds) : IIdentityCapabilityContract;

View File

@ -0,0 +1,11 @@
using BuildingBlock.Identity.Contracts.Abstractions;
namespace BuildingBlock.Identity.Contracts.Responses;
/// <summary>
/// Returns refreshed identity session token payload.
/// </summary>
/// <param name="Token">Refreshed token value.</param>
/// <param name="ExpiresInSeconds">Token lifetime in seconds.</param>
public sealed record RefreshIdentitySessionResponse(string Token, int ExpiresInSeconds)
: IIdentityCapabilityContract;

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BuildingBlock.Identity.Contracts\BuildingBlock.Identity.Contracts.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,72 @@
using BuildingBlock.Identity.Contracts.Abstractions;
using BuildingBlock.Identity.Contracts.Conventions;
using BuildingBlock.Identity.Contracts.Requests;
using BuildingBlock.Identity.Contracts.Responses;
namespace BuildingBlock.Identity.Contracts.UnitTests;
public class ContractShapeTests
{
[Fact]
public void IdentityEnvelope_WhenCreated_PreservesValues()
{
var envelope = new IdentityContractEnvelope("1.0.0", "corr-identity-001");
Assert.Equal("1.0.0", envelope.ContractVersion);
Assert.Equal("corr-identity-001", envelope.CorrelationId);
}
[Fact]
public void IdentityContracts_WhenInstantiated_ImplementMarkerInterface()
{
IIdentityCapabilityContract issueRequest = new IssueIdentityTokenRequest(
"subject-a",
"tenant-a",
IdentityAuthProvider.AzureAd,
"external-token");
IIdentityCapabilityContract issueResponse = new IssueIdentityTokenResponse("token-a", 1800);
IIdentityCapabilityContract policyRequest = new EvaluateIdentityPolicyRequest(
"subject-b",
"tenant-b",
"identity.token.issue",
IdentityAuthProvider.Google);
IIdentityCapabilityContract policyResponse = new EvaluateIdentityPolicyResponse("subject-b", "identity.token.issue", true);
IIdentityCapabilityContract refreshRequest = new RefreshIdentitySessionRequest(
"refresh-a",
"corr-refresh",
IdentityAuthProvider.InternalJwt);
IIdentityCapabilityContract refreshResponse = new RefreshIdentitySessionResponse("token-b", 900);
IIdentityCapabilityContract exchangeRequest = new ExchangeIdentityProviderTokenRequest(
"tenant-c",
IdentityAuthProvider.AzureAd,
"provider-token",
"corr-exchange");
IIdentityCapabilityContract exchangeResponse = new ExchangeIdentityProviderTokenResponse(
"subject-c",
"tenant-c",
IdentityAuthProvider.AzureAd,
true);
Assert.NotNull(issueRequest);
Assert.NotNull(issueResponse);
Assert.NotNull(policyRequest);
Assert.NotNull(policyResponse);
Assert.NotNull(refreshRequest);
Assert.NotNull(refreshResponse);
Assert.NotNull(exchangeRequest);
Assert.NotNull(exchangeResponse);
}
[Fact]
public void IssueIdentityTokenRequest_WhenProviderSpecified_PreservesProviderMetadata()
{
var request = new IssueIdentityTokenRequest(
"subject-1",
"tenant-1",
IdentityAuthProvider.Google,
"google-token");
Assert.Equal(IdentityAuthProvider.Google, request.Provider);
Assert.Equal("google-token", request.ExternalToken);
}
}