Skip to content

Commit

Permalink
fix: validations from Newtonsoft.Json removal
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddesmet committed Jun 23, 2024
1 parent 701dc6a commit a9672cc
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 141 deletions.
11 changes: 10 additions & 1 deletion src/Paseto/Validators/BaseValidator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
namespace Paseto.Validators;

using System;
using System.Text.Json;

/// <summary>
/// The Base Validator.
/// </summary>
/// <seealso cref="Paseto.Validation.IPasetoPayloadValidator" />
/// <seealso cref="IPasetoPayloadValidator" />
public abstract class BaseValidator : IPasetoPayloadValidator
{
/// <summary>
Expand Down Expand Up @@ -38,4 +39,12 @@ public abstract class BaseValidator : IPasetoPayloadValidator
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public abstract bool IsValid(IComparable expected = null);

internal static object GetValueFromJsonElement(JsonElement element) => element.ValueKind switch
{
JsonValueKind.Number => element.GetDouble(),
JsonValueKind.String => element.GetString(),
JsonValueKind.True or JsonValueKind.False => element.GetBoolean(),
_ => element.GetRawText().Trim('"')
};
}
76 changes: 76 additions & 0 deletions src/Paseto/Validators/DateValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Paseto.Validators;

using System;
using System.Text.Json;
using Paseto.Validators.Internal;

/// <summary>
/// The Base Date Validator.
/// </summary>
/// <seealso cref="IPasetoPayloadValidator" />
public abstract class DateValidator : BaseValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="DateValidator"/> class.
/// </summary>
/// <param name="payload">The payload.</param>
public DateValidator(PasetoPayload payload) : base(payload) { }

/// <summary>
/// Validates the input value against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="value">The input value to validate.</param>
/// <param name="expected">The optional expected value.</param>
public abstract void ValidateDate(IComparable value, IComparable expected = null);

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token has expired.
/// </exception>
public override void Validate(IComparable expected = null)
{
if (!Payload.TryGetValue(ClaimName, out var value))
throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");

DateTime exp;
try
{
if (value is JsonElement json)
value = GetValueFromJsonElement(json);

if (value is string s)
exp = DateTimeOffset.Parse(s).UtcDateTime;
else
exp = Convert.ToDateTime(value);
}
catch (Exception)
{
throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime");
}

expected ??= DateTime.UtcNow;

ValidateDate(exp, expected);
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public override bool IsValid(IComparable expected = null)
{
try
{
Validate(expected);
return true;
}
catch (Exception)
{
return false;
}
}
}
8 changes: 0 additions & 8 deletions src/Paseto/Validators/EqualValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,4 @@ public override bool IsValid(IComparable expected = null)
return false;
}
}

private static object GetValueFromJsonElement(JsonElement element) => element.ValueKind switch
{
JsonValueKind.Number => element.GetDouble(),
JsonValueKind.String => element.GetString(),
JsonValueKind.True or JsonValueKind.False => element.GetBoolean(),
_ => element.GetRawText().Trim('"')
};
}
49 changes: 5 additions & 44 deletions src/Paseto/Validators/ExpirationTimeValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
/// <summary>
/// The ExpirationTime Validator. This class cannot be inherited.
/// </summary>
/// <seealso cref="Paseto.Validators.BaseValidator" />
public sealed class ExpirationTimeValidator : BaseValidator
/// <seealso cref="Paseto.Validators.DateValidator" />
public sealed class ExpirationTimeValidator : DateValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpirationTimeValidator"/> class.
Expand All @@ -21,49 +21,10 @@ public ExpirationTimeValidator(PasetoPayload payload) : base(payload) { }
/// <value>The name of the claim.</value>
public override string ClaimName => PasetoRegisteredClaimNames.ExpirationTime;

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token has expired.
/// </exception>
public override void Validate(IComparable expected = null)
/// <inheritdoc />
public override void ValidateDate(IComparable value, IComparable expected = null)
{
if (!Payload.TryGetValue(ClaimName, out var value))
throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");

DateTime exp;
try
{
exp = Convert.ToDateTime(value);
}
catch (Exception)
{
throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime");
}

expected ??= DateTime.UtcNow;

if (Comparer.GetComparisonResult(exp, expected) < 0) // expected >= exp
if (Comparer.GetComparisonResult(value, expected) < 0) // expected >= exp
throw new PasetoTokenValidationException("Token has expired");
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public override bool IsValid(IComparable expected = null)
{
try
{
Validate(expected);
return true;
}
catch (Exception)
{
return false;
}
}
}
49 changes: 5 additions & 44 deletions src/Paseto/Validators/IssuedAtValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
/// <summary>
/// The NotAfter Validator. This class cannot be inherited.
/// </summary>
/// <seealso cref="Paseto.Validators.BaseValidator" />
public sealed class IssuedAtValidator : BaseValidator
/// <seealso cref="Paseto.Validators.DateValidator" />
public sealed class IssuedAtValidator : DateValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="IssuedAtValidator"/> class.
Expand All @@ -21,49 +21,10 @@ public IssuedAtValidator(PasetoPayload payload) : base(payload) { }
/// <value>The name of the claim.</value>
public override string ClaimName => PasetoRegisteredClaimNames.IssuedAt;

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token is not yet valid.
/// </exception>
public override void Validate(IComparable expected = null)
/// <inheritdoc />
public override void ValidateDate(IComparable value, IComparable expected = null)
{
if (!Payload.TryGetValue(ClaimName, out var value))
throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");

DateTime iat;
try
{
iat = Convert.ToDateTime(value);
}
catch (Exception)
{
throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime");
}

expected ??= DateTime.UtcNow;

if (Comparer.GetComparisonResult(iat, expected) > 0) // expected >= iat
if (Comparer.GetComparisonResult(value, expected) > 0) // expected >= iat
throw new PasetoTokenValidationException("Token is not yet valid");
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public override bool IsValid(IComparable expected = null)
{
try
{
Validate(expected);
return true;
}
catch (Exception)
{
return false;
}
}
}
49 changes: 5 additions & 44 deletions src/Paseto/Validators/NotBeforeValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
/// <summary>
/// The NotBefore Validator. This class cannot be inherited.
/// </summary>
/// <seealso cref="Paseto.Validators.BaseValidator" />
public sealed class NotBeforeValidator : BaseValidator
/// <seealso cref="Paseto.Validators.DateValidator" />
public sealed class NotBeforeValidator : DateValidator
{
/// <summary>
/// Initializes a new instance of the <see cref="NotBeforeValidator"/> class.
Expand All @@ -21,49 +21,10 @@ public NotBeforeValidator(PasetoPayload payload) : base(payload) { }
/// <value>The name of the claim.</value>
public override string ClaimName => PasetoRegisteredClaimNames.NotBefore;

/// <summary>
/// Validates the payload against the provided optional expected value. Throws an exception if not valid.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <exception cref="PasetoTokenValidationException">
/// Token is not yet valid.
/// </exception>
public override void Validate(IComparable expected = null)
/// <inheritdoc />
public override void ValidateDate(IComparable value, IComparable expected = null)
{
if (!Payload.TryGetValue(ClaimName, out var value))
throw new PasetoTokenValidationException($"Claim '{ClaimName}' not found");

DateTime nbf;
try
{
nbf = Convert.ToDateTime(value);
}
catch (Exception)
{
throw new PasetoTokenValidationException($"Claim '{ClaimName}' must be a DateTime");
}

expected ??= DateTime.UtcNow;

if (Comparer.GetComparisonResult(nbf, expected) >= 0) // expected <= nbf
if (Comparer.GetComparisonResult(value, expected) >= 0) // expected <= nbf
throw new PasetoTokenValidationException("Token is not yet valid");
}

/// <summary>
/// Validates the payload against the provided optional expected value.
/// </summary>
/// <param name="expected">The optional expected value.</param>
/// <returns><c>true</c> if the specified value is valid; otherwise, <c>false</c>.</returns>
public override bool IsValid(IComparable expected = null)
{
try
{
Validate(expected);
return true;
}
catch (Exception)
{
return false;
}
}
}
47 changes: 47 additions & 0 deletions tests/Paseto.Tests/PasetoBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,4 +594,51 @@ public void ShouldSucceedOnEncodeToParsekWhenIsPublicPurposeAndSecretAsymmetricK
parsek.Should().StartWith($"k{(int)version}.secret");
parsek.Split('.').Should().HaveCount(3);
}

[Theory(DisplayName = "Should succeed on Decoding with Date Validations")]
[InlineData(ProtocolVersion.V1)]
[InlineData(ProtocolVersion.V2)]
[InlineData(ProtocolVersion.V3)]
[InlineData(ProtocolVersion.V4)]
public void ShouldSucceedOnDecodingWithDateValidations(ProtocolVersion version)
{
const Purpose purpose = Purpose.Public;
var keyLength = version switch
{
ProtocolVersion.V1 => 0,
ProtocolVersion.V2 => 32,
ProtocolVersion.V3 => 32,
ProtocolVersion.V4 => 32,
_ => throw new ArgumentOutOfRangeException(nameof(version))
};

var sharedKey = new byte[keyLength];
RandomNumberGenerator.Fill(sharedKey);

var keyPair = new PasetoBuilder()
.Use(version, purpose)
.GenerateAsymmetricKeyPair(sharedKey);

var now = DateTime.UtcNow;

var encoded = new PasetoBuilder()
.Use(version, purpose)
.WithSecretKey([.. keyPair.SecretKey.Key.Span])
.IssuedAt(now.AddSeconds(-10))
.Expiration(now.AddHours(1))
.ValidFrom(now.AddSeconds(-10))
.Encode();

var validationParameters = new PasetoTokenValidationParameters
{
ValidateLifetime = true
};

var decoded = new PasetoBuilder()
.Use(version, purpose)
.WithPublicKey([.. keyPair.PublicKey.Key.Span])
.Decode(encoded, validationParameters);

decoded.IsValid.Should().BeTrue();
}
}

0 comments on commit a9672cc

Please sign in to comment.