Browse Source

Added the password strength indicator in the initialize window

jncharon 9 years ago
parent
commit
588166dce9

+ 1 - 0
main/pom.xml

@@ -238,6 +238,7 @@
 				<artifactId>hamcrest-all</artifactId>
 				<artifactId>hamcrest-all</artifactId>
 				<version>${hamcrest.version}</version>
 				<version>${hamcrest.version}</version>
 			</dependency>
 			</dependency>
+
 		</dependencies>
 		</dependencies>
 	</dependencyManagement>
 	</dependencyManagement>
 
 

+ 7 - 0
main/ui/pom.xml

@@ -99,5 +99,12 @@
 			<groupId>org.cryptomator</groupId>
 			<groupId>org.cryptomator</groupId>
 			<artifactId>commons-test</artifactId>
 			<artifactId>commons-test</artifactId>
 		</dependency>
 		</dependency>
+
+		<!-- Zxcvbn -->
+		<dependency>
+			<groupId>com.nulab-inc</groupId>
+			<artifactId>zxcvbn</artifactId>
+			<version>1.1.1</version>
+		</dependency>
 	</dependencies>
 	</dependencies>
 </project>
 </project>

+ 86 - 16
main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java

@@ -2,27 +2,12 @@
  * Copyright (c) 2014, 2016 Sebastian Stenzel
  * Copyright (c) 2014, 2016 Sebastian Stenzel
  * This file is licensed under the terms of the MIT license.
  * This file is licensed under the terms of the MIT license.
  * See the LICENSE.txt file for more info.
  * See the LICENSE.txt file for more info.
- * 
+ *
  * Contributors:
  * Contributors:
  *     Sebastian Stenzel - initial API and implementation
  *     Sebastian Stenzel - initial API and implementation
  ******************************************************************************/
  ******************************************************************************/
 package org.cryptomator.ui.controllers;
 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.application.Platform;
 import javafx.beans.binding.BooleanBinding;
 import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectProperty;
@@ -31,6 +16,26 @@ import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.fxml.FXML;
 import javafx.scene.control.Button;
 import javafx.scene.control.Button;
 import javafx.scene.control.Label;
 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.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
 @Singleton
 public class InitializeController extends LocalizedFXMLViewController {
 public class InitializeController extends LocalizedFXMLViewController {
@@ -39,6 +44,8 @@ public class InitializeController extends LocalizedFXMLViewController {
 
 
 	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	private Optional<InitializationListener> listener = Optional.empty();
 	private Optional<InitializationListener> listener = Optional.empty();
+	private Zxcvbn zxcvbn = new Zxcvbn();
+	private List<String> sanitizedInputs = new ArrayList();
 
 
 	@Inject
 	@Inject
 	public InitializeController(Localization localization) {
 	public InitializeController(Localization localization) {
@@ -57,11 +64,31 @@ public class InitializeController extends LocalizedFXMLViewController {
 	@FXML
 	@FXML
 	private Label messageLabel;
 	private Label messageLabel;
 
 
+	@FXML
+	private Label passwordStrengthLabel;
+
+	@FXML
+	private Rectangle passwordStrengthShape;
+
 	@Override
 	@Override
 	public void initialize() {
 	public void initialize() {
 		BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
 		BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
 		BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
 		BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
 		okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
 		okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
+		passwordField.textProperty().addListener((observable, oldValue, newValue) -> {
+			checkPasswordStrength(newValue);
+		});
+
+		// progressbar graphic properties
+		passwordStrengthShape.setFill(Color.web("#ff1a1a"));
+		passwordStrengthShape.setStroke(Color.GRAY);
+
+		// default password strength bar visual properties
+		changeProgressBarAspect(0f, 0f, Color.web("#ff1a1a"));
+		passwordStrengthLabel.setText(localization.getString("initialize.messageLabel.passwordStrength") + " : 0%");
+
+		// preparing inputs for the password strength checker
+		sanitizedInputs.add("cryptomator");
 	}
 	}
 
 
 	@Override
 	@Override
@@ -100,6 +127,49 @@ public class InitializeController extends LocalizedFXMLViewController {
 		this.listener = Optional.ofNullable(listener);
 		this.listener = Optional.ofNullable(listener);
 	}
 	}
 
 
+	/* Methods */
+
+	private void checkPasswordStrength(String password) {
+		int strengthPercentage = 0;
+		if (StringUtils.isEmpty(password)) {
+			changeProgressBarAspect(0f, 0f, Color.web("#FF0000"));
+			passwordStrengthLabel.setText(localization.getString("initialize.messageLabel.passwordStrength") + " : " + strengthPercentage + "%");
+		} else {
+			Color color = Color.web("#FF0000");
+			Strength strength = zxcvbn.measure(password, sanitizedInputs);
+			switch (strength.getScore()) {
+				case 0:
+					strengthPercentage = 20;
+					break;
+				case 1:
+					strengthPercentage = 40;
+					color = Color.web("#FF8000");
+					break;
+				case 2:
+					strengthPercentage = 60;
+					color = Color.web("#FFBF00");
+					break;
+				case 3:
+					strengthPercentage = 80;
+					color = Color.web("#FFFF00");
+					break;
+				case 4:
+					strengthPercentage = 100;
+					color = Color.web("#BFFF00");
+					break;
+			}
+
+			passwordStrengthLabel.setText(localization.getString("initialize.messageLabel.passwordStrength") + " : " + strengthPercentage + "%");
+			changeProgressBarAspect(0.5f, strengthPercentage * 2.23f, color); // 2.23f is the factor used to get the width to fit the window
+		}
+	}
+
+	private void changeProgressBarAspect(float strokeWidth, float length, Color color) {
+		passwordStrengthShape.setFill(color);
+		passwordStrengthShape.setStrokeWidth(strokeWidth);
+		passwordStrengthShape.setWidth(length);
+	}
+
 	/* callback */
 	/* callback */
 
 
 	private void invokeListenerLater(InitializationListener listener) {
 	private void invokeListenerLater(InitializationListener listener) {

+ 19 - 8
main/ui/src/main/resources/fxml/initialize.fxml

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
+
 <!--
 <!--
   Copyright (c) 2014 Sebastian Stenzel
   Copyright (c) 2014 Sebastian Stenzel
   This file is licensed under the terms of the MIT license.
   This file is licensed under the terms of the MIT license.
@@ -7,14 +8,19 @@
   Contributors:
   Contributors:
       Sebastian Stenzel - initial API and implementation
       Sebastian Stenzel - initial API and implementation
 -->
 -->
+
+<?import java.lang.*?>
 <?import java.net.URL?>
 <?import java.net.URL?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.HBox?>
-<?import org.cryptomator.ui.controls.SecPasswordField?>
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.control.Button?>
 <?import javafx.scene.layout.GridPane?>
 <?import javafx.scene.layout.GridPane?>
 <?import javafx.geometry.Insets?>
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.layout.ColumnConstraints?>
 <?import javafx.scene.layout.ColumnConstraints?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.Label?>
+<?import javafx.scene.control.ProgressBar?>
+<?import javafx.scene.control.ProgressIndicator?>
+<?import org.cryptomator.ui.controls.SecPasswordField?>
+<?import javafx.scene.shape.Rectangle?>
 
 
 <GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
 <GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
 	<padding>
 	<padding>
@@ -30,17 +36,22 @@
 		<!-- Row 0 -->
 		<!-- Row 0 -->
 		<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
 		<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
 		<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
 		<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
-		
+
 		<!-- Row 1 -->
 		<!-- Row 1 -->
 		<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
 		<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
 		<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
 		<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
-		
+
 		<!-- Row 2 -->
 		<!-- Row 2 -->
-		<Button fx:id="okButton" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" text="%initialize.button.ok" prefWidth="150.0" onAction="#initializeVault" focusTraversable="false" disable="true" cacheShape="true" cache="true" />
-				
+		<Label fx:id="passwordStrengthLabel" cache="true" cacheShape="true" text="" GridPane.columnIndex="1" GridPane.rowIndex="2" />
+
+		<!-- Row 2 -->
+		<Rectangle fx:id="passwordStrengthShape" width="0" height="15" GridPane.columnIndex="1" GridPane.rowIndex="3" />
+
 		<!-- Row 3 -->
 		<!-- Row 3 -->
-		<Label fx:id="messageLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
-	</children>
-</GridPane>
+		<Button fx:id="okButton" cache="true" cacheShape="true" defaultButton="true" disable="true" focusTraversable="false" onAction="#initializeVault" prefWidth="150.0" text="%initialize.button.ok" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" GridPane.rowIndex="4" />
 
 
+		<!-- Row 4 -->
+		<Label fx:id="messageLabel" cache="true" cacheShape="true" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.rowIndex="5" />
 
 
+	</children>
+</GridPane>

+ 1 - 0
main/ui/src/main/resources/localization.properties

@@ -26,6 +26,7 @@ initialize.label.retypePassword=Retype password
 initialize.button.ok=Create vault
 initialize.button.ok=Create vault
 initialize.messageLabel.alreadyInitialized=Vault already initialized
 initialize.messageLabel.alreadyInitialized=Vault already initialized
 initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
 initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
+initialize.messageLabel.passwordStrength=Password Strength
 
 
 # notfound.fxml
 # notfound.fxml
 notfound.label=Vault couldn't be found. Has it been moved?
 notfound.label=Vault couldn't be found. Has it been moved?