-
Notifications
You must be signed in to change notification settings - Fork 408
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read Token: Remove Exceptions (#2702)
* Added new version of ReadToken that receives a CallContext and returns a result wrapping exceptions and logs.
- Loading branch information
Showing
5 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
64 changes: 64 additions & 0 deletions
64
src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using Microsoft.IdentityModel.Logging; | ||
using Microsoft.IdentityModel.Tokens; | ||
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; | ||
|
||
namespace Microsoft.IdentityModel.JsonWebTokens | ||
{ | ||
/// <remarks>This partial class contains methods and logic related to the validation of tokens.</remarks> | ||
public partial class JsonWebTokenHandler : TokenHandler | ||
{ | ||
#nullable enable | ||
/// <summary> | ||
/// Converts a string into an instance of <see cref="JsonWebToken"/>, returned inside of a <see cref="TokenReadingResult"/>. | ||
/// </summary> | ||
/// <param name="token">A JSON Web Token (JWT) in JWS or JWE Compact Serialization format.</param> | ||
/// <param name="callContext"></param> | ||
/// <returns>A <see cref="TokenReadingResult"/> with the <see cref="JsonWebToken"/> if valid, or an Exception.</returns> | ||
/// <exception cref="ArgumentNullException">returned if <paramref name="token"/> is null or empty.</exception> | ||
/// <exception cref="SecurityTokenMalformedException">returned if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid <see cref="JsonWebToken"/>.</exception> | ||
/// <exception cref="SecurityTokenMalformedException">returned if <paramref name="token"/> is not a valid JWT, <see cref="JsonWebToken"/>.</exception> | ||
internal static TokenReadingResult ReadToken( | ||
string token, | ||
#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging | ||
CallContext? callContext) | ||
#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging | ||
{ | ||
if (String.IsNullOrEmpty(token)) | ||
{ | ||
return new TokenReadingResult( | ||
token, | ||
ValidationFailureType.NullArgument, | ||
new ExceptionDetail( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10000, | ||
LogHelper.MarkAsNonPII(nameof(token))), | ||
typeof(ArgumentNullException), | ||
new System.Diagnostics.StackFrame())); | ||
} | ||
|
||
try | ||
{ | ||
JsonWebToken jsonWebToken = new JsonWebToken(token); | ||
return new TokenReadingResult(jsonWebToken, token); | ||
} | ||
#pragma warning disable CA1031 // Do not catch general exception types | ||
catch (Exception ex) | ||
#pragma warning restore CA1031 // Do not catch general exception types | ||
{ | ||
return new TokenReadingResult( | ||
token, | ||
ValidationFailureType.TokenReadingFailed, | ||
new ExceptionDetail( | ||
new MessageDetail(LogMessages.IDX14107), | ||
ex.GetType(), | ||
new System.Diagnostics.StackFrame(), | ||
ex)); | ||
} | ||
} | ||
} | ||
} | ||
#nullable restore |
86 changes: 86 additions & 0 deletions
86
src/Microsoft.IdentityModel.Tokens/Validation/TokenReadingResult.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
|
||
#nullable enable | ||
namespace Microsoft.IdentityModel.Tokens | ||
{ | ||
/// <summary> | ||
/// Contains the result of reading a <see cref="SecurityToken"/>. | ||
/// The <see cref="TokenValidationResult"/> contains a collection of <see cref="ValidationResult"/> for each step in the token validation. | ||
/// </summary> | ||
internal class TokenReadingResult : ValidationResult | ||
{ | ||
private Exception? _exception; | ||
private SecurityToken? _securityToken; | ||
|
||
/// <summary> | ||
/// Creates an instance of <see cref="TokenReadingResult"/>. | ||
/// </summary> | ||
/// <paramref name="tokenInput"/> is the string from which the <see cref="SecurityToken"/> was created. | ||
/// <paramref name="securityToken"/> is the <see cref="SecurityToken"/> that was created. | ||
public TokenReadingResult(SecurityToken securityToken, string tokenInput) | ||
: base(ValidationFailureType.ValidationSucceeded) | ||
{ | ||
IsValid = true; | ||
TokenInput = tokenInput; | ||
_securityToken = securityToken; | ||
} | ||
|
||
/// <summary> | ||
/// Creates an instance of <see cref="TokenReadingResult"/> | ||
/// </summary> | ||
/// <paramref name="tokenInput"/> is the string that failed to create a <see cref="SecurityToken"/>. | ||
/// <paramref name="validationFailure"/> is the <see cref="ValidationFailureType"/> that occurred during reading. | ||
/// <paramref name="exceptionDetail"/> is the <see cref="ExceptionDetail"/> that occurred during reading. | ||
public TokenReadingResult(string? tokenInput, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail) | ||
: base(validationFailure, exceptionDetail) | ||
{ | ||
TokenInput = tokenInput; | ||
IsValid = false; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the <see cref="SecurityToken"/> that was read. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException"/> if the <see cref="SecurityToken"/> is null. | ||
/// <remarks>It is expected that the caller would check <see cref="ValidationResult.IsValid"/> returns true before accessing this.</remarks> | ||
public SecurityToken SecurityToken() | ||
{ | ||
if (_securityToken is null) | ||
throw new InvalidOperationException("Attempted to retrieve the SecurityToken from a failed TokenReading result."); | ||
|
||
return _securityToken; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the <see cref="Exception"/> that occurred during reading. | ||
/// </summary> | ||
public override Exception? Exception | ||
{ | ||
get | ||
{ | ||
if (_exception != null || ExceptionDetail == null) | ||
return _exception; | ||
|
||
HasValidOrExceptionWasRead = true; | ||
_exception = ExceptionDetail.GetException(); | ||
|
||
if (_exception is SecurityTokenException securityTokenException) | ||
{ | ||
securityTokenException.Source = "Microsoft.IdentityModel.Tokens"; | ||
securityTokenException.ExceptionDetail = ExceptionDetail; | ||
} | ||
|
||
return _exception; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the string from which the <see cref="SecurityToken"/> was read. | ||
/// </summary> | ||
public string? TokenInput { get; } | ||
} | ||
} | ||
#nullable restore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ReadTokenTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.IdentityModel.Tokens.Jwt.Tests; | ||
using Microsoft.IdentityModel.Logging; | ||
using Microsoft.IdentityModel.TestUtils; | ||
using Microsoft.IdentityModel.Tokens; | ||
using Xunit; | ||
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; | ||
|
||
namespace Microsoft.IdentityModel.JsonWebTokens.Tests | ||
{ | ||
public class JsonWebTokenHandlerReadTokenTests | ||
{ | ||
[Theory, MemberData(nameof(JsonWebTokenHandlerReadTokenTestCases), DisableDiscoveryEnumeration = true)] | ||
public void ReadToken(TokenReadingTheoryData theoryData) | ||
{ | ||
CompareContext context = TestUtilities.WriteHeader($"{this}.JsonWebTokenHandlerReadTokenTests", theoryData); | ||
TokenReadingResult tokenReadingResult = JsonWebTokenHandler.ReadToken( | ||
theoryData.Token, | ||
new CallContext()); | ||
|
||
if (tokenReadingResult.Exception != null) | ||
theoryData.ExpectedException.ProcessException(tokenReadingResult.Exception); | ||
else | ||
theoryData.ExpectedException.ProcessNoException(); | ||
|
||
IdentityComparer.AreTokenReadingResultsEqual( | ||
tokenReadingResult, | ||
theoryData.TokenReadingResult, | ||
context); | ||
|
||
TestUtilities.AssertFailIfErrors(context); | ||
} | ||
|
||
[Fact] | ||
public void ReadToken_ThrowsIfAccessingSecurityTokenOnFailedRead() | ||
{ | ||
TokenReadingResult tokenReadingResult = JsonWebTokenHandler.ReadToken( | ||
null, | ||
new CallContext()); | ||
|
||
Assert.Throws<InvalidOperationException>(() => tokenReadingResult.SecurityToken()); | ||
} | ||
|
||
public static TheoryData<TokenReadingTheoryData> JsonWebTokenHandlerReadTokenTestCases | ||
{ | ||
get | ||
{ | ||
var validToken = EncodedJwts.LiveJwt; | ||
return new TheoryData<TokenReadingTheoryData> | ||
{ | ||
new TokenReadingTheoryData | ||
{ | ||
TestId = "Valid_Jwt", | ||
Token = validToken, | ||
TokenReadingResult = new TokenReadingResult( | ||
new JsonWebToken(validToken), | ||
validToken) | ||
}, | ||
new TokenReadingTheoryData | ||
{ | ||
TestId = "Invalid_NullToken", | ||
Token = null, | ||
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), | ||
TokenReadingResult = new TokenReadingResult( | ||
null, | ||
ValidationFailureType.NullArgument, | ||
new ExceptionDetail( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10000, | ||
LogHelper.MarkAsNonPII("token")), | ||
typeof(ArgumentNullException), | ||
new System.Diagnostics.StackFrame())) | ||
}, | ||
new TokenReadingTheoryData | ||
{ | ||
TestId = "Invalid_EmptyToken", | ||
Token = string.Empty, | ||
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), | ||
TokenReadingResult = new TokenReadingResult( | ||
string.Empty, | ||
ValidationFailureType.NullArgument, | ||
new ExceptionDetail( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10000, | ||
LogHelper.MarkAsNonPII("token")), | ||
typeof(ArgumentNullException), | ||
new System.Diagnostics.StackFrame())) | ||
}, | ||
new TokenReadingTheoryData | ||
{ | ||
TestId = "Invalid_MalformedToken", | ||
Token = "malformed-token", | ||
ExpectedException = ExpectedException.SecurityTokenMalformedTokenException( | ||
"IDX14107:", | ||
typeof(SecurityTokenMalformedException)), | ||
TokenReadingResult = new TokenReadingResult( | ||
"malformed-token", | ||
ValidationFailureType.TokenReadingFailed, | ||
new ExceptionDetail( | ||
new MessageDetail( | ||
LogMessages.IDX14107, | ||
LogHelper.MarkAsNonPII("token")), | ||
typeof(SecurityTokenMalformedException), | ||
new System.Diagnostics.StackFrame())) | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
|
||
public class TokenReadingTheoryData : TheoryDataBase | ||
{ | ||
public string Token { get; set; } | ||
public object TokenReadingResult { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters