removed MASTERKEY_MISSING
new recoveryActionType RESTORE_ALL
This commit is contained in:
Jan-Peter Klein
2025-06-30 09:26:30 +02:00
parent 4280112eab
commit acf11da202
32 changed files with 468 additions and 175 deletions

View File

@@ -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<Path> 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) {

View File

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

View File

@@ -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() {

View File

@@ -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> 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() {

View File

@@ -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);
}

View File

@@ -31,9 +31,9 @@ public class VaultState extends ObservableValueBase<VaultState.Value> 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

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -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<Path> 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<Path> result, ResourceBundle resourceBundle) {
public ChooseMasterkeyFileController(@KeyLoading Stage window, //
@KeyLoading Vault vault, //
CompletableFuture<Path> 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()");

View File

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

View File

@@ -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> vault;
private final ObservableList<Vault> vaults;
private final ResourceBundle resourceBundle;
@@ -31,12 +37,17 @@ public class VaultDetailMissingVaultController implements FxController {
private final Dialogs dialogs;
@Inject
public VaultDetailMissingVaultController(ObjectProperty<Vault> vault, //
public VaultDetailMissingVaultController(FxApplicationWindows appWindows, //
@MainWindow Stage mainWindow, //
ObjectProperty<Vault> vault, //
ObservableList<Vault> 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

View File

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

View File

@@ -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);
}

View File

@@ -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<RecoveryActionType> recoverType);
}
}

View File

@@ -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<Scene> successScene;
private final Lazy<Scene> 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<RecoveryActionType> 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<Scene> 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<Scene> successScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
@RecoveryKeyWindow Vault vault, //
RecoveryKeyFactory recoveryKeyFactory, //
MasterkeyFileAccess masterkeyFileAccess, //
ExecutorService executor, //
@RecoveryKeyWindow StringProperty recoveryKey, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@Named("recoverType") ObjectProperty<RecoveryActionType> 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();

View File

@@ -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> application;
private final Lazy<Scene> resetPasswordScene;
private final Lazy<Scene> recoverScene;
public CheckBox expertSettingsCheckBox;
public NumericTextField shorteningThresholdTextField;
private final Vault vault;
private final ObjectProperty<RecoveryActionType> recoverType;
private final IntegerProperty shorteningThreshold;
private final Lazy<Scene> resetPasswordScene;
private final Lazy<Scene> createScene;
private final Lazy<Scene> onBoardingScene;
private final Lazy<Scene> 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> application, //
@RecoveryKeyWindow Vault vault, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy<Scene> createScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> 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());
}
}

View File

@@ -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<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, recoverType, cipherCombo, masterkeyFileAccess);
}

View File

@@ -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<Scene> recoverykeyRecoverScene;
private RecoveryActionType recoverType;
private final Lazy<Scene> recoverykeyExpertSettingsScene;
private ObjectProperty<RecoveryActionType> 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<Scene> recoverykeyRecoverScene, //
@Named("recoverType") RecoveryActionType recoverType, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
@Named("recoverType") ObjectProperty<RecoveryActionType> 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());
}
}
}

View File

@@ -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<Scene> nextScene;
private final Lazy<Scene> onBoardingScene;
private final ResourceBundle resourceBundle;
public ObjectProperty<RecoveryActionType> 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<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
ResourceBundle resourceBundle, @Named("recoverType") RecoveryActionType recoverType) {
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
ResourceBundle resourceBundle, //
@Named("recoverType") ObjectProperty<RecoveryActionType> 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

View File

@@ -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<Scene> recoverExpertSettingsScene;
private final Lazy<Scene> recoverykeyRecoverScene;
private final FxApplicationWindows appWindows;
private final MasterkeyFileAccess masterkeyFileAccess;
private final VaultListManager vaultListManager;
private final IntegerProperty shorteningThreshold;
private final RecoveryActionType recoverType;
private final ObjectProperty<RecoveryActionType> recoverType;
private final ObjectProperty<CryptorProvider.Scheme> 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<Scene> recoverExpertSettingsScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
FxApplicationWindows appWindows, //
MasterkeyFileAccess masterkeyFileAccess, //
VaultListManager vaultListManager, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@Named("recoverType") RecoveryActionType recoverType, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> 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() {

View File

@@ -44,7 +44,7 @@ public class RecoveryKeyValidateController implements FxController {
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
private final AutoCompleter autoCompleter;
private final RecoveryActionType recoverType;
private final ObjectProperty<RecoveryActionType> 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<RecoveryActionType> recoverType, //
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> 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);

View File

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

View File

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

View File

@@ -40,7 +40,7 @@
<Insets bottom="6" top="6"/>
</padding>
</Label>
<FormattedLabel format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
<FormattedLabel fx:id="descriptionLabel" format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
<padding>
<Insets bottom="6"/>
</padding>
@@ -49,8 +49,8 @@
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
<Button fx:id="cancelButton" text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
</buttons>
</ButtonBar>
</VBox>

View File

@@ -6,6 +6,7 @@
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
@@ -13,6 +14,7 @@
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.RadioButton?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyOnboardingController"
@@ -33,10 +35,10 @@
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<Label text="%recoveryKey.recoverVaultConfig.title" styleClass="label-extra-large"/>
<Label fx:id="titleLabel" text="%recoveryKey.recoverVaultConfig.title" styleClass="label-extra-large"/>
<Region minHeight="15"/>
<VBox>
<Label text="%recoveryKey.recover.onBoarding.message" wrapText="true"/>
<Label fx:id="messageLabel" text="%recoveryKey.recover.onBoarding.message" wrapText="true"/>
<GridPane alignment="CENTER_LEFT" >
<padding>
<Insets left="6"/>
@@ -53,19 +55,28 @@
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0" />
<Label text="%recoveryKey.recover.onBoarding.intro1" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0" />
<Label text="%recoveryKey.recover.onBoarding.intro2" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<Label text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
<Label text="%recoveryKey.recover.onBoarding.intro3" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
<Label fx:id="secondTextDesc" text="%recoveryKey.recover.onBoarding.intro2" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<Label fx:id="thirdTextIndex" text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
<Label fx:id="thirdTextDesc" text="%recoveryKey.recover.onBoarding.intro3" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
</GridPane>
<Region minHeight="15"/>
<CheckBox text="%recoveryKey.recover.onBoarding.affirmation" fx:id="affirmationBox"/>
<VBox fx:id="chooseMethodeBox">
<Label text="%recoveryKey.recover.onBoarding.chooseMethod"/>
<RadioButton fx:id="recoveryKeyRadio" text="%recoveryKey.recover.onBoarding.useRecoveryKey">
<toggleGroup>
<ToggleGroup fx:id="methodToggleGroup"/>
</toggleGroup>
</RadioButton>
<RadioButton fx:id="passwordRadio" text="%recoveryKey.recover.onBoarding.usePassword"/>
</VBox>
<CheckBox fx:id="affirmationBox" text="%recoveryKey.recover.onBoarding.affirmation"/>
</VBox>
</VBox>
</HBox>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!affirmationBox.selected}" defaultButton="true" onAction="#next"/>
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!affirmationBox.selected}" defaultButton="true" onAction="#next"/>
</buttons>
</ButtonBar>
</children>

View File

@@ -23,9 +23,9 @@
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<ButtonBar buttonMinWidth="120" buttonOrder="+BX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="cancelButton" text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validateController.recoveryKeyCorrect}"/>
</buttons>
</ButtonBar>

View File

@@ -24,9 +24,8 @@
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="${controller.buttonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%recoveryKey.recover.resetBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#resetPassword" managed="${!controller.vaultConfigMissing}" disable="${!controller.passwordSufficientAndMatching}"/>
<Button text="%recoveryKey.recover.recoverBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#restorePassword" managed="${controller.vaultConfigMissing}" disable="${!controller.passwordSufficientAndMatching}"/>
<Button fx:id="backButton" text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" text="%recoveryKey.recover.recoverBtn" ButtonBar.buttonData="FINISH" defaultButton="true" disable="${!controller.passwordSufficientAndMatching}"/>
</buttons>
</ButtonBar>
</VBox>

View File

@@ -12,6 +12,7 @@
<?import javafx.scene.Group?>
<?import javafx.scene.layout.Region?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.scene.control.CheckBox?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.masterkeyfile.ChooseMasterkeyFileController"
@@ -40,12 +41,13 @@
</padding>
</Label>
<FormattedLabel format="%unlock.chooseMasterkey.description" arg1="${controller.displayName}" wrapText="true"/>
<CheckBox fx:id="restoreInsteadCheckBox" text="%unlock.chooseMasterkey.restoreInstead" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
<Button text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
<Button fx:id="chooseButton" text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
</buttons>
</ButtonBar>
</VBox>

View File

@@ -53,7 +53,6 @@
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing.fxml" visible="${controller.vault.missing}" managed="${controller.vault.missing}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_needsmigration.fxml" visible="${controller.vault.needsMigration}" managed="${controller.vault.needsMigration}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unknownerror.fxml" visible="${controller.vault.unknownError}" managed="${controller.vault.unknownError}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_masterkey.fxml" visible="${controller.vault.missingMasterkey}" managed="${controller.vault.missingMasterkey}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_vault_config.fxml" visible="${controller.vault.missingVaultConfig}" managed="${controller.vault.missingVaultConfig}"/>
</children>
</VBox>

View File

@@ -29,6 +29,11 @@
<FontAwesome5IconView glyph="REDO"/>
</graphic>
</Button>
<Button text="Select Masterkey" minWidth="120" onAction="#unlock">
<graphic>
<FontAwesome5IconView glyph="MAGIC"/>
</graphic>
</Button>
<Button text="%main.vaultDetail.missingMasterkey.restore" minWidth="120" onAction="#restoreMasterkey">
<graphic>
<FontAwesome5IconView glyph="MAGIC"/>

View File

@@ -135,6 +135,7 @@ unlock.unlockBtn=Unlock
## Select
unlock.chooseMasterkey.message=Masterkey file not found
unlock.chooseMasterkey.description=Cryptomator could not find the masterkey file for vault "%s". Please choose the key file manually.
unlock.chooseMasterkey.restoreInstead=Restore instead... blablabla
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
@@ -511,6 +512,7 @@ vaultOptions.hub.convertBtn=Convert to Password-Based Vault
recoveryKey.display.title=Show Recovery Key
recoveryKey.create.message=Password required
recoveryKey.create.description=Enter the password for "%s" to show its recovery key.
recoveryKey.recover.description=Enter the password for "%s" to recover the vault config.
recoveryKey.display.description=The following recovery key can be used to restore access to "%s":
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
## Reset Password
@@ -547,6 +549,9 @@ recoveryKey.recover.onBoarding.confirm=Use RecoveryKey
recoveryKey.recover.onBoarding.intro1=Ensure all files are completely synced.
recoveryKey.recover.onBoarding.intro2=You will need the Recovery Key of the vault.
recoveryKey.recover.onBoarding.intro3=A new vault password and possible some expert settings.
recoveryKey.recover.onBoarding.chooseMethod=Choose recovery method:
recoveryKey.recover.onBoarding.useRecoveryKey=Use Recovery Key
recoveryKey.recover.onBoarding.usePassword=Use Password
recoveryKey.recover.onBoarding.affirmation=I have read and understood the above information
@@ -568,6 +573,7 @@ passwordStrength.messageLabel.1=Weak
passwordStrength.messageLabel.2=Fair
passwordStrength.messageLabel.3=Strong
passwordStrength.messageLabel.4=Very strong
password.promptText=Enter password
# Quit
quit.title=Quit Application