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 @@