From acf11da202655cd36e9c11fff6fc25a13243929c Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Mon, 30 Jun 2025 09:26:30 +0200 Subject: [PATCH] cleanup removed MASTERKEY_MISSING new recoveryActionType RESTORE_ALL --- .../common/recovery/BackupRestorer.java | 20 +-- .../common/recovery/RecoveryActionType.java | 3 +- .../common/recovery/RecoveryDirectory.java | 8 +- .../org/cryptomator/common/vaults/Vault.java | 14 +-- .../common/vaults/VaultListManager.java | 28 ++--- .../cryptomator/common/vaults/VaultState.java | 4 +- .../ChooseExistingVaultController.java | 11 +- .../ui/convertvault/ConvertVaultModule.java | 2 +- .../ui/fxapp/FxApplicationModule.java | 7 +- .../ChooseMasterkeyFileController.java | 36 +++++- .../ui/mainwindow/VaultDetailController.java | 2 +- .../VaultDetailMissingVaultController.java | 26 +++- .../mainwindow/VaultListCellController.java | 2 +- .../VaultListContextMenuController.java | 4 +- .../ui/recoverykey/RecoveryKeyComponent.java | 3 +- .../RecoveryKeyCreationController.java | 106 +++++++++++++++- .../RecoveryKeyExpertSettingsController.java | 46 +++++-- .../ui/recoverykey/RecoveryKeyModule.java | 2 +- .../RecoveryKeyOnboardingController.java | 119 ++++++++++++++++-- .../RecoveryKeyRecoverController.java | 28 ++++- .../RecoveryKeyResetPasswordController.java | 100 ++++++++------- .../RecoveryKeyValidateController.java | 8 +- .../cryptomator/ui/unlock/UnlockModule.java | 3 +- .../MasterkeyOptionsController.java | 5 +- .../resources/fxml/recoverykey_create.fxml | 6 +- .../fxml/recoverykey_onboarding.fxml | 25 ++-- .../resources/fxml/recoverykey_recover.fxml | 4 +- .../fxml/recoverykey_reset_password.fxml | 5 +- .../fxml/unlock_select_masterkeyfile.fxml | 4 +- src/main/resources/fxml/vault_detail.fxml | 1 - .../fxml/vault_detail_missing_masterkey.fxml | 5 + src/main/resources/i18n/strings.properties | 6 + 32 files changed, 468 insertions(+), 175 deletions(-) diff --git a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java index 76cc5d6ca..488e5b6e1 100644 --- a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java +++ b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java @@ -7,21 +7,17 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.FileTime; import java.util.stream.Stream; -import org.cryptomator.common.vaults.VaultState.Value; +import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX; public final class BackupRestorer { private BackupRestorer() {} - public static boolean restoreIfPresent(Path vaultPath, Value vaultState) { - Path targetFile = switch (vaultState) { - case VAULT_CONFIG_MISSING -> vaultPath.resolve("vault.cryptomator"); - case MASTERKEY_MISSING -> vaultPath.resolve("masterkey.cryptomator"); - default -> throw new IllegalArgumentException("Unexpected vault state: " + vaultState); - }; + public static boolean restoreIfPresent(Path vaultPath, String fileName) { + Path targetFile = vaultPath.resolve(fileName); try (Stream files = Files.list(vaultPath)) { - return files.filter(file -> isValidBackupFileForState(file.getFileName().toString(), vaultState)) + return files.filter(file -> isValidBackupFileForState(file.getFileName().toString(), fileName)) .max((f1, f2) -> { try { FileTime time1 = Files.getLastModifiedTime(f1); @@ -38,12 +34,8 @@ public final class BackupRestorer { } } - private static boolean isValidBackupFileForState(String fileName, Value vaultState) { - return switch (vaultState) { - case VAULT_CONFIG_MISSING -> fileName.startsWith("vault.cryptomator") && fileName.endsWith(".bkup"); - case MASTERKEY_MISSING -> fileName.startsWith("masterkey.cryptomator") && fileName.endsWith(".bkup"); - default -> false; - }; + private static boolean isValidBackupFileForState(String fileName, String vaultState) { + return fileName.startsWith(vaultState) && fileName.endsWith(MASTERKEY_BACKUP_SUFFIX); } private static boolean copyBackupFile(Path backupFile, Path configPath) { diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java index 32f28d613..a05073dad 100644 --- a/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java @@ -1,8 +1,9 @@ package org.cryptomator.common.recovery; public enum RecoveryActionType { - RESTORE_VAULT_CONFIG, + RESTORE_ALL, RESTORE_MASTERKEY, + RESTORE_VAULT_CONFIG, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java index 63dd61ac6..1fa52cf61 100644 --- a/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java @@ -11,9 +11,6 @@ import java.util.Comparator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; -import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; - public final class RecoveryDirectory implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class); @@ -34,9 +31,8 @@ public final class RecoveryDirectory implements AutoCloseable { return new RecoveryDirectory(vaultPath, tempDir); } - public void moveRecoveredFiles() 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), StandardCopyOption.REPLACE_EXISTING); + public void moveRecoveredFile(String file) throws IOException { + Files.move(recoveryPath.resolve(file), vaultPath.resolve(file), StandardCopyOption.REPLACE_EXISTING); } private void deleteRecoveryDirectory() { diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index 5c315283b..2fa613e3e 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -23,7 +23,6 @@ import org.cryptomator.cryptofs.event.FilesystemEvent; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.cryptomator.event.VaultEvent; import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.mount.UnmountFailedException; @@ -35,7 +34,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; -import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -75,7 +73,6 @@ public class Vault { private final BooleanBinding missing; private final BooleanBinding needsMigration; private final BooleanBinding unknownError; - private final BooleanBinding missingMasterkey; private final BooleanBinding missingVaultConfig; private final ObjectBinding mountPoint; private final Mounter mounter; @@ -105,7 +102,6 @@ public class Vault { this.processing = Bindings.createBooleanBinding(this::isProcessing, state); this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state); this.missing = Bindings.createBooleanBinding(this::isMissing, state); - this.missingMasterkey = Bindings.createBooleanBinding(this::isMissingMasterkey, state); this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state); this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state); this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state); @@ -340,20 +336,12 @@ public class Vault { return state.get() == VaultState.Value.ERROR; } - public BooleanBinding missingMasterkeyProperty() { - return missingMasterkey; - } - - public boolean isMissingMasterkey() { - return state.get() == VaultState.Value.MASTERKEY_MISSING; - } - public BooleanBinding missingVaultConfigProperty() { return missingVaultConfig; } public boolean isMissingVaultConfig() { - return state.get() == VaultState.Value.VAULT_CONFIG_MISSING; + return state.get() == VaultState.Value.VAULT_CONFIG_MISSING || state.get() == VaultState.Value.ALL_MISSING; } public ReadOnlyStringProperty displayNameProperty() { diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 2e24bd404..c7e03ae70 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -25,10 +25,11 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; import static org.cryptomator.common.vaults.VaultState.Value.ERROR; import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; -import static org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING; import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION; import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING; import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED; +import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Constants; @@ -39,7 +40,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.DirStructure; import org.cryptomator.cryptofs.migration.Migrators; import org.cryptomator.integrations.mount.MountService; -import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -146,7 +146,7 @@ public class VaultListManager { vaultSettings.lastKnownKeyLoader.set(Constants.DEFAULT_KEY_ID.toString()); } - if (vaultState != VaultState.Value.VAULT_CONFIG_MISSING) { + if (vaultState != VAULT_CONFIG_MISSING) { initializeLastKnownKeyLoaderIfPossible(vaultSettings, wrapper); } @@ -179,10 +179,6 @@ public class VaultListManager { try { VaultState.Value determined = determineVaultState(vault.getPath(), vault.getVaultSettings()); - if (determined == MASTERKEY_MISSING && KeyLoadingStrategy.isHubVault(vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme())) { - determined = LOCKED; - } - if (determined == LOCKED) { vault.getVaultConfigCache().reloadConfig(); } @@ -205,24 +201,18 @@ public class VaultListManager { return VaultState.Value.MISSING; } - boolean vaultConfigRestored = Files.notExists(pathToVaultConfig) - && BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VaultState.Value.VAULT_CONFIG_MISSING); + BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VAULTCONFIG_FILENAME); - boolean masterkeyRestored = Files.notExists(pathToMasterkey) - && KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get()) - && BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), VaultState.Value.MASTERKEY_MISSING); + BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), MASTERKEY_FILENAME); - if (vaultConfigRestored || masterkeyRestored) { - return LOCKED; + if (!Files.exists(pathToVaultConfig) && !Files.exists(pathToMasterkey)) { + return ALL_MISSING; } - if (Files.notExists(pathToVaultConfig)) { - return VaultState.Value.VAULT_CONFIG_MISSING; + if (!Files.exists(pathToVaultConfig)) { + return VAULT_CONFIG_MISSING; } - if (Files.notExists(pathToMasterkey) && KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get())) { - return VaultState.Value.MASTERKEY_MISSING; - } return checkDirStructure(pathToVault); } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultState.java b/src/main/java/org/cryptomator/common/vaults/VaultState.java index 0d65d5c2e..f8b9b412a 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultState.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultState.java @@ -31,9 +31,9 @@ public class VaultState extends ObservableValueBase implements VAULT_CONFIG_MISSING, /** - * No masterkey found at the provided path + * No vault config and masterkey found at the provided path */ - MASTERKEY_MISSING, + ALL_MISSING, /** * Vault requires migration to a newer vault format diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index f39fd56a2..490b08303 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -18,13 +18,10 @@ 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; import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; -import static org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING; -import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; @@ -36,7 +33,6 @@ import org.cryptomator.common.vaults.VaultConfigCache; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.integrations.mount.MountService; -import org.cryptomator.integrations.mount.Mountpoint; import org.cryptomator.integrations.uiappearance.Theme; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; @@ -151,10 +147,11 @@ public class ChooseExistingVaultController implements FxController { } Vault preparedVault = prepareVault(selectedDirectory, vaultComponentFactory, mountServices); + VaultListManager.redetermineVaultState(preparedVault); VaultState.Value state = preparedVault.getState(); switch (state) { - case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, window, RecoveryActionType.RESTORE_VAULT_CONFIG).showOnboardingDialogWindow(); - case MASTERKEY_MISSING -> recoveryKeyWindow.create(preparedVault, window, RecoveryActionType.RESTORE_MASTERKEY).showOnboardingDialogWindow(); + case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow(); + case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow(); default -> { vaultListManager.addVault(preparedVault); vault.set(preparedVault); @@ -174,7 +171,7 @@ public class ChooseExistingVaultController implements FxController { } var wrapper = new VaultConfigCache(vaultSettings); - Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, VAULT_CONFIG_MISSING, null).vault(); + Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, LOCKED, null).vault(); try { VaultListManager.determineVaultState(vault.getPath(), vaultSettings); } catch (IOException e) { diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java index 438ae369a..a5fc761aa 100644 --- a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java @@ -122,7 +122,7 @@ abstract class ConvertVaultModule { @IntoMap @FxControllerKey(RecoveryKeyValidateController.class) static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) { - return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, RecoveryActionType.CONVERT_VAULT, null, null); + return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), null, null); } } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 8eb221883..74abac546 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -15,15 +15,13 @@ import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; import org.cryptomator.ui.quit.QuitComponent; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.sharevault.ShareVaultComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.unlock.UnlockComponent; import org.cryptomator.ui.updatereminder.UpdateReminderComponent; import org.cryptomator.ui.vaultoptions.VaultOptionsComponent; -import javax.inject.Named; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.image.Image; import java.io.IOException; import java.io.InputStream; @@ -40,7 +38,8 @@ import java.io.InputStream; HealthCheckComponent.class, // UpdateReminderComponent.class, // ShareVaultComponent.class, // - EventViewComponent.class}) + EventViewComponent.class, // + RecoveryKeyComponent.class}) abstract class FxApplicationModule { private static Image createImageFromResource(String resourceName) throws IOException { diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java index 9b2231921..722c780b4 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java @@ -1,14 +1,18 @@ package org.cryptomator.ui.keyloading.masterkeyfile; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.keyloading.KeyLoading; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.beans.binding.StringBinding; +import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.stage.WindowEvent; @@ -27,17 +31,39 @@ public class ChooseMasterkeyFileController implements FxController { private final Stage window; private final Vault vault; private final CompletableFuture result; + private final RecoveryKeyComponent.Factory recoveryKeyWindow; private final ResourceBundle resourceBundle; + @FXML private CheckBox restoreInsteadCheckBox; + @FXML private Button chooseButton; + @Inject - public ChooseMasterkeyFileController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, ResourceBundle resourceBundle) { + public ChooseMasterkeyFileController(@KeyLoading Stage window, // + @KeyLoading Vault vault, // + CompletableFuture result, // + RecoveryKeyComponent.Factory recoveryKeyWindow, // + ResourceBundle resourceBundle) { this.window = window; this.vault = vault; this.result = result; + this.recoveryKeyWindow = recoveryKeyWindow; this.resourceBundle = resourceBundle; this.window.setOnHiding(this::windowClosed); } + @FXML + private void initialize() { + restoreInsteadCheckBox.selectedProperty().addListener((_, _, newVal) -> { + if (newVal) { + chooseButton.setText(resourceBundle.getString("addvaultwizard.existing.restore")); + chooseButton.setOnAction(e -> restoreMasterkey()); + } else { + chooseButton.setText(resourceBundle.getString("generic.button.choose")); + chooseButton.setOnAction(e -> proceed()); + } + }); + } + @FXML public void cancel() { window.close(); @@ -47,6 +73,12 @@ public class ChooseMasterkeyFileController implements FxController { result.cancel(true); } + @FXML + void restoreMasterkey() { + window.close(); + recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showOnboardingDialogWindow(); + } + @FXML public void proceed() { LOG.trace("proceed()"); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index 7838ba3cb..be4f7f78c 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -52,7 +52,7 @@ public class VaultDetailController implements FxController { case LOCKED -> FontAwesome5Icon.LOCK; case PROCESSING -> FontAwesome5Icon.SPINNER; case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN; - case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; + case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; }; } else { return FontAwesome5Icon.EXCLAMATION_TRIANGLE; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index e23c4d95d..dff3c0a4e 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -3,8 +3,10 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; +import org.cryptomator.common.vaults.VaultState; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.dialogs.Dialogs; +import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; @@ -16,13 +18,17 @@ import javafx.fxml.FXML; import javafx.stage.FileChooser; import javafx.stage.Stage; import java.io.File; +import java.nio.file.Files; import java.util.ResourceBundle; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; @MainWindowScoped public class VaultDetailMissingVaultController implements FxController { + private final FxApplicationWindows appWindows; + private final Stage mainWindow; private final ObjectProperty vault; private final ObservableList vaults; private final ResourceBundle resourceBundle; @@ -31,12 +37,17 @@ public class VaultDetailMissingVaultController implements FxController { private final Dialogs dialogs; @Inject - public VaultDetailMissingVaultController(ObjectProperty vault, // + public VaultDetailMissingVaultController(FxApplicationWindows appWindows, // + @MainWindow Stage mainWindow, // + ObjectProperty vault, // ObservableList vaults, // ResourceBundle resourceBundle, // @MainWindow Stage window, // Dialogs dialogs, // RecoveryKeyComponent.Factory recoveryKeyWindow) { + + this.appWindows = appWindows; + this.mainWindow = mainWindow; this.vault = vault; this.vaults = vaults; this.resourceBundle = resourceBundle; @@ -60,14 +71,23 @@ public class VaultDetailMissingVaultController implements FxController { if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){ dialogs.prepareContactHubAdmin(window).build().showAndWait(); } + else if(Files.exists(vault.get().getPath().resolve(MASTERKEY_FILENAME))){ + recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow(); + } else { - recoveryKeyWindow.create(vault.get(), window, RecoveryActionType.RESTORE_VAULT_CONFIG).showOnboardingDialogWindow(); + recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow(); } } @FXML void restoreMasterkey() { - recoveryKeyWindow.create(vault.get(), window, RecoveryActionType.RESTORE_MASTERKEY).showRecoveryKeyRecoverWindow(); + recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showRecoveryKeyRecoverWindow(); + } + + @FXML + public void unlock() { + vault.get().stateProperty().set(VaultState.Value.LOCKED); + appWindows.startUnlockWorkflow(vault.get(), mainWindow); } @FXML diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java index 3697906c0..9324c8c7b 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java @@ -55,7 +55,7 @@ public class VaultListCellController implements FxController { case LOCKED -> FontAwesome5Icon.LOCK; case PROCESSING -> FontAwesome5Icon.SPINNER; case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN; - case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; + case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; }; } else { return FontAwesome5Icon.EXCLAMATION_TRIANGLE; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index 5a26967fa..e4b942503 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -22,7 +22,7 @@ import java.util.Objects; import static org.cryptomator.common.vaults.VaultState.Value.ERROR; import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; -import static org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING; import static org.cryptomator.common.vaults.VaultState.Value.MISSING; import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION; import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED; @@ -65,7 +65,7 @@ public class VaultListContextMenuController implements FxController { this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null); this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false); - this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, MASTERKEY_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false); + this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false); this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false); this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index ddd9dffdd..6bfe36a4f 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -9,6 +9,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import javax.inject.Named; +import javafx.beans.property.ObjectProperty; import javafx.scene.Scene; import javafx.stage.Stage; @@ -54,7 +55,7 @@ public interface RecoveryKeyComponent { RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, // @BindsInstance @Named("keyRecoveryOwner") Stage owner, // - @BindsInstance @Named("recoverType") RecoveryActionType recoverType); + @BindsInstance @Named("recoverType") ObjectProperty recoverType); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java index 77f191015..0b1c69286 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java @@ -1,28 +1,45 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; +import org.cryptomator.common.recovery.CryptoFsInitializer; +import org.cryptomator.common.recovery.MasterkeyService; +import org.cryptomator.common.recovery.RecoveryActionType; +import org.cryptomator.common.recovery.RecoveryDirectory; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.controls.FormattedLabel; import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.cryptomator.ui.dialogs.Dialogs; 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.ObjectProperty; import javafx.beans.property.StringProperty; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.Scene; +import javafx.scene.control.Button; import javafx.stage.Stage; import java.io.IOException; +import java.nio.file.Path; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; + @RecoveryKeyScoped public class RecoveryKeyCreationController implements FxController { @@ -30,23 +47,74 @@ public class RecoveryKeyCreationController implements FxController { private final Stage window; private final Lazy successScene; + private final Lazy recoverykeyExpertSettingsScene; + private final MasterkeyFileAccess masterkeyFileAccess; private final Vault vault; private final ExecutorService executor; private final RecoveryKeyFactory recoveryKeyFactory; private final StringProperty recoveryKeyProperty; private final FxApplicationWindows appWindows; public NiceSecurePasswordField passwordField; + private final IntegerProperty shorteningThreshold; + private final ObjectProperty recoverType; + private final ResourceBundle resourceBundle; + public FormattedLabel descriptionLabel; + public Button cancelButton; + public Button nextButton; + private final VaultListManager vaultListManager; + private final Dialogs dialogs; + private final Stage owner; @Inject - public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows, ResourceBundle resourceBundle) { + public RecoveryKeyCreationController(FxApplicationWindows appWindows, // + @RecoveryKeyWindow Stage window, // + @Named("keyRecoveryOwner") Stage owner, // + @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy successScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, // + @RecoveryKeyWindow Vault vault, // + RecoveryKeyFactory recoveryKeyFactory, // + MasterkeyFileAccess masterkeyFileAccess, // + ExecutorService executor, // + @RecoveryKeyWindow StringProperty recoveryKey, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // + @Named("recoverType") ObjectProperty recoverType, // + VaultListManager vaultListManager, // + ResourceBundle resourceBundle, // + Dialogs dialogs) { this.window = window; - window.setTitle(resourceBundle.getString("recoveryKey.display.title")); this.successScene = successScene; + this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene; this.vault = vault; this.executor = executor; this.recoveryKeyFactory = recoveryKeyFactory; this.recoveryKeyProperty = recoveryKey; this.appWindows = appWindows; + this.recoverType = recoverType; + this.resourceBundle = resourceBundle; + this.masterkeyFileAccess = masterkeyFileAccess; + this.shorteningThreshold = shorteningThreshold; + this.vaultListManager = vaultListManager; + this.owner = owner; + this.dialogs = dialogs; + } + + @FXML + public void initialize() { + if (recoverType.get().equals(RecoveryActionType.SHOW_KEY)) { + window.setTitle(resourceBundle.getString("recoveryKey.display.title")); + } else if (recoverType.get().equals(RecoveryActionType.RESTORE_VAULT_CONFIG)) { + window.setTitle(resourceBundle.getString("recoveryKey.recoverVaultConfig.title")); + descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description")); + cancelButton.setOnAction((_) -> back()); + cancelButton.setText(resourceBundle.getString("generic.button.back")); + nextButton.setOnAction((_) -> restoreWithPassword()); + } + } + + @FXML + public void back() { + window.setScene(recoverykeyExpertSettingsScene.get()); + window.centerOnScreen(); } @FXML @@ -71,6 +139,40 @@ public class RecoveryKeyCreationController implements FxController { executor.submit(task); } + @FXML + public void restoreWithPassword() { + + try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) { + Path recoveryPath = recoveryDirectory.getRecoveryPath(); + + Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME); + + try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) { + var combo = MasterkeyService.detect(masterkey.getEncoded(), vault.getPath()); + CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo.get()); + } + + recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME); + + if (!vaultListManager.containsVault(vault.getPath())) { + vaultListManager.add(vault.getPath()); + } + window.close(); + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) // + .setTitleKey("recoveryKey.recoverVaultConfig.title") // + .setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") // + .build().showAndWait(); + + } catch (InvalidPassphraseException e) { + LOG.info("Password invalid", e); + Animations.createShakeWindowAnimation(window).play(); + } catch (IOException | CryptoException e) { + LOG.error("Recovery process failed", e); + appWindows.showErrorWindow(e, window, null); + } + } + + @FXML public void close() { window.close(); diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java index ce9ad4a26..1555eac68 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java @@ -6,17 +6,23 @@ import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.scene.control.CheckBox; import javafx.stage.Stage; import dagger.Lazy; +import org.cryptomator.common.recovery.RecoveryActionType; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; @RecoveryKeyScoped public class RecoveryKeyExpertSettingsController implements FxController { @@ -27,28 +33,40 @@ public class RecoveryKeyExpertSettingsController implements FxController { private final Stage window; private final Lazy application; - private final Lazy resetPasswordScene; - private final Lazy recoverScene; - - public CheckBox expertSettingsCheckBox; - public NumericTextField shorteningThresholdTextField; + private final Vault vault; + private final ObjectProperty recoverType; private final IntegerProperty shorteningThreshold; + private final Lazy resetPasswordScene; + private final Lazy createScene; + private final Lazy onBoardingScene; + private final Lazy recoverScene; private final BooleanBinding validShorteningThreshold; + @FXML public CheckBox expertSettingsCheckBox; + @FXML public NumericTextField shorteningThresholdTextField; + + private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyExpertSettingsController.class); @Inject public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, // Lazy application, // + @RecoveryKeyWindow Vault vault, // + @Named("recoverType") ObjectProperty recoverType, // @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy createScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, // @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverScene) { this.window = window; this.application = application; - this.resetPasswordScene = resetPasswordScene; - this.recoverScene = recoverScene; + this.vault = vault; + this.recoverType = recoverType; this.shorteningThreshold = shorteningThreshold; + this.resetPasswordScene = resetPasswordScene; + this.createScene = createScene; + this.onBoardingScene = onBoardingScene; + this.recoverScene = recoverScene; this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold); - } @FXML @@ -87,11 +105,19 @@ public class RecoveryKeyExpertSettingsController implements FxController { @FXML public void back() { - window.setScene(recoverScene.get()); + if(recoverType.get().equals(RecoveryActionType.RESTORE_ALL) && vault.getState().equals(VaultState.Value.VAULT_CONFIG_MISSING)) + window.setScene(recoverScene.get()); + else if(recoverType.get().equals(RecoveryActionType.RESTORE_ALL) && vault.getState().equals(VaultState.Value.ALL_MISSING)) + window.setScene(recoverScene.get()); + else if(recoverType.get().equals(RecoveryActionType.RESTORE_VAULT_CONFIG)) + window.setScene(onBoardingScene.get()); } @FXML public void next() { - window.setScene(resetPasswordScene.get()); + if(recoverType.get().equals(RecoveryActionType.RESTORE_VAULT_CONFIG)) + window.setScene(createScene.get()); + else + window.setScene(resetPasswordScene.get()); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index e2ffb8428..17f3bc6ba 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -176,7 +176,7 @@ abstract class RecoveryKeyModule { @Provides @IntoMap @FxControllerKey(RecoveryKeyValidateController.class) - static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") RecoveryActionType recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) { + static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) { return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, recoverType, cipherCombo, masterkeyFileAccess); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java index 460ec7436..da46a6741 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java @@ -1,38 +1,119 @@ package org.cryptomator.ui.recoverykey; -import javax.inject.Inject; -import javax.inject.Named; -import javafx.beans.property.ObjectProperty; -import javafx.fxml.FXML; -import javafx.scene.Scene; -import javafx.stage.Stage; -import java.util.ResourceBundle; - import dagger.Lazy; import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import javax.inject.Inject; +import javax.inject.Named; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import java.util.ResourceBundle; + +import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_ALL; +import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_VAULT_CONFIG; + @RecoveryKeyScoped public class RecoveryKeyOnboardingController implements FxController { private final Stage window; private final Lazy recoverykeyRecoverScene; - private RecoveryActionType recoverType; + private final Lazy recoverykeyExpertSettingsScene; + private ObjectProperty recoverType; + + public Label titleLabel; + public Label messageLabel; + public Label secondTextDesc; + public Label thirdTextIndex; + public Label thirdTextDesc; + + @FXML + private CheckBox affirmationBox; + @FXML + private RadioButton recoveryKeyRadio; + @FXML + private RadioButton passwordRadio; + @FXML + private Button nextButton; + @FXML + private VBox chooseMethodeBox; + private final ToggleGroup methodToggleGroup = new ToggleGroup(); + @Inject public RecoveryKeyOnboardingController(@RecoveryKeyWindow Stage window, // @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, // - @Named("recoverType") RecoveryActionType recoverType, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, // + @Named("recoverType") ObjectProperty recoverType, // ResourceBundle resourceBundle) { this.window = window; window.setTitle(resourceBundle.getString("recoveryKey.recoverVaultConfig.title")); this.recoverykeyRecoverScene = recoverykeyRecoverScene; + this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene; this.recoverType = recoverType; } + @FXML + public void initialize() { + + recoveryKeyRadio.setToggleGroup(methodToggleGroup); + passwordRadio.setToggleGroup(methodToggleGroup); + + boolean showMethodSelection = (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG); + chooseMethodeBox.setVisible(showMethodSelection); + chooseMethodeBox.setManaged(showMethodSelection); + + nextButton.disableProperty().bind( // + affirmationBox.selectedProperty().not() // + .or(methodToggleGroup.selectedToggleProperty().isNull() // + .and(showMethodSelectionProperty()))); + + switch (recoverType.get()) { + case RESTORE_VAULT_CONFIG -> { + window.setTitle("Recover Vault Config"); + messageLabel.setText("Read this:"); + secondTextDesc.setText("You will need the vault password or recovery key, a new password and possible some expert settings."); + thirdTextIndex.setVisible(false); + thirdTextIndex.setManaged(false); + thirdTextDesc.setVisible(false); + thirdTextDesc.setManaged(false); + } + case RESTORE_MASTERKEY -> { + window.setTitle("Recover Masterkey"); + messageLabel.setText("Read this:"); + titleLabel.setText("Recover Masterkey"); + secondTextDesc.setText("You will need the vault recovery key."); + thirdTextIndex.setVisible(false); + thirdTextIndex.setManaged(false); + thirdTextDesc.setVisible(false); + thirdTextDesc.setManaged(false); + } + default -> { + thirdTextIndex.setVisible(true); + thirdTextIndex.setManaged(true); + thirdTextDesc.setVisible(true); + thirdTextDesc.setManaged(true); + } + } + } + + private BooleanProperty showMethodSelectionProperty() { + return new SimpleBooleanProperty(recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG); + } + @FXML public void close() { window.close(); @@ -40,7 +121,21 @@ public class RecoveryKeyOnboardingController implements FxController { @FXML public void next() { - recoverType = RecoveryActionType.RESTORE_VAULT_CONFIG; - window.setScene(recoverykeyRecoverScene.get()); + switch (recoverType.get()) { + case RESTORE_VAULT_CONFIG, RESTORE_ALL -> { + Object selectedToggle = methodToggleGroup.getSelectedToggle(); + if (selectedToggle == recoveryKeyRadio) { + recoverType.set(RESTORE_ALL); + window.setScene(recoverykeyRecoverScene.get()); + } else if (selectedToggle == passwordRadio) { + recoverType.set(RESTORE_VAULT_CONFIG); + window.setScene(recoverykeyExpertSettingsScene.get()); + } else { + window.setScene(recoverykeyRecoverScene.get()); + window.centerOnScreen(); + } + } + case RESTORE_MASTERKEY -> window.setScene(recoverykeyRecoverScene.get()); + } } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 289740bf5..1045e026a 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -11,6 +11,7 @@ import javax.inject.Named; import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.scene.Scene; +import javafx.scene.control.Button; import javafx.stage.Stage; import java.util.ResourceBundle; @@ -19,6 +20,12 @@ public class RecoveryKeyRecoverController implements FxController { private final Stage window; private final Lazy nextScene; + private final Lazy onBoardingScene; + private final ResourceBundle resourceBundle; + public ObjectProperty recoverType; + + @FXML + private Button cancelButton; @FXML RecoveryKeyValidateController recoveryKeyValidateController; @@ -27,11 +34,15 @@ public class RecoveryKeyRecoverController implements FxController { public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, // @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy expertSettingsScene, // - ResourceBundle resourceBundle, @Named("recoverType") RecoveryActionType recoverType) { + @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, // + ResourceBundle resourceBundle, // + @Named("recoverType") ObjectProperty recoverType) { this.window = window; - - this.nextScene = switch (recoverType) { - case RESTORE_VAULT_CONFIG -> { + this.recoverType = recoverType; + this.onBoardingScene = onBoardingScene; + this.resourceBundle = resourceBundle; + this.nextScene = switch (recoverType.get()) { + case RESTORE_ALL, RESTORE_VAULT_CONFIG -> { window.setTitle(resourceBundle.getString("recoveryKey.recoverVaultConfig.title")); yield expertSettingsScene; } @@ -56,11 +67,18 @@ public class RecoveryKeyRecoverController implements FxController { @FXML public void initialize() { + switch (recoverType.get()) { + case RESTORE_ALL, RESTORE_VAULT_CONFIG -> cancelButton.setText(resourceBundle.getString("generic.button.back")); + case RESET_PASSWORD -> cancelButton.setText(resourceBundle.getString("generic.button.cancel")); + } } @FXML public void close() { - window.close(); + switch (recoverType.get()) { + case RESTORE_ALL, RESTORE_VAULT_CONFIG -> window.setScene(onBoardingScene.get()); + case RESET_PASSWORD -> window.close(); + } } @FXML diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 23badcfcf..2733e4a7a 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -1,24 +1,5 @@ package org.cryptomator.ui.recoverykey; -import javax.inject.Inject; -import javax.inject.Named; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.ReadOnlyBooleanWrapper; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.concurrent.Task; -import javafx.fxml.FXML; -import javafx.scene.Scene; -import javafx.stage.Stage; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ResourceBundle; -import java.util.concurrent.ExecutorService; - -import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; - import dagger.Lazy; import org.cryptomator.common.recovery.CryptoFsInitializer; import org.cryptomator.common.recovery.MasterkeyService; @@ -39,6 +20,27 @@ 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.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.stage.Stage; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ResourceBundle; +import java.util.concurrent.ExecutorService; + +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; + @RecoveryKeyScoped public class RecoveryKeyResetPasswordController implements FxController { @@ -50,11 +52,12 @@ public class RecoveryKeyResetPasswordController implements FxController { private final ExecutorService executor; private final StringProperty recoveryKey; private final Lazy recoverExpertSettingsScene; + private final Lazy recoverykeyRecoverScene; private final FxApplicationWindows appWindows; private final MasterkeyFileAccess masterkeyFileAccess; private final VaultListManager vaultListManager; private final IntegerProperty shorteningThreshold; - private final RecoveryActionType recoverType; + private final ObjectProperty recoverType; private final ObjectProperty cipherCombo; private final ResourceBundle resourceBundle; private final StringProperty buttonText = new SimpleStringProperty(); @@ -62,6 +65,8 @@ public class RecoveryKeyResetPasswordController implements FxController { private final Stage owner; public NewPasswordController newPasswordController; + public Button backButton; + public Button nextButton; @Inject public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, // @@ -70,11 +75,12 @@ public class RecoveryKeyResetPasswordController implements FxController { ExecutorService executor, // @Named("keyRecoveryOwner") Stage owner, @RecoveryKeyWindow StringProperty recoveryKey, // @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverExpertSettingsScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, // FxApplicationWindows appWindows, // MasterkeyFileAccess masterkeyFileAccess, // VaultListManager vaultListManager, // @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // - @Named("recoverType") RecoveryActionType recoverType, // + @Named("recoverType") ObjectProperty recoverType, // @Named("cipherCombo") ObjectProperty cipherCombo,// ResourceBundle resourceBundle, Dialogs dialogs) { this.window = window; @@ -83,6 +89,7 @@ public class RecoveryKeyResetPasswordController implements FxController { this.executor = executor; this.recoveryKey = recoveryKey; this.recoverExpertSettingsScene = recoverExpertSettingsScene; + this.recoverykeyRecoverScene = recoverykeyRecoverScene; this.appWindows = appWindows; this.masterkeyFileAccess = masterkeyFileAccess; this.vaultListManager = vaultListManager; @@ -92,25 +99,35 @@ public class RecoveryKeyResetPasswordController implements FxController { this.resourceBundle = resourceBundle; this.dialogs = dialogs; this.owner = owner; - initButtonText(recoverType); } - private void initButtonText(RecoveryActionType type) { - if (type == RecoveryActionType.RESTORE_MASTERKEY) { - buttonText.set(resourceBundle.getString("generic.button.close")); - } else { - buttonText.set(resourceBundle.getString("generic.button.back")); + @FXML + public void initialize() { + switch (recoverType.get()) { + case RESTORE_MASTERKEY -> { + nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); + nextButton.setOnAction((_) -> resetPassword()); + } + case RESTORE_ALL -> { + nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); + nextButton.setOnAction((_) -> restorePassword()); + } + case RESET_PASSWORD -> { + nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn")); + nextButton.setOnAction((_) -> resetPassword()); + } } } @FXML public void close() { - if (recoverType.equals(RecoveryActionType.RESTORE_MASTERKEY)) { - window.close(); - } else { - window.setScene(recoverExpertSettingsScene.get()); + switch (recoverType.get()) { + case RESTORE_ALL -> window.setScene(recoverExpertSettingsScene.get()); + case RESTORE_MASTERKEY, RESET_PASSWORD -> window.setScene(recoverykeyRecoverScene.get()); + default -> window.close(); } } + @FXML public void restorePassword() { try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) { @@ -123,16 +140,14 @@ public class RecoveryKeyResetPasswordController implements FxController { CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get()); } - recoveryDirectory.moveRecoveredFiles(); + recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME); + recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME); if (!vaultListManager.containsVault(vault.getPath())) { vaultListManager.add(vault.getPath()); } window.close(); - dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) - .setTitleKey("recoveryKey.recoverVaultConfig.title") - .setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") - .build().showAndWait(); + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).setTitleKey("recoveryKey.recoverVaultConfig.title").setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message").build().showAndWait(); } catch (IOException | CryptoException e) { LOG.error("Recovery process failed", e); @@ -148,14 +163,12 @@ public class RecoveryKeyResetPasswordController implements FxController { task.setOnSucceeded(_ -> { LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath()); - if (vault.getState().equals(org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING)) { - dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) - .setTitleKey("recoveryKey.recoverMasterkey.title") - .setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message") - .build().showAndWait(); + if (recoverType.get().equals(RecoveryActionType.RESET_PASSWORD)) { + window.close(); + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).build().showAndWait(); } else { - dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) - .build().showAndWait(); + window.close(); + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).setTitleKey("recoveryKey.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait(); } window.close(); }); @@ -185,6 +198,7 @@ public class RecoveryKeyResetPasswordController implements FxController { public boolean isPasswordSufficientAndMatching() { return newPasswordController.isGoodPassword(); } + private final ReadOnlyBooleanWrapper vaultConfigMissing = new ReadOnlyBooleanWrapper(); public ReadOnlyBooleanProperty vaultConfigMissingProperty() { diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java index 9a6e3d8b0..163d734ed 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java @@ -44,7 +44,7 @@ public class RecoveryKeyValidateController implements FxController { private final ObjectProperty recoveryKeyState; private final ObjectProperty cipherCombo; private final AutoCompleter autoCompleter; - private final RecoveryActionType recoverType; + private final ObjectProperty recoverType; private final MasterkeyFileAccess masterkeyFileAccess; private volatile boolean isWrongKey; @@ -55,7 +55,7 @@ public class RecoveryKeyValidateController implements FxController { @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, // StringProperty recoveryKey, // RecoveryKeyFactory recoveryKeyFactory, // - @Named("recoverType") RecoveryActionType recoverType, // + @Named("recoverType") ObjectProperty recoverType, // @Named("cipherCombo") ObjectProperty cipherCombo,// MasterkeyFileAccess masterkeyFileAccess) { this.vault = vault; @@ -135,8 +135,8 @@ public class RecoveryKeyValidateController implements FxController { } private void validateRecoveryKey() { - switch (recoverType) { - case RESTORE_VAULT_CONFIG -> { + switch (recoverType.get()) { + case RESTORE_ALL, RESTORE_VAULT_CONFIG -> { try{ var combo = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess); combo.ifPresent(cipherCombo::set); diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index 95f13d383..1c8d758fc 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.jetbrains.annotations.Nullable; import javax.inject.Named; @@ -27,7 +28,7 @@ import javafx.stage.Stage; import java.util.Map; import java.util.ResourceBundle; -@Module(subcomponents = {KeyLoadingComponent.class}) +@Module(subcomponents = {KeyLoadingComponent.class, RecoveryKeyComponent.class}) abstract class UnlockModule { @Provides diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java index ffc0ad8f8..67ae2f42d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java @@ -9,7 +9,6 @@ import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import javax.inject.Inject; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; @@ -50,12 +49,12 @@ public class MasterkeyOptionsController implements FxController { @FXML public void showRecoveryKey() { - recoveryKeyWindow.create(vault, window, RecoveryActionType.SHOW_KEY).showRecoveryKeyCreationWindow(); + recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY)).showRecoveryKeyCreationWindow(); } @FXML public void showRecoverVaultDialog() { - recoveryKeyWindow.create(vault, window, RecoveryActionType.RESET_PASSWORD).showRecoveryKeyRecoverWindow(); + recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD)).showRecoveryKeyRecoverWindow(); } @FXML diff --git a/src/main/resources/fxml/recoverykey_create.fxml b/src/main/resources/fxml/recoverykey_create.fxml index 784ba49ed..2d9f0f418 100644 --- a/src/main/resources/fxml/recoverykey_create.fxml +++ b/src/main/resources/fxml/recoverykey_create.fxml @@ -40,7 +40,7 @@ - + @@ -49,8 +49,8 @@ - +