Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated documentation for the new validation model and restructured internals #3056

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.IdentityModel.Logging;
Expand Down Expand Up @@ -31,49 +30,42 @@ internal ValidationResult<string> DecryptToken(
{
if (jwtToken == null)
{
StackFrame tokenNullStackFrame = StackFrames.DecryptionTokenNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(jwtToken),
tokenNullStackFrame);
ValidationError.GetCurrentStackFrame());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ValidationError.GetCurrentStackFrame());
ValidationError.GetCurrentStackFrame(),
"The JWT token provided is null. Ensure that a valid token is passed.");

}

if (validationParameters == null)
{
StackFrame validationParametersNullStackFrame = StackFrames.DecryptionValidationParametersNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(validationParameters),
validationParametersNullStackFrame);
ValidationError.GetCurrentStackFrame());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ValidationError.GetCurrentStackFrame());
ValidationError.GetCurrentStackFrame()
"Validation parameters are missing. Provide the necessary parameters for token validation.");

}

if (string.IsNullOrEmpty(jwtToken.Enc))
{
StackFrame headerMissingStackFrame = StackFrames.DecryptionHeaderMissing ??= new StackFrame(true);
return new ValidationError(
new MessageDetail(TokenLogMessages.IDX10612),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenException),
headerMissingStackFrame);
ValidationError.GetCurrentStackFrame());
}

(IList<SecurityKey>? contentEncryptionKeys, ValidationError? validationError) result =
GetContentEncryptionKeys(jwtToken, validationParameters, configuration, callContext);

if (result.validationError != null)
{
StackFrame decryptionGetKeysStackFrame = StackFrames.DecryptionGetEncryptionKeys ??= new StackFrame(true);
return result.validationError.AddStackFrame(decryptionGetKeysStackFrame);
}
return result.validationError.AddCurrentStackFrame();

if (result.contentEncryptionKeys == null || result.contentEncryptionKeys.Count == 0)
{
StackFrame noKeysTriedStackFrame = StackFrames.DecryptionNoKeysTried ??= new StackFrame(true);
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10609,
LogHelper.MarkAsSecurityArtifact(jwtToken, JwtTokenUtilities.SafeLogJwtToken)),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenDecryptionFailedException),
noKeysTriedStackFrame);
ValidationError.GetCurrentStackFrame());
}

return JwtTokenUtilities.DecryptJwtToken(
Expand Down Expand Up @@ -211,7 +203,6 @@ internal ValidationResult<string> DecryptToken(
return (unwrappedKeys, null);
else
{
StackFrame decryptionKeyUnwrapFailedStackFrame = StackFrames.DecryptionKeyUnwrapFailed ??= new StackFrame(true);
ValidationError validationError = new(
new MessageDetail(
TokenLogMessages.IDX10618,
Expand All @@ -220,7 +211,7 @@ internal ValidationResult<string> DecryptToken(
LogHelper.MarkAsSecurityArtifact(jwtToken, JwtTokenUtilities.SafeLogJwtToken)),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenKeyWrapException),
decryptionKeyUnwrapFailedStackFrame);
ValidationError.GetCurrentStackFrame());

return (null, validationError);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using Microsoft.IdentityModel.Tokens;

#nullable enable
Expand All @@ -28,10 +27,9 @@ internal static ValidationResult<SecurityToken> ReadToken(
{
if (string.IsNullOrEmpty(token))
{
StackFrame nullTokenStackFrame = StackFrames.ReadTokenNullOrEmpty ?? new StackFrame(true);
return ValidationError.NullParameter(
nameof(token),
nullTokenStackFrame);
ValidationError.GetCurrentStackFrame());
}

try
Expand All @@ -43,12 +41,11 @@ internal static ValidationResult<SecurityToken> ReadToken(
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
StackFrame malformedTokenStackFrame = StackFrames.ReadTokenMalformed ?? new StackFrame(true);
return new ValidationError(
new MessageDetail(LogMessages.IDX14107),
ValidationFailureType.TokenReadingFailed,
typeof(SecurityTokenMalformedException),
malformedTokenStackFrame,
ValidationError.GetCurrentStackFrame(),
ex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -18,22 +17,14 @@ public partial class JsonWebTokenHandler : TokenHandler
{
/// <summary>
/// Validates a token.
/// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property.
/// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result.
/// On a validation failure, no exception will be thrown; instead, the <see cref="ValidationError"/> will contain the information about the error that occurred.
/// Callers should always check the ValidationResult.IsValid property to verify the validity of the result.
/// </summary>
/// <param name="token">The token to be validated.</param>
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
/// <param name="callContext">A <see cref="CallContext"/> that contains useful information for logging.</param>
/// <param name="callContext">A <see cref="CallContext"/> that contains call information.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to request cancellation of the asynchronous operation.</param>
/// <returns>A <see cref="ValidationResult{TResult}"/> with either a <see cref="ValidatedToken"/> if the token was validated or an <see cref="ValidationError"/> with the failure information and exception otherwise.</returns>
/// <remarks>
/// <para>ValidationError.GetException() will return one of the following exceptions if the <paramref name="token"/> is invalid.</para>
/// </remarks>
/// <exception cref="ArgumentNullException">Returned if <paramref name="token"/> is null or empty.</exception>
/// <exception cref="ArgumentNullException">Returned if <paramref name="validationParameters"/> is null.</exception>
/// <exception cref="ArgumentException">Returned if 'token.Length' is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception>
/// <exception cref="SecurityTokenMalformedException">Returned if <paramref name="token"/> is not a valid <see cref="JsonWebToken"/>, <see cref="ReadToken(string, CallContext)"/></exception>
/// <exception cref="SecurityTokenMalformedException">Returned if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid <see cref="JsonWebToken"/>, <see cref="ReadToken(string, CallContext)"/></exception>
internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
string token,
ValidationParameters validationParameters,
Expand All @@ -42,31 +33,28 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
{
if (string.IsNullOrEmpty(token))
{
StackFrame nullTokenStackFrame = StackFrames.TokenStringNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(token),
nullTokenStackFrame);
ValidationError.GetCurrentStackFrame());
}

if (validationParameters is null)
{
StackFrame nullValidationParametersStackFrame = StackFrames.TokenStringValidationParametersNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(validationParameters),
nullValidationParametersStackFrame);
ValidationError.GetCurrentStackFrame());
}

if (token.Length > MaximumTokenSizeInBytes)
{
StackFrame invalidTokenLengthStackFrame = StackFrames.InvalidTokenLength ??= new StackFrame(true);
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10209,
LogHelper.MarkAsNonPII(token.Length),
LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
ValidationFailureType.InvalidSecurityToken,
typeof(ArgumentException),
invalidTokenLengthStackFrame);
ValidationError.GetCurrentStackFrame());
}

ValidationResult<SecurityToken> readResult = ReadToken(token, callContext);
Expand All @@ -82,12 +70,10 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
if (validationResult.IsValid)
return validationResult; // No need to unwrap and re-wrap the result.

StackFrame validationFailureStackFrame = StackFrames.TokenStringValidationFailed ??= new StackFrame(true);
return validationResult.UnwrapError().AddStackFrame(validationFailureStackFrame);
return validationResult.UnwrapError().AddCurrentStackFrame();
}

StackFrame readFailureStackFrame = StackFrames.TokenStringReadFailed ??= new StackFrame(true);
return readResult.UnwrapError().AddStackFrame(readFailureStackFrame);
return readResult.UnwrapError().AddCurrentStackFrame();
}

/// <inheritdoc/>
Expand All @@ -99,28 +85,25 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
{
if (token is null)
{
StackFrame nullTokenStackFrame = StackFrames.TokenNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(token),
nullTokenStackFrame);
ValidationError.GetCurrentStackFrame());
}

if (validationParameters is null)
{
StackFrame nullValidationParametersStackFrame = StackFrames.TokenValidationParametersNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(validationParameters),
nullValidationParametersStackFrame);
ValidationError.GetCurrentStackFrame());
}

if (token is not JsonWebToken jsonWebToken)
{
StackFrame notJwtStackFrame = StackFrames.TokenNotJWT ??= new StackFrame(true);
return new ValidationError(
new MessageDetail(TokenLogMessages.IDX10001, nameof(token), nameof(JsonWebToken)),
ValidationFailureType.InvalidSecurityToken,
typeof(ArgumentException),
notJwtStackFrame);
ValidationError.GetCurrentStackFrame());
}

BaseConfiguration? currentConfiguration =
Expand Down Expand Up @@ -200,8 +183,7 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, lkgConfiguration, cal
}

// If we reach this point, the token validation failed and we should return the error.
StackFrame stackFrame = StackFrames.TokenValidationFailed ??= new StackFrame(true);
return result.UnwrapError().AddStackFrame(stackFrame);
return result.UnwrapError().AddCurrentStackFrame();
}

private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWEAsync(
Expand All @@ -215,15 +197,13 @@ private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWEAsync(
jwtToken, validationParameters, configuration, callContext);
if (!decryptionResult.IsValid)
{
StackFrame decryptionFailureStackFrame = StackFrames.DecryptionFailed ??= new StackFrame(true);
return decryptionResult.UnwrapError().AddStackFrame(decryptionFailureStackFrame);
return decryptionResult.UnwrapError().AddCurrentStackFrame();
}

ValidationResult<SecurityToken> readResult = ReadToken(decryptionResult.UnwrapResult(), callContext);
if (!readResult.IsValid)
{
StackFrame readFailureStackFrame = StackFrames.DecryptedReadFailed ??= new StackFrame(true);
return readResult.UnwrapError().AddStackFrame(readFailureStackFrame);
return readResult.UnwrapError().AddCurrentStackFrame();
}

JsonWebToken decryptedToken = (readResult.UnwrapResult() as JsonWebToken)!;
Expand All @@ -233,8 +213,7 @@ await ValidateJWSAsync(decryptedToken!, validationParameters, configuration, cal

if (!validationResult.IsValid)
{
StackFrame validationFailureStackFrame = StackFrames.JWEValidationFailed ??= new StackFrame(true);
return validationResult.UnwrapError().AddStackFrame(validationFailureStackFrame);
return validationResult.UnwrapError().AddCurrentStackFrame();
}

JsonWebToken jsonWebToken = (validationResult.UnwrapResult().SecurityToken as JsonWebToken)!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 248, can we do better (more precise?) than catching the general Exception class?

Expand Down Expand Up @@ -289,10 +268,7 @@ private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWSAsync(
tokenAudiences, jsonWebToken, validationParameters, callContext);

if (!audienceValidationResult.IsValid)
{
StackFrame audienceValidationFailureStackFrame = StackFrames.AudienceValidationFailed ??= new StackFrame(true);
return audienceValidationResult.UnwrapError().AddStackFrame(audienceValidationFailureStackFrame);
}
return audienceValidationResult.UnwrapError().AddCurrentStackFrame();
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
Expand Down Expand Up @@ -362,10 +338,7 @@ private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWSAsync(
{
ValidationResult<SecurityToken> actorReadingResult = ReadToken(jsonWebToken.Actor, callContext);
if (!actorReadingResult.IsValid)
{
StackFrame actorReadingFailureStackFrame = StackFrames.ActorReadFailed ??= new StackFrame(true);
return actorReadingResult.UnwrapError().AddStackFrame(actorReadingFailureStackFrame);
}
return actorReadingResult.UnwrapError().AddCurrentStackFrame();

JsonWebToken actorToken = (actorReadingResult.UnwrapResult() as JsonWebToken)!;
ValidationParameters actorParameters = validationParameters.ActorValidationParameters;
Expand All @@ -374,10 +347,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext,
.ConfigureAwait(false);

if (!innerActorValidationResult.IsValid)
{
StackFrame actorValidationFailureStackFrame = StackFrames.ActorValidationFailed ??= new StackFrame(true);
return innerActorValidationResult.UnwrapError().AddStackFrame(actorValidationFailureStackFrame);
}
return innerActorValidationResult.UnwrapError().AddCurrentStackFrame();

actorValidationResult = innerActorValidationResult;
}
Expand Down Expand Up @@ -410,10 +380,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext,
ValidationResult<SecurityKey> signatureValidationResult = ValidateSignature(
jsonWebToken, validationParameters, configuration, callContext);
if (!signatureValidationResult.IsValid)
{
StackFrame signatureValidationFailureStackFrame = StackFrames.SignatureValidationFailed ??= new StackFrame(true);
return signatureValidationResult.UnwrapError().AddStackFrame(signatureValidationFailureStackFrame);
}
return signatureValidationResult.UnwrapError().AddCurrentStackFrame();

ValidationResult<ValidatedSigningKeyLifetime> issuerSigningKeyValidationResult;

Expand Down

This file was deleted.

Loading
Loading