MDS: Adjust SupportedCtapOptions parsing logic#419
Merged
Conversation
Test Results2 271 tests 2 263 ✅ 1m 3s ⏱️ Results for commit a94d148. ♻️ This comment has been updated with latest results. |
emlun
requested changes
Jun 3, 2025
Member
emlun
left a comment
There was a problem hiding this comment.
The logic looks correct to spec, just needs a few small tweaks. 🙂
webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
Show resolved
Hide resolved
webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
Outdated
Show resolved
Hide resolved
webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
Show resolved
Hide resolved
webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
Show resolved
Hide resolved
This test will be needed later for a more focused view on why the sibling test of `MetadataBLOBPayload` serialization stability begins to fail.
This commit was constructed through a series of steps:
Step 1: Add the new test "SupportedCtapOptions can be parsed from an empty JSON
object". It initially fails with:
```
true was not false
ScalaTestFailureLocation: com.yubico.fido.metadata.MetadataBlobSpec at (MetadataBlobSpec.scala:68)
org.scalatest.exceptions.TestFailedException: true was not false
at org.scalatest.matchers.MatchersHelper$.indicateFailure(MatchersHelper.scala:392)
at org.scalatest.matchers.should.Matchers$ShouldMethodHelperClass.shouldMatcher(Matchers.scala:7304)
at org.scalatest.matchers.should.Matchers$AnyShouldWrapper.should(Matchers.scala:7347)
at com.yubico.fido.metadata.MetadataBlobSpec.$anonfun$new$5(MetadataBlobSpec.scala:68)
```
At the line:
```
options.isUv should be(false)
```
This is because `SupportedCtapOptions` is still annotated with `@Jacksonized`,
so Jackson uses the builder to construct the object, and the class has
```
@Builder.Default boolean uv = false;
```
so the builder initializes the builder field to the primitive `false` value,
which causes the constructor argument to be `Boolean.FALSE`, which is not null,
and therefore the constructor sets `this.uv` to `true`.
Step 2: Remove `@Jacksonized` from `SupportedCtapOptions`. This causes Jackson
to invoke the constructor directly instead of going through the builder, which
bypasses the builder field defaults.
The test "SupportedCtapOptions can be parsed from an empty JSON object" now
fails with a different cause:
```
Cannot construct instance of `com.yubico.fido.metadata.SupportedCtapOptions`, problem: `java.lang.NullPointerException`
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of `com.yubico.fido.metadata.SupportedCtapOptions`, problem: `java.lang.NullPointerException`
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.ValueInstantiationException.from(ValueInstantiationException.java:46)
[...]
Caused by: java.lang.NullPointerException
at com.yubico.fido.metadata.SupportedCtapOptions.<init>(SupportedCtapOptions.java:180)
[...]
```
which is the line:
```
this.plat = plat;
```
The test "FIDO Metadata Service 3 blob payloads are structurally identical after
multiple (de)serialization round-trips." also begins failing now.
Step 3: Use `Boolean.TRUE.equals(...)` to fix `NullPointerException`s while
setting `plat`, `rk` and `up` in `SupportedCtapOptions` constructor.
The test "SupportedCtapOptions can be parsed from an empty JSON object" now
passes. Instead the test "FIDO Metadata Service 3 blob payloads are structurally
identical after multiple (de)serialization round-trips" now fails. This is why
we added the test in the previous commit, to get a more focused view of this
failure. This failure is:
```
TestFailedException was thrown during property evaluation.
Message: SupportedCtapOptions(plat=false, rk=true, clientPin=true, up=true, uv=true, pinUvAuthToken=false, noMcGaPermissionsWithClientPin=false, largeBlobs=false, ep=true, bioEnroll=true, userVerificationMgmtPreview=true, uvBioEnroll=false, authnrCfg=false, uvAcfg=false, credMgmt=false, credentialMgmtPreview=false, setMinPINLength=false, makeCredUvNotRqd=false, alwaysUv=true) did not equal SupportedCtapOptions(plat=false, rk=true, clientPin=true, up=true, uv=true, pinUvAuthToken=false, noMcGaPermissionsWithClientPin=false, largeBlobs=false, ep=false, bioEnroll=false, userVerificationMgmtPreview=false, uvBioEnroll=false, authnrCfg=false, uvAcfg=false, credMgmt=false, credentialMgmtPreview=false, setMinPINLength=false, makeCredUvNotRqd=false, alwaysUv=false)
Location: (MetadataBlobSpec.scala:108)
Occurred when passed generated values (
arg0 = SupportedCtapOptions(plat=false, rk=true, clientPin=true, up=true, uv=true, pinUvAuthToken=false, noMcGaPermissionsWithClientPin=false, largeBlobs=false, ep=false, bioEnroll=false, userVerificationMgmtPreview=false, uvBioEnroll=false, authnrCfg=false, uvAcfg=false, credMgmt=false, credentialMgmtPreview=false, setMinPINLength=false, makeCredUvNotRqd=false, alwaysUv=false)
)
```
This is because the constructor sets some of the the tri-state inputs to `true`
when non-null, including when `false`. This means that when the input is
absent (null), it is set to `false` and then emitted as `false` when serialized
back to JSON, which is then interpreted as non-null and converted to `true` when
deserializing again.
Step 4: Add `@JsonInclude` directives to omit the tri-state fields that are
true-when-non-null when their value is false.
All tests now pass!
Member
|
Out of curiosity I tried what happens if you add the aforementioned test of parsing from an empty JSON object, and it turned out to have wider repercussions than I expected 😄 it also interacts with the builder annotations in subtle ways. I'll post a meta-PR with what I found. |
This comment was marked as off-topic.
This comment was marked as off-topic.
emlun
approved these changes
Jul 8, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #382
This PR adjusts the parsing logic of SupportedCtapOptions in order to closer reflect what options are supported.
trueshould mean it is supported,falseshould mean it is not supported.