diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java index 5a9e4b80f..d50e3a68c 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java @@ -19,8 +19,11 @@ package org.apache.guacamole.auth.totp.user; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import org.apache.guacamole.form.BooleanField; +import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.DelegatingUser; import org.apache.guacamole.net.auth.User; @@ -40,7 +43,23 @@ public class TOTPUser extends DelegatingUser { * confirmed by the user, and the user is thus fully enrolled. */ public static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed"; + + /** + * The name of the field used to trigger a reset of the TOTP data. + */ + public static final String TOTP_KEY_SECRET_RESET_FIELD = "guac-totp-reset"; + /** + * The form which contains all configurable properties for this user. + */ + public static final Form TOTP_CONFIG_FORM = new Form("totp-config-form", + Arrays.asList( + new BooleanField(TOTP_KEY_SECRET_RESET_FIELD, "true"), + new BooleanField(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, "true") + ) + ); + + /** * Wraps the given User object, hiding and blocking access to the core * attributes used by TOTP. @@ -66,14 +85,19 @@ public class TOTPUser extends DelegatingUser { public Map getAttributes() { // Create independent, mutable copy of attributes - Map attributes = - new HashMap(super.getAttributes()); + Map attributes = new HashMap<>(super.getAttributes()); + + // Protect the secret value by removing it + String secret = attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME); + + // If secret is null or empty, mark the reset as true. + if (secret == null || secret.isEmpty()) + attributes.put(TOTP_KEY_SECRET_RESET_FIELD, "true"); + + // If secret has a value, mark the reset as false. + else + attributes.put(TOTP_KEY_SECRET_RESET_FIELD, "false"); - // Do not expose any TOTP-related attributes outside this extension - attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME); - attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME); - - // Expose only non-TOTP attributes return attributes; } @@ -82,13 +106,20 @@ public class TOTPUser extends DelegatingUser { public void setAttributes(Map attributes) { // Create independent, mutable copy of attributes - attributes = new HashMap(attributes); - - // Do not expose any TOTP-related attributes outside this extension + attributes = new HashMap<>(attributes); + + // Do not expose any TOTP secret attribute outside this extension attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME); - attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME); + + // Pull off the boolean reset field + String reset = attributes.remove(TOTP_KEY_SECRET_RESET_FIELD); + + // If reset has been set to true, clear the secret. + if (reset != null && reset.equals("true")) { + attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, null); + attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, null); + } - // Set only non-TOTP attributes super.setAttributes(attributes); } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java index 980bbf782..f4785193c 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java @@ -19,7 +19,11 @@ package org.apache.guacamole.auth.totp.user; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.form.Form; import org.apache.guacamole.net.auth.DecoratingDirectory; import org.apache.guacamole.net.auth.DelegatingUserContext; import org.apache.guacamole.net.auth.Directory; @@ -60,5 +64,12 @@ public class TOTPUserContext extends DelegatingUserContext { }; } + + @Override + public Collection
getUserAttributes() { + Collection userAttrs = new HashSet<>(super.getUserAttributes()); + userAttrs.add(TOTPUser.TOTP_CONFIG_FORM); + return Collections.unmodifiableCollection(userAttrs); + } } diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java index cdf4071ad..adc1bdf1f 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java @@ -108,7 +108,7 @@ public class UserVerificationService { // If no key is defined, attempt to generate a new key String secret = attributes.get(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME); - if (secret == null) { + if (secret == null || secret.isEmpty()) { // Generate random key for user TOTPGenerator.Mode mode = confService.getMode(); diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json index 6f73aa02d..55bd69ac4 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-totp/src/main/resources/translations/en.json @@ -29,6 +29,15 @@ "SECTION_HEADER_DETAILS" : "Details:" + }, + + "USER_ATTRIBUTES" : { + + "FIELD_HEADER_GUAC_TOTP_RESET" : "Clear TOTP secret:", + "FIELD_HEADER_GUAC_TOTP_KEY_CONFIRMED" : "TOTP key confirmed:", + + "SECTION_HEADER_TOTP_CONFIG_FORM" : "Configure TOTP" + } }