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

Merged
merged 24 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a664f72
Removed static stack frames and replaced with the simplified approach…
iNinja Dec 10, 2024
9b66954
Updated IssuerValidationSource to be extensible. Extracted validated …
iNinja Dec 10, 2024
073aae5
Updated documentation
iNinja Dec 10, 2024
143daa7
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Dec 10, 2024
206c0be
Added nullability annotations to ValidationParameters. Enabled settin…
iNinja Dec 16, 2024
aa94abc
Handle case where ValidateActor is true, there is an actor token, but…
iNinja Dec 16, 2024
9e2746c
Updated documentation, added missing interfaces and methods required …
iNinja Dec 16, 2024
0ba56b4
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Dec 16, 2024
42a41bc
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Dec 17, 2024
54063b3
Added missing documentation around validation errors
iNinja Dec 17, 2024
5ac80f6
Added CLSCompliant flag to Log methods to address the build issue unt…
iNinja Dec 17, 2024
fa12679
Moved signature error back to internal after merging from the new val…
iNinja Jan 13, 2025
b402f51
Cache exceptions from ValidationErrors
iNinja Jan 13, 2025
9a3e03a
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Jan 13, 2025
3403ac5
Added log level checks for log methods in ValidatedToken and Validati…
iNinja Jan 13, 2025
08acf78
Updated comment for issuer validation source for clarity
iNinja Jan 13, 2025
1ccb3b6
Removed use of "this" in constructors.
iNinja Jan 14, 2025
00090eb
Updated documentation based on PR feedback
iNinja Jan 14, 2025
0cb18a9
Removed primary constructor from ValidatedToken in favour of clarity …
iNinja Jan 14, 2025
9dd20d5
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Jan 14, 2025
3c83d26
Overridden ToString method for validation objects.
iNinja Jan 15, 2025
197e401
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Jan 15, 2025
db68a7c
Added default value for the cancellation token on the entry points fo…
iNinja Jan 16, 2025
8fff0a6
Merge branch 'dev' into iinglese/tidy-up-new-validation-model
iNinja Jan 17, 2025
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());
iNinja marked this conversation as resolved.
Show resolved Hide resolved
}

if (validationParameters == null)
{
StackFrame validationParametersNullStackFrame = StackFrames.DecryptionValidationParametersNull ??= new StackFrame(true);
return ValidationError.NullParameter(
nameof(validationParameters),
validationParametersNullStackFrame);
ValidationError.GetCurrentStackFrame());
iNinja marked this conversation as resolved.
Show resolved Hide resolved
}

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,13 @@ 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 validation failure no exception will be thrown. 'see cref="ValidationError"' will contain information pertaining to the error.
/// </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 +32,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 +69,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 +84,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 +182,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();
iNinja marked this conversation as resolved.
Show resolved Hide resolved
}

private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWEAsync(
Expand All @@ -215,15 +196,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 +212,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)!;
iNinja marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -289,10 +267,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 +337,12 @@ 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();

if (validationParameters.ActorValidationParameters is null)
return ValidationError.NullParameter(
nameof(validationParameters.ActorValidationParameters),
ValidationError.GetCurrentStackFrame());

JsonWebToken actorToken = (actorReadingResult.UnwrapResult() as JsonWebToken)!;
ValidationParameters actorParameters = validationParameters.ActorValidationParameters;
Expand All @@ -374,10 +351,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 +384,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