Skip to content

Commit

Permalink
New token validation model: Simplify stack frame caching (#2976)
Browse files Browse the repository at this point in the history
* Added methods to ValidationError to simplify the caching of StackFrame objects by using a concurrent dictionary

* Changed methods to be internal
  • Loading branch information
iNinja authored Nov 5, 2024
1 parent 96b6b36 commit c964f16
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError
Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void
Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError
Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception
Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.get -> Microsoft.IdentityModel.Tokens.TokenTypeValidationDelegate
Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.set -> void
Expand All @@ -29,6 +30,7 @@ static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidateAudienceFa
static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParametersAudiencesCountZero -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParametersNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList<string> strings) -> string
static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the MIT License.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.IdentityModel.Tokens
Expand Down Expand Up @@ -73,7 +75,7 @@ internal Exception GetException(Type exceptionType, Exception innerException)
if (innerException == null && InnerValidationError == null)
{
if (exceptionType == typeof(SecurityTokenArgumentNullException))
return new SecurityTokenArgumentNullException(MessageDetail.Message);
exception = new SecurityTokenArgumentNullException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenInvalidAudienceException))
exception = new SecurityTokenInvalidAudienceException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenInvalidIssuerException))
Expand Down Expand Up @@ -187,6 +189,11 @@ internal Exception GetException(Type exceptionType, Exception innerException)
}
}

if (exception is SecurityTokenException securityTokenException)
securityTokenException.SetValidationError(this);
else if (exception is SecurityTokenArgumentNullException securityTokenArgumentNullException)
securityTokenArgumentNullException.SetValidationError(this);

return exception;
}

Expand Down Expand Up @@ -236,5 +243,41 @@ public ValidationError AddStackFrame(StackFrame stackFrame)
StackFrames.Add(stackFrame);
return this;
}

/// <summary>
/// Adds the current stack frame to the list of stack frames and returns the updated object.
/// If there is no cache entry for the given file path and line number, a new stack frame is created and added to the cache.
/// </summary>
/// <param name="filePath">The path to the file from which this method is called. Captured automatically by default.</param>
/// <param name="lineNumber">The line number from which this method is called. CAptured automatically by default.</param>
/// <param name="skipFrames">The number of stack frames to skip when capturing. Used to avoid capturing this method and get the caller instead.</param>
/// <returns>The updated object.</returns>
internal ValidationError AddCurrentStackFrame([CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, int skipFrames = 1)
{
// We add 1 to the skipped frames to skip the current method
StackFrames.Add(GetCurrentStackFrame(filePath, lineNumber, skipFrames + 1));
return this;
}

/// <summary>
/// Returns the stack frame corresponding to the file path and line number from which this method is called.
/// If there is no cache entry for the given file path and line number, a new stack frame is created and added to the cache.
/// </summary>
/// <param name="filePath">The path to the file from which this method is called. Captured automatically by default.</param>
/// <param name="lineNumber">The line number from which this method is called. CAptured automatically by default.</param>
/// <param name="skipFrames">The number of stack frames to skip when capturing. Used to avoid capturing this method and get the caller instead.</param>
/// <returns>The captured stack frame.</returns>
/// <remarks>If this is called from a helper method, consider adding an extra skip frame to avoid capturing the helper instead.</remarks>
internal static StackFrame GetCurrentStackFrame(
[CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, int skipFrames = 1)
{
// String is allocated, but it goes out of scope immediately after the call
string key = filePath + lineNumber;
StackFrame frame = CachedStackFrames.GetOrAdd(key, new StackFrame(skipFrames, true));
return frame;
}

// ConcurrentDictionary is thread-safe and only locks when adding a new item.
private static ConcurrentDictionary<string, StackFrame> CachedStackFrames { get; } = new();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;

namespace Microsoft.IdentityModel.Tokens.Tests
{
public class ValidationErrorTests
{
[Fact]
public void ExceptionCreatedFromValidationError_ContainsTheRightStackTrace()
{
var validationError = new ValidationErrorReturningClass().firstMethod();
Assert.NotNull(validationError);
Assert.NotNull(validationError.StackFrames);
Assert.Equal(3, validationError.StackFrames.Count);
Assert.NotNull(validationError.GetException());
Assert.NotNull(validationError.GetException().StackTrace);
Assert.Equal("thirdMethod", validationError.StackFrames[0].GetMethod().Name);
Assert.Equal("secondMethod", validationError.StackFrames[1].GetMethod().Name);
Assert.Equal("firstMethod", validationError.StackFrames[2].GetMethod().Name);
}
class ValidationErrorReturningClass
{
public ValidationError firstMethod()
{
return secondMethod().AddCurrentStackFrame();
}

public ValidationError secondMethod()
{
return thirdMethod().AddCurrentStackFrame();
}

public ValidationError thirdMethod()
{
return new ValidationError(
new MessageDetail("This is a test error"),
ValidationFailureType.NullArgument,
typeof(SecurityTokenArgumentNullException),
ValidationError.GetCurrentStackFrame());
}
}
}
}

0 comments on commit c964f16

Please sign in to comment.