diff --git a/main/pom.xml b/main/pom.xml
index 1aac82079..39715d6b6 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -254,6 +254,7 @@
hamcrest-all
${hamcrest.version}
+
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..2b5e644c9 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,22 +5,35 @@
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
+ * Jean-Noël Charon - password strength meter
*******************************************************************************/
package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
+import com.nulabinc.zxcvbn.Strength;
+import com.nulabinc.zxcvbn.Zxcvbn;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.scene.control.Label;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
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;
@@ -43,6 +56,7 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
private final Application app;
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
+ final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
public ChangePasswordController(Application app, Localization localization) {
@@ -50,6 +64,9 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
this.app = app;
}
+ @Inject
+ PasswordStrengthUtil strengthRater;
+
@FXML
private SecPasswordField oldPasswordField;
@@ -68,12 +85,24 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
@FXML
private Hyperlink downloadsPageLink;
+ @FXML
+ private Label passwordStrengthLabel;
+
+ @FXML
+ private Rectangle passwordStrengthShape;
+
@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));
+
+ passwordStrengthShape.widthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getWidth));
+ passwordStrengthShape.fillProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthColor));
+ passwordStrengthShape.strokeWidthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrokeWidth));
+ 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..0a87c4fd6 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,35 +2,42 @@
* 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;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.URL;
-import java.nio.file.FileAlreadyExistsException;
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import org.cryptomator.ui.controls.SecPasswordField;
-import org.cryptomator.ui.model.Vault;
-import org.cryptomator.ui.settings.Localization;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import org.apache.commons.lang3.StringUtils;
+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 javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.nio.file.FileAlreadyExistsException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import com.nulabinc.zxcvbn.*;
@Singleton
public class InitializeController extends LocalizedFXMLViewController {
@@ -39,12 +46,16 @@ public class InitializeController extends LocalizedFXMLViewController {
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
+ final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
public InitializeController(Localization localization) {
super(localization);
}
+ @Inject
+ PasswordStrengthUtil strengthRater;
+
@FXML
private SecPasswordField passwordField;
@@ -57,11 +68,23 @@ public class InitializeController extends LocalizedFXMLViewController {
@FXML
private Label messageLabel;
+ @FXML
+ private Label passwordStrengthLabel;
+
+ @FXML
+ private Rectangle passwordStrengthShape;
+
@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));
+
+ passwordStrengthShape.widthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getWidth));
+ passwordStrengthShape.fillProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthColor));
+ passwordStrengthShape.strokeWidthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrokeWidth));
+ 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..fe6af2f61
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * 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 com.nulabinc.zxcvbn.Zxcvbn;
+import javafx.beans.property.IntegerProperty;
+import javafx.scene.paint.Color;
+import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.ui.settings.Localization;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
+
+@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) {
+ Color strengthColor = Color.web("#FF0000");
+ switch (score.intValue()) {
+ case 0:
+ strengthColor = Color.web("#FF0000");
+ break;
+ case 1:
+ strengthColor = Color.web("#FF8000");
+ break;
+ case 2:
+ strengthColor = Color.web("#FFBF00");
+ break;
+ case 3:
+ strengthColor = Color.web("#FFFF00");
+ break;
+ case 4:
+ strengthColor = Color.web("#BFFF00");
+ break;
+ default:
+ strengthColor = Color.web("#FF0000");
+ break;
+ }
+ return strengthColor;
+ }
+
+ public int getWidth(Number score) {
+ int width = 0;
+ switch (score.intValue()) {
+ case 0:
+ width += 5;
+ break;
+ case 1:
+ width += 25;
+ break;
+ case 2:
+ width += 50;
+ break;
+ case 3:
+ width += 75;
+ break;
+ case 4:
+ width = 100;
+ break;
+ default:
+ width = 0;
+ break;
+ }
+ return Math.round(width*2.23f);
+ }
+
+ public float getStrokeWidth(Number score) {
+ if (score.intValue() >= 0) {
+ return 0.5f;
+ } else {
+ return 0;
+ }
+ }
+
+ public String getStrengthDescription(Number score) {
+ if (score.intValue() >= 0) {
+ return String.format(localization.getString("initialize.messageLabel.passwordStrength"),
+ localization.getString("initialize.messageLabel.passwordStrength." + score.intValue()));
+ } else {
+ return "";
+ }
+ }
+
+}
diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml
index 93b7f18b0..22c17ce9b 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
-->
@@ -22,6 +23,7 @@
+
@@ -44,12 +46,18 @@
-
+
-
-
+
+
-
+
+
+
+
+
+
+
diff --git a/main/ui/src/main/resources/fxml/initialize.fxml b/main/ui/src/main/resources/fxml/initialize.fxml
index 65ca244c6..e5fcd6deb 100644
--- a/main/ui/src/main/resources/fxml/initialize.fxml
+++ b/main/ui/src/main/resources/fxml/initialize.fxml
@@ -1,4 +1,5 @@
+
+
+
-
+
+
+
+
@@ -30,17 +37,22 @@
-
+
-
+
-
-
+
+
+
+
+
-
+
+
+
+
+
-
-
diff --git a/main/ui/src/main/resources/fxml/welcome.fxml b/main/ui/src/main/resources/fxml/welcome.fxml
index 14405ead3..aa6af8885 100644
--- a/main/ui/src/main/resources/fxml/welcome.fxml
+++ b/main/ui/src/main/resources/fxml/welcome.fxml
@@ -25,7 +25,7 @@
-
+
diff --git a/main/ui/src/main/resources/localization/en.txt b/main/ui/src/main/resources/localization/en.txt
index 2cd432e99..d112a19ac 100644
--- a/main/ui/src/main/resources/localization/en.txt
+++ b/main/ui/src/main/resources/localization/en.txt
@@ -27,6 +27,12 @@ initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
initialize.messageLabel.alreadyInitialized=Vault already initialized
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
+initialize.messageLabel.passwordStrength=Password Strength: %s
+initialize.messageLabel.passwordStrength.0=Weak
+initialize.messageLabel.passwordStrength.1=Fair
+initialize.messageLabel.passwordStrength.2=Good
+initialize.messageLabel.passwordStrength.3=Strong
+initialize.messageLabel.passwordStrength.4=Very strong
# notfound.fxml
notfound.label=Vault couldn't be found. Has it been moved?