mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 12:41:27 +00:00
restore config by recoverykey
auto restore when bkup exists added new VaultStates
This commit is contained in:
@@ -70,6 +70,8 @@ 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;
|
||||
private final Settings settings;
|
||||
@@ -96,6 +98,8 @@ 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);
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
@@ -315,6 +319,22 @@ 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;
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty displayNameProperty() {
|
||||
return vaultSettings.displayName;
|
||||
}
|
||||
|
||||
@@ -25,15 +25,20 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.MISSING;
|
||||
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
|
||||
|
||||
@Singleton
|
||||
public class VaultListManager {
|
||||
@@ -129,7 +134,7 @@ public class VaultListManager {
|
||||
VaultState state = vault.stateProperty();
|
||||
VaultState.Value previousState = state.getValue();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING -> {
|
||||
try {
|
||||
var determinedState = determineVaultState(vault.getPath());
|
||||
if (determinedState == LOCKED) {
|
||||
@@ -149,7 +154,56 @@ public class VaultListManager {
|
||||
}
|
||||
|
||||
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||
if (!Files.exists(pathToVault)) {
|
||||
Path pathToVaultConfig = Path.of(pathToVault.toString(),"vault.cryptomator");
|
||||
Path pathToMasterkey = Path.of(pathToVault.toString(),"masterkey.cryptomator");
|
||||
if (!Files.exists(pathToVaultConfig)) {
|
||||
try (Stream<Path> files = Files.list(pathToVaultConfig.getParent())) {
|
||||
Path backupFile = files.filter(file -> {
|
||||
String fileName = file.getFileName().toString();
|
||||
return fileName.startsWith("vault.cryptomator") && fileName.endsWith(".bkup");
|
||||
}).findFirst().orElse(null);
|
||||
|
||||
if (backupFile != null) {
|
||||
try {
|
||||
Files.copy(backupFile, pathToVaultConfig, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
LOG.error("error",e);
|
||||
return VAULT_CONFIG_MISSING;
|
||||
}
|
||||
} else {
|
||||
return VAULT_CONFIG_MISSING;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("error",e);
|
||||
return VAULT_CONFIG_MISSING;
|
||||
}
|
||||
|
||||
}
|
||||
else if (!Files.exists(pathToMasterkey)) {
|
||||
//return VaultState.Value.MASTERKEY_MISSING;
|
||||
try (Stream<Path> files = Files.list(pathToMasterkey.getParent())) {
|
||||
Path backupFile = files.filter(file -> {
|
||||
String fileName = file.getFileName().toString();
|
||||
return fileName.startsWith("masterkey.cryptomator") && fileName.endsWith(".bkup");
|
||||
}).findFirst().orElse(null);
|
||||
|
||||
if (backupFile != null) {
|
||||
try {
|
||||
Files.copy(backupFile, pathToMasterkey, StandardCopyOption.REPLACE_EXISTING);
|
||||
return MASTERKEY_MISSING;
|
||||
} catch (IOException e) {
|
||||
LOG.error("error",e);
|
||||
return MASTERKEY_MISSING;
|
||||
}
|
||||
} else {
|
||||
return MASTERKEY_MISSING;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("error",e);
|
||||
return MASTERKEY_MISSING;
|
||||
}
|
||||
}
|
||||
else if (!Files.exists(pathToVault)) {
|
||||
return VaultState.Value.MISSING;
|
||||
}
|
||||
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||
|
||||
@@ -25,6 +25,10 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
|
||||
*/
|
||||
MISSING,
|
||||
|
||||
VAULT_CONFIG_MISSING,
|
||||
|
||||
MASTERKEY_MISSING,
|
||||
|
||||
/**
|
||||
* Vault requires migration to a newer vault format
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.cryptomator.ui.common.StageInitializer;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
import org.cryptomator.ui.stats.VaultStatisticsComponent;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
@@ -30,7 +31,7 @@ import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class})
|
||||
abstract class MainWindowModule {
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -53,6 +53,8 @@ public class VaultDetailController implements FxController {
|
||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case VAULT_CONFIG_MISSING -> FontAwesome5Icon.COGS;
|
||||
case MASTERKEY_MISSING -> FontAwesome5Icon.KEY;
|
||||
};
|
||||
} else {
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.mainwindow;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -22,14 +23,17 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
private final RemoveVaultComponent.Builder removeVault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Stage window;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
|
||||
|
||||
|
||||
@Inject
|
||||
public VaultDetailMissingVaultController(ObjectProperty<Vault> vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window) {
|
||||
public VaultDetailMissingVaultController(ObjectProperty<Vault> vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window, RecoveryKeyComponent.Factory recoveryKeyWindow) {
|
||||
this.vault = vault;
|
||||
this.removeVault = removeVault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.window = window;
|
||||
this.recoveryKeyWindow = recoveryKeyWindow;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -42,6 +46,15 @@ public class VaultDetailMissingVaultController implements FxController {
|
||||
removeVault.vault(vault.get()).build().showRemoveVault();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void restoreVaultConfig(){
|
||||
recoveryKeyWindow.create(vault.get(), window).showRecoveryKeyRecoverWindow("Recover VaultConfig");
|
||||
}
|
||||
@FXML
|
||||
void restoreMasterkey(){
|
||||
recoveryKeyWindow.create(vault.get(), window).showRecoveryKeyRecoverWindow("Recover Masterkey");
|
||||
}
|
||||
|
||||
@FXML
|
||||
void changeLocation() {
|
||||
// copied from ChooseExistingVaultController class
|
||||
|
||||
@@ -47,6 +47,8 @@ public class VaultListCellController implements FxController {
|
||||
case PROCESSING -> FontAwesome5Icon.SPINNER;
|
||||
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
|
||||
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
case VAULT_CONFIG_MISSING -> FontAwesome5Icon.COGS;
|
||||
case MASTERKEY_MISSING -> FontAwesome5Icon.KEY;
|
||||
};
|
||||
} else {
|
||||
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
|
||||
|
||||
@@ -38,6 +38,13 @@ public interface RecoveryKeyComponent {
|
||||
stage.show();
|
||||
}
|
||||
|
||||
default void showRecoveryKeyRecoverWindow(String title) {
|
||||
Stage stage = window();
|
||||
stage.setScene(recoverScene().get());
|
||||
stage.setTitle(title);
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
@@ -2,6 +2,13 @@ package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -18,8 +25,17 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@@ -32,11 +48,12 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
private final StringProperty recoveryKey;
|
||||
private final Lazy<Scene> recoverResetPasswordSuccessScene;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
|
||||
public NewPasswordController newPasswordController;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) {
|
||||
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, FxApplicationWindows appWindows, MasterkeyFileAccess masterkeyFileAccess) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
@@ -44,6 +61,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene;
|
||||
this.appWindows = appWindows;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -53,19 +71,60 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void resetPassword() {
|
||||
Task<Void> task = new ResetPasswordTask();
|
||||
task.setOnScheduled(event -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
executor.submit(task);
|
||||
if(vault.isMissingVaultConfig()){
|
||||
Path vaultPath = vault.getPath();
|
||||
Path recoveryPath = vaultPath.resolve("r");
|
||||
try {
|
||||
Files.createDirectory(recoveryPath);
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Creating directory or recovering masterkey failed", e);
|
||||
}
|
||||
|
||||
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
|
||||
try {
|
||||
MasterkeyLoader loader = ignored -> masterkey.copy();
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||
.withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC) //
|
||||
.withKeyLoader(loader) //
|
||||
.withShorteningThreshold(220) //
|
||||
.build();
|
||||
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
|
||||
} catch (CryptoException | IOException e) {
|
||||
LOG.error("Recovering vault failed", e);
|
||||
}
|
||||
Files.move(masterkeyFilePath, vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME));
|
||||
try (var paths = Files.walk(recoveryPath)) {
|
||||
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
LOG.info("Unable to delete {}. Please delete it manually.", p);
|
||||
}
|
||||
});
|
||||
}
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Moving recovered files failed", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Task<Void> task = new ResetPasswordTask();
|
||||
task.setOnScheduled(event -> {
|
||||
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
task.setOnSucceeded(event -> {
|
||||
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
|
||||
window.setScene(recoverResetPasswordSuccessScene.get());
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
LOG.error("Resetting password failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
executor.submit(task);
|
||||
}
|
||||
}
|
||||
|
||||
private class ResetPasswordTask extends Task<Void> {
|
||||
|
||||
Reference in New Issue
Block a user