From efaf5a155346249b043f7549b1eea070804eff28 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 4 Sep 2019 17:16:24 +0200 Subject: [PATCH] Pimped Password Field Added reveal icon as well, capslock warnings as well a warning for unprintable chars (fixes #458) --- .../org/cryptomator/common/Environment.java | 1 + .../CreateNewVaultPasswordController.java | 6 +- .../ChangePasswordController.java | 9 +- .../controllers/ChangePasswordController.java | 8 +- .../ui/controllers/InitializeController.java | 6 +- .../ui/controllers/UnlockController.java | 4 +- .../ui/controllers/UpgradeController.java | 4 +- .../ui/controls/FontAwesome5Icon.java | 2 + .../ui/controls/NiceSecurePasswordField.java | 89 ++++++++++ ...ordField.java => SecurePasswordField.java} | 161 ++++++++++-------- .../ui/unlock/UnlockController.java | 5 +- main/ui/src/main/resources/css/dark_theme.css | 9 + .../ui/src/main/resources/css/light_theme.css | 9 + .../resources/fxml/addvault_new_password.fxml | 7 +- .../main/resources/fxml/changepassword.fxml | 8 +- .../src/main/resources/fxml/initialize.fxml | 6 +- main/ui/src/main/resources/fxml/unlock.fxml | 4 +- main/ui/src/main/resources/fxml/unlock2.fxml | 4 +- main/ui/src/main/resources/fxml/upgrade.fxml | 4 +- ...Test.java => SecurePasswordFieldTest.java} | 4 +- 20 files changed, 242 insertions(+), 108 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java rename main/ui/src/main/java/org/cryptomator/ui/controls/{SecPasswordField.java => SecurePasswordField.java} (63%) rename main/ui/src/test/java/org/cryptomator/ui/controls/{SecPasswordFieldTest.java => SecurePasswordFieldTest.java} (98%) diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java index 861cfbb31..52e23ff07 100644 --- a/main/commons/src/main/java/org/cryptomator/common/Environment.java +++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java @@ -28,6 +28,7 @@ public class Environment { @Inject public Environment() { + LOG.debug("java.library.path: {}", System.getProperty("java.library.path")); LOG.debug("user.language: {}", System.getProperty("user.language")); LOG.debug("user.region: {}", System.getProperty("user.region")); LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile")); diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java index c0c169184..f53f0f2d6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java @@ -28,7 +28,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.controls.FontAwesome5IconView; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.util.PasswordStrengthUtil; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; @@ -71,8 +71,8 @@ public class CreateNewVaultPasswordController implements FxController { private final BooleanProperty readyToCreateVault; private final ObjectBinding createVaultButtonState; - public SecPasswordField passwordField; - public SecPasswordField reenterField; + public NiceSecurePasswordField passwordField; + public NiceSecurePasswordField reenterField; public Label passwordStrengthLabel; public HBox passwordMatchBox; public FontAwesome5IconView checkmark; diff --git a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java index f55ff2b33..b73c0259c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java @@ -14,7 +14,7 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.controls.FontAwesome5IconView; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.util.PasswordStrengthUtil; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; @@ -35,9 +35,9 @@ public class ChangePasswordController implements FxController { private final PasswordStrengthUtil strengthRater; private final IntegerProperty passwordStrength; - public SecPasswordField oldPasswordField; - public SecPasswordField newPasswordField; - public SecPasswordField reenterPasswordField; + public NiceSecurePasswordField oldPasswordField; + public NiceSecurePasswordField newPasswordField; + public NiceSecurePasswordField reenterPasswordField; public Label passwordStrengthLabel; public HBox passwordMatchBox; public FontAwesome5IconView checkmark; @@ -71,7 +71,6 @@ public class ChangePasswordController implements FxController { cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty)); cross.managedProperty().bind(cross.visibleProperty()); passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("changepassword.passwordsMatch")).otherwise(resourceBundle.getString("changepassword.passwordsDoNotMatch"))); - passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription)); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java index 62ba46424..f766a5078 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java @@ -25,7 +25,7 @@ import javafx.scene.layout.Region; import javafx.scene.text.Text; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.SecurePasswordField; import org.cryptomator.ui.l10n.Localization; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.util.PasswordStrengthUtil; @@ -58,13 +58,13 @@ public class ChangePasswordController implements ViewController { } @FXML - private SecPasswordField oldPasswordField; + private SecurePasswordField oldPasswordField; @FXML - private SecPasswordField newPasswordField; + private SecurePasswordField newPasswordField; @FXML - private SecPasswordField retypePasswordField; + private SecurePasswordField retypePasswordField; @FXML private Button changePasswordButton; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java index 5a78d4f39..ee8076eca 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java @@ -20,7 +20,7 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.Region; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.SecurePasswordField; import org.cryptomator.ui.l10n.Localization; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.util.PasswordStrengthUtil; @@ -51,10 +51,10 @@ public class InitializeController implements ViewController { } @FXML - private SecPasswordField passwordField; + private SecurePasswordField passwordField; @FXML - private SecPasswordField retypePasswordField; + private SecurePasswordField retypePasswordField; @FXML private Button okButton; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 9ec347190..0af76805e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -41,7 +41,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; import org.cryptomator.keychain.KeychainAccess; import org.cryptomator.keychain.KeychainAccessException; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.SecurePasswordField; import org.cryptomator.ui.l10n.Localization; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.WindowsDriveLetters; @@ -99,7 +99,7 @@ public class UnlockController implements ViewController { } @FXML - private SecPasswordField passwordField; + private SecurePasswordField passwordField; @FXML private Button advancedOptionsButton; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java index dea8608f5..ca35ec57b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java @@ -21,7 +21,7 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.GridPane; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.SecurePasswordField; import org.cryptomator.ui.model.upgrade.UpgradeStrategies; import org.cryptomator.ui.model.upgrade.UpgradeStrategy; import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException; @@ -50,7 +50,7 @@ public class UpgradeController implements ViewController { private Label upgradeMsgLabel; @FXML - private SecPasswordField passwordField; + private SecurePasswordField passwordField; @FXML private CheckBox confirmationCheckbox; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index 101a000c6..f4bea2918 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -5,11 +5,13 @@ package org.cryptomator.ui.controls; */ public enum FontAwesome5Icon { ANCHOR("\uF13D"), // + ARROW_ALT_UP("\uF357"), // CHECK("\uF00C"), // COG("\uF013"), // COGS("\uF085"), // EXCLAMATION_TRIANGLE("\uF071"), // EYE("\uF06E"), // + EYE_SLASH("\uF070"), // FOLDER_OPEN("\uF07C"), // HDD("\uF0A0"), // KEY("\uF084"), // diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java new file mode 100644 index 000000000..3285fc2da --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java @@ -0,0 +1,89 @@ +package org.cryptomator.ui.controls; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.StringProperty; +import javafx.geometry.Pos; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ToggleButton; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; + +public class NiceSecurePasswordField extends StackPane { + + private static final String STYLE_CLASS = "nice-secure-password-field"; + private static final String ICONS_STLYE_CLASS = "icons"; + private static final String REVEAL_BUTTON_STLYE_CLASS = "reveal-button"; + private static final int ICON_SPACING = 6; + private static final double ICON_SIZE = 14.0; + + private final SecurePasswordField passwordField = new SecurePasswordField(); + private final FontAwesome5IconView capsLockedIcon = new FontAwesome5IconView(); + private final FontAwesome5IconView nonPrintableCharsIcon = new FontAwesome5IconView(); + private final FontAwesome5IconView revealPasswordIcon = new FontAwesome5IconView(); + private final ToggleButton revealPasswordButton = new ToggleButton(null, revealPasswordIcon); + private final HBox iconContainer = new HBox(ICON_SPACING, nonPrintableCharsIcon, capsLockedIcon, revealPasswordButton); + + public NiceSecurePasswordField() { + getStyleClass().add(STYLE_CLASS); + + iconContainer.setAlignment(Pos.CENTER_RIGHT); + iconContainer.setMaxWidth(Double.NEGATIVE_INFINITY); + iconContainer.setPrefWidth(42); // TODO + iconContainer.getStyleClass().add(ICONS_STLYE_CLASS); + StackPane.setAlignment(iconContainer, Pos.CENTER_RIGHT); + + capsLockedIcon.setGlyph(FontAwesome5Icon.ARROW_ALT_UP); + capsLockedIcon.setGlyphSize(ICON_SIZE); + capsLockedIcon.visibleProperty().bind(passwordField.capsLockedProperty()); + capsLockedIcon.managedProperty().bind(passwordField.capsLockedProperty()); + + nonPrintableCharsIcon.setGlyph(FontAwesome5Icon.EXCLAMATION_TRIANGLE); + nonPrintableCharsIcon.setGlyphSize(ICON_SIZE); + nonPrintableCharsIcon.visibleProperty().bind(passwordField.containingNonPrintableCharsProperty()); + nonPrintableCharsIcon.managedProperty().bind(passwordField.containingNonPrintableCharsProperty()); + + revealPasswordIcon.setGlyph(FontAwesome5Icon.EYE); + revealPasswordIcon.glyphProperty().bind(Bindings.createObjectBinding(this::getRevealPasswordGlyph, revealPasswordButton.selectedProperty())); + revealPasswordIcon.setGlyphSize(ICON_SIZE); + + revealPasswordButton.setContentDisplay(ContentDisplay.LEFT); + revealPasswordButton.setFocusTraversable(false); + revealPasswordButton.visibleProperty().bind(passwordField.focusedProperty()); + revealPasswordButton.managedProperty().bind(passwordField.focusedProperty()); + revealPasswordButton.getStyleClass().add(REVEAL_BUTTON_STLYE_CLASS); + + passwordField.revealPasswordProperty().bind(revealPasswordButton.selectedProperty()); + + getChildren().addAll(passwordField, iconContainer); + } + + private FontAwesome5Icon getRevealPasswordGlyph() { + return revealPasswordButton.isSelected() ? FontAwesome5Icon.EYE_SLASH : FontAwesome5Icon.EYE; + } + + /* Passthrough */ + + public StringProperty textProperty() { + return passwordField.textProperty(); + } + + public CharSequence getCharacters() { + return passwordField.getCharacters(); + } + + public void setPassword(char[] password) { + passwordField.setPassword(password); + } + + public void swipe() { + passwordField.swipe();; + } + + public void selectAll() { + passwordField.selectAll(); + } + + public void selectRange(int anchor, int caretPosition) { + passwordField.selectRange(anchor, caretPosition); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java similarity index 63% rename from main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java rename to main/ui/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java index 392800ca7..fb7807dc7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java @@ -11,20 +11,19 @@ package org.cryptomator.ui.controls; import com.google.common.base.Strings; import javafx.beans.NamedArg; import javafx.beans.Observable; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Label; -import javafx.scene.control.OverrunStyle; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.AccessibleAttribute; +import javafx.scene.AccessibleRole; +import javafx.scene.control.IndexRange; import javafx.scene.control.PasswordField; -import javafx.scene.control.Tooltip; +import javafx.scene.control.TextField; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.Text; import java.awt.Toolkit; import java.nio.CharBuffer; @@ -33,53 +32,54 @@ import java.text.Normalizer.Form; import java.util.Arrays; /** - * Patched PasswordField that doesn't create String copies of the password in memory. Instead the password is stored in a char[] that can be swiped. + * Patched PasswordField that doesn't create String copies of the password in memory (unless explicitly revealed). Instead the password is stored in a char[] that can be swiped. * * @implNote Since {@link #setText(String)} is final, we can not override its behaviour. For that reason you should not use the {@link #textProperty()} for anything else than display purposes. */ -public class SecPasswordField extends PasswordField { +public class SecurePasswordField extends TextField { private static final char SWIPE_CHAR = ' '; private static final int INITIAL_BUFFER_SIZE = 50; private static final int GROW_BUFFER_SIZE = 50; - private static final String PLACEHOLDER = "*"; - private static final double PADDING = 2.0; - private static final double INDICATOR_PADDING = 4.0; - private static final Color INDICATOR_COLOR = new Color(0.901, 0.494, 0.133, 1.0); + private static final String DEFAULT_PLACEHOLDER = "●"; + private static final String STYLE_CLASS = "secure-password-field"; - private final Tooltip tooltip = new Tooltip(); - private final Label indicator = new Label(); - private final String nonPrintableCharsWarning; - private final String capslockWarning; + private final String placeholderChar; + private final BooleanProperty capsLocked = new SimpleBooleanProperty(); + private final BooleanProperty containingNonPrintableChars = new SimpleBooleanProperty(); + private final BooleanProperty revealPassword = new SimpleBooleanProperty(); private char[] content = new char[INITIAL_BUFFER_SIZE]; private int length = 0; - public SecPasswordField() { - this("", ""); + public SecurePasswordField() { + this(DEFAULT_PLACEHOLDER); } - public SecPasswordField(@NamedArg("nonPrintableCharsWarning") String nonPrintableCharsWarning, @NamedArg("capslockWarning") String capslockWarning) { - this.nonPrintableCharsWarning = nonPrintableCharsWarning; - this.capslockWarning = capslockWarning; - indicator.setPadding(new Insets(PADDING, INDICATOR_PADDING, PADDING, INDICATOR_PADDING)); - indicator.setAlignment(Pos.CENTER_RIGHT); - indicator.setMouseTransparent(true); - indicator.setTextOverrun(OverrunStyle.CLIP); - indicator.setTextFill(INDICATOR_COLOR); - indicator.setFont(Font.font(indicator.getFont().getFamily(), 15.0)); - this.getChildren().add(indicator); + public SecurePasswordField(@NamedArg("placeholderChar") String placeholderChar) { + this.getStyleClass().add(STYLE_CLASS); + this.placeholderChar = placeholderChar; + this.setAccessibleRole(AccessibleRole.PASSWORD_FIELD); this.addEventHandler(DragEvent.DRAG_OVER, this::handleDragOver); this.addEventHandler(DragEvent.DRAG_DROPPED, this::handleDragDropped); this.addEventHandler(KeyEvent.ANY, this::handleKeyEvent); + this.revealPasswordProperty().addListener(this::revealPasswordChanged); this.focusedProperty().addListener(this::focusedChanged); } - @Override - protected void layoutChildren() { - super.layoutChildren(); - indicator.relocate(0.0, 0.0); - indicator.resize(getWidth(), getHeight()); + public void cut() { + } + + public void copy() { + } + + public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { + switch(attribute) { + case TEXT: + return null; + default: + return super.queryAccessibleAttribute(attribute, parameters); + } } private void handleDragOver(DragEvent event) { @@ -100,42 +100,32 @@ public class SecPasswordField extends PasswordField { private void handleKeyEvent(KeyEvent e) { if (e.getCode() == KeyCode.CAPS) { - updateVisualHints(true); + updateCapsLocked(); } } + private void revealPasswordChanged(@SuppressWarnings("unused") Observable observable) { + IndexRange selection = getSelection(); + if (isRevealPassword()) { + super.setText(this.getCharacters().toString()); + } else { + String placeholderText = Strings.repeat(placeholderChar, length); + super.setText(placeholderText); + } + selectRange(selection.getStart(), selection.getEnd()); + } + private void focusedChanged(@SuppressWarnings("unused") Observable observable) { - updateVisualHints(isFocused()); + updateCapsLocked(); } - private void updateVisualHints(boolean focused) { - StringBuilder tooltipSb = new StringBuilder(); - StringBuilder indicatorSb = new StringBuilder(); - if (containsNonPrintableCharacters()) { - indicatorSb.append('⚠'); - tooltipSb.append("- ").append(nonPrintableCharsWarning).append('\n'); - } + private void updateCapsLocked() { // AWT code needed until https://bugs.openjdk.java.net/browse/JDK-8090882 is closed: - if (focused && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)) { - indicatorSb.append('⇪'); - tooltipSb.append("- ").append(capslockWarning).append('\n'); - } - indicator.setText(indicatorSb.toString()); - if (!indicator.getText().isEmpty()) { - setPadding(new Insets(PADDING, getIndicatorWidth(), PADDING, PADDING)); - } else { - setPadding(new Insets(PADDING)); - } - tooltip.setText(tooltipSb.toString()); - if (tooltip.getText().isEmpty()) { - setTooltip(null); - } else { - setTooltip(tooltip); - } + capsLocked.set(isFocused() && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)); } - private double getIndicatorWidth() { - return new Text(indicator.getText()).getLayoutBounds().getWidth() + INDICATOR_PADDING * 2.0; + private void updateContainingNonPrintableChars() { + containingNonPrintableChars.set(containsNonPrintableCharacters()); } /** @@ -144,7 +134,7 @@ public class SecPasswordField extends PasswordField { */ boolean containsNonPrintableCharacters() { for (int i = 0; i < length; i++) { - if (Character.isISOControl(content[i])) { + if (Character.isDigit(content[i])) { return true; } } @@ -183,9 +173,13 @@ public class SecPasswordField extends PasswordField { normalizedText.getChars(0, normalizedText.length(), content, start); // trigger visual hints - updateVisualHints(true); - String placeholderString = Strings.repeat(PLACEHOLDER, normalizedText.length()); - super.replaceText(start, end, placeholderString); + updateContainingNonPrintableChars(); + if (isRevealPassword()) { + super.replaceText(start, end, text); + } else { + String placeholderString = Strings.repeat(placeholderChar, normalizedText.length()); + super.replaceText(start, end, placeholderString); + } } private void growContentIfNeeded() { @@ -200,9 +194,9 @@ public class SecPasswordField extends PasswordField { /** * Creates a CharSequence by wrapping the password characters. * - * @return A character sequence backed by the SecPasswordField's buffer (not a copy). + * @return A character sequence backed by the SecurePasswordField's buffer (not a copy). * @implNote The CharSequence will not copy the backing char[]. - * Therefore any mutation to the SecPasswordField's content will mutate or eventually swipe the returned CharSequence. + * Therefore any mutation to the SecurePasswordField's content will mutate or eventually swipe the returned CharSequence. * @implSpec The CharSequence is usually in NFC representation (unless NFD-encoded char[] is set via {@link #setPassword(char[])}). * @see #swipe() */ @@ -238,7 +232,7 @@ public class SecPasswordField extends PasswordField { content = Arrays.copyOf(password, password.length); length = password.length; - String placeholderString = Strings.repeat(PLACEHOLDER, password.length); + String placeholderString = Strings.repeat(placeholderChar, password.length); setText(placeholderString); } @@ -255,4 +249,33 @@ public class SecPasswordField extends PasswordField { Arrays.fill(buffer, SWIPE_CHAR); } + /* Observable Properties */ + + public ReadOnlyBooleanProperty capsLockedProperty() { + return capsLocked; + } + + public boolean isCapsLocked() { + return capsLocked.get(); + } + + public ReadOnlyBooleanProperty containingNonPrintableCharsProperty() { + return containingNonPrintableChars; + } + + public boolean isContainingNonPrintableChars() { + return containingNonPrintableChars.get(); + } + + public BooleanProperty revealPasswordProperty() { + return revealPassword; + } + + public boolean isRevealPassword() { + return revealPassword.get(); + } + + public void setRevealPassword(boolean revealPassword) { + this.revealPassword.set(revealPassword); + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java index cefd059e2..485ff7fce 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockController.java @@ -29,7 +29,8 @@ import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.Tasks; -import org.cryptomator.ui.controls.SecPasswordField; +import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.cryptomator.ui.controls.SecurePasswordField; import org.cryptomator.ui.util.DialogBuilderUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +56,7 @@ public class UnlockController implements FxController { private final ResourceBundle resourceBundle; private final Lazy successScene; private final BooleanProperty unlockButtonDisabled; - public SecPasswordField passwordField; + public NiceSecurePasswordField passwordField; public CheckBox savePassword; @Inject diff --git a/main/ui/src/main/resources/css/dark_theme.css b/main/ui/src/main/resources/css/dark_theme.css index 4c98c617d..c4467ecf4 100644 --- a/main/ui/src/main/resources/css/dark_theme.css +++ b/main/ui/src/main/resources/css/dark_theme.css @@ -455,6 +455,15 @@ -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED; } +.nice-secure-password-field .secure-password-field { + -fx-padding: 0.3em 48px 0.3em 0.5em; +} + +.nice-secure-password-field .icons { + -fx-width: 42px; + -fx-padding: 4px 6px 4px 0; +} + /******************************************************************************* * * * Buttons * diff --git a/main/ui/src/main/resources/css/light_theme.css b/main/ui/src/main/resources/css/light_theme.css index 50b90a707..73028cf7a 100644 --- a/main/ui/src/main/resources/css/light_theme.css +++ b/main/ui/src/main/resources/css/light_theme.css @@ -455,6 +455,15 @@ -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED; } +.nice-secure-password-field .secure-password-field { + -fx-padding: 0.3em 48px 0.3em 0.5em; +} + +.nice-secure-password-field .icons { + -fx-width: 42px; + -fx-padding: 4px 6px 4px 0; +} + /******************************************************************************* * * * Buttons * diff --git a/main/ui/src/main/resources/fxml/addvault_new_password.fxml b/main/ui/src/main/resources/fxml/addvault_new_password.fxml index 9586bb0c7..a0cdbad64 100644 --- a/main/ui/src/main/resources/fxml/addvault_new_password.fxml +++ b/main/ui/src/main/resources/fxml/addvault_new_password.fxml @@ -11,7 +11,8 @@ - + +