diff --git a/main/ui/pom.xml b/main/ui/pom.xml index c1dc24234..6808f3887 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -99,5 +99,12 @@ org.cryptomator commons-test + + + + com.nulab-inc + zxcvbn + 1.1.1 + 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 33f7ae11f..5944cf999 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 @@ -5,6 +5,7 @@ * * Contributors: * Sebastian Stenzel - initial API and implementation + * Jean-Noël Charon - password strength meter *******************************************************************************/ package org.cryptomator.ui.controllers; @@ -21,18 +22,24 @@ import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; import org.cryptomator.ui.controls.SecPasswordField; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.settings.Localization; +import org.cryptomator.ui.util.PasswordStrengthUtil; +import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; import javafx.scene.text.Text; @Singleton @@ -41,13 +48,16 @@ public class ChangePasswordController extends LocalizedFXMLViewController { private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class); private final Application app; + private final PasswordStrengthUtil strengthRater; final ObjectProperty vault = new SimpleObjectProperty<>(); private Optional listener = Optional.empty(); + private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4 @Inject - public ChangePasswordController(Application app, Localization localization) { + public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) { super(localization); this.app = app; + this.strengthRater = strengthRater; } @FXML @@ -68,12 +78,43 @@ public class ChangePasswordController extends LocalizedFXMLViewController { @FXML private Hyperlink downloadsPageLink; + @FXML + private Label passwordStrengthLabel; + + @FXML + private Region passwordStrengthLevel0; + + @FXML + private Region passwordStrengthLevel1; + + @FXML + private Region passwordStrengthLevel2; + + @FXML + private Region passwordStrengthLevel3; + + @FXML + private Region passwordStrengthLevel4; + @Override public void initialize() { BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty(); BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty(); BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty()); changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer))); + passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate)); + + passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0)); + passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1)); + passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2)); + passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3)); + passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4)); + passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription)); } @Override 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 19a1187df..96016aa55 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 @@ -2,9 +2,10 @@ * Copyright (c) 2014, 2016 Sebastian Stenzel * This file is licensed under the terms of the MIT license. * See the LICENSE.txt file for more info. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation + * Jean-Noël Charon - password strength meter ******************************************************************************/ package org.cryptomator.ui.controllers; @@ -20,29 +21,37 @@ import javax.inject.Singleton; import org.cryptomator.ui.controls.SecPasswordField; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.settings.Localization; +import org.cryptomator.ui.util.PasswordStrengthUtil; +import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javafx.application.Platform; import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.layout.Region; @Singleton public class InitializeController extends LocalizedFXMLViewController { private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class); + private final PasswordStrengthUtil strengthRater; final ObjectProperty vault = new SimpleObjectProperty<>(); private Optional listener = Optional.empty(); + private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4 @Inject - public InitializeController(Localization localization) { + public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) { super(localization); + this.strengthRater = strengthRater; } @FXML @@ -57,11 +66,42 @@ public class InitializeController extends LocalizedFXMLViewController { @FXML private Label messageLabel; + @FXML + private Label passwordStrengthLabel; + + @FXML + private Region passwordStrengthLevel0; + + @FXML + private Region passwordStrengthLevel1; + + @FXML + private Region passwordStrengthLevel2; + + @FXML + private Region passwordStrengthLevel3; + + @FXML + private Region passwordStrengthLevel4; + @Override public void initialize() { BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty(); BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty()); okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer)); + passwordStrength.bind(EasyBind.map(passwordField.textProperty(), strengthRater::computeRate)); + + passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0)); + passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1)); + passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2)); + passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3)); + passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4)); + passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor)); + passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription)); } @Override diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index 5c976e317..0ac30e0b2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -154,6 +154,7 @@ public class WelcomeController extends LocalizedFXMLViewController { Platform.runLater(() -> { this.updateLink.setText(msg); this.updateLink.setVisible(true); + this.updateLink.setDisable(false); }); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java new file mode 100644 index 000000000..b2871a9b3 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Jean-Noël Charon - initial API and implementation + *******************************************************************************/ +package org.cryptomator.ui.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.apache.commons.lang3.StringUtils; +import org.cryptomator.ui.settings.Localization; + +import com.nulabinc.zxcvbn.Zxcvbn; + +import javafx.geometry.Insets; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.paint.Color; + +@Singleton +public class PasswordStrengthUtil { + + private final Zxcvbn zxcvbn; + private final List sanitizedInputs; + private final Localization localization; + + @Inject + public PasswordStrengthUtil(Localization localization) { + this.localization = localization; + this.zxcvbn = new Zxcvbn(); + this.sanitizedInputs = new ArrayList<>(); + this.sanitizedInputs.add("cryptomator"); + } + + public int computeRate(String password) { + if (StringUtils.isEmpty(password)) { + return -1; + } else { + return zxcvbn.measure(password, sanitizedInputs).getScore(); + } + } + + public Color getStrengthColor(Number score) { + switch (score.intValue()) { + case 0: + return Color.web("#e74c3c"); + case 1: + return Color.web("#e67e22"); + case 2: + return Color.web("#f1c40f"); + case 3: + return Color.web("#40d47e"); + case 4: + return Color.web("#27ae60"); + default: + return Color.TRANSPARENT; + } + } + + public Background getBackgroundWithStrengthColor(Number score) { + Color c = this.getStrengthColor(score); + BackgroundFill fill = new BackgroundFill(c, CornerRadii.EMPTY, Insets.EMPTY); + return new Background(fill); + } + + public String getStrengthDescription(Number score) { + String key = "initialize.messageLabel.passwordStrength." + score.intValue(); + if (localization.containsKey(key)) { + return localization.getString(key); + } else { + return ""; + } + } + +} diff --git a/main/ui/src/main/resources/css/linux_theme.css b/main/ui/src/main/resources/css/linux_theme.css index 4beabf9e8..9c8fc2f3a 100644 --- a/main/ui/src/main/resources/css/linux_theme.css +++ b/main/ui/src/main/resources/css/linux_theme.css @@ -50,6 +50,10 @@ -fx-font-family: Ionicons; } +.caption-label { + -fx-font-size: 0.9em; +} + /**************************************************************************** * * * Hyperlinks * diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css index bac4a985d..33d1e223e 100644 --- a/main/ui/src/main/resources/css/mac_theme.css +++ b/main/ui/src/main/resources/css/mac_theme.css @@ -49,6 +49,10 @@ -fx-font-family: Ionicons; } +.caption-label { + -fx-font-size: 0.9em; +} + /**************************************************************************** * * * Hyperlinks * diff --git a/main/ui/src/main/resources/css/win_theme.css b/main/ui/src/main/resources/css/win_theme.css index f0109b004..bfd569b54 100644 --- a/main/ui/src/main/resources/css/win_theme.css +++ b/main/ui/src/main/resources/css/win_theme.css @@ -42,6 +42,10 @@ -fx-font-family: Ionicons; } +.caption-label { + -fx-font-size: 0.9em; +} + /**************************************************************************** * * * Hyperlinks * diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml index 93b7f18b0..44d985526 100644 --- a/main/ui/src/main/resources/fxml/change_password.fxml +++ b/main/ui/src/main/resources/fxml/change_password.fxml @@ -6,6 +6,7 @@ Contributors: Sebastian Stenzel - initial API and implementation + Jean-Noël Charon - password strength meter --> @@ -20,7 +21,9 @@ - + + + @@ -46,10 +49,22 @@ -