Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions src/WalletFramework.Core/ClaimPaths/ClaimPath.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
using System.Text;
using LanguageExt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.ClaimPaths.Errors;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Path;
using Newtonsoft.Json.Linq;

namespace WalletFramework.Core.ClaimPaths;

[JsonConverter(typeof(ClaimPathJsonConverter))]
public readonly struct ClaimPath
{
private readonly IReadOnlyList<ClaimPathComponent> _components;

private ClaimPath(IReadOnlyList<ClaimPathComponent> components)
{
_components = components;
}
private ClaimPath(IReadOnlyList<ClaimPathComponent> components) => _components = components;

public IReadOnlyList<ClaimPathComponent> GetPathComponents() => _components;

Expand All @@ -26,13 +25,10 @@ public static Validation<ClaimPath> FromComponents(IEnumerable<ClaimPathComponen
return new ClaimPath(list);
}

public static Validation<ClaimPath> FromJArray(JArray array)
{
return
from components in array.TraverseAll(ClaimPathComponent.Create)
from path in FromComponents(components)
select path;
}
public static Validation<ClaimPath> FromJArray(JArray array) =>
from components in array.TraverseAll(ClaimPathComponent.Create)
from path in FromComponents(components)
select path;

public static JArray ToJArray(ClaimPath claimPath)
{
Expand All @@ -42,23 +38,24 @@ public static JArray ToJArray(ClaimPath claimPath)
component.Match(
key =>
{
array.Add(new JValue(key));
array.Add(new JValue(key));
return Unit.Default;
},
index =>
{
array.Add(new JValue(index));
array.Add(new JValue(index));
return Unit.Default;
},
_ =>
{
array.Add(JValue.CreateNull());
array.Add(JValue.CreateNull());
return Unit.Default;
}
);
}

return array;
}
}
}

public static class ClaimPathFun
Expand Down Expand Up @@ -87,7 +84,7 @@ public static JsonPath ToJsonPath(this ClaimPath claimPath)
return Unit.Default;
});
}

return JsonPath.ValidJsonPath(jsonPath.ToString()).UnwrapOrThrow();
}
}
2 changes: 1 addition & 1 deletion src/WalletFramework.Core/ClaimPaths/ClaimPathComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static Validation<ClaimPathComponent> Create(JToken token) =>
? new ClaimPathComponent(token.Value<string>()!)
: new UnknownComponentError(),
JTokenType.Integer => new ClaimPathComponent(token.Value<int>()),
JTokenType.Null => new ClaimPathComponent(new SelectAllElementsInArrayComponent()),
JTokenType.Null or JTokenType.Undefined => new ClaimPathComponent(new SelectAllElementsInArrayComponent()),
_ => new UnknownComponentError()
};

Expand Down
8 changes: 6 additions & 2 deletions src/WalletFramework.Core/ClaimPaths/ClaimPathJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ public override void WriteJson(JsonWriter writer, ClaimPath value, JsonSerialize

public override ClaimPath ReadJson(JsonReader reader, Type objectType, ClaimPath existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var array = JArray.Load(reader);
var validation = ClaimPath.FromJArray(array);
var token = JToken.Load(reader);
var validation = token.Type switch
{
JTokenType.Array => ClaimPath.FromJArray((JArray)token),
_ => throw new JsonSerializationException($"Expected claim path array, but got {token.Type}.")
};
return validation.UnwrapOrThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using LanguageExt;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WalletFramework.Core.ClaimPaths;
using WalletFramework.Core.Functional;
using WalletFramework.Core.Json;
using WalletFramework.Oid4Vc.Oid4Vp.DcApi.Models;
Expand Down Expand Up @@ -192,9 +193,14 @@ public static Validation<AuthorizationRequestCancellation, AuthorizationRequest>
JObject authRequestJObject)
{
var responseUriOption = AuthorizationRequestExtensions.GetResponseUriMaybe(authRequestJObject);

var serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings
{
Converters = { new ClaimPathJsonConverter() }
});

var authRequestValidation =
authRequestJObject.ToObject<AuthorizationRequest>()
authRequestJObject.ToObject<AuthorizationRequest>(serializer)
?? new InvalidRequestError("Could not parse the Authorization Request")
.ToInvalid<AuthorizationRequest>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ await credentialSetSdJwtRecords.Match(
await credentialSetMdocRecords.Match(
async mDocRecords =>
{
if (mDocRecords.Count() > 1)
if (mDocRecords.Count > 1)
await _mdocRepository.Delete(mDocRecord.GetId());
},
() => Task.CompletedTask);
Expand Down
19 changes: 19 additions & 0 deletions test/WalletFramework.Core.Tests/Path/ClaimPathTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ public void ClaimPathJsonConverter_Can_ReadJson()
var expected = ClaimPath.FromJArray(new JArray("address", "street_address")).UnwrapOrThrow();
Assert.Equal(expected.GetPathComponents(), claimPath.GetPathComponents());
}

[Fact]
public void ClaimPathJsonConverter_Can_ReadJson_With_Null_Component_As_SelectAll()
{
// Arrange
var json = "[\"degrees\",null,\"type\"]";
var settings = new JsonSerializerSettings { Converters = { new ClaimPathJsonConverter() } };

// Act
var claimPath = JsonConvert.DeserializeObject<ClaimPath>(json, settings);

// Assert
Assert.Equal("$.degrees[*].type", claimPath.ToJsonPath().ToString());
Assert.True(claimPath.GetPathComponents()[1]
.Match(
_ => false,
_ => false,
_ => true));
}

[Fact]
public void ClaimPathJsonConverter_Can_WriteJson()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ public static class SdJwtConfigurationSample
["locale"] = "en-US"
}
},
["claims"] = new JArray()
["claims"] = new JArray
{
new JObject
{
["path"] = new JArray(){"given_name"},
["path"] = new JArray {"given_name"},
["display"] = new JArray
{
new JObject
Expand All @@ -71,7 +71,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"family_name"},
["path"] = new JArray {"family_name"},
["display"] = new JArray
{
new JObject
Expand All @@ -88,7 +88,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"email"},
["path"] = new JArray {"email"},
["display"] = new JArray
{
new JObject
Expand All @@ -105,7 +105,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"address" },
["path"] = new JArray {"address" },
["display"] = new JArray
{
new JObject
Expand All @@ -122,7 +122,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"address", "street"},
["path"] = new JArray {"address", "street"},
["display"] = new JArray
{
new JObject
Expand All @@ -139,7 +139,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"address", "zip"},
["path"] = new JArray {"address", "zip"},
["display"] = new JArray
{
new JObject
Expand All @@ -156,7 +156,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"address", "zip", "building"},
["path"] = new JArray {"address", "zip", "building"},
["display"] = new JArray
{
new JObject
Expand All @@ -173,7 +173,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"degrees"},
["path"] = new JArray {"degrees"},
["display"] = new JArray
{
new JObject
Expand All @@ -190,7 +190,7 @@ public static class SdJwtConfigurationSample
},
new JObject
{
["path"] = new JArray(){"degrees", JValue.CreateNull(), "type"},
["path"] = new JArray {"degrees", JValue.CreateNull(), "type"},
["display"] = new JArray
{
new JObject
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,69 @@
using FluentAssertions;
using WalletFramework.Core.ClaimPaths;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt.Samples;
using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt.SdJwtConfiguration;

namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt;

public class SdJwtConfigurationTests
{
// TODO: Implement this
[Fact]
public void Can_Parse()
{
// Arrange
var sample = SdJwtConfigurationSample.ValidSample;

// Act
ValidSdJwtCredentialConfiguration(sample).Match(
// Assert
sut =>
{
sut.Format.Should().Be(SdJwtConfigurationSample.Format);
sut.Vct.Should().Be(SdJwtConfigurationSample.Vct);

sut.CredentialConfiguration.Scope.Match(
scope => scope.Should().Be(SdJwtConfigurationSample.Scope),
() => Assert.Fail("Scope must be some"));

sut.CredentialConfiguration.CryptographicBindingMethodsSupported.Match(
methods => methods.Should().ContainSingle(method => method.ToString() == "jwk"),
() => Assert.Fail("CryptographicBindingMethodsSupported must be some"));

sut.CredentialConfiguration.CredentialSigningAlgValuesSupported.Match(
values => values.Should().ContainSingle(value => value.ToString() == "ES256"),
() => Assert.Fail("CredentialSigningAlgValuesSupported must be some"));

sut.CredentialConfiguration.ProofTypesSupported.Match(
proofTypes =>
{
proofTypes.Keys.Should().ContainSingle(key => key.ToString() == "jwt");
proofTypes.Values.Single().ProofSigningAlgValuesSupported
.Should()
.ContainSingle(value => value.ToString() == "ES256");
},
() => Assert.Fail("ProofTypesSupported must be some"));

sut.CredentialConfiguration.CredentialMetadata.Match(
metadata =>
{
metadata.Display.Match(
displays => displays.Should().HaveCount(1),
() => Assert.Fail("Display must be some"));

metadata.Claims.Match(
claims =>
{
claims.Should().HaveCount(9);
claims.Select(claim => claim.Path.ToJsonPath().ToString())
.Should()
.Contain("$.degrees[*].type");
},
() => Assert.Fail("Claims must be some"));
},
() => Assert.Fail("CredentialMetadata must be some"));
},
_ => Assert.Fail("Must be valid")
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using FluentAssertions;
using WalletFramework.Core.ClaimPaths;
using WalletFramework.Core.Functional;
using WalletFramework.Oid4Vc.Oid4Vp.Models;
using WalletFramework.Oid4Vc.Tests.Oid4Vp.AuthRequest.Models;

Expand All @@ -23,4 +25,47 @@ public void Can_Parse_Authorization_Request_With_Attachments()

authRequest.IsSuccess.Should().BeTrue();
}

[Fact]
public void Can_Parse_Null_ClaimPath_Component_As_Array_Wildcard()
{
var json = """
{
"client_id": "x509_san_dns:funke-wallet.de",
"response_uri": "https://funke-wallet.de/oid4vp/response",
"response_mode": "direct_post",
"nonce": "860303407528324526871313",
"dcql_query": {
"credentials": [
{
"id": "pid",
"format": "dc+sd-jwt",
"meta": {
"vct_values": [
"https://demo.pid-issuer.bundesdruckerei.de/credentials/pid/1.0"
]
},
"claims": [
{
"path": ["degrees", null, "type"]
}
]
}
]
}
}
""";

var authorizationRequest = AuthorizationRequest.CreateAuthorizationRequest(json).UnwrapOrThrow();
var claimPath = authorizationRequest.DcqlQuery.CredentialQueries[0].Claims![0].Path;

claimPath.ToJsonPath().ToString().Should().Be("$.degrees[*].type");
claimPath.GetPathComponents()[1]
.Match(
_ => false,
_ => false,
_ => true)
.Should()
.BeTrue();
}
}
Loading