Skip to content

Commit

Permalink
SAML and SAML2 new model validation: Read Token (#2980)
Browse files Browse the repository at this point in the history
* Added ReadToken to SamlTokenHandler returning a ValidationResult. Added log messages for the malformed case, and SamlValidationError to return the correct exception.

* Added ReadToken to Saml2TokenHandler returning a ValidationResult. Added log messages for the malformed case, and Saml2ValidationError to return the correct exception.

* Addressed PR feedback
  • Loading branch information
iNinja authored Nov 8, 2024
1 parent 73e2c18 commit a275ee4
Show file tree
Hide file tree
Showing 14 changed files with 463 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string
const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string
Expand All @@ -7,13 +9,22 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
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.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, 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.Saml.SamlValidationError
Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> void
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.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, 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.Saml2ValidationError
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> void
override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.SecurityKey>
static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationParameters>
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.AssertionConditionsValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame
Expand All @@ -32,4 +43,6 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.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.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken>
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>
virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

namespace Microsoft.IdentityModel.Tokens.Saml
{
internal class SamlValidationError : ValidationError
{
internal SamlValidationError(MessageDetail messageDetail, ValidationFailureType failureType, Type exceptionType, StackFrame stackFrame, Exception innerException) : base(messageDetail, failureType, exceptionType, stackFrame, innerException)
{
}

internal override Exception GetException()
{
if (ExceptionType == typeof(SamlSecurityTokenReadException))
{
var exception = new SamlSecurityTokenReadException(MessageDetail.Message, InnerException);
return exception;
}

return base.GetException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal static class LogMessages
// SecurityTokenHandler messages
internal const string IDX11400 = "IDX11400: The '{0}', can only process SecurityTokens of type: '{1}'. The SecurityToken received is of type: '{2}'.";
internal const string IDX11401 = "IDX11401: Unable to validate token. TokenValidationParameters.RequireAudience is true but no AudienceRestrictions were found in the inbound token.";
internal const string IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'.";

// signature creation / validation
internal const string IDX11312 = "IDX11312: Unable to validate token. A SamlSamlAttributeStatement can only have one SamlAttribute of type 'Actor'. This special SamlAttribute is used in delegation scenarios.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text;
using System.Xml;
using Microsoft.IdentityModel.Logging;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;

namespace Microsoft.IdentityModel.Tokens.Saml
{
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
{
/// <summary>
/// Converts a string into an instance of <see cref="SamlSecurityToken"/>.
/// </summary>
/// <param name="token">a Saml token as a string.</param>
/// <param name="callContext">An opaque context used to store work when working with authentication artifacts.</param>
/// <returns>A <see cref="SamlSecurityToken"/></returns>
/// <exception cref="ArgumentNullException">If <paramref name="token"/> is null or empty.</exception>
/// <exception cref="ArgumentException">If 'token.Length' is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception>
internal virtual ValidationResult<SamlSecurityToken> ReadSamlToken(string token, CallContext callContext)
{
if (string.IsNullOrEmpty(token))
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

if (token.Length > MaximumTokenSizeInBytes)
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10209,
LogHelper.MarkAsNonPII(token.Length),
LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
ValidationFailureType.TokenExceedsMaximumSize,
typeof(ArgumentOutOfRangeException),
ValidationError.GetCurrentStackFrame());

try
{
using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(token), XmlDictionaryReaderQuotas.Max))
{
return ReadSamlToken(reader);
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
return new SamlValidationError(
new MessageDetail(LogMessages.IDX11402, ex.Message),
ValidationFailureType.TokenReadingFailed,
typeof(SamlSecurityTokenReadException),
ValidationError.GetCurrentStackFrame(),
ex);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,26 @@ namespace Microsoft.IdentityModel.Tokens.Saml
/// </summary>
public partial class SamlSecurityTokenHandler : 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
string token,
ValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken)
{
if (token is null)
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

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

var tokenReadingResult = ReadSamlToken(token, callContext);
if (!tokenReadingResult.IsValid)
return tokenReadingResult.UnwrapError().AddCurrentStackFrame();

return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false);
}

internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
SamlSecurityToken samlToken,
ValidationParameters validationParameters,
CallContext callContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

namespace Microsoft.IdentityModel.Tokens.Saml2
{
internal class Saml2ValidationError : ValidationError
{
internal Saml2ValidationError(MessageDetail messageDetail, ValidationFailureType failureType, Type exceptionType, StackFrame stackFrame, Exception innerException) : base(messageDetail, failureType, exceptionType, stackFrame, innerException)
{
}

internal override Exception GetException()
{
if (ExceptionType == typeof(Saml2SecurityTokenReadException))
{
var exception = new Saml2SecurityTokenReadException(MessageDetail.Message, InnerException);
return exception;
}

return base.GetException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal static class LogMessages
internal const string IDX13400 = "IDX13400: The '{0}', can only process SecurityTokens of type: '{1}'. The SecurityToken received is of type: '{2}'.";
internal const string IDX13001 = "IDX13001: A SAML2 assertion that specifies an AuthenticationContext DeclarationReference is not supported.To handle DeclarationReference, extend the Saml2SecurityTokenHandler and override ProcessAuthenticationStatement.";
internal const string IDX13002 = "IDX13002: Unable to validate token. TokenValidationParameters.RequireAudience is true but no AudienceRestrictions were found in the inbound token.";
internal const string IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'.";

// signature creation / validation
internal const string IDX13509 = "IDX13509: Unable to validate token, Subject is null.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text;
using System.Xml;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens.Saml;

using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;

namespace Microsoft.IdentityModel.Tokens.Saml2
{
public partial class Saml2SecurityTokenHandler : SecurityTokenHandler
{
/// <summary>
/// Converts a string into an instance of <see cref="SamlSecurityToken"/>.
/// </summary>
/// <param name="token">a Saml token as a string.</param>
/// <param name="callContext">An opaque context used to store work when working with authentication artifacts.</param>
/// <returns>A <see cref="SamlSecurityToken"/></returns>
/// <exception cref="ArgumentNullException">If <paramref name="token"/> is null or empty.</exception>
/// <exception cref="ArgumentException">If 'token.Length' is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception>
internal virtual ValidationResult<Saml2SecurityToken> ReadSaml2Token(string token, CallContext callContext)
{
if (string.IsNullOrEmpty(token))
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

if (token.Length > MaximumTokenSizeInBytes)
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10209,
LogHelper.MarkAsNonPII(token.Length),
LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
ValidationFailureType.TokenReadingFailed,
typeof(ArgumentException),
ValidationError.GetCurrentStackFrame());

try
{
using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(token), XmlDictionaryReaderQuotas.Max))
{
return ReadSaml2Token(reader);
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
return new Saml2ValidationError(
new MessageDetail(LogMessages.IDX13003, ex.Message),
ValidationFailureType.TokenReadingFailed,
typeof(Saml2SecurityTokenReadException),
ValidationError.GetCurrentStackFrame(),
ex);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ namespace Microsoft.IdentityModel.Tokens.Saml2
/// </summary>
public partial class Saml2SecurityTokenHandler : SecurityTokenHandler
{
internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
string token,
ValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken)
{
if (token is null)
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

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

var tokenReadingResult = ReadSaml2Token(token, callContext);
if (!tokenReadingResult.IsValid)
return tokenReadingResult.UnwrapError().AddCurrentStackFrame();

return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false);
}

internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
Saml2SecurityToken samlToken,
ValidationParameters validationParameters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(strin
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
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenExceedsMaximumSize -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
Loading

0 comments on commit a275ee4

Please sign in to comment.