Преглед изворни кода

Added change password functionality (fixes #20)
Moved controllers to new package
Small UI improvements

Sebastian Stenzel пре 10 година
родитељ
комит
7edd303f2e

+ 1 - 0
main/ui/src/main/java/org/cryptomator/ui/MainApplication.java

@@ -24,6 +24,7 @@ import javafx.stage.Stage;
 
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.MainModule.ControllerFactory;
+import org.cryptomator.ui.controllers.MainController;
 import org.cryptomator.ui.model.Vault;
 import org.cryptomator.ui.util.ActiveWindowStyleSupport;
 import org.cryptomator.ui.util.DeferredCloser;

+ 175 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java

@@ -0,0 +1,175 @@
+package org.cryptomator.ui.controllers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.ResourceBundle;
+
+import javafx.application.Platform;
+import javafx.beans.value.ObservableValue;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+
+import org.cryptomator.crypto.exceptions.DecryptFailedException;
+import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.WrongPasswordException;
+import org.cryptomator.ui.controls.SecPasswordField;
+import org.cryptomator.ui.model.Vault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+
+public class ChangePasswordController implements Initializable {
+
+	private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
+
+	private ResourceBundle rb;
+	private ChangePasswordListener listener;
+	private Vault vault;
+
+	@FXML
+	private SecPasswordField oldPasswordField;
+
+	@FXML
+	private SecPasswordField newPasswordField;
+
+	@FXML
+	private SecPasswordField retypePasswordField;
+
+	@FXML
+	private Button changePasswordButton;
+
+	@FXML
+	private Label messageLabel;
+
+	@Inject
+	public ChangePasswordController() {
+		super();
+	}
+
+	@Override
+	public void initialize(URL location, ResourceBundle rb) {
+		this.rb = rb;
+
+		oldPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
+		newPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
+		retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
+	}
+
+	// ****************************************
+	// Password fields
+	// ****************************************
+
+	private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
+		boolean oldPasswordIsEmpty = oldPasswordField.getText().isEmpty();
+		boolean newPasswordIsEmpty = newPasswordField.getText().isEmpty();
+		boolean passwordsAreEqual = newPasswordField.getText().equals(retypePasswordField.getText());
+		changePasswordButton.setDisable(oldPasswordIsEmpty || newPasswordIsEmpty || !passwordsAreEqual);
+	}
+
+	// ****************************************
+	// Change password button
+	// ****************************************
+
+	@FXML
+	private void didClickChangePasswordButton(ActionEvent event) {
+		final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
+		final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
+
+		// decrypt with old password:
+		final CharSequence oldPassword = oldPasswordField.getCharacters();
+		try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
+			vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
+			Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
+		} catch (DecryptFailedException | IOException ex) {
+			messageLabel.setText(rb.getString("changePassword.errorMessage.decryptionFailed"));
+			LOG.error("Decryption failed for technical reasons.", ex);
+			newPasswordField.swipe();
+			retypePasswordField.swipe();
+			return;
+		} catch (WrongPasswordException e) {
+			messageLabel.setText(rb.getString("changePassword.errorMessage.wrongPassword"));
+			newPasswordField.swipe();
+			retypePasswordField.swipe();
+			Platform.runLater(oldPasswordField::requestFocus);
+			return;
+		} catch (UnsupportedKeyLengthException ex) {
+			messageLabel.setText(rb.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
+			LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
+			newPasswordField.swipe();
+			retypePasswordField.swipe();
+			return;
+		} finally {
+			oldPasswordField.swipe();
+		}
+
+		// when we reach this line, decryption was successful.
+
+		// encrypt with new password:
+		final CharSequence newPassword = newPasswordField.getCharacters();
+		try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
+			vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
+			messageLabel.setText(rb.getString("changePassword.infoMessage.success"));
+			Platform.runLater(this::didChangePassword);
+			// At this point the backup is still using the old password.
+			// It will be changed as soon as the user unlocks the vault the next time.
+			// This way he can still restore the old password, if he doesn't remember the new one.
+		} catch (IOException ex) {
+			LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
+			this.restoreBackupQuietly();
+		} finally {
+			newPasswordField.swipe();
+			retypePasswordField.swipe();
+		}
+	}
+
+	private void restoreBackupQuietly() {
+		final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
+		final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
+		try {
+			Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
+		} catch (IOException ex) {
+			LOG.error("Restoring Backup failed.", ex);
+		}
+	}
+
+	private void didChangePassword() {
+		if (listener != null) {
+			listener.didChangePassword(this);
+		}
+	}
+
+	/* Getter/Setter */
+
+	public Vault getVault() {
+		return vault;
+	}
+
+	public void setVault(Vault vault) {
+		this.vault = vault;
+	}
+
+	public ChangePasswordListener getListener() {
+		return listener;
+	}
+
+	public void setListener(ChangePasswordListener listener) {
+		this.listener = listener;
+	}
+
+	/* callback */
+
+	interface ChangePasswordListener {
+		void didChangePassword(ChangePasswordController ctrl);
+	}
+
+}

+ 8 - 8
main/ui/src/main/java/org/cryptomator/ui/InitializeController.java

@@ -6,7 +6,7 @@
  * Contributors:
  *     Sebastian Stenzel - initial API and implementation
  ******************************************************************************/
-package org.cryptomator.ui;
+package org.cryptomator.ui.controllers;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,7 +35,7 @@ public class InitializeController implements Initializable {
 	private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
 
 	private ResourceBundle localization;
-	private Vault directory;
+	private Vault vault;
 	private InitializationListener listener;
 
 	@FXML
@@ -74,10 +74,10 @@ public class InitializeController implements Initializable {
 	@FXML
 	protected void initializeVault(ActionEvent event) {
 		setControlsDisabled(true);
-		final Path masterKeyPath = directory.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
+		final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
 		final CharSequence password = passwordField.getCharacters();
 		try (OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
-			directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
+			vault.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
 			if (listener != null) {
 				listener.didInitialize(this);
 			}
@@ -102,12 +102,12 @@ public class InitializeController implements Initializable {
 
 	/* Getter/Setter */
 
-	public Vault getDirectory() {
-		return directory;
+	public Vault getVault() {
+		return vault;
 	}
 
-	public void setDirectory(Vault directory) {
-		this.directory = directory;
+	public void setVault(Vault vault) {
+		this.vault = vault;
 	}
 
 	public InitializationListener getListener() {

+ 31 - 13
main/ui/src/main/java/org/cryptomator/ui/MainController.java

@@ -6,7 +6,7 @@
  * Contributors:
  *     Sebastian Stenzel - initial API and implementation
  ******************************************************************************/
-package org.cryptomator.ui;
+package org.cryptomator.ui.controllers;
 
 import java.io.File;
 import java.io.IOException;
@@ -38,10 +38,11 @@ import javafx.stage.FileChooser;
 import javafx.stage.Stage;
 import javafx.stage.WindowEvent;
 
-import org.cryptomator.ui.InitializeController.InitializationListener;
 import org.cryptomator.ui.MainModule.ControllerFactory;
-import org.cryptomator.ui.UnlockController.UnlockListener;
-import org.cryptomator.ui.UnlockedController.LockListener;
+import org.cryptomator.ui.controllers.ChangePasswordController.ChangePasswordListener;
+import org.cryptomator.ui.controllers.InitializeController.InitializationListener;
+import org.cryptomator.ui.controllers.UnlockController.UnlockListener;
+import org.cryptomator.ui.controllers.UnlockedController.LockListener;
 import org.cryptomator.ui.controls.DirectoryListCell;
 import org.cryptomator.ui.model.Vault;
 import org.cryptomator.ui.model.VaultFactory;
@@ -51,7 +52,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 
-public class MainController implements Initializable, InitializationListener, UnlockListener, LockListener {
+public class MainController implements Initializable, InitializationListener, UnlockListener, LockListener, ChangePasswordListener {
 
 	private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
 
@@ -152,7 +153,7 @@ public class MainController implements Initializable, InitializationListener, Un
 	 * 
 	 * @param path non-null, writable, existing directory
 	 */
-	void addVault(final Path path, boolean select) {
+	public void addVault(final Path path, boolean select) {
 		if (path == null || !Files.isWritable(path)) {
 			return;
 		}
@@ -199,11 +200,17 @@ public class MainController implements Initializable, InitializationListener, Un
 
 	@FXML
 	private void didClickRemoveSelectedEntry(ActionEvent e) {
-		final Vault selectedDir = vaultList.getSelectionModel().getSelectedItem();
-		vaultList.getItems().remove(selectedDir);
+		final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
+		vaultList.getItems().remove(selectedVault);
 		vaultList.getSelectionModel().clearSelection();
 	}
 
+	@FXML
+	private void didClickChangePassword(ActionEvent e) {
+		final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
+		showChangePasswordView(selectedVault);
+	}
+
 	// ****************************************
 	// Subcontroller for right panel
 	// ****************************************
@@ -239,20 +246,20 @@ public class MainController implements Initializable, InitializationListener, Un
 		this.showView("/fxml/welcome.fxml");
 	}
 
-	private void showInitializeView(Vault directory) {
+	private void showInitializeView(Vault vault) {
 		final InitializeController ctrl = showView("/fxml/initialize.fxml");
-		ctrl.setDirectory(directory);
+		ctrl.setVault(vault);
 		ctrl.setListener(this);
 	}
 
 	@Override
 	public void didInitialize(InitializeController ctrl) {
-		showUnlockView(ctrl.getDirectory());
+		showUnlockView(ctrl.getVault());
 	}
 
-	private void showUnlockView(Vault directory) {
+	private void showUnlockView(Vault vault) {
 		final UnlockController ctrl = showView("/fxml/unlock.fxml");
-		ctrl.setVault(directory);
+		ctrl.setVault(vault);
 		ctrl.setListener(this);
 	}
 
@@ -276,6 +283,17 @@ public class MainController implements Initializable, InitializationListener, Un
 		}
 	}
 
+	private void showChangePasswordView(Vault vault) {
+		final ChangePasswordController ctrl = showView("/fxml/change_password.fxml");
+		ctrl.setVault(vault);
+		ctrl.setListener(this);
+	}
+
+	@Override
+	public void didChangePassword(ChangePasswordController ctrl) {
+		showUnlockView(ctrl.getVault());
+	}
+
 	/* Convenience */
 
 	public Collection<Vault> getDirectories() {

+ 14 - 7
main/ui/src/main/java/org/cryptomator/ui/UnlockController.java

@@ -6,7 +6,7 @@
  * Contributors:
  *     Sebastian Stenzel - initial API and implementation
  ******************************************************************************/
-package org.cryptomator.ui;
+package org.cryptomator.ui.controllers;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -30,7 +30,6 @@ import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.TextField;
 import javafx.scene.input.KeyEvent;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.CharUtils;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
@@ -78,10 +77,20 @@ public class UnlockController implements Initializable {
 	public void initialize(URL url, ResourceBundle rb) {
 		this.rb = rb;
 
+		passwordField.textProperty().addListener(this::passwordFieldsDidChange);
 		mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
 		mountName.textProperty().addListener(this::mountNameDidChange);
 	}
 
+	// ****************************************
+	// Password field
+	// ****************************************
+
+	private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
+		boolean passwordIsEmpty = passwordField.getText().isEmpty();
+		unlockButton.setDisable(passwordIsEmpty);
+	}
+
 	// ****************************************
 	// Unlock button
 	// ****************************************
@@ -89,13 +98,11 @@ public class UnlockController implements Initializable {
 	@FXML
 	private void didClickUnlockButton(ActionEvent event) {
 		setControlsDisabled(true);
+		progressIndicator.setVisible(true);
 		final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
 		final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
 		final CharSequence password = passwordField.getCharacters();
-		InputStream masterKeyInputStream = null;
-		try {
-			progressIndicator.setVisible(true);
-			masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
+		try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
 			vault.getCryptor().decryptMasterKey(masterKeyInputStream, password);
 			if (!vault.startServer()) {
 				messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed"));
@@ -127,12 +134,12 @@ public class UnlockController implements Initializable {
 			LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
 		} finally {
 			passwordField.swipe();
-			IOUtils.closeQuietly(masterKeyInputStream);
 		}
 	}
 
 	private void setControlsDisabled(boolean disable) {
 		passwordField.setDisable(disable);
+		mountName.setDisable(disable);
 		unlockButton.setDisable(disable);
 	}
 

+ 1 - 1
main/ui/src/main/java/org/cryptomator/ui/UnlockedController.java

@@ -6,7 +6,7 @@
  * Contributors:
  *     Sebastian Stenzel - initial API and implementation
  ******************************************************************************/
-package org.cryptomator.ui;
+package org.cryptomator.ui.controllers;
 
 import java.net.URL;
 import java.util.ResourceBundle;

+ 1 - 1
main/ui/src/main/java/org/cryptomator/ui/util/DeferredCloser.java

@@ -15,7 +15,7 @@ import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.cryptomator.ui.MainController;
+import org.cryptomator.ui.controllers.MainController;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 

+ 2 - 1
main/ui/src/main/resources/css/mac_theme.css

@@ -305,7 +305,8 @@
 	-fx-background-color: linear-gradient(to bottom, #4AA0F9 0%, #045FFF 100%), linear-gradient(to bottom, #69B2FA 0%, #0D81FF 100%);
     -fx-text-fill: -fx-light-text-color;
 }
-.button:default:disabled {
+.button:default:disabled,
+.root.active-window .button:default:disabled {
 	-fx-background-color: linear-gradient(to bottom, #D2D2D2 0%, #C4C4C4 100%), #F2F2F2;
 	-fx-background-insets: 0, 1;
     -fx-text-fill: -fx-mid-text-color;

+ 53 - 0
main/ui/src/main/resources/fxml/change_password.fxml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright (c) 2014 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
+-->
+<?import java.net.*?>
+<?import javafx.geometry.*?>
+<?import javafx.scene.control.*?>
+<?import javafx.scene.layout.*?>
+<?import javafx.scene.text.*?>
+<?import java.lang.String?>
+<?import org.cryptomator.ui.controls.SecPasswordField?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.ProgressIndicator?>
+<?import javafx.scene.control.CheckBox?>
+
+
+<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.controllers.ChangePasswordController" xmlns:fx="http://javafx.com/fxml">
+	<padding>
+		<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
+	</padding>
+	
+	<columnConstraints>
+		<ColumnConstraints percentWidth="38.2"/>
+		<ColumnConstraints percentWidth="61.8"/>
+	</columnConstraints>
+
+	<children>
+		<!-- Row 0 -->
+		<Label text="%changePassword.label.oldPassword" GridPane.rowIndex="0" GridPane.columnIndex="0" />
+		<SecPasswordField fx:id="oldPasswordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
+		
+		<!-- Row 1 -->
+		<Label text="%changePassword.label.newPassword" GridPane.rowIndex="1" GridPane.columnIndex="0" />
+		<SecPasswordField fx:id="newPasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
+		
+		<!-- Row 2 -->
+		<Label text="%changePassword.label.retypePassword" GridPane.rowIndex="2" GridPane.columnIndex="0" />
+		<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
+		
+		<!-- Row 3 -->
+		<Button fx:id="changePasswordButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickChangePasswordButton" disable="true"/>
+		
+		<!-- Row 4 -->
+		<Label fx:id="messageLabel" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" />
+	</children>
+</GridPane>
+
+

+ 1 - 1
main/ui/src/main/resources/fxml/initialize.fxml

@@ -18,7 +18,7 @@
 <?import javafx.scene.control.TextField?>
 
 
-<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.InitializeController" xmlns:fx="http://javafx.com/fxml">
+<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.controllers.InitializeController" xmlns:fx="http://javafx.com/fxml">
 	<padding>
 		<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
 	</padding>

+ 2 - 3
main/ui/src/main/resources/fxml/main.fxml

@@ -19,7 +19,7 @@
 <?import javafx.scene.control.Separator?>
 <?import javafx.geometry.Insets?>
 
-<HBox fx:id="rootPane" prefHeight="440.0" prefWidth="640.0" fx:controller="org.cryptomator.ui.MainController" xmlns:fx="http://javafx.com/fxml">
+<HBox fx:id="rootPane" prefHeight="440.0" prefWidth="640.0" fx:controller="org.cryptomator.ui.controllers.MainController" xmlns:fx="http://javafx.com/fxml">
 
 	<padding><Insets top="20" right="20" bottom="20" left="20.0"/></padding>
 
@@ -28,8 +28,7 @@
 		<ContextMenu fx:id="vaultListCellContextMenu">
 			<items>
 				<MenuItem text="%main.directoryList.contextMenu.remove" onAction="#didClickRemoveSelectedEntry" />
-				<!-- TODO: -->
-				<MenuItem text="%main.directoryList.contextMenu.changePassword" disable="true" />
+				<MenuItem text="%main.directoryList.contextMenu.changePassword" onAction="#didClickChangePassword" />
 			</items>
 		</ContextMenu>
 		<ContextMenu fx:id="addVaultContextMenu" onShowing="#willShowAddVaultContextMenu" onHidden="#didHideAddVaultContextMenu">

+ 2 - 2
main/ui/src/main/resources/fxml/unlock.fxml

@@ -19,7 +19,7 @@
 <?import javafx.scene.control.CheckBox?>
 
 
-<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockController" xmlns:fx="http://javafx.com/fxml">
+<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.controllers.UnlockController" xmlns:fx="http://javafx.com/fxml">
 	<padding>
 		<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
 	</padding>
@@ -39,7 +39,7 @@
 		<TextField fx:id="mountName" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
 		
 		<!-- Row 2 -->
-		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
+		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true"/>
 		
 		<!-- Row 3-->
 		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>

+ 1 - 1
main/ui/src/main/resources/fxml/unlocked.fxml

@@ -18,7 +18,7 @@
 <?import javafx.scene.chart.NumberAxis?>
 
 
-<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockedController" xmlns:fx="http://javafx.com/fxml">
+<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.controllers.UnlockedController" xmlns:fx="http://javafx.com/fxml">
 	<padding>
 		<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
 	</padding>

+ 10 - 4
main/ui/src/main/resources/localization.properties

@@ -25,8 +25,6 @@ welcome.addButtonInstructionLabel=Start by adding a new vault :-)
 initialize.label.password=Password
 initialize.label.retypePassword=Retype password
 initialize.button.ok=Create vault
-initialize.alert.directoryIsNotEmpty.title=The chosen directory is not empty
-initialize.alert.directoryIsNotEmpty.content=All existing files inside this directory will get encrypted. Continue?
 
 
 # unlock.fxml
@@ -35,12 +33,20 @@ unlock.label.mountName=Drive name
 unlock.button.unlock=Unlock vault
 unlock.errorMessage.wrongPassword=Wrong password.
 unlock.errorMessage.decryptionFailed=Decryption failed.
-unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
 unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
 
+# change_password.fxml
+changePassword.label.oldPassword=Old password
+changePassword.label.newPassword=New password
+changePassword.label.retypePassword=Retype password
+changePassword.button.unlock=Change password
+changePassword.errorMessage.wrongPassword=Wrong password.
+changePassword.errorMessage.decryptionFailed=Decryption failed.
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
+changePassword.infoMessage.success=Password changed.
 
 # unlocked.fxml
-unlocked.messageLabel.runningOnPort=Vault is accessible via WebDAV on local port %d.
 unlocked.button.lock=Lock vault
 unlocked.ioGraph.yAxis.label=Throughput (MiB/s)