mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Pimped Password Field
Added reveal icon as well, capslock warnings as well a warning for unprintable chars (fixes #458)
This commit is contained in:
@@ -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"));
|
||||
|
||||
@@ -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<ContentDisplay> createVaultButtonState;
|
||||
|
||||
public SecPasswordField passwordField;
|
||||
public SecPasswordField reenterField;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public NiceSecurePasswordField reenterField;
|
||||
public Label passwordStrengthLabel;
|
||||
public HBox passwordMatchBox;
|
||||
public FontAwesome5IconView checkmark;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"), //
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<Scene> successScene;
|
||||
private final BooleanProperty unlockButtonDisabled;
|
||||
public SecPasswordField passwordField;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public CheckBox savePassword;
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import org.cryptomator.ui.controls.SecurePasswordField?>
|
||||
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultPasswordController"
|
||||
@@ -27,13 +28,13 @@
|
||||
|
||||
<VBox spacing="6">
|
||||
<Label text="%addvaultwizard.new.enterPassword" labelFor="$passwordField"/>
|
||||
<SecPasswordField fx:id="passwordField"/>
|
||||
<NiceSecurePasswordField fx:id="passwordField"/>
|
||||
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
|
||||
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" labelFor="$passwordField" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
|
||||
</VBox>
|
||||
<VBox spacing="6">
|
||||
<Label text="%addvaultwizard.new.reenterPassword" labelFor="$reenterField"/>
|
||||
<SecPasswordField fx:id="reenterField"/>
|
||||
<NiceSecurePasswordField fx:id="reenterField"/>
|
||||
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
|
||||
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
|
||||
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
||||
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.changepassword.ChangePasswordController"
|
||||
@@ -24,20 +24,20 @@
|
||||
<children>
|
||||
<VBox spacing="6">
|
||||
<FormattedLabel format="%changepassword.enterOldPassword" arg1="${controller.vault.displayableName}" wrapText="true"/>
|
||||
<SecPasswordField fx:id="oldPasswordField"/>
|
||||
<NiceSecurePasswordField fx:id="oldPasswordField"/>
|
||||
</VBox>
|
||||
|
||||
<Region prefHeight="12" VBox.vgrow="NEVER"/>
|
||||
|
||||
<VBox spacing="6">
|
||||
<Label labelFor="$newPasswordField" text="%changepassword.enterNewPassword"/>
|
||||
<SecPasswordField fx:id="newPasswordField"/>
|
||||
<NiceSecurePasswordField fx:id="newPasswordField"/>
|
||||
<PasswordStrengthIndicator prefHeight="6" spacing="6" strength="${controller.passwordStrength}"/>
|
||||
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
|
||||
</VBox>
|
||||
<VBox spacing="6">
|
||||
<Label labelFor="$reenterPasswordField" text="%changepassword.reenterNewPassword"/>
|
||||
<SecPasswordField fx:id="reenterPasswordField"/>
|
||||
<NiceSecurePasswordField fx:id="reenterPasswordField"/>
|
||||
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
|
||||
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
|
||||
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressBar?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import org.cryptomator.ui.controls.SecurePasswordField?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
<SecurePasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
<SecurePasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import org.cryptomator.ui.controls.SecurePasswordField?>
|
||||
<VBox fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" spacing="12" alignment="BOTTOM_CENTER" xmlns:fx="http://javafx.com/fxml" prefWidth="400">
|
||||
|
||||
<padding>
|
||||
@@ -31,7 +31,7 @@
|
||||
<!-- Password Field -->
|
||||
<HBox spacing="12" alignment="BASELINE_LEFT">
|
||||
<Label text="%unlock.label.password" HBox.hgrow="NEVER"/>
|
||||
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" maxWidth="Infinity" HBox.hgrow="ALWAYS"/>
|
||||
<SecurePasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" maxWidth="Infinity" HBox.hgrow="ALWAYS"/>
|
||||
</HBox>
|
||||
|
||||
<!-- Unlock Button / Advanced Options Button -->
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.unlock.UnlockController"
|
||||
@@ -21,7 +21,7 @@
|
||||
<children>
|
||||
<VBox spacing="6">
|
||||
<FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
|
||||
<SecPasswordField fx:id="passwordField"/>
|
||||
<NiceSecurePasswordField fx:id="passwordField"/>
|
||||
<CheckBox fx:id="savePassword" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox"/>
|
||||
</VBox>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import org.cryptomator.ui.controls.SecurePasswordField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
@@ -39,7 +39,7 @@
|
||||
</Pane>
|
||||
|
||||
<Label text="%unlock.label.password" GridPane.rowIndex="3" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="3" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
<SecurePasswordField fx:id="passwordField" GridPane.rowIndex="3" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<CheckBox fx:id="confirmationCheckbox" text="%upgrade.confirmation.label" wrapText="true" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import java.awt.GraphicsEnvironment;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class SecPasswordFieldTest {
|
||||
class SecurePasswordFieldTest {
|
||||
|
||||
private SecPasswordField pwField = new SecPasswordField();
|
||||
private SecurePasswordField pwField = new SecurePasswordField();
|
||||
|
||||
@BeforeAll
|
||||
static void initJavaFx() throws InterruptedException {
|
||||
Reference in New Issue
Block a user