-
Notifications
You must be signed in to change notification settings - Fork 409
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Validate Audience for SAML2TokenHandler with New Model (#2863)
* Initial test to check for painpoints integrating Wilson validation model for SAML * Added more test cases and removed duplicate skipDelegates.cs * removed failing non-applicable test * Address PR feedback part 1 * Added new ValidateTokeAsync method to InternalAPI.Unshipped.txt * Clean up PR feedback * Simplify test into comparisson only tests. * Update test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.Audience.cs Co-authored-by: Westin Musser <[email protected]> * Addressing PR feedback * Addressing PR feedback --------- Co-authored-by: Franco Fung <[email protected]> Co-authored-by: Westin Musser <[email protected]>
- Loading branch information
1 parent
2347f09
commit 3dab668
Showing
7 changed files
with
440 additions
and
95 deletions.
There are no files selected for viewing
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
150 changes: 150 additions & 0 deletions
150
...osoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.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,150 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
#nullable enable | ||
namespace Microsoft.IdentityModel.Tokens.Saml2 | ||
{ | ||
/// <summary> | ||
/// A <see cref="SecurityTokenHandler"/> designed for creating and validating Saml2 Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf | ||
/// </summary> | ||
public partial class Saml2SecurityTokenHandler : SecurityTokenHandler | ||
{ | ||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously | ||
internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync( | ||
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously | ||
Saml2SecurityToken samlToken, | ||
ValidationParameters validationParameters, | ||
CallContext callContext, | ||
#pragma warning disable CA1801 // Review unused parameters | ||
CancellationToken cancellationToken) | ||
#pragma warning restore CA1801 // Review unused parameters | ||
{ | ||
if (samlToken is null) | ||
{ | ||
StackFrames.TokenNull ??= new StackFrame(true); | ||
return ValidationError.NullParameter( | ||
nameof(samlToken), | ||
StackFrames.TokenNull); | ||
} | ||
|
||
if (validationParameters is null) | ||
{ | ||
StackFrames.TokenValidationParametersNull ??= new StackFrame(true); | ||
return ValidationError.NullParameter( | ||
nameof(validationParameters), | ||
StackFrames.TokenValidationParametersNull); | ||
} | ||
|
||
var conditionsResult = ValidateConditions(samlToken, validationParameters, callContext); | ||
|
||
if (!conditionsResult.IsSuccess) | ||
{ | ||
return conditionsResult.UnwrapError().AddStackFrame(new StackFrame(true)); | ||
} | ||
|
||
return new ValidatedToken(samlToken, this, validationParameters); | ||
} | ||
|
||
// ValidatedConditions is basically a named tuple but using a record struct better expresses the intent. | ||
internal record struct ValidatedConditions(string? ValidatedAudience, ValidatedLifetime? ValidatedLifetime); | ||
|
||
internal virtual ValidationResult<ValidatedConditions> ValidateConditions(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) | ||
{ | ||
if (samlToken.Assertion is null) | ||
{ | ||
StackFrames.AssertionNull ??= new StackFrame(true); | ||
return ValidationError.NullParameter( | ||
nameof(samlToken.Assertion), | ||
StackFrames.AssertionNull); | ||
} | ||
|
||
if (samlToken.Assertion.Conditions is null) | ||
{ | ||
StackFrames.AssertionConditionsNull ??= new StackFrame(true); | ||
return ValidationError.NullParameter( | ||
nameof(samlToken.Assertion.Conditions), | ||
StackFrames.AssertionConditionsNull); | ||
} | ||
|
||
var lifetimeValidationResult = validationParameters.LifetimeValidator( | ||
samlToken.Assertion.Conditions.NotBefore, | ||
samlToken.Assertion.Conditions.NotOnOrAfter, | ||
samlToken, | ||
validationParameters, | ||
callContext); | ||
|
||
if (!lifetimeValidationResult.IsSuccess) | ||
{ | ||
StackFrames.LifetimeValidationFailed ??= new StackFrame(true); | ||
return lifetimeValidationResult.UnwrapError().AddStackFrame(StackFrames.LifetimeValidationFailed); | ||
} | ||
|
||
if (samlToken.Assertion.Conditions.OneTimeUse) | ||
{ | ||
//ValidateOneTimeUseCondition(samlToken, validationParameters); | ||
// We can keep an overridable method for this, or rely on the TokenReplayValidator delegate. | ||
var oneTimeUseValidationResult = validationParameters.TokenReplayValidator( | ||
samlToken.Assertion.Conditions.NotOnOrAfter, | ||
samlToken.Assertion.CanonicalString, | ||
validationParameters, | ||
callContext); | ||
|
||
if (!oneTimeUseValidationResult.IsSuccess) | ||
{ | ||
StackFrames.OneTimeUseValidationFailed ??= new StackFrame(true); | ||
return oneTimeUseValidationResult.UnwrapError().AddStackFrame(StackFrames.OneTimeUseValidationFailed); | ||
} | ||
} | ||
|
||
if (samlToken.Assertion.Conditions.ProxyRestriction != null) | ||
{ | ||
//throw LogExceptionMessage(new SecurityTokenValidationException(LogMessages.IDX13511)); | ||
var proxyValidationError = ValidateProxyRestriction( | ||
samlToken, | ||
validationParameters, | ||
callContext); | ||
|
||
if (proxyValidationError is not null) | ||
{ | ||
return proxyValidationError; | ||
} | ||
} | ||
|
||
string? validatedAudience = null; | ||
foreach (var audienceRestriction in samlToken.Assertion.Conditions.AudienceRestrictions) | ||
{ | ||
// AudienceRestriction.Audiences is a List<string> but returned as ICollection<string> | ||
// no conversion occurs, ToList() is never called but we have to account for the possibility. | ||
if (audienceRestriction.Audiences is not List<string> audiencesAsList) | ||
audiencesAsList = [.. audienceRestriction.Audiences]; | ||
|
||
var audienceValidationResult = validationParameters.AudienceValidator( | ||
audiencesAsList, | ||
samlToken, | ||
validationParameters, | ||
callContext); | ||
if (!audienceValidationResult.IsSuccess) | ||
return audienceValidationResult.UnwrapError(); | ||
|
||
// Audience is valid, save it for later. | ||
validatedAudience = audienceValidationResult.UnwrapResult(); | ||
} | ||
|
||
return new ValidatedConditions(validatedAudience, lifetimeValidationResult.UnwrapResult()); | ||
} | ||
|
||
#pragma warning disable CA1801 // Review unused parameters | ||
internal virtual ValidationError? ValidateProxyRestriction(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) | ||
#pragma warning restore CA1801 // Review unused parameters | ||
{ | ||
// return an error, or ignore and allow overriding? | ||
return null; | ||
} | ||
} | ||
} | ||
#nullable restore |
27 changes: 27 additions & 0 deletions
27
...ft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.StackFrames.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,27 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Diagnostics; | ||
|
||
#nullable enable | ||
namespace Microsoft.IdentityModel.Tokens.Saml2 | ||
{ | ||
public partial class Saml2SecurityTokenHandler : SecurityTokenHandler | ||
{ | ||
// Cached stack frames to build exceptions from validation errors | ||
internal static class StackFrames | ||
{ | ||
// Stack frames from ValidateTokenAsync using SecurityToken | ||
internal static StackFrame? TokenNull; | ||
internal static StackFrame? TokenValidationParametersNull; | ||
|
||
// Stack frames from ValidateConditions | ||
internal static StackFrame? AudienceValidationFailed; | ||
internal static StackFrame? AssertionNull; | ||
internal static StackFrame? AssertionConditionsNull; | ||
internal static StackFrame? LifetimeValidationFailed; | ||
internal static StackFrame? OneTimeUseValidationFailed; | ||
} | ||
} | ||
} | ||
#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
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
Oops, something went wrong.