Skip to content

Commit 8979e0d

Browse files
committed
Add timeout parameter to start ceremony options
Fixes issue #23 See #23
2 parents 3b92984 + 0290ee5 commit 8979e0d

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed

NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
== Version 1.3.0 (unreleased) ==
2+
3+
New features:
4+
5+
* New optional parameter `timeout` added to `StartRegistrationOptions` and
6+
`StartAssertionOptions`
7+
8+
19
== Version 1.2.0 ==
210

311
New features:

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ public PublicKeyCredentialCreationOptions startRegistration(StartRegistrationOpt
292292
)
293293
.authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
294294
.extensions(startRegistrationOptions.getExtensions())
295+
.timeout(startRegistrationOptions.getTimeout())
295296
;
296297
attestationConveyancePreference.ifPresent(builder::attestation);
297298
return builder.build();
@@ -344,6 +345,7 @@ public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptio
344345
.appid(appId)
345346
.build()
346347
)
348+
.timeout(startAssertionOptions.getTimeout())
347349
;
348350

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

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,19 @@ public class StartAssertionOptions {
8080
@NonNull
8181
private final Optional<UserVerificationRequirement> userVerification;
8282

83+
/**
84+
* The value for {@link PublicKeyCredentialRequestOptions#getTimeout()} for this authentication operation.
85+
* <p>
86+
* The default is empty.
87+
* </p>
88+
*/
89+
@NonNull
90+
private final Optional<Long> timeout;
91+
8392
public static class StartAssertionOptionsBuilder {
8493
private @NonNull Optional<String> username = Optional.empty();
8594
private @NonNull Optional<UserVerificationRequirement> userVerification = Optional.empty();
95+
private @NonNull Optional<Long> timeout = Optional.empty();
8696

8797
/**
8898
* The username of the user to authenticate, if the user has already been identified.
@@ -141,5 +151,29 @@ public StartAssertionOptionsBuilder userVerification(@NonNull Optional<UserVerif
141151
public StartAssertionOptionsBuilder userVerification(@NonNull UserVerificationRequirement userVerification) {
142152
return this.userVerification(Optional.of(userVerification));
143153
}
154+
155+
/**
156+
* The value for {@link PublicKeyCredentialRequestOptions#getTimeout()} for this authentication operation.
157+
* <p>
158+
* The default is empty.
159+
* </p>
160+
*/
161+
public StartAssertionOptionsBuilder timeout(@NonNull Optional<Long> timeout) {
162+
if (timeout.isPresent() && timeout.get() <= 0) {
163+
throw new IllegalArgumentException("timeout must be positive, was: " + timeout.get());
164+
}
165+
this.timeout = timeout;
166+
return this;
167+
}
168+
169+
/**
170+
* The value for {@link PublicKeyCredentialRequestOptions#getTimeout()} for this authentication operation.
171+
* <p>
172+
* The default is empty.
173+
* </p>
174+
*/
175+
public StartAssertionOptionsBuilder timeout(long timeout) {
176+
return this.timeout(Optional.of(timeout));
177+
}
144178
}
145179
}

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package com.yubico.webauthn;
2626

2727
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria;
28+
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
2829
import com.yubico.webauthn.data.RegistrationExtensionInputs;
2930
import com.yubico.webauthn.data.UserIdentity;
3031
import java.util.Optional;
@@ -58,12 +59,22 @@ public class StartRegistrationOptions {
5859
@Builder.Default
5960
private final RegistrationExtensionInputs extensions = RegistrationExtensionInputs.builder().build();
6061

62+
/**
63+
* The value for {@link PublicKeyCredentialCreationOptions#getTimeout()} for this registration operation.
64+
* <p>
65+
* The default is empty.
66+
* </p>
67+
*/
68+
@NonNull
69+
private final Optional<Long> timeout;
70+
6171
public static StartRegistrationOptionsBuilder.MandatoryStages builder() {
6272
return new StartRegistrationOptionsBuilder.MandatoryStages();
6373
}
6474

6575
public static class StartRegistrationOptionsBuilder {
6676
private @NonNull Optional<AuthenticatorSelectionCriteria> authenticatorSelection = Optional.empty();
77+
private @NonNull Optional<Long> timeout = Optional.empty();
6778

6879
public static class MandatoryStages {
6980
private final StartRegistrationOptionsBuilder builder = new StartRegistrationOptionsBuilder();
@@ -87,6 +98,30 @@ public StartRegistrationOptionsBuilder authenticatorSelection(@NonNull Optional<
8798
public StartRegistrationOptionsBuilder authenticatorSelection(@NonNull AuthenticatorSelectionCriteria authenticatorSelection) {
8899
return this.authenticatorSelection(Optional.of(authenticatorSelection));
89100
}
101+
102+
/**
103+
* The value for {@link PublicKeyCredentialCreationOptions#getTimeout()} for this registration operation.
104+
* <p>
105+
* The default is empty.
106+
* </p>
107+
*/
108+
public StartRegistrationOptionsBuilder timeout(@NonNull Optional<Long> timeout) {
109+
if (timeout.isPresent() && timeout.get() <= 0) {
110+
throw new IllegalArgumentException("timeout must be positive, was: " + timeout.get());
111+
}
112+
this.timeout = timeout;
113+
return this;
114+
}
115+
116+
/**
117+
* The value for {@link PublicKeyCredentialCreationOptions#getTimeout()} for this registration operation.
118+
* <p>
119+
* The default is empty.
120+
* </p>
121+
*/
122+
public StartRegistrationOptionsBuilder timeout(long timeout) {
123+
return this.timeout(Optional.of(timeout));
124+
}
90125
}
91126

92127
}

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import java.util.Optional
2828

2929
import com.yubico.internal.util.scala.JavaConverters._
3030
import com.yubico.scalacheck.gen.JavaGenerators._
31+
import com.yubico.webauthn.data.AuthenticatorAttachment
32+
import com.yubico.webauthn.data.AuthenticatorSelectionCriteria
3133
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor
3234
import com.yubico.webauthn.data.ByteArray
3335
import com.yubico.webauthn.data.UserIdentity
@@ -38,6 +40,7 @@ import com.yubico.webauthn.extension.appid.AppId
3840
import com.yubico.webauthn.extension.appid.Generators._
3941
import org.junit.runner.RunWith
4042
import org.scalacheck.Arbitrary._
43+
import org.scalacheck.Gen
4144
import org.scalatest.FunSpec
4245
import org.scalatest.Matchers
4346
import org.scalatest.junit.JUnitRunner
@@ -104,6 +107,56 @@ class RelyingPartyStartOperationSpec extends FunSpec with Matchers with Generato
104107
request2.getChallenge.size should be >= 32
105108
}
106109

110+
it("allows setting the timeout to empty.") {
111+
val pkcco = relyingParty().startRegistration(
112+
StartRegistrationOptions.builder()
113+
.user(userId)
114+
.timeout(Optional.empty[java.lang.Long])
115+
.build())
116+
pkcco.getTimeout.asScala shouldBe 'empty
117+
}
118+
119+
it("allows setting the timeout to a positive value.") {
120+
val rp = relyingParty()
121+
122+
forAll(Gen.posNum[Long]) { timeout: Long =>
123+
val pkcco = rp.startRegistration(
124+
StartRegistrationOptions.builder()
125+
.user(userId)
126+
.timeout(timeout)
127+
.build())
128+
129+
pkcco.getTimeout.asScala should equal (Some(timeout))
130+
}
131+
}
132+
133+
it("does not allow setting the timeout to zero or negative.") {
134+
an [IllegalArgumentException] should be thrownBy {
135+
StartRegistrationOptions.builder()
136+
.user(userId)
137+
.timeout(0)
138+
}
139+
140+
an [IllegalArgumentException] should be thrownBy {
141+
StartRegistrationOptions.builder()
142+
.user(userId)
143+
.timeout(Optional.of[java.lang.Long](0L))
144+
}
145+
146+
forAll(Gen.negNum[Long]) { timeout: Long =>
147+
an [IllegalArgumentException] should be thrownBy {
148+
StartRegistrationOptions.builder()
149+
.user(userId)
150+
.timeout(timeout)
151+
}
152+
153+
an [IllegalArgumentException] should be thrownBy {
154+
StartRegistrationOptions.builder()
155+
.user(userId)
156+
.timeout(Optional.of[java.lang.Long](timeout))
157+
}
158+
}
159+
}
107160
}
108161

109162
describe("RelyingParty.startAssertion") {
@@ -152,6 +205,50 @@ class RelyingPartyStartOperationSpec extends FunSpec with Matchers with Generato
152205
}
153206
}
154207

208+
it("allows setting the timeout to empty.") {
209+
val req = relyingParty().startAssertion(
210+
StartAssertionOptions.builder()
211+
.timeout(Optional.empty[java.lang.Long])
212+
.build())
213+
req.getPublicKeyCredentialRequestOptions.getTimeout.asScala shouldBe 'empty
214+
}
215+
216+
it("allows setting the timeout to a positive value.") {
217+
val rp = relyingParty()
218+
219+
forAll(Gen.posNum[Long]) { timeout: Long =>
220+
val req = rp.startAssertion(
221+
StartAssertionOptions.builder()
222+
.timeout(timeout)
223+
.build())
224+
225+
req.getPublicKeyCredentialRequestOptions.getTimeout.asScala should equal (Some(timeout))
226+
}
227+
}
228+
229+
it("does not allow setting the timeout to zero or negative.") {
230+
an [IllegalArgumentException] should be thrownBy {
231+
StartAssertionOptions.builder()
232+
.timeout(0)
233+
}
234+
235+
an [IllegalArgumentException] should be thrownBy {
236+
StartAssertionOptions.builder()
237+
.timeout(Optional.of[java.lang.Long](0L))
238+
}
239+
240+
forAll(Gen.negNum[Long]) { timeout: Long =>
241+
an [IllegalArgumentException] should be thrownBy {
242+
StartAssertionOptions.builder()
243+
.timeout(timeout)
244+
}
245+
246+
an [IllegalArgumentException] should be thrownBy {
247+
StartAssertionOptions.builder()
248+
.timeout(Optional.of[java.lang.Long](timeout))
249+
}
250+
}
251+
}
155252
}
156253

157254
}

0 commit comments

Comments
 (0)