diff --git a/.gitignore b/.gitignore index 31c7257..89d521f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ .tasks/ .agile/ +bin/ +obj/ +TestResults/ +.vs/ +*.user +*.suo diff --git a/BuildingBlock.Identity.slnx b/BuildingBlock.Identity.slnx new file mode 100644 index 0000000..3ca3850 --- /dev/null +++ b/BuildingBlock.Identity.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/migration/contract-extraction-map.md b/docs/migration/contract-extraction-map.md index a1a120e..e0860fa 100644 --- a/docs/migration/contract-extraction-map.md +++ b/docs/migration/contract-extraction-map.md @@ -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. diff --git a/src/BuildingBlock.Identity.Contracts/Abstractions/IIdentityCapabilityContract.cs b/src/BuildingBlock.Identity.Contracts/Abstractions/IIdentityCapabilityContract.cs new file mode 100644 index 0000000..f5a73bc --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Abstractions/IIdentityCapabilityContract.cs @@ -0,0 +1,8 @@ +namespace BuildingBlock.Identity.Contracts.Abstractions; + +/// +/// Marker contract for identity capability request/response definitions. +/// +public interface IIdentityCapabilityContract +{ +} diff --git a/src/BuildingBlock.Identity.Contracts/Adapters/IIdentityGrpcContractAdapter.cs b/src/BuildingBlock.Identity.Contracts/Adapters/IIdentityGrpcContractAdapter.cs new file mode 100644 index 0000000..b985ccb --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Adapters/IIdentityGrpcContractAdapter.cs @@ -0,0 +1,20 @@ +using BuildingBlock.Identity.Contracts.Grpc; +using BuildingBlock.Identity.Contracts.Requests; + +namespace BuildingBlock.Identity.Contracts.Adapters; + +/// +/// Defines gRPC mapping boundary for identity capability requests. +/// +public interface IIdentityGrpcContractAdapter +{ + /// + /// Maps request into gRPC contract shape. + /// + IdentityPolicyGrpcContract ToGrpc(EvaluateIdentityPolicyRequest request); + + /// + /// Maps gRPC contract shape into request. + /// + EvaluateIdentityPolicyRequest FromGrpc(IdentityPolicyGrpcContract contract); +} diff --git a/src/BuildingBlock.Identity.Contracts/BuildingBlock.Identity.Contracts.csproj b/src/BuildingBlock.Identity.Contracts/BuildingBlock.Identity.Contracts.csproj new file mode 100644 index 0000000..04a4bbc --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/BuildingBlock.Identity.Contracts.csproj @@ -0,0 +1,10 @@ + + + net10.0 + enable + enable + + + + + diff --git a/src/BuildingBlock.Identity.Contracts/Conventions/IIdentityContractRequest.cs b/src/BuildingBlock.Identity.Contracts/Conventions/IIdentityContractRequest.cs new file mode 100644 index 0000000..bd263d7 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Conventions/IIdentityContractRequest.cs @@ -0,0 +1,12 @@ +namespace BuildingBlock.Identity.Contracts.Conventions; + +/// +/// Defines shared envelope metadata for identity capability requests. +/// +public interface IIdentityContractRequest +{ + /// + /// Gets request envelope metadata. + /// + IdentityContractEnvelope Envelope { get; } +} diff --git a/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs new file mode 100644 index 0000000..6d4001b --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs @@ -0,0 +1,22 @@ +namespace BuildingBlock.Identity.Contracts.Conventions; + +/// +/// Supported identity authentication providers. +/// +public enum IdentityAuthProvider +{ + /// + /// AgileWebs-issued internal JWT flow. + /// + InternalJwt = 0, + + /// + /// Microsoft Entra ID / Azure AD OAuth/OIDC flow. + /// + AzureAd = 1, + + /// + /// Google OAuth/OIDC flow. + /// + Google = 2 +} diff --git a/src/BuildingBlock.Identity.Contracts/Conventions/IdentityContractEnvelope.cs b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityContractEnvelope.cs new file mode 100644 index 0000000..30f6546 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityContractEnvelope.cs @@ -0,0 +1,8 @@ +namespace BuildingBlock.Identity.Contracts.Conventions; + +/// +/// Defines transport-neutral envelope metadata for identity contract messages. +/// +/// Contract schema version. +/// Correlation identifier for cross-service tracing. +public sealed record IdentityContractEnvelope(string ContractVersion, string CorrelationId); diff --git a/src/BuildingBlock.Identity.Contracts/Conventions/IdentityPackageContract.cs b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityPackageContract.cs new file mode 100644 index 0000000..fee6a59 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityPackageContract.cs @@ -0,0 +1,8 @@ +namespace BuildingBlock.Identity.Contracts.Conventions; + +/// +/// Marker type for identity capability package discovery. +/// +public sealed class IdentityPackageContract +{ +} diff --git a/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs new file mode 100644 index 0000000..d508935 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs @@ -0,0 +1,14 @@ +namespace BuildingBlock.Identity.Contracts.Grpc; + +/// +/// Defines transport-neutral shape for policy evaluation gRPC contract mapping. +/// +/// Identity subject identifier. +/// Tenant identifier. +/// Permission code. +/// Auth provider. +public sealed record IdentityPolicyGrpcContract( + string SubjectId, + string TenantId, + string PermissionCode, + string Provider = "InternalJwt"); diff --git a/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs new file mode 100644 index 0000000..f60ad23 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs @@ -0,0 +1,18 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Requests; + +/// +/// Requests identity policy evaluation for a subject and permission. +/// +/// Identity subject identifier. +/// Tenant identifier. +/// Permission code to evaluate. +/// Auth provider used for the request. +public sealed record EvaluateIdentityPolicyRequest( + string SubjectId, + string TenantId, + string PermissionCode, + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt) + : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs new file mode 100644 index 0000000..8b992f5 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs @@ -0,0 +1,17 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Requests; + +/// +/// Requests identity provider token exchange for a subject token. +/// +/// Tenant identifier. +/// External identity provider. +/// Provider-issued token (id/access token). +/// Correlation identifier. +public sealed record ExchangeIdentityProviderTokenRequest( + string TenantId, + IdentityAuthProvider Provider, + string ExternalToken, + string CorrelationId = "") : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs new file mode 100644 index 0000000..7ab75d5 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs @@ -0,0 +1,18 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Requests; + +/// +/// Requests identity policy context lookup. +/// +/// Identity subject identifier. +/// Tenant identifier. +/// Permission code to evaluate. +/// Auth provider used for the request. +public sealed record IdentityPolicyContextRequest( + string SubjectId, + string TenantId, + string PermissionCode, + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt) + : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs new file mode 100644 index 0000000..7154b43 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs @@ -0,0 +1,17 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Requests; + +/// +/// Requests identity token issuance. +/// +/// Identity subject identifier. +/// Tenant identifier. +/// Auth provider used for the request. +/// External provider token when applicable. +public sealed record IssueIdentityTokenRequest( + string SubjectId, + string TenantId, + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt, + string ExternalToken = "") : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs new file mode 100644 index 0000000..9050aae --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs @@ -0,0 +1,16 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Requests; + +/// +/// Requests identity session refresh. +/// +/// Refresh token value. +/// Correlation identifier. +/// Auth provider used for the request. +public sealed record RefreshIdentitySessionRequest( + string RefreshToken, + string CorrelationId, + IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt) + : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Responses/EvaluateIdentityPolicyResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/EvaluateIdentityPolicyResponse.cs new file mode 100644 index 0000000..c6ca817 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Responses/EvaluateIdentityPolicyResponse.cs @@ -0,0 +1,12 @@ +using BuildingBlock.Identity.Contracts.Abstractions; + +namespace BuildingBlock.Identity.Contracts.Responses; + +/// +/// Returns identity policy evaluation result. +/// +/// Identity subject identifier. +/// Permission code evaluated. +/// Whether access is granted. +public sealed record EvaluateIdentityPolicyResponse(string SubjectId, string PermissionCode, bool IsAllowed) + : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs new file mode 100644 index 0000000..3ebee51 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs @@ -0,0 +1,17 @@ +using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; + +namespace BuildingBlock.Identity.Contracts.Responses; + +/// +/// Returns token exchange result from an external provider token. +/// +/// Resolved identity subject identifier. +/// Tenant identifier. +/// External identity provider. +/// Whether provider token was accepted. +public sealed record ExchangeIdentityProviderTokenResponse( + string SubjectId, + string TenantId, + IdentityAuthProvider Provider, + bool IsAuthenticated) : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Responses/IdentityPolicyContextResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/IdentityPolicyContextResponse.cs new file mode 100644 index 0000000..6a2e77f --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Responses/IdentityPolicyContextResponse.cs @@ -0,0 +1,12 @@ +using BuildingBlock.Identity.Contracts.Abstractions; + +namespace BuildingBlock.Identity.Contracts.Responses; + +/// +/// Returns policy context lookup result. +/// +/// Identity subject identifier. +/// Permission code evaluated. +/// Whether contextual policy requirements were satisfied. +public sealed record IdentityPolicyContextResponse(string SubjectId, string PermissionCode, bool ContextSatisfied) + : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Responses/IssueIdentityTokenResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/IssueIdentityTokenResponse.cs new file mode 100644 index 0000000..8a77465 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Responses/IssueIdentityTokenResponse.cs @@ -0,0 +1,10 @@ +using BuildingBlock.Identity.Contracts.Abstractions; + +namespace BuildingBlock.Identity.Contracts.Responses; + +/// +/// Returns issued identity token payload. +/// +/// Issued token value. +/// Token lifetime in seconds. +public sealed record IssueIdentityTokenResponse(string Token, int ExpiresInSeconds) : IIdentityCapabilityContract; diff --git a/src/BuildingBlock.Identity.Contracts/Responses/RefreshIdentitySessionResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/RefreshIdentitySessionResponse.cs new file mode 100644 index 0000000..77d43b5 --- /dev/null +++ b/src/BuildingBlock.Identity.Contracts/Responses/RefreshIdentitySessionResponse.cs @@ -0,0 +1,11 @@ +using BuildingBlock.Identity.Contracts.Abstractions; + +namespace BuildingBlock.Identity.Contracts.Responses; + +/// +/// Returns refreshed identity session token payload. +/// +/// Refreshed token value. +/// Token lifetime in seconds. +public sealed record RefreshIdentitySessionResponse(string Token, int ExpiresInSeconds) + : IIdentityCapabilityContract; diff --git a/tests/BuildingBlock.Identity.Contracts.UnitTests/BuildingBlock.Identity.Contracts.UnitTests.csproj b/tests/BuildingBlock.Identity.Contracts.UnitTests/BuildingBlock.Identity.Contracts.UnitTests.csproj new file mode 100644 index 0000000..6b898fe --- /dev/null +++ b/tests/BuildingBlock.Identity.Contracts.UnitTests/BuildingBlock.Identity.Contracts.UnitTests.csproj @@ -0,0 +1,26 @@ + + + net10.0 + enable + enable + false + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs new file mode 100644 index 0000000..fa0ab12 --- /dev/null +++ b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs @@ -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); + } +}