Skip to content

Commit

Permalink
Migrate Security WebAuth config classes to @ConfigMapping
Browse files Browse the repository at this point in the history
  • Loading branch information
michalvavrik committed Mar 12, 2024
1 parent 9d46911 commit 7396dae
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static class IsEnabled implements BooleanSupplier {
WebAuthnBuildTimeConfig config;

public boolean getAsBoolean() {
return config.enabled;
return config.enabled();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package io.quarkus.security.webauthn;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

@ConfigRoot(name = "webauthn")
public class WebAuthnBuildTimeConfig {
@ConfigRoot
@ConfigMapping(prefix = "quarkus.webauthn")
public interface WebAuthnBuildTimeConfig {

/**
* If the WebAuthn extension is enabled.
*/
@ConfigItem(defaultValue = "true")
public boolean enabled;
@WithDefault("true")
boolean enabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class WebAuthnController {
public WebAuthnController(WebAuthnSecurity security, WebAuthnRunTimeConfig config,
IdentityProviderManager identityProviderManager,
WebAuthnAuthenticationMechanism authMech) {
origin = config.origin.orElse(null);
origin = config.origin().orElse(null);
if (origin != null) {
Origin o = Origin.parse(origin);
domain = o.host();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ public WebAuthnAuthenticationMechanism get() {
key = httpConfiguration.getValue().encryptionKey.get();
}
WebAuthnRunTimeConfig config = WebAuthnRecorder.this.config.getValue();
PersistentLoginManager loginManager = new PersistentLoginManager(key, config.cookieName,
config.sessionTimeout.toMillis(),
config.newCookieInterval.toMillis(), false, config.cookieSameSite.name(),
config.cookiePath.orElse(null));
String loginPage = config.loginPage.startsWith("/") ? config.loginPage : "/" + config.loginPage;
PersistentLoginManager loginManager = new PersistentLoginManager(key, config.cookieName(),
config.sessionTimeout().toMillis(),
config.newCookieInterval().toMillis(), false, config.cookieSameSite().name(),
config.cookiePath().orElse(null));
String loginPage = config.loginPage().startsWith("/") ? config.loginPage() : "/" + config.loginPage();
return new WebAuthnAuthenticationMechanism(loginManager, loginPage);
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import java.util.Optional;
import java.util.OptionalInt;

import io.quarkus.runtime.annotations.ConfigDocDefault;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;
import io.vertx.ext.auth.webauthn.Attestation;
import io.vertx.ext.auth.webauthn.AuthenticatorAttachment;
import io.vertx.ext.auth.webauthn.AuthenticatorTransport;
Expand All @@ -18,13 +20,14 @@
/**
* Webauthn runtime configuration object.
*/
@ConfigRoot(name = "webauthn", phase = ConfigPhase.RUN_TIME)
public class WebAuthnRunTimeConfig {
@ConfigMapping(prefix = "quarkus.webauthn")
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
public interface WebAuthnRunTimeConfig {

/**
* SameSite attribute values for the session cookie.
*/
public enum CookieSameSite {
enum CookieSameSite {
STRICT,
LAX,
NONE
Expand All @@ -42,8 +45,7 @@ public enum CookieSameSite {
* Please note that WebAuthn API will not work on pages loaded over HTTP, unless it is localhost,
* which is considered secure context.
*/
@ConfigItem
public Optional<String> origin;
Optional<String> origin();

/**
* Authenticator Transports allowed by the application. Authenticators can interact with the user web browser
Expand All @@ -62,15 +64,14 @@ public enum CookieSameSite {
* <li>{@code INTERNAL} - Hardware security chips (e.g.: Intel TPM2.0)</li>
* </ul>
*/
@ConfigItem(defaultValueDocumentation = "USB,NFC,BLE,INTERNAL")
public Optional<List<AuthenticatorTransport>> transports;
@ConfigDocDefault("USB,NFC,BLE,INTERNAL")
Optional<List<AuthenticatorTransport>> transports();

/**
* Your application is a relying party. In order for the user browser to correctly present you to the user, basic
* information should be provided that will be presented during the user verification popup messages.
*/
@ConfigItem
public RelyingPartyConfig relyingParty;
RelyingPartyConfig relyingParty();

/**
* Kind of Authenticator Attachment allowed. Authenticators can connect to your device in two forms:
Expand All @@ -83,15 +84,14 @@ public enum CookieSameSite {
* For security reasons your application may choose to restrict to a specific attachment mode. If omitted, then
* any mode is permitted.
*/
@ConfigItem
public Optional<AuthenticatorAttachment> authenticatorAttachment;
Optional<AuthenticatorAttachment> authenticatorAttachment();

/**
* Resident key required. A resident (private) key, is a key that cannot leave your authenticator device, this
* means that you cannot reuse the authenticator to log into a second computer.
*/
@ConfigItem(defaultValueDocumentation = "false")
public Optional<Boolean> requireResidentKey;
@ConfigDocDefault("false")
Optional<Boolean> requireResidentKey();

/**
* User Verification requirements. Webauthn applications may choose {@code REQUIRED} verification to assert that
Expand All @@ -104,16 +104,16 @@ public enum CookieSameSite {
* <li>{@code DISCOURAGED} - User should avoid interact with the browser</li>
* </ul>
*/
@ConfigItem(defaultValueDocumentation = "REQUIRED")
public Optional<UserVerification> userVerification;
@ConfigDocDefault("REQUIRED")
Optional<UserVerification> userVerification();

/**
* Non-negative User Verification timeout. Authentication must occur within the timeout, this will prevent the user
* browser from being blocked with a pop-up required user verification, and the whole ceremony must be completed
* within the timeout period. After the timeout, any previously issued challenge is automatically invalidated.
*/
@ConfigItem(defaultValueDocumentation = "60s")
public Optional<Duration> timeout;
@ConfigDocDefault("60s")
Optional<Duration> timeout();

/**
* Device Attestation Preference. During registration, applications may want to attest the device. Attestation is a
Expand All @@ -133,8 +133,8 @@ public enum CookieSameSite {
* unaltered.</li>
* </ul>
*/
@ConfigItem(defaultValueDocumentation = "NONE")
public Optional<Attestation> attestation;
@ConfigDocDefault("NONE")
Optional<Attestation> attestation();

/**
* Allowed Public Key Credential algorithms by preference order. Webauthn mandates that all authenticators must
Expand All @@ -145,15 +145,15 @@ public enum CookieSameSite {
* Note that the use of stronger algorithms, e.g.: {@code EdDSA} may require Java 15 or a cryptographic {@code JCE}
* provider that implements the algorithms.
*/
@ConfigItem(defaultValueDocumentation = "ES256,RS256")
public Optional<List<PublicKeyCredential>> pubKeyCredParams;
@ConfigDocDefault("ES256,RS256")
Optional<List<PublicKeyCredential>> pubKeyCredParams();

/**
* Length of the challenges exchanged between the application and the browser.
* Challenges must be at least 32 bytes.
*/
@ConfigItem(defaultValueDocumentation = "64")
public OptionalInt challengeLength;
@ConfigDocDefault("64")
OptionalInt challengeLength();

/**
* Extensions are optional JSON blobs that can be used during registration or authentication that can enhance the
Expand All @@ -178,35 +178,34 @@ public enum CookieSameSite {
//private List<X509CRL> rootCrls;

@ConfigGroup
public static class RelyingPartyConfig {
interface RelyingPartyConfig {
/**
* The id (or domain name of your server)
*/
@ConfigItem
public Optional<String> id;
Optional<String> id();

/**
* A user friendly name for your server
*/
@ConfigItem(defaultValue = "Quarkus server")
public String name;
@WithDefault("Quarkus server")
String name();
}

// FIXME: merge with form config?

/**
* The login page
*/
@ConfigItem(defaultValue = "/login.html")
public String loginPage;
@WithDefault("/login.html")
String loginPage();

/**
* The inactivity (idle) timeout
*
* When inactivity timeout is reached, cookie is not renewed and a new login is enforced.
*/
@ConfigItem(defaultValue = "PT30M")
public Duration sessionTimeout;
@WithDefault("PT30M")
Duration sessionTimeout();

/**
* How old a cookie can get before it will be replaced with a new cookie with an updated timeout, also
Expand All @@ -223,24 +222,24 @@ public static class RelyingPartyConfig {
* That is, no timeout is tracked on the server side; the timestamp is encoded and encrypted in the cookie
* itself, and it is decrypted and parsed with each request.
*/
@ConfigItem(defaultValue = "PT1M")
public Duration newCookieInterval;
@WithDefault("PT1M")
Duration newCookieInterval();

/**
* The cookie that is used to store the persistent session
*/
@ConfigItem(defaultValue = "quarkus-credential")
public String cookieName;
@WithDefault("quarkus-credential")
String cookieName();

/**
* SameSite attribute for the session cookie.
*/
@ConfigItem(defaultValue = "strict")
public CookieSameSite cookieSameSite = CookieSameSite.STRICT;
@WithDefault("strict")
CookieSameSite cookieSameSite();

/**
* The cookie path for the session cookies.
*/
@ConfigItem(defaultValue = "/")
public Optional<String> cookiePath = Optional.of("/");
@WithDefault("/")
Optional<String> cookiePath();
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,41 +36,41 @@ public WebAuthnSecurity(WebAuthnRunTimeConfig config, Vertx vertx, WebAuthnAuthe
// create the webauthn security object
WebAuthnOptions options = new WebAuthnOptions();
RelyingParty relyingParty = new RelyingParty();
if (config.relyingParty.id.isPresent()) {
relyingParty.setId(config.relyingParty.id.get());
if (config.relyingParty().id().isPresent()) {
relyingParty.setId(config.relyingParty().id().get());
}
// this is required
relyingParty.setName(config.relyingParty.name);
relyingParty.setName(config.relyingParty().name());
options.setRelyingParty(relyingParty);
if (config.attestation.isPresent()) {
options.setAttestation(config.attestation.get());
if (config.attestation().isPresent()) {
options.setAttestation(config.attestation().get());
}
if (config.authenticatorAttachment.isPresent()) {
options.setAuthenticatorAttachment(config.authenticatorAttachment.get());
if (config.authenticatorAttachment().isPresent()) {
options.setAuthenticatorAttachment(config.authenticatorAttachment().get());
}
if (config.challengeLength.isPresent()) {
options.setChallengeLength(config.challengeLength.getAsInt());
if (config.challengeLength().isPresent()) {
options.setChallengeLength(config.challengeLength().getAsInt());
}
if (config.pubKeyCredParams.isPresent()) {
options.setPubKeyCredParams(config.pubKeyCredParams.get());
if (config.pubKeyCredParams().isPresent()) {
options.setPubKeyCredParams(config.pubKeyCredParams().get());
}
if (config.requireResidentKey.isPresent()) {
options.setRequireResidentKey(config.requireResidentKey.get());
if (config.requireResidentKey().isPresent()) {
options.setRequireResidentKey(config.requireResidentKey().get());
}
if (config.timeout.isPresent()) {
options.setTimeoutInMilliseconds(config.timeout.get().toMillis());
if (config.timeout().isPresent()) {
options.setTimeoutInMilliseconds(config.timeout().get().toMillis());
}
if (config.transports.isPresent()) {
options.setTransports(config.transports.get());
if (config.transports().isPresent()) {
options.setTransports(config.transports().get());
}
if (config.userVerification.isPresent()) {
options.setUserVerification(config.userVerification.get());
if (config.userVerification().isPresent()) {
options.setUserVerification(config.userVerification().get());
}
webAuthn = WebAuthn.create(vertx, options)
// where to load/update authenticators data
.authenticatorFetcher(database::fetcher)
.authenticatorUpdater(database::updater);
origin = config.origin.orElse(null);
origin = config.origin().orElse(null);
if (origin != null) {
Origin o = Origin.parse(origin);
domain = o.host();
Expand Down

0 comments on commit 7396dae

Please sign in to comment.