Skip to content

Commit a16f29a

Browse files
committed
Don't overwrite per-request extension inputs in RelyingParty methods
1 parent c9dda48 commit a16f29a

File tree

5 files changed

+66
-24
lines changed

5 files changed

+66
-24
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Deprecated features will be removed in the next major version release.
1313

1414
Changes:
1515

16+
* `RelyingParty.startAssertion()` no longer overwrites the `appid` extension
17+
input in the `StartAssertionOptions` argument.
1618
* `RelyingParty.appId` setting now also activates the `appidExclude` extension in
1719
addition to the `appid` extension.
1820
* `RelyingParty.startRegistration()` now enables the `credProps` extension by

webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,13 @@ public PublicKeyCredentialCreationOptions startRegistration(
405405
startRegistrationOptions.getUser().getName()))
406406
.authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
407407
.extensions(
408-
startRegistrationOptions.getExtensions().toBuilder()
409-
.appidExclude(appId)
410-
.credProps()
411-
.build())
408+
startRegistrationOptions
409+
.getExtensions()
410+
.merge(
411+
RegistrationExtensionInputs.builder()
412+
.appidExclude(appId)
413+
.credProps()
414+
.build()))
412415
.timeout(startRegistrationOptions.getTimeout());
413416
attestationConveyancePreference.ifPresent(builder::attestation);
414417
return builder.build();
@@ -469,7 +472,10 @@ public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptio
469472
.map(
470473
un ->
471474
new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un))))
472-
.extensions(startAssertionOptions.getExtensions().toBuilder().appid(appId).build())
475+
.extensions(
476+
startAssertionOptions
477+
.getExtensions()
478+
.merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build()))
473479
.timeout(startAssertionOptions.getTimeout());
474480

475481
startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification);

webauthn-server-core/src/main/java/com/yubico/webauthn/data/AssertionExtensionInputs.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,8 @@
5454
public class AssertionExtensionInputs implements ExtensionInputs {
5555

5656
private final AppId appid;
57-
5857
private final Extensions.LargeBlob.LargeBlobAuthenticationInput largeBlob;
59-
60-
private final boolean uvm;
58+
private final Boolean uvm;
6159

6260
@JsonCreator
6361
private AssertionExtensionInputs(
@@ -66,7 +64,21 @@ private AssertionExtensionInputs(
6664
@JsonProperty("uvm") Boolean uvm) {
6765
this.appid = appid;
6866
this.largeBlob = largeBlob;
69-
this.uvm = uvm != null && uvm;
67+
this.uvm = (uvm != null && uvm) ? true : null;
68+
}
69+
70+
/**
71+
* Merge <code>other</code> into <code>this</code>. Non-null field values from <code>this</code>
72+
* take precedence.
73+
*
74+
* @return a new {@link AssertionExtensionInputs} instance with the settings from both <code>this
75+
* </code> and <code>other</code>.
76+
*/
77+
public AssertionExtensionInputs merge(AssertionExtensionInputs other) {
78+
return new AssertionExtensionInputs(
79+
this.appid != null ? this.appid : other.appid,
80+
this.largeBlob != null ? this.largeBlob : other.largeBlob,
81+
this.uvm != null ? this.uvm : other.uvm);
7082
}
7183

7284
/**
@@ -83,7 +95,7 @@ public Set<String> getExtensionIds() {
8395
if (largeBlob != null) {
8496
ids.add(Extensions.LargeBlob.EXTENSION_ID);
8597
}
86-
if (uvm) {
98+
if (getUvm()) {
8799
ids.add(Extensions.Uvm.EXTENSION_ID);
88100
}
89101
return ids;
@@ -229,12 +241,12 @@ private Extensions.LargeBlob.LargeBlobAuthenticationInput getLargeBlobJson() {
229241
* User Verification Method Extension (uvm)</a>
230242
*/
231243
public boolean getUvm() {
232-
return uvm;
244+
return uvm != null && uvm;
233245
}
234246

235247
/** For JSON serialization, to omit false values. */
236248
@JsonProperty("uvm")
237249
private Boolean getUvmJson() {
238-
return uvm ? true : null;
250+
return getUvm() ? true : null;
239251
}
240252
}

webauthn-server-core/src/main/java/com/yubico/webauthn/data/RegistrationExtensionInputs.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,37 @@
5454
public final class RegistrationExtensionInputs implements ExtensionInputs {
5555

5656
private final AppId appidExclude;
57-
58-
private final boolean credProps;
57+
private final Boolean credProps;
5958
private final Extensions.LargeBlob.LargeBlobRegistrationInput largeBlob;
60-
private final boolean uvm;
59+
private final Boolean uvm;
6160

6261
@JsonCreator
6362
private RegistrationExtensionInputs(
6463
@JsonProperty("appidExclude") AppId appidExclude,
65-
@JsonProperty("credProps") boolean credProps,
64+
@JsonProperty("credProps") Boolean credProps,
6665
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobRegistrationInput largeBlob,
67-
@JsonProperty("uvm") boolean uvm) {
66+
@JsonProperty("uvm") Boolean uvm) {
6867
this.appidExclude = appidExclude;
6968
this.credProps = credProps;
7069
this.largeBlob = largeBlob;
7170
this.uvm = uvm;
7271
}
7372

73+
/**
74+
* Merge <code>other</code> into <code>this</code>. Non-null field values from <code>this</code>
75+
* take precedence.
76+
*
77+
* @return a new {@link RegistrationExtensionInputs} instance with the settings from both <code>
78+
* this</code> and <code>other</code>.
79+
*/
80+
public RegistrationExtensionInputs merge(RegistrationExtensionInputs other) {
81+
return new RegistrationExtensionInputs(
82+
this.appidExclude != null ? this.appidExclude : other.appidExclude,
83+
this.credProps != null ? this.credProps : other.credProps,
84+
this.largeBlob != null ? this.largeBlob : other.largeBlob,
85+
this.uvm != null ? this.uvm : other.uvm);
86+
}
87+
7488
/**
7589
* @return The value of the FIDO AppID Exclusion Extension (<code>appidExclude</code>) input if
7690
* configured, empty otherwise.
@@ -92,13 +106,13 @@ public Optional<AppId> getAppidExclude() {
92106
* Credential Properties Extension (credProps)</a>
93107
*/
94108
public boolean getCredProps() {
95-
return credProps;
109+
return credProps != null && credProps;
96110
}
97111

98112
/** For JSON serialization, to omit false values. */
99113
@JsonProperty("credProps")
100114
private Boolean getCredPropsJson() {
101-
return credProps ? true : null;
115+
return getCredProps() ? true : null;
102116
}
103117

104118
/**
@@ -124,13 +138,13 @@ public Optional<Extensions.LargeBlob.LargeBlobRegistrationInput> getLargeBlob()
124138
* User Verification Method Extension (uvm)</a>
125139
*/
126140
public boolean getUvm() {
127-
return uvm;
141+
return uvm != null && uvm;
128142
}
129143

130144
/** For JSON serialization, to omit false values. */
131145
@JsonProperty("uvm")
132146
private Boolean getUvmJson() {
133-
return uvm ? true : null;
147+
return getUvm() ? true : null;
134148
}
135149

136150
/**
@@ -144,13 +158,13 @@ public Set<String> getExtensionIds() {
144158
if (appidExclude != null) {
145159
ids.add(Extensions.AppidExclude.EXTENSION_ID);
146160
}
147-
if (credProps) {
161+
if (getCredProps()) {
148162
ids.add(Extensions.CredentialProperties.EXTENSION_ID);
149163
}
150164
if (largeBlob != null) {
151165
ids.add(Extensions.LargeBlob.EXTENSION_ID);
152166
}
153-
if (uvm) {
167+
if (getUvm()) {
154168
ids.add(Extensions.Uvm.EXTENSION_ID);
155169
}
156170
return Collections.unmodifiableSet(ids);
@@ -164,6 +178,10 @@ public static class RegistrationExtensionInputsBuilder {
164178
* is present, then {@link RelyingParty#startRegistration(StartRegistrationOptions)} will enable
165179
* this extension automatically.
166180
*
181+
* <p>If this is set to empty, then {@link
182+
* RelyingParty#startRegistration(StartRegistrationOptions)} may overwrite it.
183+
*
184+
* @see RelyingParty#startRegistration(StartRegistrationOptions)
167185
* @see <a
168186
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
169187
* FIDO AppID Exclusion Extension (appidExclude)</a>
@@ -180,6 +198,10 @@ public RegistrationExtensionInputsBuilder appidExclude(Optional<AppId> appidExcl
180198
* is present, then {@link RelyingParty#startRegistration(StartRegistrationOptions)} will enable
181199
* this extension automatically.
182200
*
201+
* <p>If this is set to null, then {@link
202+
* RelyingParty#startRegistration(StartRegistrationOptions)} may overwrite it.
203+
*
204+
* @see RelyingParty#startRegistration(StartRegistrationOptions)
183205
* @see <a
184206
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
185207
* FIDO AppID Exclusion Extension (appidExclude)</a>

webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ object Generators {
350350
largeBlobGen: Gen[
351351
Option[com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobRegistrationInput]
352352
] = Gen.option(LargeBlob.largeBlobRegistrationInput),
353-
uvmGen: Gen[Option[Boolean]] = Gen.option(true),
353+
uvmGen: Gen[Option[Boolean]] = Gen.option(Gen.const(true)),
354354
): Gen[RegistrationExtensionInputs] =
355355
for {
356356
appidExclude <- appidExcludeGen

0 commit comments

Comments
 (0)