From bc9aa10bf0b3c80a6fe968661e7779b915a4d263 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 4 Dec 2024 14:49:22 +0000 Subject: [PATCH 1/3] Implemented the claimsidentity creation methods to allow ValidatedToken to lazily create the claims when accessed in SAML and SAML2 token handlers --- .../InternalAPI.Unshipped.txt | 5 ++ ...SamlSecurityTokenHandler.ClaimsIdentity.cs | 85 +++++++++++++++++++ ...aml2SecurityTokenHandler.ClaimsIdentity.cs | 44 ++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs create mode 100644 src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index 6e9837ef35..55944f8e39 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -1,6 +1,7 @@ const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames +Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.set -> void @@ -12,12 +13,15 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync( Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml.SamlValidationError Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void +override Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception +override Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame @@ -44,6 +48,7 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult +virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ProcessStatements(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, string issuer, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> System.Collections.Generic.IEnumerable virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs new file mode 100644 index 0000000000..7778754731 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.IdentityModel.Logging; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml +{ + /// + /// A designed for creating and validating Saml Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + /// + public partial class SamlSecurityTokenHandler : SecurityTokenHandler + { + internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) + { + return CreateClaimsIdentity(securityToken as SamlSecurityToken, validationParameters, issuer); + } + + internal ClaimsIdentity CreateClaimsIdentity(SamlSecurityToken? samlToken, ValidationParameters validationParameters, string issuer) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (samlToken.Assertion == null) + throw LogHelper.LogArgumentNullException(LogMessages.IDX11110); + + var actualIssuer = issuer; + if (string.IsNullOrWhiteSpace(issuer)) + actualIssuer = ClaimsIdentity.DefaultIssuer; + + IEnumerable identities = ProcessStatements( + samlToken, + actualIssuer, + validationParameters); + + return identities.First(); + } + + /// + /// Processes all statements to generate claims. + /// + /// A that will be used to create the claims. + /// The issuer. + /// The to be used for validating the token. + /// A containing the claims from the . + /// if the statement is not a . + internal virtual IEnumerable ProcessStatements(SamlSecurityToken samlToken, string issuer, ValidationParameters validationParameters) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + var identityDict = new Dictionary(SamlSubjectEqualityComparer); + foreach (SamlStatement? item in samlToken.Assertion.Statements) + { + if (item is not SamlSubjectStatement statement) + throw LogHelper.LogExceptionMessage(new SamlSecurityTokenException(LogMessages.IDX11515)); + + if (!identityDict.TryGetValue(statement.Subject, out ClaimsIdentity? identity)) + { + identity = validationParameters.CreateClaimsIdentity(samlToken, issuer); + ProcessSubject(statement.Subject, identity, issuer); + identityDict.Add(statement.Subject, identity); + } + + if (statement is SamlAttributeStatement attrStatement) + ProcessAttributeStatement(attrStatement, identity, issuer); + else if (statement is SamlAuthenticationStatement authnStatement) + ProcessAuthenticationStatement(authnStatement, identity, issuer); + else if (statement is SamlAuthorizationDecisionStatement authzStatement) + ProcessAuthorizationDecisionStatement(authzStatement, identity, issuer); + else + ProcessCustomSubjectStatement(statement, identity, issuer); + } + + return identityDict.Values; + } + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs new file mode 100644 index 0000000000..9286149fc7 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.Claims; +using Microsoft.IdentityModel.Logging; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2 +{ + /// + /// A designed for creating and validating Saml Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + /// + public partial class Saml2SecurityTokenHandler : SecurityTokenHandler + { + internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) + { + return CreateClaimsIdentity(securityToken as Saml2SecurityToken, validationParameters, issuer); + } + + internal ClaimsIdentity CreateClaimsIdentity(Saml2SecurityToken? samlToken, ValidationParameters validationParameters, string issuer) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (samlToken.Assertion == null) + throw LogHelper.LogArgumentNullException(LogMessages.IDX13110); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + string actualIssuer = issuer; + if (string.IsNullOrWhiteSpace(issuer)) + actualIssuer = ClaimsIdentity.DefaultIssuer; + + ClaimsIdentity identity = validationParameters.CreateClaimsIdentity(samlToken, actualIssuer); + + ProcessSubject(samlToken.Assertion.Subject, identity, actualIssuer); + ProcessStatements(samlToken.Assertion.Statements, identity, actualIssuer); + + return identity; + } + } +} +#nullable restore From d0479fb645df5bb5c598aba04159de0c277eeee4 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Thu, 5 Dec 2024 18:34:13 +0000 Subject: [PATCH 2/3] Added tests, updated returned ValidatedToken to generate the right ClaimsIdentity --- ...rityTokenHandler.ValidateToken.Internal.cs | 22 +++-- ...rityTokenHandler.ValidateToken.Internal.cs | 22 +++-- .../SamlClaimsIdentityComparisonTestBase.cs | 91 +++++++++++++++++++ .../Tests/ExtensibilityTheoryData.cs | 4 +- ...okenHandler.cs => ITestingTokenHandler.cs} | 82 ++++++++++++++++- ...yTokenHandler.Comparison.ClaimsIdentity.cs | 23 +++++ ...yTokenHandler.Comparison.ClaimsIdentity.cs | 23 +++++ 7 files changed, 249 insertions(+), 18 deletions(-) create mode 100644 test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs rename test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/{IExtensibilityTestingTokenHandler.cs => ITestingTokenHandler.cs} (54%) create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs index 23b4444503..55b8443ed7 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -76,9 +76,11 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult issuerValidationResult; + try { - ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + issuerValidationResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, samlToken, validationParameters, @@ -101,10 +103,10 @@ internal async Task> ValidateTokenAsync( ex); } + ValidationResult? tokenReplayValidationResult = null; + if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult; - try { tokenReplayValidationResult = validationParameters.TokenReplayValidator( @@ -113,8 +115,8 @@ internal async Task> ValidateTokenAsync( validationParameters, callContext); - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!tokenReplayValidationResult.Value.IsValid) + return tokenReplayValidationResult.Value.UnwrapError().AddCurrentStackFrame(); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -165,7 +167,15 @@ internal async Task> ValidateTokenAsync( ex); } - return new ValidatedToken(samlToken, this, validationParameters); + return new ValidatedToken(samlToken, this, validationParameters) + { + ValidatedAudience = conditionsResult.UnwrapResult().ValidatedAudience, + ValidatedLifetime = conditionsResult.UnwrapResult().ValidatedLifetime, + ValidatedIssuer = issuerValidationResult.UnwrapResult(), + ValidatedTokenReplayExpirationTime = tokenReplayValidationResult?.UnwrapResult(), + ValidatedSigningKey = signatureValidationResult.UnwrapResult(), + ValidatedSigningKeyLifetime = issuerSigningKeyValidationResult.UnwrapResult(), + }; } // ValidatedConditions is basically a named tuple but using a record struct better expresses the intent. diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs index 81ae5762f8..df8d5c97f8 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -80,9 +80,11 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult issuerValidationResult; + try { - ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + issuerValidationResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, samlToken, validationParameters, @@ -105,10 +107,10 @@ internal async Task> ValidateTokenAsync( ex); } + ValidationResult? tokenReplayValidationResult = null; + if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult; - try { tokenReplayValidationResult = validationParameters.TokenReplayValidator( @@ -117,8 +119,8 @@ internal async Task> ValidateTokenAsync( validationParameters, callContext); - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!tokenReplayValidationResult.Value.IsValid) + return tokenReplayValidationResult.Value.UnwrapError().AddCurrentStackFrame(); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -168,7 +170,15 @@ internal async Task> ValidateTokenAsync( ex); } - return new ValidatedToken(samlToken, this, validationParameters); + return new ValidatedToken(samlToken, this, validationParameters) + { + ValidatedAudience = conditionsResult.UnwrapResult().ValidatedAudience, + ValidatedLifetime = conditionsResult.UnwrapResult().ValidatedLifetime, + ValidatedIssuer = issuerValidationResult.UnwrapResult(), + ValidatedTokenReplayExpirationTime = tokenReplayValidationResult?.UnwrapResult(), + ValidatedSigningKey = signatureValidationResult.UnwrapResult(), + ValidatedSigningKeyLifetime = issuerSigningKeyValidationResult.UnwrapResult(), + }; } // ValidatedConditions is basically a named tuple but using a record struct better expresses the intent. diff --git a/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs new file mode 100644 index 0000000000..00f8656c52 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Saml; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + public partial class SamlClaimsIdentityComparisonTestBase + { + public static async Task ValidateTokenAsync_ClaimsIdentity_Comparison( + object testInstance, string methodName, string tokenHandlerType) + { + var context = new CompareContext($"{testInstance}.{methodName}"); + + List issuers = [Default.Issuer]; + List audiences = [Default.Audience]; + var signingKey = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + + ValidationParameters validationParameters = CreateValidationParameters( + issuers, audiences, signingKey); + TokenValidationParameters tokenValidationParameters = CreateTokenValidationParameters( + issuers, audiences, signingKey); + + ITestingTokenHandler tokenHandler = ExtensibilityTheoryData + .CreateSecurityTokenHandlerForType(tokenHandlerType); + + DateTime utcNow = DateTime.UtcNow; + string token = tokenHandler.CreateStringToken(new() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + IssuedAt = utcNow.AddHours(-1), + Expires = utcNow.AddHours(1), + NotBefore = utcNow.AddHours(-1), + }); + + ValidationResult validationResult = await tokenHandler.ValidateTokenAsync( + token, + validationParameters, + new CallContext(), + CancellationToken.None); + + TokenValidationResult tokenValidationResult = await tokenHandler.ValidateTokenAsync( + token, + tokenValidationParameters); + + IdentityComparer.AreBoolsEqual( + validationResult.IsValid, + tokenValidationResult.IsValid, context); + + IdentityComparer.AreClaimsIdentitiesEqual( + validationResult.UnwrapResult().ClaimsIdentity, + tokenValidationResult.ClaimsIdentity, context); + + TestUtilities.AssertFailIfErrors(context); + } + + static ValidationParameters CreateValidationParameters( + List issuers, + List audiences, + SecurityKey signingKey) + { + var validationParameters = new ValidationParameters(); + validationParameters.IssuerSigningKeys.Add(signingKey); + audiences.ForEach(validationParameters.ValidAudiences.Add); + issuers.ForEach(validationParameters.ValidIssuers.Add); + return validationParameters; + } + + static TokenValidationParameters CreateTokenValidationParameters( + List issuers, + List audiences, + SecurityKey signingKey) + { + var tokenValidationParameters = new TokenValidationParameters(); + tokenValidationParameters.ValidAudiences = audiences; + tokenValidationParameters.ValidIssuers = issuers; + tokenValidationParameters.IssuerSigningKey = signingKey; + return tokenValidationParameters; + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs index 6a2763c5c3..839e636701 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs @@ -25,7 +25,7 @@ internal ExtensibilityTheoryData( ValidationParameters = CreateValidationParametersSkippingValidations(); } - private IExtensibilityTestingTokenHandler CreateSecurityTokenHandlerForType(string tokenHandlerType) + internal static ITestingTokenHandler CreateSecurityTokenHandlerForType(string tokenHandlerType) { return tokenHandlerType switch { @@ -74,7 +74,7 @@ public SecurityTokenDescriptor SecurityTokenDescriptor set => _securityTokenDescriptor = PopulateSubjectForSecurityTokenDescriptor(value, _tokenHandlerType); } - internal IExtensibilityTestingTokenHandler TokenHandler { get; } + internal ITestingTokenHandler TokenHandler { get; } public bool IsValid { get; set; } diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs similarity index 54% rename from test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs rename to test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs index 8d2bfd5a03..ebfb32d418 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs @@ -14,8 +14,18 @@ namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests // This interface is used to test the extensibility of the ValidateTokenAsync method // in the JsonWebTokenHandler, SamlSecurityTokenHandler, and Saml2SecurityTokenHandler classes, // since the ValidateTokenAsync method with ValidationParameters is not part of any shared interface. - internal interface IExtensibilityTestingTokenHandler + internal interface ITestingTokenHandler { + Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken); + + Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters); + Task> ValidateTokenAsync( SecurityToken token, ValidationParameters validationParameters, @@ -23,11 +33,12 @@ Task> ValidateTokenAsync( CancellationToken cancellationToken); SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor); + string CreateStringToken(SecurityTokenDescriptor tokenDescriptor); } // Because the ValidateTokenAsync method in the token handler subclasses is internal, we need // to create classes that implement the IValidateTokenAsyncResult interface to use in tests. - internal class JsonWebTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class JsonWebTokenHandlerWithResult : ITestingTokenHandler { private readonly JsonWebTokenHandler _handler = new JsonWebTokenHandler(); @@ -44,13 +55,34 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { return _handler.ReadToken(_handler.CreateToken(tokenDescriptor)); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return _handler.CreateToken(tokenDescriptor); + } } - internal class SamlSecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class SamlSecurityTokenHandlerWithResult : ITestingTokenHandler { private readonly SamlSecurityTokenHandler _handler = new SamlSecurityTokenHandler(); @@ -63,6 +95,22 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { SamlSecurityToken token = (SamlSecurityToken)_handler.CreateToken(tokenDescriptor); @@ -70,9 +118,14 @@ public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) // Reading the token from the CanonicalString will set the signature correctly. return _handler.ReadToken(token.Assertion.CanonicalString); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return ((SamlSecurityToken)_handler.CreateToken(tokenDescriptor)).Assertion.CanonicalString; + } } - internal class Saml2SecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class Saml2SecurityTokenHandlerWithResult : ITestingTokenHandler { private readonly Saml2SecurityTokenHandler _handler = new Saml2SecurityTokenHandler(); @@ -85,6 +138,22 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { Saml2SecurityToken token = (Saml2SecurityToken)_handler.CreateToken(tokenDescriptor); @@ -92,6 +161,11 @@ public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) // Reading the token from the CanonicalString will set the signature correctly. return _handler.ReadToken(token.Assertion.CanonicalString); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return ((Saml2SecurityToken)_handler.CreateToken(tokenDescriptor)).Assertion.CanonicalString; + } } } #nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs new file mode 100644 index 0000000000..29ae44e4d2 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Tests +{ + public partial class Saml2SecurityTokenHandlerTests + { + [Fact] + public async Task ValidateTokenAsync_ClaimsIdentity_Comparison() + { + await SamlClaimsIdentityComparisonTestBase.ValidateTokenAsync_ClaimsIdentity_Comparison( + this, + nameof(ValidateTokenAsync_ClaimsIdentity_Comparison), + "SAML2"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs new file mode 100644 index 0000000000..72cd31d127 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Tests +{ + public partial class SamlSecurityTokenHandlerTests + { + [Fact] + public async Task ValidateTokenAsync_ClaimsIdentity_Comparison() + { + await SamlClaimsIdentityComparisonTestBase.ValidateTokenAsync_ClaimsIdentity_Comparison( + this, + nameof(ValidateTokenAsync_ClaimsIdentity_Comparison), + "SAML"); + } + } +} +#nullable restore From e02d39e7a318658f13409da8779c8f051e0bbf4f Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 9 Dec 2024 19:50:49 +0000 Subject: [PATCH 3/3] Addressed PR feedback --- .../Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs | 4 ++-- .../Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs index 7778754731..1e1cef4c4d 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs @@ -16,10 +16,10 @@ public partial class SamlSecurityTokenHandler : SecurityTokenHandler { internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) { - return CreateClaimsIdentity(securityToken as SamlSecurityToken, validationParameters, issuer); + return CreateClaimsIdentity((SamlSecurityToken)securityToken, validationParameters, issuer); } - internal ClaimsIdentity CreateClaimsIdentity(SamlSecurityToken? samlToken, ValidationParameters validationParameters, string issuer) + internal ClaimsIdentity CreateClaimsIdentity(SamlSecurityToken samlToken, ValidationParameters validationParameters, string issuer) { if (samlToken == null) throw LogHelper.LogArgumentNullException(nameof(samlToken)); diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs index 9286149fc7..f719f1ef17 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs @@ -8,16 +8,16 @@ namespace Microsoft.IdentityModel.Tokens.Saml2 { /// - /// A designed for creating and validating Saml Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + /// A designed for creating and validating Saml2 Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf /// public partial class Saml2SecurityTokenHandler : SecurityTokenHandler { internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) { - return CreateClaimsIdentity(securityToken as Saml2SecurityToken, validationParameters, issuer); + return CreateClaimsIdentity((Saml2SecurityToken)securityToken, validationParameters, issuer); } - internal ClaimsIdentity CreateClaimsIdentity(Saml2SecurityToken? samlToken, ValidationParameters validationParameters, string issuer) + internal ClaimsIdentity CreateClaimsIdentity(Saml2SecurityToken samlToken, ValidationParameters validationParameters, string issuer) { if (samlToken == null) throw LogHelper.LogArgumentNullException(nameof(samlToken));