mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Adjust validation of htu for FAPI 2.0 profile conformance
This commit is contained in:
parent
163047b238
commit
844ffae374
2 changed files with 93 additions and 1 deletions
|
|
@ -293,7 +293,7 @@ internal class DPoPProofValidator : IDPoPProofValidator
|
|||
return;
|
||||
}
|
||||
|
||||
if (!result.Payload.TryGetValue(JwtClaimTypes.DPoPHttpUrl, out var htu) || !context.ExpectedUrl.Equals(htu))
|
||||
if (!result.Payload.TryGetValue(JwtClaimTypes.DPoPHttpUrl, out var htu) || !HtuValueIsValid(context.ExpectedUrl, htu as string))
|
||||
{
|
||||
result.SetError("Invalid 'htu' value.");
|
||||
return;
|
||||
|
|
@ -328,6 +328,31 @@ internal class DPoPProofValidator : IDPoPProofValidator
|
|||
}
|
||||
}
|
||||
|
||||
private bool HtuValueIsValid(string requestedUri, string? htuValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(requestedUri) || string.IsNullOrEmpty(htuValue))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var uri1 = new Uri(requestedUri);
|
||||
var uri2 = new Uri(htuValue);
|
||||
|
||||
return Uri.Compare(
|
||||
uri1,
|
||||
uri2,
|
||||
UriComponents.Scheme | UriComponents.HostAndPort | UriComponents.Path,
|
||||
UriFormat.SafeUnescaped,
|
||||
StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates if the token has been replayed.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,73 @@ public class PayloadTests : DPoPProofValidatorTestBase
|
|||
Result.ShouldBeInvalidProofWithDescription("Invalid 'htu' value.");
|
||||
ProofValidator.ReplayCacheShouldNotBeCalled();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://example.com?query=1#fragment")]
|
||||
[InlineData("https://example.com/#fragment")]
|
||||
[InlineData("https://example.com/?query=1")]
|
||||
[Trait("Category", "Unit")]
|
||||
public void htu_ignores_query_and_fragment_parts_in_comparison_against_requested_url(string payloadUrl)
|
||||
{
|
||||
Result.Payload = new Dictionary<string, object>
|
||||
{
|
||||
{ JwtClaimTypes.DPoPAccessTokenHash, AccessTokenHash },
|
||||
{ JwtClaimTypes.JwtId, TokenId },
|
||||
{ JwtClaimTypes.DPoPHttpMethod, HttpMethod },
|
||||
{ JwtClaimTypes.DPoPHttpUrl, payloadUrl },
|
||||
{ JwtClaimTypes.IssuedAt, IssuedAt }
|
||||
};
|
||||
|
||||
ProofValidator.TestTimeProvider.SetUtcNow(DateTimeOffset.FromUnixTimeSeconds(IssuedAt));
|
||||
ProofValidator.ValidatePayload(Context, Result);
|
||||
|
||||
Result.IsError.ShouldBeFalse(Result.ErrorDescription);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://example.com")]
|
||||
[InlineData("HTTPS://EXAMPLE.COM")]
|
||||
[InlineData("https://EXAMPLE.com")]
|
||||
[InlineData("HtTpS://eXaMpLe.CoM")]
|
||||
[Trait("Category", "Unit")]
|
||||
public void htu_ignores_casing_in_comparison_against_requested_url(string payloadUrl)
|
||||
{
|
||||
Result.Payload = new Dictionary<string, object>
|
||||
{
|
||||
{ JwtClaimTypes.DPoPAccessTokenHash, AccessTokenHash },
|
||||
{ JwtClaimTypes.JwtId, TokenId },
|
||||
{ JwtClaimTypes.DPoPHttpMethod, HttpMethod },
|
||||
{ JwtClaimTypes.DPoPHttpUrl, payloadUrl },
|
||||
{ JwtClaimTypes.IssuedAt, IssuedAt }
|
||||
};
|
||||
|
||||
ProofValidator.TestTimeProvider.SetUtcNow(DateTimeOffset.FromUnixTimeSeconds(IssuedAt));
|
||||
ProofValidator.ValidatePayload(Context, Result);
|
||||
|
||||
Result.IsError.ShouldBeFalse(Result.ErrorDescription);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("https://example.com", "https://example.com:443")]
|
||||
[InlineData("http://example.com", "http://example.com:80")]
|
||||
[Trait("Category", "Unit")]
|
||||
public void htu_uses_scheme_based_normalization_in_comparison_against_requested_url(string expectedUrl, string payloadUrl)
|
||||
{
|
||||
Context = Context with { ExpectedUrl = expectedUrl };
|
||||
Result.Payload = new Dictionary<string, object>
|
||||
{
|
||||
{ JwtClaimTypes.DPoPAccessTokenHash, AccessTokenHash },
|
||||
{ JwtClaimTypes.JwtId, TokenId },
|
||||
{ JwtClaimTypes.DPoPHttpMethod, HttpMethod },
|
||||
{ JwtClaimTypes.DPoPHttpUrl, payloadUrl },
|
||||
{ JwtClaimTypes.IssuedAt, IssuedAt }
|
||||
};
|
||||
|
||||
ProofValidator.TestTimeProvider.SetUtcNow(DateTimeOffset.FromUnixTimeSeconds(IssuedAt));
|
||||
ProofValidator.ValidatePayload(Context, Result);
|
||||
|
||||
Result.IsError.ShouldBeFalse(Result.ErrorDescription);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Unit")]
|
||||
|
|
|
|||
Loading…
Reference in a new issue