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

Implement and Test Audience and Lifetime validations in SamlSecurityTokenHandler with New Validation Model #2925

Merged
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.set -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedConditions() -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedConditions(string ValidatedAudience, Microsoft.IdentityModel.Tokens.ValidatedLifetime? ValidatedLifetime) -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.get -> Microsoft.IdentityModel.Tokens.ValidatedLifetime?
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.set -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml2.SamlSecurityTokenHandler.ValidateTokenAsync(SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AudienceValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.LifetimeValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.OneTimeUseValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AudienceValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.LifetimeValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.OneTimeUseValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame
virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

#nullable enable
namespace Microsoft.IdentityModel.Tokens.Saml
{
/// <summary>
/// A <see cref="SecurityTokenHandler"/> designed for creating and validating Saml Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
/// </summary>
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
{
#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
SamlSecurityToken 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));
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
}

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(SamlSecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext)
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}

string? validatedAudience = null;
foreach (var condition in samlToken.Assertion.Conditions.Conditions)
{

if (condition is SamlAudienceRestrictionCondition audienceRestriction)
{

// AudienceRestriction.Audiences is an ICollection<Uri> so we need make a conversion to List<string> before calling our audience validator
var audiencesAsList = audienceRestriction.Audiences.Select(static x => x.OriginalString).ToList();

var audienceValidationResult = validationParameters.AudienceValidator(
audiencesAsList,
samlToken,
validationParameters,
callContext);

if (!audienceValidationResult.IsSuccess)
return audienceValidationResult.UnwrapError();

validatedAudience = audienceValidationResult.UnwrapResult();
}

if (validatedAudience != null)
break;
}

return new ValidatedConditions(validatedAudience, lifetimeValidationResult.UnwrapResult());
}
}
}
#nullable restore
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.Saml
{
public partial class SamlSecurityTokenHandler : 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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace Microsoft.IdentityModel.Tokens.Saml
/// which supports validating tokens passed as strings using <see cref="TokenValidationParameters"/>.
/// </summary>
///
public class SamlSecurityTokenHandler : SecurityTokenHandler
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
{
internal const string Actor = "Actor";
private const string _className = "Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler";
Expand Down
Loading
Loading