mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-23 05:01:28 +00:00
created more static methods in RecoverUtil, added first flow for choose dir and restore
This commit is contained in:
@@ -1,30 +1,60 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
|
||||
import static org.cryptomator.cryptolib.api.CryptorProvider.Scheme.SIV_CTRMAC;
|
||||
import static org.cryptomator.cryptolib.api.CryptorProvider.Scheme.SIV_GCM;
|
||||
|
||||
public class RecoverUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoverUtil.class);
|
||||
|
||||
public static CryptorProvider.Scheme detectCipherCombo(byte[] masterkey, Path pathToVault) {
|
||||
try (Stream<Path> paths = Files.walk(pathToVault.resolve(DATA_DIR_NAME))) {
|
||||
return paths.filter(path -> path.toString().endsWith(".c9r"))
|
||||
.findFirst()
|
||||
.map(c9rFile -> determineScheme(c9rFile, masterkey))
|
||||
.orElseThrow(() -> new IllegalStateException("No .c9r file found."));
|
||||
return paths.filter(path -> path.toString().endsWith(".c9r")).findFirst().map(c9rFile -> determineScheme(c9rFile, masterkey)).orElseThrow(() -> new IllegalStateException("No .c9r file found."));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to detect cipher combo.", e);
|
||||
}
|
||||
@@ -33,8 +63,7 @@ public class RecoverUtil {
|
||||
private static CryptorProvider.Scheme determineScheme(Path c9rFile, byte[] masterkey) {
|
||||
try {
|
||||
ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile));
|
||||
return tryDecrypt(header, new Masterkey(masterkey), SIV_GCM) ? SIV_GCM :
|
||||
tryDecrypt(header, new Masterkey(masterkey), SIV_CTRMAC) ? SIV_CTRMAC : null;
|
||||
return tryDecrypt(header, new Masterkey(masterkey), SIV_GCM) ? SIV_GCM : tryDecrypt(header, new Masterkey(masterkey), SIV_CTRMAC) ? SIV_CTRMAC : null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -51,11 +80,7 @@ public class RecoverUtil {
|
||||
|
||||
public static boolean restoreBackupIfAvailable(Path configPath, VaultState.Value vaultState) {
|
||||
try (Stream<Path> files = Files.list(configPath.getParent())) {
|
||||
return files
|
||||
.filter(file -> matchesBackupFile(file.getFileName().toString(), vaultState))
|
||||
.findFirst()
|
||||
.map(backupFile -> copyBackupFile(backupFile, configPath))
|
||||
.orElse(false);
|
||||
return files.filter(file -> matchesBackupFile(file.getFileName().toString(), vaultState)).findFirst().map(backupFile -> copyBackupFile(backupFile, configPath)).orElse(false);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
@@ -78,4 +103,138 @@ public class RecoverUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static Path createRecoveryDirectory(Path vaultPath) throws IOException {
|
||||
Path recoveryPath = vaultPath.resolve("r");
|
||||
Files.createDirectory(recoveryPath);
|
||||
return recoveryPath;
|
||||
}
|
||||
|
||||
public static void createNewMasterkeyFile(RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, String recoveryKey, CharSequence newPassword) throws IOException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
|
||||
}
|
||||
|
||||
public static Masterkey loadMasterkey(org.cryptomator.cryptolib.common.MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
|
||||
return masterkeyFileAccess.load(masterkeyFilePath, password);
|
||||
}
|
||||
|
||||
public static void initializeCryptoFileSystem(Path recoveryPath, Path vaultPath, Masterkey masterkey, IntegerProperty shorteningThreshold) throws IOException, CryptoException {
|
||||
var combo = RecoverUtil.detectCipherCombo(masterkey.getEncoded(), vaultPath);
|
||||
org.cryptomator.cryptolib.api.MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(combo).withKeyLoader(loader).withShorteningThreshold(shorteningThreshold.get()).build();
|
||||
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||
}
|
||||
|
||||
public static void moveRecoveredFiles(Path recoveryPath, Path vaultPath) throws IOException {
|
||||
Files.move(recoveryPath.resolve(MASTERKEY_FILENAME), vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME));
|
||||
}
|
||||
|
||||
public static void deleteRecoveryDirectory(Path recoveryPath) {
|
||||
try (var paths = Files.walk(recoveryPath)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clean up recovery directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addVaultToList(VaultListManager vaultListManager, Path vaultPath) throws IOException {
|
||||
if (!vaultListManager.containsVault(vaultPath)) {
|
||||
vaultListManager.add(vaultPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<Void> createResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController, Stage window, Lazy<Scene> recoverResetPasswordSuccessScene, Lazy<Scene> recoverResetVaultConfigSuccessScene, FxApplicationWindows appWindows) {
|
||||
|
||||
Task<Void> task = new ResetPasswordTask(recoveryKeyFactory, vault, recoveryKey, newPasswordController);
|
||||
|
||||
task.setOnScheduled(_ -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
|
||||
task.setOnSucceeded(_ -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
if (vault.getState().equals(VAULT_CONFIG_MISSING)) {
|
||||
window.setScene(recoverResetVaultConfigSuccessScene.get());
|
||||
} else {
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(_ -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
public static class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final Vault vault;
|
||||
private final StringProperty recoveryKey;
|
||||
private final NewPasswordController newPasswordController;
|
||||
|
||||
public ResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController) {
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.vault = vault;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.newPasswordController = newPasswordController;
|
||||
|
||||
setOnFailed(event -> LOG.error("Failed to reset password", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() throws IOException, IllegalArgumentException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Vault> prepareVaultFromDirectory(DirectoryChooser directoryChooser, Stage window, Dialogs dialogs, VaultComponent.Factory vaultComponentFactory, List<MountService> mountServices) {
|
||||
|
||||
File selectedDirectory;
|
||||
do {
|
||||
selectedDirectory = directoryChooser.showDialog(window);
|
||||
if (selectedDirectory == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
boolean hasSubfolderD = new File(selectedDirectory, "d").isDirectory();
|
||||
|
||||
if (!hasSubfolderD) {
|
||||
dialogs.prepareNoDDirectorySelectedDialog(window).build().showAndWait();
|
||||
selectedDirectory = null;
|
||||
}
|
||||
} while (selectedDirectory == null);
|
||||
|
||||
Path selectedPath = selectedDirectory.toPath();
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path.set(selectedPath);
|
||||
if (selectedPath.getFileName() != null) {
|
||||
vaultSettings.displayName.set(selectedPath.getFileName().toString());
|
||||
} else {
|
||||
vaultSettings.displayName.set("defaultVaultName");
|
||||
}
|
||||
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, VAULT_CONFIG_MISSING, null).vault();
|
||||
|
||||
// Spezialbehandlung für Windows + Dropbox + WinFsp
|
||||
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
|
||||
if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
|
||||
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
|
||||
}
|
||||
|
||||
return Optional.of(vault);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public class VaultConfigCache {
|
||||
private final VaultSettings settings;
|
||||
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
|
||||
|
||||
VaultConfigCache(VaultSettings settings) {
|
||||
public VaultConfigCache(VaultSettings settings) {
|
||||
this.settings = settings;
|
||||
this.config = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,12 @@ public class VaultListManager {
|
||||
autoLocker.init();
|
||||
}
|
||||
|
||||
public boolean containsVault(Path vaultPath) {
|
||||
assert vaultPath.isAbsolute();
|
||||
assert vaultPath.normalize().equals(vaultPath);
|
||||
return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
|
||||
}
|
||||
|
||||
public Vault add(Path pathToVault) throws IOException {
|
||||
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
||||
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
||||
|
||||
@@ -2,14 +2,19 @@ package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationStyle;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,12 +24,15 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
|
||||
@@ -42,6 +50,11 @@ public class ChooseExistingVaultController implements FxController {
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObservableValue<Image> screenshot;
|
||||
private final Dialogs dialogs;
|
||||
private final VaultComponent.Factory vaultComponentFactory;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
private final List<MountService> mountServices;
|
||||
|
||||
|
||||
@Inject
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, //
|
||||
@@ -51,7 +64,11 @@ public class ChooseExistingVaultController implements FxController {
|
||||
@AddVaultWizardWindow ObjectProperty<Vault> vault, //
|
||||
VaultListManager vaultListManager, //
|
||||
ResourceBundle resourceBundle, //
|
||||
FxApplicationStyle applicationStyle) {
|
||||
FxApplicationStyle applicationStyle, //
|
||||
RecoveryKeyComponent.Factory recoveryKeyWindow, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
List<MountService> mountServices, //
|
||||
Dialogs dialogs) {
|
||||
this.window = window;
|
||||
this.successScene = successScene;
|
||||
this.appWindows = appWindows;
|
||||
@@ -60,6 +77,10 @@ public class ChooseExistingVaultController implements FxController {
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot);
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.vaultComponentFactory = vaultComponentFactory;
|
||||
this.mountServices = mountServices;
|
||||
this.dialogs = dialogs;
|
||||
}
|
||||
|
||||
private Image selectScreenshot(Theme theme) {
|
||||
@@ -96,7 +117,24 @@ public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void restoreVaultConfigWithRecoveryKey() {
|
||||
//appWindows.showErrorWindow(e, window, window.getScene());
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
directoryChooser.setTitle(resourceBundle.getString("generic.button.cancel"));
|
||||
|
||||
Optional<Vault> optionalVault = RecoverUtil.prepareVaultFromDirectory(directoryChooser, window, dialogs, vaultComponentFactory, mountServices);
|
||||
|
||||
optionalVault.ifPresent(vault -> {
|
||||
dialogs.prepareContactHubAdmin(window) //
|
||||
.setTitleKey("a.title", vault.getVaultSettings().displayName.get() + " " + vault.getState()) //
|
||||
.setDescriptionKey("a.description") //
|
||||
.setMessageKey("a.message") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkButtonKey("generic.button.next") //
|
||||
.setOkAction(stage -> {
|
||||
recoveryKeyWindow.create(vault, window).showIsHubVaultDialogWindow();
|
||||
stage.close();
|
||||
}) //
|
||||
.build().showAndWait();
|
||||
});
|
||||
}
|
||||
|
||||
/* Getter */
|
||||
@@ -109,5 +147,4 @@ public class ChooseExistingVaultController implements FxController {
|
||||
return screenshot.getValue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ public enum FxmlFile {
|
||||
QUIT_FORCED("/fxml/quit_forced.fxml"), //
|
||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||
RECOVERYKEY_IS_HUB_VAULT("/fxml/recoverykey_is_hub_vault.fxml"), //
|
||||
RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), //
|
||||
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
||||
|
||||
@@ -97,4 +97,18 @@ public class Dialogs {
|
||||
.setOkAction(okAction) //
|
||||
.setCancelAction(Stage::close);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
.setTitleKey("recoveryKey.noDDirDetected.title") //
|
||||
.setMessageKey("recoveryKey.noDDirDetected.message") //
|
||||
.setDescriptionKey("recoveryKey.noDDirDetected.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("generic.button.change") //
|
||||
.setCancelButtonKey("generic.button.close") //
|
||||
.setOkAction(Stage::close) //
|
||||
.setCancelAction(Stage::close);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,16 +43,16 @@ public interface RecoveryKeyComponent {
|
||||
|
||||
default void showRecoveryKeyRecoverWindow(String title) {
|
||||
Stage stage = window();
|
||||
stage.setScene(recoverScene().get());
|
||||
stage.setTitle(title);
|
||||
stage.setScene(recoverScene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
default void showIsHubVaultDialogWindow(){
|
||||
default void showIsHubVaultDialogWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(recoverIsHubVaultScene().get());
|
||||
stage.setTitle("Recover Vault Config");
|
||||
stage.setTitle("Recover Config");
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.controls.NumericTextField;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyExpertSettingsController implements FxController {
|
||||
|
||||
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final Lazy<Scene> recoverScene;
|
||||
|
||||
public CheckBox expertSettingsCheckBox;
|
||||
public NumericTextField shorteningThresholdTextField;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
private final BooleanBinding validShorteningThreshold;
|
||||
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
|
||||
Lazy<Application> application, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
this.recoverScene = recoverScene;
|
||||
this.shorteningThreshold = shorteningThreshold;
|
||||
this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
|
||||
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
|
||||
shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> {
|
||||
try {
|
||||
int intValue = Integer.parseInt(newValue);
|
||||
shorteningThreshold.set(intValue);
|
||||
} catch (NumberFormatException e) {
|
||||
shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void toggleUseExpertSettings() {
|
||||
if (!expertSettingsCheckBox.isSelected()) {
|
||||
shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD));
|
||||
}
|
||||
}
|
||||
|
||||
public void openDocs() {
|
||||
application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
|
||||
}
|
||||
|
||||
public BooleanBinding validShorteningThresholdProperty() {
|
||||
return validShorteningThreshold;
|
||||
}
|
||||
|
||||
public boolean isValidShorteningThreshold() {
|
||||
var value = shorteningThreshold.get();
|
||||
return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(recoverScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -9,11 +8,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyIsHubVaultController implements FxController {
|
||||
@@ -25,12 +22,8 @@ public class RecoveryKeyIsHubVaultController implements FxController {
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyIsHubVaultController(@RecoveryKeyWindow Stage window,
|
||||
@RecoveryKeyWindow Vault vault,
|
||||
@RecoveryKeyWindow StringProperty recoveryKey,
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene,
|
||||
ResourceBundle resourceBundle) {
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene) {
|
||||
this.window = window;
|
||||
//window.setTitle("Is it a hub vault? Huh?");
|
||||
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
|
||||
}
|
||||
|
||||
@@ -45,6 +38,7 @@ public class RecoveryKeyIsHubVaultController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void recover() {
|
||||
window.setTitle("Recover Config");
|
||||
window.setScene(recoverykeyRecoverScene.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
@@ -19,6 +20,8 @@ import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
@@ -119,6 +122,13 @@ abstract class RecoveryKeyModule {
|
||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_IS_HUB_VAULT);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@@ -133,6 +143,19 @@ abstract class RecoveryKeyModule {
|
||||
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("shorteningThreshold")
|
||||
@RecoveryKeyScoped
|
||||
static IntegerProperty provideShorteningThreshold() {
|
||||
return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
|
||||
}
|
||||
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyExpertSettingsController.class)
|
||||
abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyIsHubVaultController.class)
|
||||
|
||||
@@ -4,8 +4,6 @@ import dagger.Lazy;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -16,10 +14,8 @@ import java.util.ResourceBundle;
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyRecoverController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final Lazy<Scene> nextScene;
|
||||
|
||||
@FXML
|
||||
RecoveryKeyValidateController recoveryKeyValidateController;
|
||||
@@ -27,10 +23,16 @@ public class RecoveryKeyRecoverController implements FxController {
|
||||
@Inject
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
if (window.getTitle().equals("Recover Config")) {
|
||||
this.nextScene = expertSettingsScene;
|
||||
} else if (window.getTitle().equals(resourceBundle.getString("recoveryKey.recover.title"))) {
|
||||
this.nextScene = resetPasswordScene;
|
||||
} else {
|
||||
this.nextScene = resetPasswordScene;
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -44,7 +46,7 @@ public class RecoveryKeyRecoverController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void recover() {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
window.setScene(nextScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
@@ -3,23 +3,21 @@ package org.cryptomator.ui.recoverykey;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.RecoverUtil;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
@@ -27,16 +25,10 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordController implements FxController {
|
||||
@@ -52,6 +44,8 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
private final Lazy<Scene> recoverResetVaultConfigSuccessScene;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final IntegerProperty shorteningThreshold;
|
||||
|
||||
public NewPasswordController newPasswordController;
|
||||
|
||||
@@ -64,7 +58,9 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, //
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS) Lazy<Scene> recoverResetVaultConfigSuccessScene, //
|
||||
FxApplicationWindows appWindows, //
|
||||
MasterkeyFileAccess masterkeyFileAccess) {
|
||||
MasterkeyFileAccess masterkeyFileAccess, //
|
||||
VaultListManager vaultListManager, //
|
||||
@Named("shorteningThreshold") IntegerProperty shorteningThreshold) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
@@ -74,6 +70,8 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
this.recoverResetVaultConfigSuccessScene = recoverResetVaultConfigSuccessScene;
|
||||
this.appWindows = appWindows;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.shorteningThreshold = shorteningThreshold;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -83,82 +81,38 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void resetPassword() {
|
||||
if(vault.isMissingVaultConfig()){
|
||||
Path vaultPath = vault.getPath();
|
||||
Path recoveryPath = vaultPath.resolve("r");
|
||||
if (vault.isMissingVaultConfig()) {
|
||||
try {
|
||||
Files.createDirectory(recoveryPath);
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Creating directory or recovering masterkey failed", e);
|
||||
}
|
||||
Path recoveryPath = RecoverUtil.createRecoveryDirectory(vault.getPath());
|
||||
RecoverUtil.createNewMasterkeyFile(recoveryKeyFactory, recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
|
||||
try {
|
||||
var combo = RecoverUtil.detectCipherCombo(masterkey.getEncoded(),vaultPath);
|
||||
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||
.withCipherCombo(combo) //
|
||||
.withKeyLoader(loader) //
|
||||
.withShorteningThreshold(220) //
|
||||
.build();
|
||||
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||
} catch (CryptoException | IOException e) {
|
||||
LOG.error("Recovering vault failed", e);
|
||||
}
|
||||
Files.move(masterkeyFilePath, vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME));
|
||||
try (var paths = Files.walk(recoveryPath)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||
}
|
||||
});
|
||||
try (Masterkey masterkey = RecoverUtil.loadMasterkey(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
|
||||
RecoverUtil.initializeCryptoFileSystem(recoveryPath, vault.getPath(), masterkey, shorteningThreshold);
|
||||
}
|
||||
|
||||
RecoverUtil.moveRecoveredFiles(recoveryPath, vault.getPath());
|
||||
RecoverUtil.deleteRecoveryDirectory(recoveryPath);
|
||||
RecoverUtil.addVaultToList(vaultListManager, vault.getPath());
|
||||
|
||||
window.setScene(recoverResetVaultConfigSuccessScene.get());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Moving recovered files failed", e);
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Task<Void> task = new ResetPasswordTask();
|
||||
task.setOnScheduled(event -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
if(vault.getState().equals(VaultState.Value.VAULT_CONFIG_MISSING)){
|
||||
window.setScene(recoverResetVaultConfigSuccessScene.get());
|
||||
}
|
||||
else {
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
}
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
} else {
|
||||
Task<Void> task = RecoverUtil.createResetPasswordTask( //
|
||||
recoveryKeyFactory, //
|
||||
vault, //
|
||||
recoveryKey, //
|
||||
newPasswordController, //
|
||||
window, //
|
||||
recoverResetPasswordSuccessScene, //
|
||||
recoverResetVaultConfigSuccessScene, //
|
||||
appWindows);
|
||||
executor.submit(task);
|
||||
}
|
||||
}
|
||||
|
||||
private class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
private ResetPasswordTask() {
|
||||
setOnFailed(event -> LOG.error("Failed to reset password", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() throws IOException, IllegalArgumentException {
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ReadOnlyBooleanProperty passwordSufficientAndMatchingProperty() {
|
||||
|
||||
@@ -3,21 +3,29 @@ package org.cryptomator.ui.recoverykey;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordSuccessController implements FxController {
|
||||
public class RecoveryKeyResetPasswordSuccessController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Stage owner;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) {
|
||||
public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window, //
|
||||
@Named("keyRecoveryOwner") Stage owner) {
|
||||
|
||||
this.window = window;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
if (!owner.getTitle().equals("Cryptomator")) {
|
||||
owner.close();
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@VaultOptionsScoped
|
||||
public class MasterkeyOptionsController implements FxController {
|
||||
@@ -27,16 +28,19 @@ public class MasterkeyOptionsController implements FxController {
|
||||
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
|
||||
private final KeychainManager keychain;
|
||||
private final ObservableValue<Boolean> passwordSaved;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
|
||||
@Inject
|
||||
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) {
|
||||
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain, //
|
||||
ResourceBundle resourceBundle) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.changePasswordWindow = changePasswordWindow;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
this.forgetPasswordWindow = forgetPasswordWindow;
|
||||
this.keychain = keychain;
|
||||
this.resourceBundle = resourceBundle;
|
||||
if (keychain.isSupported() && !keychain.isLocked()) {
|
||||
this.passwordSaved = keychain.getPassphraseStoredProperty(vault.getId()).orElse(false);
|
||||
} else {
|
||||
@@ -56,7 +60,7 @@ public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void showRecoverVaultDialog() {
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow(resourceBundle.getString("recoveryKey.recover.title"));
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
Reference in New Issue
Block a user