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/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/Grpc/IdentityPolicyGrpcContract.cs b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs index d4416e2..d508935 100644 --- a/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs +++ b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs @@ -6,4 +6,9 @@ namespace BuildingBlock.Identity.Contracts.Grpc; /// Identity subject identifier. /// Tenant identifier. /// Permission code. -public sealed record IdentityPolicyGrpcContract(string SubjectId, string TenantId, string PermissionCode); +/// 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 index 078c350..f60ad23 100644 --- a/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs +++ b/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs @@ -1,4 +1,5 @@ using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; namespace BuildingBlock.Identity.Contracts.Requests; @@ -8,5 +9,10 @@ namespace BuildingBlock.Identity.Contracts.Requests; /// Identity subject identifier. /// Tenant identifier. /// Permission code to evaluate. -public sealed record EvaluateIdentityPolicyRequest(string SubjectId, string TenantId, string PermissionCode) +/// 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 index 60a2515..7ab75d5 100644 --- a/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs +++ b/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs @@ -1,4 +1,5 @@ using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; namespace BuildingBlock.Identity.Contracts.Requests; @@ -8,5 +9,10 @@ namespace BuildingBlock.Identity.Contracts.Requests; /// Identity subject identifier. /// Tenant identifier. /// Permission code to evaluate. -public sealed record IdentityPolicyContextRequest(string SubjectId, string TenantId, string PermissionCode) +/// 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 index 23d5b4e..7154b43 100644 --- a/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs +++ b/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs @@ -1,4 +1,5 @@ using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; namespace BuildingBlock.Identity.Contracts.Requests; @@ -7,4 +8,10 @@ namespace BuildingBlock.Identity.Contracts.Requests; /// /// Identity subject identifier. /// Tenant identifier. -public sealed record IssueIdentityTokenRequest(string SubjectId, string TenantId) : IIdentityCapabilityContract; +/// 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 index d0a1617..9050aae 100644 --- a/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs +++ b/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs @@ -1,4 +1,5 @@ using BuildingBlock.Identity.Contracts.Abstractions; +using BuildingBlock.Identity.Contracts.Conventions; namespace BuildingBlock.Identity.Contracts.Requests; @@ -7,5 +8,9 @@ namespace BuildingBlock.Identity.Contracts.Requests; /// /// Refresh token value. /// Correlation identifier. -public sealed record RefreshIdentitySessionRequest(string RefreshToken, string CorrelationId) +/// 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/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/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs index 898e31d..fa0ab12 100644 --- a/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs +++ b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs @@ -19,12 +19,33 @@ public class ContractShapeTests [Fact] public void IdentityContracts_WhenInstantiated_ImplementMarkerInterface() { - IIdentityCapabilityContract issueRequest = new IssueIdentityTokenRequest("subject-a", "tenant-a"); + 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"); + 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"); + 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); @@ -32,5 +53,20 @@ public class ContractShapeTests 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); } }