Skip to content

Commit c9dda48

Browse files
committed
Test that RelyingParty does not override per-request extension inputs
1 parent 8d50db5 commit c9dda48

File tree

3 files changed

+136
-12
lines changed

3 files changed

+136
-12
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,22 @@ public RegistrationExtensionInputsBuilder credProps() {
200200
this.credProps = true;
201201
return this;
202202
}
203-
/** For compatibility with {@link Builder}(toBuilder = true) */
204-
private RegistrationExtensionInputsBuilder credProps(Boolean credProps) {
203+
204+
/**
205+
* Enable or disable the Credential Properties (<code>credProps</code>) Extension.
206+
*
207+
* <p>A <code>true</code> argument enables the extension. A <code>false</code> argument disables
208+
* the extension, and will not be overwritten by {@link
209+
* RelyingParty#startRegistration(StartRegistrationOptions)}. A null argument disables the
210+
* extension, and will be overwritten by {@link
211+
* RelyingParty#startRegistration(StartRegistrationOptions)}.
212+
*
213+
* @see RelyingParty#startRegistration(StartRegistrationOptions)
214+
* @see <a
215+
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-credential-properties-extension">§10.4.
216+
* Credential Properties Extension (credProps)</a>
217+
*/
218+
public RegistrationExtensionInputsBuilder credProps(Boolean credProps) {
205219
this.credProps = credProps;
206220
return this;
207221
}

webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.yubico.webauthn.data.AuthenticatorAttachment
3232
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria
3333
import com.yubico.webauthn.data.AuthenticatorTransport
3434
import com.yubico.webauthn.data.ByteArray
35+
import com.yubico.webauthn.data.Generators.Extensions.registrationExtensionInputs
3536
import com.yubico.webauthn.data.Generators._
3637
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor
3738
import com.yubico.webauthn.data.PublicKeyCredentialParameters
@@ -296,21 +297,84 @@ class RelyingPartyStartOperationSpec
296297
result.getExtensions.getAppidExclude.asScala should equal(None)
297298
}
298299

299-
it("by default always sets the credProps extension.") {
300-
forAll { extensions: RegistrationExtensionInputs =>
301-
println(extensions.getExtensionIds)
302-
println(extensions)
303-
304-
val rp = relyingParty(userId = userId)
300+
it("does not override the appidExclude extension with an empty value if already non-null in StartRegistrationOptions.") {
301+
forAll { requestAppId: AppId =>
302+
val rp = relyingParty(appId = None, userId = userId)
305303
val result = rp.startRegistration(
306304
StartRegistrationOptions
307305
.builder()
308306
.user(userId)
309-
.extensions(extensions)
307+
.extensions(
308+
RegistrationExtensionInputs
309+
.builder()
310+
.appidExclude(requestAppId)
311+
.build()
312+
)
310313
.build()
311314
)
312315

313-
result.getExtensions.getCredProps should be(true)
316+
result.getExtensions.getAppidExclude.asScala should equal(
317+
Some(requestAppId)
318+
)
319+
}
320+
}
321+
322+
it("does not override the appidExclude extension if already non-null in StartRegistrationOptions.") {
323+
forAll { (requestAppId: AppId, rpAppId: AppId) =>
324+
whenever(requestAppId != rpAppId) {
325+
val rp = relyingParty(appId = Some(rpAppId), userId = userId)
326+
val result = rp.startRegistration(
327+
StartRegistrationOptions
328+
.builder()
329+
.user(userId)
330+
.extensions(
331+
RegistrationExtensionInputs
332+
.builder()
333+
.appidExclude(requestAppId)
334+
.build()
335+
)
336+
.build()
337+
)
338+
339+
result.getExtensions.getAppidExclude.asScala should equal(
340+
Some(requestAppId)
341+
)
342+
}
343+
}
344+
}
345+
346+
it("by default sets the credProps extension.") {
347+
forAll(registrationExtensionInputs(credPropsGen = None)) {
348+
extensions: RegistrationExtensionInputs =>
349+
println(extensions.getExtensionIds)
350+
println(extensions)
351+
352+
val rp = relyingParty(userId = userId)
353+
val result = rp.startRegistration(
354+
StartRegistrationOptions
355+
.builder()
356+
.user(userId)
357+
.extensions(extensions)
358+
.build()
359+
)
360+
361+
result.getExtensions.getCredProps should be(true)
362+
}
363+
}
364+
365+
it("does not override the credProps extension if explicitly set to false in StartRegistrationOptions.") {
366+
forAll(registrationExtensionInputs(credPropsGen = Some(false))) {
367+
extensions: RegistrationExtensionInputs =>
368+
val rp = relyingParty(userId = userId)
369+
val result = rp.startRegistration(
370+
StartRegistrationOptions
371+
.builder()
372+
.user(userId)
373+
.extensions(extensions)
374+
.build()
375+
)
376+
377+
result.getExtensions.getCredProps should be(false)
314378
}
315379
}
316380

@@ -629,6 +693,52 @@ class RelyingPartyStartOperationSpec
629693
)
630694
}
631695

696+
it("does not override the appid extension with an empty value if already non-null in StartAssertionOptions.") {
697+
forAll { requestAppId: AppId =>
698+
val rp = relyingParty(appId = None, userId = userId)
699+
val result = rp.startAssertion(
700+
StartAssertionOptions
701+
.builder()
702+
.username(userId.getName)
703+
.extensions(
704+
AssertionExtensionInputs
705+
.builder()
706+
.appid(requestAppId)
707+
.build()
708+
)
709+
.build()
710+
)
711+
712+
result.getPublicKeyCredentialRequestOptions.getExtensions.getAppid.asScala should equal(
713+
Some(requestAppId)
714+
)
715+
}
716+
}
717+
718+
it("does not override the appid extension if already non-null in StartAssertionOptions.") {
719+
forAll { (requestAppId: AppId, rpAppId: AppId) =>
720+
whenever(requestAppId != rpAppId) {
721+
val rp = relyingParty(appId = Some(rpAppId), userId = userId)
722+
val result = rp.startAssertion(
723+
StartAssertionOptions
724+
.builder()
725+
.username(userId.getName)
726+
.extensions(
727+
AssertionExtensionInputs
728+
.builder()
729+
.appid(requestAppId)
730+
.build()
731+
)
732+
.build()
733+
)
734+
735+
result.getPublicKeyCredentialRequestOptions.getExtensions.getAppid.asScala should equal(
736+
Some(requestAppId)
737+
)
738+
}
739+
}
740+
}
741+
632742
it("allows setting the timeout to empty.") {
633743
val req = relyingParty(userId = userId).startAssertion(
634744
StartAssertionOptions

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ object Generators {
346346

347347
def registrationExtensionInputs(
348348
appidExcludeGen: Gen[Option[AppId]] = Gen.option(arbitrary[AppId]),
349-
credPropsGen: Gen[Option[Boolean]] = Gen.option(true),
349+
credPropsGen: Gen[Option[Boolean]] = Gen.option(arbitrary[Boolean]),
350350
largeBlobGen: Gen[
351351
Option[com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobRegistrationInput]
352352
] = Gen.option(LargeBlob.largeBlobRegistrationInput),
@@ -360,7 +360,7 @@ object Generators {
360360
} yield {
361361
val b = RegistrationExtensionInputs.builder()
362362
appidExclude.foreach({ i => b.appidExclude(i) })
363-
if (credProps.contains(true)) { b.credProps() }
363+
credProps.foreach({ i => b.credProps(i) })
364364
largeBlob.foreach({ i => b.largeBlob(i) })
365365
if (uvm.contains(true)) { b.uvm() }
366366
val result = b.build()

0 commit comments

Comments
 (0)