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/ .tasks/
.agile/ .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 contracts | Contracts/Policies | Preserve policy semantics and required fields |
| thalos-service policy context contracts | Contracts/Context | Keep field naming stable for compatibility window | | 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 | | 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 ## Namespace Strategy
- Current Thalos namespaces are mapped to `BuildingBlock.Identity.Contracts.*`. - Current Thalos namespaces are mapped to `BuildingBlock.Identity.Contracts.*`.
@ -18,3 +19,4 @@
2. Add compatibility bridge in Thalos consumers. 2. Add compatibility bridge in Thalos consumers.
3. Migrate service consumers first, then BFF consumers. 3. Migrate service consumers first, then BFF consumers.
4. Deprecate old namespace usage after compatibility window. 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);
}
}