diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.JsonWebTokens/InternalsVisibleTo.cs index 5c33df843c..cea056ffc8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalsVisibleTo.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalsVisibleTo.cs @@ -4,3 +4,4 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Validators, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.JsonWebTokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.TestUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.SampleTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs index a37d327fcd..fdc9ee8451 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs @@ -45,7 +45,7 @@ internal static Result ReadToken( { StackFrame malformedTokenStackFrame = StackFrames.ReadTokenMalformed ?? new StackFrame(true); return new ExceptionDetail( - new MessageDetail(LogMessages.IDX14107), + new MessageDetail(LogMessages.IDX14100), ValidationFailureType.TokenReadingFailed, ExceptionType.SecurityTokenMalformed, malformedTokenStackFrame, diff --git a/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs index 7575da28eb..d1900f34b8 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs +++ b/src/Microsoft.IdentityModel.Tokens/InternalsVisibleTo.cs @@ -16,3 +16,4 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.S2S.Tokens.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.Tokens.Saml.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.SampleTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ReadTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ReadTokenTests.cs index 01912a41ed..5c4c51634e 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ReadTokenTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ReadTokenTests.cs @@ -100,11 +100,11 @@ public static TheoryData JsonWebTokenHandlerReadTokenTes TestId = "Invalid_MalformedToken", Token = "malformed-token", ExpectedException = ExpectedException.SecurityTokenMalformedTokenException( - "IDX14107:", + "IDX14100:", typeof(SecurityTokenMalformedException)), Result = new ExceptionDetail( new MessageDetail( - LogMessages.IDX14107, + LogMessages.IDX14100, LogHelper.MarkAsNonPII("token")), ValidationFailureType.TokenReadingFailed, ExceptionType.SecurityTokenMalformed, diff --git a/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClass.cs b/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClass.cs index c325c89db9..e66b799486 100644 --- a/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClass.cs +++ b/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClass.cs @@ -4,6 +4,7 @@ using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using System.Threading; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; @@ -33,6 +34,10 @@ public SampleTokenValidationClass() ValidIssuer = "http://Default.Issuer.com", IssuerSigningKey = KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key }; + ValidationParameters = new ValidationParameters(); + ValidationParameters.ValidAudiences.Add("http://Default.Audience.com"); + ValidationParameters.ValidIssuers.Add("http://Default.Issuer.com"); + ValidationParameters.IssuerSigningKeys.Add(KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key); } /// @@ -40,6 +45,8 @@ public SampleTokenValidationClass() /// public TokenValidationParameters TokenValidationParameters { get; set; } + public ValidationParameters ValidationParameters { get; set; } + /// /// Gets or sets the instance used for the validation operations. /// @@ -59,6 +66,11 @@ public void ValidateTokenShim(string token) ValidateTokenShim(token, TokenValidationParameters); } + public Result ValidateTokenShimWithNewPath(string token) + { + return ValidateTokenShimWithNewPath(token, ValidationParameters); + } + /// /// Validates the passed token using the instance of the deprecated . /// @@ -75,14 +87,20 @@ public void ValidateTokenShimWithDeprecatedModel(string token) /// /// The to use instead of the instance's value. /// - public void ValidateTokenShim(string token, TokenValidationParameters tokenValidationParameters) + public TokenValidationResult ValidateTokenShim(string token, TokenValidationParameters tokenValidationParameters) { var result = JsonWebTokenHandler.ValidateTokenAsync(token, tokenValidationParameters).Result; if (!result.IsValid) - { throw new SampleTestTokenValidationException("Validation Issue Encountered", result.Exception); - } + + return result; + } + + internal Result ValidateTokenShimWithNewPath(string token, ValidationParameters validationParameters) + { + CallContext callContext = new CallContext(); + return JsonWebTokenHandler.ValidateTokenAsync(token, validationParameters, callContext, CancellationToken.None).Result; } /// diff --git a/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClassTests.cs b/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClassTests.cs index 0898d42d9b..b4706dc679 100644 --- a/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClassTests.cs +++ b/test/Microsoft.IdentityModel.SampleTests/SampleTokenValidationClassTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Threading.Tasks; using Microsoft.IdentityModel.TestExtensions; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; @@ -221,6 +222,218 @@ public void TokenWithMissingSecurityCredentials() } #endregion + #region New Model Token Validation Tests + /// + /// Tests how the class under test handles a valid token. + /// + [Fact] + public void ValidToken_NewPath() + { + SampleTokenValidationClass classUnderTest = new SampleTokenValidationClass(); + classUnderTest.ValidateTokenShimWithNewPath(testTokenCreator.CreateDefaultValidToken()); + } + + /// + /// Tests how the class under test handles a bogus token; one that in no way conforms to the expected JWS format. + /// + [Fact] + public void BogusToken_NewPath() + { + TestWithGeneratedToken_NewPath( + () => "InvalidToken", + typeof(SecurityTokenMalformedException), + "IDX14100"); + } + + /// + /// Tests how the class under test handles a token with a missing signature. + /// + [Fact] + public void TokenWithoutSignature_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithNoSignature, + typeof(SecurityTokenInvalidSignatureException), + "IDX10504:"); + } + + /// + /// Tests how the class under test handles a token with a malformed signature. + /// + [Fact] + public void TokenWithBadSignature_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithInvalidSignature, + typeof(SecurityTokenInvalidSignatureException), + "IDX10500:"); // 10500 indicates no signature key was found. Current path returns 10511 which indicates a bad signature and provides the list of keys attempted + } + + /// + /// Tests how the class under test handles a token which is expired. + /// + [Fact] + public void ExpiredToken_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateExpiredToken, + typeof(SecurityTokenExpiredException), + "IDX10223"); + } + + /// + /// Tests how the class under test handles a token which is not yet valid + /// + [Fact] + public void NetYetValidToken_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateNotYetValidToken, + typeof(SecurityTokenNotYetValidException), + "IDX10222"); + } + + /// + /// Tests how the class under test handles a token with an issuer that doesn't match expectations. + /// + [Fact] + public void TokenWithWrongIssuer_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithBadIssuer, + typeof(SecurityTokenInvalidIssuerException), + "IDX10212"); + // Current path returns IDX10205 which contains Issuer, ValidIssuer, and ValidIssuers. + // This is a new error code that drops ValidIssuer as it is no longer used. + } + + /// + /// Tests how the class under test handles a token with an audience that doesn't match expectations. + /// + [Fact] + public void TokenWithWrongAudience_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithBadAudience, + typeof(SecurityTokenInvalidAudienceException), + "IDX10215"); + // Current path returns IDX10214 which contains Audience, ValidAudience, and ValidAudiences. + // This is a new error code that drops ValidAudience as it is no longer used. + } + + /// + /// Tests how the class under test handles a token signed with a key different than the one expected. + /// + [Fact] + public void TokenWithBadSignatureKey_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithBadSignatureKey, + typeof(SecurityTokenSignatureKeyNotFoundException), + "IDX10500"); // By default, the new path defaults to not trying all signing keys. + } + + /// + /// Tests how the class under test handles a token signed with a key different than the one expected. + /// + [Fact] + public void TokenWithBadSignatureKey_NewPath_TryAllKeys() + { + Action updateParameters = (validationParameters) => + validationParameters.TryAllIssuerSigningKeys = true; + + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithBadSignatureKey, + typeof(SecurityTokenSignatureKeyNotFoundException), + "IDX10503", + updateParameters); + } + + /// + /// Tests how the class under test handles a token missing the iss claim. + /// + [Fact] + public void TokenWithMissingIssuer_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithMissingIssuer, + typeof(SecurityTokenInvalidIssuerException), + "IDX10211"); + } + + /// + /// Tests how the class under test handles a token missing the aud claim. + /// + [Fact] + public void TokenWithMissingAudience_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithMissingAudience, + typeof(SecurityTokenInvalidAudienceException), + "IDX10206"); + } + + /// + /// Tests how the class under test handles a token with a iat claim indicating it has not yet been issued. + /// + [Fact] + public void TokenWithFutureIssuedAt_NewPath() + { + // NOTE: This is not currently validated and there's no way to enforce its presence. + // It may be enforceable in the future, in which case this will be updated with proper checks. + SampleTokenValidationClass classUnderTest = new SampleTokenValidationClass(); + classUnderTest.ValidateTokenShimWithNewPath(testTokenCreator.CreateTokenWithFutureIssuedAt()); + } + + /// + /// Tests how the class under test handles a token missing the iat claim. + /// + [Fact] + public void TokenWithMissingIssuedAt_NewPath() + { + // NOTE: This is not currently validated and there's no way to enforce its presence. + // It may be enforceable in the future, in which case this will be updated with proper checks. + SampleTokenValidationClass classUnderTest = new SampleTokenValidationClass(); + classUnderTest.ValidateTokenShimWithNewPath(testTokenCreator.CreateTokenWithMissingIssuedAt()); + } + + /// + /// Tests how the class under test handles a token missing the nbf claim. + /// + [Fact] + public void TokenWithMissingNotBefore_NewPath() + { + // NOTE: This is not currently validated and there's no way to enforce its presence. + // It may be enforceable in the future, in which case this will be updated with proper checks. + SampleTokenValidationClass classUnderTest = new SampleTokenValidationClass(); + classUnderTest.ValidateTokenShimWithNewPath(testTokenCreator.CreateTokenWithMissingNotBefore()); + } + + /// + /// Tests how the class under test handles a token missing the exp claim. + /// + [Fact] + public void TokenWithMissingExpires_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithMissingExpires, + typeof(SecurityTokenNoExpirationException), + "IDX10225"); + } + + /// + /// Test how the class under test handles a token without a signing key (i.e. alg=none, no signature). + /// + [Fact] + public void TokenWithMissingSecurityCredentials_NewPath() + { + TestWithGeneratedToken_NewPath( + testTokenCreator.CreateTokenWithMissingKey, + typeof(SecurityTokenInvalidSignatureException), + "IDX10504"); + } + #endregion + #region Deprecated Model Token Validation Tests /// /// Tests how a class under test using JwtSecurityTokenHandler handles a valid token. @@ -451,6 +664,32 @@ internal void TestWithGeneratedToken(Func generateTokenToTest, Type expe expectedInnerExceptionMessagePart); } + internal void TestWithGeneratedToken_NewPath( + Func generateTokenToTest, + Type expectedInnerExceptionType, + string expectedInnerExceptionMessagePart, + Action modifyValidationParameters = null) + { + SampleTokenValidationClass classUnderTest = new SampleTokenValidationClass(); + if (modifyValidationParameters != null) + modifyValidationParameters(classUnderTest.ValidationParameters); + + string token = generateTokenToTest(); + Result result = classUnderTest.ValidateTokenShimWithNewPath(token); + + if (!result.IsSuccess) + AssertException(expectedInnerExceptionType, expectedInnerExceptionMessagePart, result.UnwrapError().GetException()); + else + { + if (expectedInnerExceptionType != null || !string.IsNullOrEmpty(expectedInnerExceptionMessagePart)) + throw new TestException( + string.Format( + "Expected an exception of type '{0}' containing '{1}' in the message.", + expectedInnerExceptionType, + expectedInnerExceptionMessagePart)); + } + } + /// /// Calls a passed action with a generated token and validates the outcome. /// @@ -494,13 +733,16 @@ internal void AssertValidationException(Action action, Type innerExceptionType, catch (Exception e) { Assert.Equal(typeof(SampleTestTokenValidationException), e.GetType()); - Assert.Equal(innerExceptionType, e.InnerException.GetType()); - - if (!string.IsNullOrEmpty(innerExceptionMessagePart)) - { - Assert.Contains(innerExceptionMessagePart, e.InnerException.Message); - } + AssertException(innerExceptionType, innerExceptionMessagePart, e.InnerException); } } + + private static void AssertException(Type exceptionType, string exceptionMessagePart, Exception exception) + { + Assert.Equal(exceptionType, exception.GetType()); + + if (!string.IsNullOrEmpty(exceptionMessagePart)) + Assert.Contains(exceptionMessagePart, exception.Message); + } } }