mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-19 03:01:27 +00:00
Merge pull request #2840 from cryptomator/feature/convert-hub-to-local
Feature: convert hub-based vault to password-based
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public interface Constants {
|
||||
|
||||
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
@@ -7,6 +11,7 @@ public interface Constants {
|
||||
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
||||
String CRYPTOMATOR_FILENAME_EXT = ".cryptomator";
|
||||
String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator";
|
||||
URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME);
|
||||
byte[] PEPPER = new byte[0];
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
@@ -48,13 +48,13 @@ import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
|
||||
private static final URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); // TODO better place?
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseLocationScene;
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -10,8 +10,6 @@ import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
|
||||
@@ -91,4 +93,8 @@ public class NewPasswordController implements FxController {
|
||||
return passwordStrength.get();
|
||||
}
|
||||
|
||||
public Passphrase getNewPassword() {
|
||||
return passwordField.getCharacters();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* Contributors:
|
||||
* Jean-Noël Charon - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
import org.cryptomator.common.Environment;
|
||||
@@ -9,6 +9,9 @@ public enum FxmlFile {
|
||||
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
|
||||
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
|
||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
|
||||
@@ -18,6 +18,7 @@ public enum FontAwesome5Icon {
|
||||
COPY("\uF0C5"), //
|
||||
CROWN("\uF521"), //
|
||||
EDIT("\uF044"), //
|
||||
EXCHANGE_ALT("\uF362"), //
|
||||
EXCLAMATION("\uF12A"), //
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
EXCLAMATION_TRIANGLE("\uF071"), //
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@ConvertVaultScoped
|
||||
@Subcomponent(modules = {ConvertVaultModule.class})
|
||||
public interface ConvertVaultComponent {
|
||||
|
||||
@ConvertVaultWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START)
|
||||
Lazy<Scene> hubToPasswordScene();
|
||||
|
||||
default void showHubToPasswordWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(hubToPasswordScene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
ConvertVaultComponent create(@BindsInstance @ConvertVaultWindow Vault vault, @BindsInstance @Named("convertVaultOwner") Stage owner);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class ConvertVaultModule {
|
||||
|
||||
//TODO: if this fails, we cannot display an error
|
||||
@Provides
|
||||
@ConvertVaultWindow
|
||||
@ConvertVaultScoped
|
||||
static VaultConfig.UnverifiedVaultConfig vaultConfig(@ConvertVaultWindow Vault vault) {
|
||||
try {
|
||||
return vault.getVaultConfigCache().get();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ConvertVaultWindow
|
||||
@ConvertVaultScoped
|
||||
static StringProperty provideRecoveryKeyProperty() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ConvertVaultWindow
|
||||
@ConvertVaultScoped
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ConvertVaultWindow
|
||||
@ConvertVaultScoped
|
||||
static Stage provideStage(StageFactory factory, @Named("convertVaultOwner") Stage owner, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.setTitle(resourceBundle.getString("convertVault.title"));
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START)
|
||||
@ConvertVaultScoped
|
||||
static Scene provideHubToPasswordStartScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT)
|
||||
@ConvertVaultScoped
|
||||
static Scene provideHubToPasswordConvertScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS)
|
||||
@ConvertVaultScoped
|
||||
static Scene provideHubToPasswordSuccessScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(HubToPasswordStartController.class)
|
||||
abstract FxController bindHubToPasswordStartController(HubToPasswordStartController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(HubToPasswordConvertController.class)
|
||||
abstract FxController bindHubToPasswordConvertController(HubToPasswordConvertController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(HubToPasswordSuccessController.class)
|
||||
abstract FxController bindHubToPasswordSuccessController(HubToPasswordSuccessController controller);
|
||||
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) {
|
||||
return new NewPasswordController(resourceBundle, strengthRater);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ConvertVaultScoped {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface ConvertVaultWindow {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultVersionMismatchException;
|
||||
import org.cryptomator.cryptofs.common.BackupHelper;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE_NEW;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||
|
||||
public class HubToPasswordConvertController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HubToPasswordConvertController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final FxApplicationWindows applicationWindows;
|
||||
private final Vault vault;
|
||||
private final StringProperty recoveryKey;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final MasterkeyFileAccess masterkeyFileAccess;
|
||||
private final ExecutorService backgroundExecutorService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final BooleanProperty conversionStarted;
|
||||
|
||||
@FXML
|
||||
NewPasswordController newPasswordController;
|
||||
public Button convertBtn;
|
||||
|
||||
@Inject
|
||||
public HubToPasswordConvertController(@ConvertVaultWindow Stage window, @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS) Lazy<Scene> successScene, FxApplicationWindows applicationWindows, @ConvertVaultWindow Vault vault, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess, ExecutorService backgroundExecutorService, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.successScene = successScene;
|
||||
this.applicationWindows = applicationWindows;
|
||||
this.vault = vault;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.masterkeyFileAccess = masterkeyFileAccess;
|
||||
this.backgroundExecutorService = backgroundExecutorService;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.conversionStarted = new SimpleBooleanProperty(false);
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
convertBtn.disableProperty().bind(Bindings.createBooleanBinding( //
|
||||
() -> !newPasswordController.isGoodPassword() || conversionStarted.get(), //
|
||||
newPasswordController.goodPasswordProperty(), //
|
||||
conversionStarted));
|
||||
convertBtn.contentDisplayProperty().bind(Bindings.createObjectBinding( //
|
||||
() -> conversionStarted.getValue() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY, //
|
||||
conversionStarted));
|
||||
convertBtn.textProperty().bind(Bindings.createStringBinding( //
|
||||
() -> resourceBundle.getString("convertVault.convert.convertBtn." + (conversionStarted.get() ? "processing" : "before")), //
|
||||
conversionStarted));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void convert() {
|
||||
Preconditions.checkState(newPasswordController.isGoodPassword());
|
||||
LOG.info("Converting access method of vault {} from hub to password", vault.getPath());
|
||||
CompletableFuture.runAsync(() -> conversionStarted.setValue(true), Platform::runLater) //
|
||||
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
|
||||
window.setScene(successScene.get());
|
||||
} else {
|
||||
LOG.error("Conversion of vault {} failed.", vault.getPath(), exception);
|
||||
applicationWindows.showErrorWindow(exception, window, null);
|
||||
}
|
||||
}, Platform::runLater); //
|
||||
}
|
||||
|
||||
//visible for testing
|
||||
void convertInternal() throws CompletionException, IllegalArgumentException {
|
||||
var passphrase = newPasswordController.getNewPassword();
|
||||
var vaultPath = vault.getPath();
|
||||
try {
|
||||
//create masterkey
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vaultPath, recoveryKey.get(), passphrase);
|
||||
LOG.debug("Successfully created masterkey file for vault {}", vaultPath);
|
||||
//create password config
|
||||
Path passwordConfigPath = vaultPath.resolve("passwordBased." + VAULTCONFIG_FILENAME + ".tmp");
|
||||
passwordConfigPath = createPasswordConfig(passwordConfigPath, vaultPath.resolve(MASTERKEY_FILENAME), passphrase);
|
||||
//backup hub config
|
||||
var hubConfigPath = vaultPath.resolve(VAULTCONFIG_FILENAME);
|
||||
backupHubConfig(hubConfigPath);
|
||||
//replace hub by password
|
||||
Files.move(passwordConfigPath, hubConfigPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (MasterkeyLoadingFailedException e) {
|
||||
throw new CompletionException(new IOException("Vault conversion failed", e));
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException("Vault conversion failed", e);
|
||||
} finally {
|
||||
passphrase.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
//visible for testing
|
||||
void backupHubConfig(Path hubConfigPath) throws IOException {
|
||||
byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath);
|
||||
Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX);
|
||||
Files.copy(hubConfigPath, backupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
LOG.debug("Successfully created hub config backup {}", backupPath.getFileName());
|
||||
}
|
||||
|
||||
//visible for testing
|
||||
Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException {
|
||||
var unverifiedVaultConfig = vault.getVaultConfigCache().get();
|
||||
try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) {
|
||||
var hubConfig = unverifiedVaultConfig.verify(masterkey.getEncoded(), unverifiedVaultConfig.allegedVaultVersion());
|
||||
var passwordConfig = VaultConfig.createNew() //
|
||||
.cipherCombo(hubConfig.getCipherCombo()) //
|
||||
.shorteningThreshold(hubConfig.getShorteningThreshold()) //
|
||||
.build();
|
||||
if (passwordConfig.getVaultVersion() != hubConfig.getVaultVersion()) {
|
||||
throw new VaultVersionMismatchException("Only vaults of version " + passwordConfig.getVaultVersion() + " can be converted.");
|
||||
}
|
||||
var token = passwordConfig.toToken(Constants.DEFAULT_KEY_ID.toString(), masterkey.getEncoded());
|
||||
Files.writeString(passwordConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW);
|
||||
LOG.debug("Successfully created password config {}", passwordConfigPath);
|
||||
return passwordConfigPath;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class HubToPasswordStartController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> convertScene;
|
||||
|
||||
@FXML
|
||||
RecoveryKeyValidateController recoveryKeyValidateController;
|
||||
|
||||
@Inject
|
||||
public HubToPasswordStartController(@ConvertVaultWindow Stage window, @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT) Lazy<Scene> convertScene) {
|
||||
this.window = window;
|
||||
this.convertScene = convertScene;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
window.setScene(convertScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public RecoveryKeyValidateController getValidateController() {
|
||||
return recoveryKeyValidateController;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class HubToPasswordSuccessController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
|
||||
@Inject
|
||||
HubToPasswordSuccessController(@ConvertVaultWindow Stage window, @ConvertVaultWindow Vault vault) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
window.getOwner().hide();
|
||||
}
|
||||
|
||||
/* Observables */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.error;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.error;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.ErrorCode;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -1,10 +1,16 @@
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.error;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.ErrorCode;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.forgetPassword;
|
||||
package org.cryptomator.ui.forgetpassword;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.forgetPassword;
|
||||
package org.cryptomator.ui.forgetpassword;
|
||||
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.forgetPassword;
|
||||
package org.cryptomator.ui.forgetpassword;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.forgetPassword;
|
||||
package org.cryptomator.ui.forgetpassword;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.forgetPassword;
|
||||
package org.cryptomator.ui.forgetpassword;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
@@ -7,9 +7,7 @@ package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
@@ -18,13 +16,9 @@ import org.cryptomator.ui.quit.QuitComponent;
|
||||
import org.cryptomator.ui.traymenu.TrayMenuComponent;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.image.Image;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
|
||||
abstract class FxApplicationModule {
|
||||
|
||||
@@ -5,7 +5,7 @@ import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.lock.LockComponent;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
|
||||
@@ -15,8 +15,6 @@ import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
@@ -153,13 +151,6 @@ public abstract class HubKeyLoadingModule {
|
||||
@FxControllerKey(AuthFlowController.class)
|
||||
abstract FxController bindAuthFlowController(AuthFlowController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) {
|
||||
return new NewPasswordController(resourceBundle, strengthRater);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(InvalidLicenseController.class)
|
||||
|
||||
@@ -29,8 +29,8 @@ import java.util.concurrent.ExecutionException;
|
||||
public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
|
||||
private static final String SCHEME_PREFIX = "hub+";
|
||||
static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
|
||||
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
||||
public static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
|
||||
public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
||||
|
||||
private final Stage window;
|
||||
private final KeychainManager keychainManager;
|
||||
|
||||
@@ -8,7 +8,7 @@ import dagger.multibindings.StringKey;
|
||||
import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.WeakBindings;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -6,7 +6,7 @@ import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.error.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
|
||||
@@ -38,16 +38,11 @@ public interface RecoveryKeyComponent {
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder vault(@RecoveryKeyWindow Vault vault);
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
@BindsInstance
|
||||
Builder owner(@Named("keyRecoveryOwner") Stage owner);
|
||||
|
||||
RecoveryKeyComponent build();
|
||||
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class RecoveryKeyFactory {
|
||||
* @throws IllegalArgumentException If the recoveryKey is invalid
|
||||
* @apiNote This is a long-running operation and should be invoked in a background thread
|
||||
*/
|
||||
public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
|
||||
public void newMasterkeyFileWithPassphrase(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException {
|
||||
final byte[] rawKey = decodeRecoveryKey(recoveryKey);
|
||||
try (var masterkey = new Masterkey(rawKey)) {
|
||||
Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
@@ -13,8 +13,8 @@ import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -140,6 +140,13 @@ abstract class RecoveryKeyModule {
|
||||
@FxControllerKey(RecoveryKeyResetPasswordSuccessController.class)
|
||||
abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(RecoveryKeyValidateController.class)
|
||||
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
@@ -16,96 +11,34 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyRecoverController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig;
|
||||
private final StringProperty recoveryKey;
|
||||
private final ObservableValue<Boolean> recoveryKeyCorrect;
|
||||
private final ObservableValue<Boolean> recoveryKeyWrong;
|
||||
private final ObservableValue<Boolean> recoveryKeyInvalid;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
||||
private final Lazy<Scene> resetPasswordScene;
|
||||
private final AutoCompleter autoCompleter;
|
||||
|
||||
private volatile boolean isWrongKey;
|
||||
|
||||
public TextArea textarea;
|
||||
@FXML
|
||||
RecoveryKeyValidateController recoveryKeyValidateController;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
|
||||
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
|
||||
this.vault = vault;
|
||||
this.unverifiedVaultConfig = unverifiedVaultConfig;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.resetPasswordScene = resetPasswordScene;
|
||||
this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary());
|
||||
this.recoveryKeyState = new SimpleObjectProperty<>();
|
||||
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
|
||||
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
|
||||
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
recoveryKey.bind(textarea.textProperty());
|
||||
textarea.textProperty().addListener(((observable, oldValue, newValue) -> validateRecoveryKey()));
|
||||
}
|
||||
|
||||
private TextFormatter.Change filterTextChange(TextFormatter.Change change) {
|
||||
if (Strings.isNullOrEmpty(change.getText())) {
|
||||
// pass-through caret/selection changes that don't affect the text
|
||||
return change;
|
||||
}
|
||||
if (!ALLOWED_CHARS.matchesAllOf(change.getText())) {
|
||||
return null; // reject change
|
||||
}
|
||||
|
||||
String text = change.getControlNewText();
|
||||
int caretPos = change.getCaretPosition();
|
||||
if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word?
|
||||
int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0);
|
||||
String currentWord = text.substring(beginOfWord, caretPos);
|
||||
Optional<String> suggestion = autoCompleter.autocomplete(currentWord);
|
||||
if (suggestion.isPresent()) {
|
||||
String completion = suggestion.get().substring(currentWord.length());
|
||||
change.setText(change.getText() + completion);
|
||||
change.setAnchor(caretPos + completion.length());
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void onKeyPressed(KeyEvent keyEvent) {
|
||||
if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) {
|
||||
// apply autocompletion:
|
||||
int pos = textarea.getAnchor();
|
||||
textarea.insertText(pos, " ");
|
||||
textarea.positionCaret(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -118,85 +51,10 @@ public class RecoveryKeyRecoverController implements FxController {
|
||||
window.setScene(resetPasswordScene.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if vault config is signed with the given key.
|
||||
*
|
||||
* @param key byte array of possible signing key
|
||||
* @return true, if vault config is signed with this key
|
||||
*/
|
||||
private boolean checkKeyAgainstVaultConfig(byte[] key) {
|
||||
try {
|
||||
var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion());
|
||||
LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId());
|
||||
return true;
|
||||
} catch (VaultKeyInvalidException e) {
|
||||
LOG.debug("Provided recovery key does not match vault config signature.");
|
||||
isWrongKey = true;
|
||||
return false;
|
||||
} catch (VaultConfigLoadException e) {
|
||||
LOG.error("Failed to parse vault config", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRecoveryKey() {
|
||||
isWrongKey = false;
|
||||
var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
||||
if (valid) {
|
||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||
} else {
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
public RecoveryKeyValidateController getValidateController() {
|
||||
return recoveryKeyValidateController;
|
||||
}
|
||||
|
||||
public TextFormatter getRecoveryKeyTextFormatter() {
|
||||
return new TextFormatter<>(this::filterTextChange);
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyInvalidProperty() {
|
||||
return recoveryKeyInvalid;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyInvalid() {
|
||||
return recoveryKeyInvalid.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyCorrectProperty() {
|
||||
return recoveryKeyCorrect;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyCorrect() {
|
||||
return recoveryKeyCorrect.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyWrongProperty() {
|
||||
return recoveryKeyWrong;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyWrong() {
|
||||
return recoveryKeyWrong.getValue();
|
||||
}
|
||||
|
||||
private enum RecoveryKeyState {
|
||||
/**
|
||||
* Recovery key is a valid key and belongs to this vault
|
||||
*/
|
||||
CORRECT,
|
||||
/**
|
||||
* Recovery key is a valid key, but does not belong to this vault
|
||||
*/
|
||||
WRONG,
|
||||
/**
|
||||
* Recovery key is not a valid key.
|
||||
*/
|
||||
INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -76,7 +76,7 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
@Override
|
||||
protected Void call() throws IOException, IllegalArgumentException {
|
||||
recoveryKeyFactory.resetPasswordWithRecoveryKey(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptofs.VaultConfigLoadException;
|
||||
import org.cryptomator.cryptofs.VaultKeyInvalidException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextFormatter;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
public class RecoveryKeyValidateController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
|
||||
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
|
||||
|
||||
private final Vault vault;
|
||||
private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig;
|
||||
private final StringProperty recoveryKey;
|
||||
private final ObservableValue<Boolean> recoveryKeyCorrect;
|
||||
private final ObservableValue<Boolean> recoveryKeyWrong;
|
||||
private final ObservableValue<Boolean> recoveryKeyInvalid;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
|
||||
private final AutoCompleter autoCompleter;
|
||||
|
||||
private volatile boolean isWrongKey;
|
||||
|
||||
public TextArea textarea;
|
||||
|
||||
public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
|
||||
this.vault = vault;
|
||||
this.unverifiedVaultConfig = vaultConfig;
|
||||
this.recoveryKey = recoveryKey;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary());
|
||||
this.recoveryKeyState = new SimpleObjectProperty<>();
|
||||
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
|
||||
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
|
||||
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
recoveryKey.bind(textarea.textProperty());
|
||||
textarea.textProperty().addListener(((observable, oldValue, newValue) -> validateRecoveryKey()));
|
||||
}
|
||||
|
||||
private TextFormatter.Change filterTextChange(TextFormatter.Change change) {
|
||||
if (Strings.isNullOrEmpty(change.getText())) {
|
||||
// pass-through caret/selection changes that don't affect the text
|
||||
return change;
|
||||
}
|
||||
if (!ALLOWED_CHARS.matchesAllOf(change.getText())) {
|
||||
return null; // reject change
|
||||
}
|
||||
|
||||
String text = change.getControlNewText();
|
||||
int caretPos = change.getCaretPosition();
|
||||
if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word?
|
||||
int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0);
|
||||
String currentWord = text.substring(beginOfWord, caretPos);
|
||||
var suggestion = autoCompleter.autocomplete(currentWord);
|
||||
if (suggestion.isPresent()) {
|
||||
String completion = suggestion.get().substring(currentWord.length());
|
||||
change.setText(change.getText() + completion);
|
||||
change.setAnchor(caretPos + completion.length());
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void onKeyPressed(KeyEvent keyEvent) {
|
||||
if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) {
|
||||
// apply autocompletion:
|
||||
int pos = textarea.getAnchor();
|
||||
textarea.insertText(pos, " ");
|
||||
textarea.positionCaret(pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if vault config is signed with the given key.
|
||||
*
|
||||
* @param key byte array of possible signing key
|
||||
* @return true, if vault config is signed with this key
|
||||
*/
|
||||
private boolean checkKeyAgainstVaultConfig(byte[] key) {
|
||||
assert unverifiedVaultConfig != null;
|
||||
try {
|
||||
var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion());
|
||||
LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId());
|
||||
return true;
|
||||
} catch (VaultKeyInvalidException e) {
|
||||
LOG.debug("Provided recovery key does not match vault config signature.");
|
||||
isWrongKey = true;
|
||||
return false;
|
||||
} catch (VaultConfigLoadException e) {
|
||||
LOG.error("Failed to parse vault config", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRecoveryKey() {
|
||||
isWrongKey = false;
|
||||
var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
|
||||
if (valid) {
|
||||
recoveryKeyState.set(RecoveryKeyState.CORRECT);
|
||||
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
|
||||
recoveryKeyState.set(RecoveryKeyState.WRONG);
|
||||
} else {
|
||||
recoveryKeyState.set(RecoveryKeyState.INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public TextFormatter getRecoveryKeyTextFormatter() {
|
||||
return new TextFormatter<>(this::filterTextChange);
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyInvalidProperty() {
|
||||
return recoveryKeyInvalid;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyInvalid() {
|
||||
return recoveryKeyInvalid.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyCorrectProperty() {
|
||||
return recoveryKeyCorrect;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyCorrect() {
|
||||
return recoveryKeyCorrect.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> recoveryKeyWrongProperty() {
|
||||
return recoveryKeyWrong;
|
||||
}
|
||||
|
||||
public boolean isRecoveryKeyWrong() {
|
||||
return recoveryKeyWrong.getValue();
|
||||
}
|
||||
|
||||
private enum RecoveryKeyState {
|
||||
/**
|
||||
* Recovery key is a valid key and belongs to this vault
|
||||
*/
|
||||
CORRECT,
|
||||
/**
|
||||
* Recovery key is a valid key, but does not belong to this vault
|
||||
*/
|
||||
WRONG,
|
||||
/**
|
||||
* Recovery key is not a valid key.
|
||||
*/
|
||||
INVALID;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.convertvault.ConvertVaultComponent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class HubOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ConvertVaultComponent.Factory convertVaultFactory;
|
||||
|
||||
|
||||
@Inject
|
||||
public HubOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ConvertVaultComponent.Factory convertVaultFactory) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.convertVaultFactory = convertVaultFactory;
|
||||
}
|
||||
|
||||
public void startConversion() {
|
||||
convertVaultFactory.create(vault,window).showHubToPasswordWindow();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import org.cryptomator.common.keychain.KeychainManager;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -23,14 +23,14 @@ public class MasterkeyOptionsController implements FxController {
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final ChangePasswordComponent.Builder changePasswordWindow;
|
||||
private final RecoveryKeyComponent.Builder recoveryKeyWindow;
|
||||
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
|
||||
private final ForgetPasswordComponent.Builder forgetPasswordWindow;
|
||||
private final KeychainManager keychain;
|
||||
private final ObservableValue<Boolean> passwordSaved;
|
||||
|
||||
|
||||
@Inject
|
||||
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) {
|
||||
MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.changePasswordWindow = changePasswordWindow;
|
||||
@@ -51,12 +51,12 @@ public class MasterkeyOptionsController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void showRecoveryKey() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow();
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showRecoverVaultDialog() {
|
||||
recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyRecoverWindow();
|
||||
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -21,4 +21,9 @@ public enum SelectedVaultOptionsTab {
|
||||
*/
|
||||
KEY,
|
||||
|
||||
/**
|
||||
* Show hub tab
|
||||
*/
|
||||
HUB
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -25,6 +27,7 @@ public class VaultOptionsController implements FxController {
|
||||
public Tab generalTab;
|
||||
public Tab mountTab;
|
||||
public Tab keyTab;
|
||||
public Tab hubTab;
|
||||
|
||||
@Inject
|
||||
VaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty) {
|
||||
@@ -38,9 +41,13 @@ public class VaultOptionsController implements FxController {
|
||||
window.setOnShowing(this::windowWillAppear);
|
||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged());
|
||||
if(!vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme().equals("masterkeyfile")){
|
||||
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
|
||||
if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){
|
||||
tabPane.getTabs().remove(keyTab);
|
||||
}
|
||||
if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
|
||||
tabPane.getTabs().remove(hubTab);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectChosenTab() {
|
||||
@@ -53,6 +60,7 @@ public class VaultOptionsController implements FxController {
|
||||
case ANY, GENERAL -> generalTab;
|
||||
case MOUNT -> mountTab;
|
||||
case KEY -> keyTab;
|
||||
case HUB -> hubTab;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.convertvault.ConvertVaultComponent;
|
||||
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
@@ -26,7 +27,7 @@ import javafx.stage.Stage;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class, ForgetPasswordComponent.class})
|
||||
@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class, ForgetPasswordComponent.class, ConvertVaultComponent.class})
|
||||
abstract class VaultOptionsModule {
|
||||
|
||||
@Provides
|
||||
@@ -84,4 +85,9 @@ abstract class VaultOptionsModule {
|
||||
@IntoMap
|
||||
@FxControllerKey(MasterkeyOptionsController.class)
|
||||
abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(HubOptionsController.class)
|
||||
abstract FxController bindHubOptionsController(HubOptionsController controller);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordConvertController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<fx:include fx:id="newPassword" source="new_password.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert"> <!-- for button logic, see controller -->
|
||||
<graphic>
|
||||
<FontAwesome5Spinner glyphSize="12"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordStartController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<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="#next" disable="${!controller.validateController.recoveryKeyCorrect}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.Group?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordSuccessController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_LEFT">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Group>
|
||||
<StackPane>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
|
||||
</StackPane>
|
||||
</Group>
|
||||
|
||||
<VBox HBox.hgrow="ALWAYS">
|
||||
<Label styleClass="label-large" text="%convertVault.success.message" wrapText="true" textAlignment="LEFT">
|
||||
<padding>
|
||||
<Insets bottom="6" top="6"/>
|
||||
</padding>
|
||||
</Label>
|
||||
|
||||
<Label text="%convertVault.hubToPassword.success.description" wrapText="true" textAlignment="LEFT"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
</children>
|
||||
</HBox>
|
||||
@@ -15,7 +15,7 @@
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.common.ErrorController"
|
||||
fx:controller="org.cryptomator.ui.error.ErrorController"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="18"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.forgetPassword.ForgetPasswordController"
|
||||
fx:controller="org.cryptomator.ui.forgetpassword.ForgetPasswordController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.common.NewPasswordController"
|
||||
fx:controller="org.cryptomator.ui.changepassword.NewPasswordController"
|
||||
spacing="6"
|
||||
alignment="CENTER_LEFT">
|
||||
<fx:define>
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.Group?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyRecoverController"
|
||||
@@ -23,32 +17,8 @@
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
|
||||
|
||||
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
|
||||
|
||||
<StackPane>
|
||||
<Label text="Just some Filler" visible="false" graphicTextGap="6">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="ANCHOR"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyCorrect}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyWrong}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyInvalid}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
</StackPane>
|
||||
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
@@ -56,7 +26,7 @@
|
||||
<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="#recover" disable="${!controller.recoveryKeyCorrect}"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validateController.recoveryKeyCorrect}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
49
src/main/resources/fxml/recoverykey_validate.fxml
Normal file
49
src/main/resources/fxml/recoverykey_validate.fxml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyValidateController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
|
||||
|
||||
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
|
||||
|
||||
<StackPane>
|
||||
<Label text="Just some Filler" visible="false" graphicTextGap="6">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="ANCHOR"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyCorrect}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyWrong}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) && controller.recoveryKeyInvalid}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
</StackPane>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -36,5 +36,13 @@
|
||||
<fx:include source="vault_options_masterkey.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="hubTab" id="HUB" text="%vaultOptions.hub"> <!-- is removed in controller, when config.keyid.scheme is not cryptomator-hub -->
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="KEY"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="vault_options_hub.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
|
||||
25
src/main/resources/fxml/vault_options_hub.fxml
Normal file
25
src/main/resources/fxml/vault_options_hub.fxml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.HubOptionsController"
|
||||
spacing="6"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
|
||||
<Label maxWidth="-Infinity" text="%vaultOptions.hub.convertInfo" wrapText="true"/>
|
||||
<VBox spacing="6" alignment="CENTER">
|
||||
<Button fx:id="convertHubToPasswordButton" text="%vaultOptions.hub.convertBtn" onAction="#startConversion" maxWidth="Infinity">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="EXCHANGE_ALT"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</VBox>
|
||||
</VBox>
|
||||
@@ -434,7 +434,10 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
|
||||
vaultOptions.masterkey.recoveryKeyExplanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Reset Password
|
||||
|
||||
## Hub
|
||||
vaultOptions.hub=Recovery
|
||||
vaultOptions.hub.convertInfo=You can use the recovery key to convert this Hub vault to a password-based vault in an emergency.
|
||||
vaultOptions.hub.convertBtn=Convert to Password-Based Vault
|
||||
|
||||
# Recovery Key
|
||||
## Display Recovery Key
|
||||
@@ -446,7 +449,7 @@ recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Stor
|
||||
## Reset Password
|
||||
### Enter Recovery Key
|
||||
recoveryKey.recover.title=Reset Password
|
||||
recoveryKey.recover.prompt=Enter your recovery key for "%s":
|
||||
recoveryKey.recover.prompt=Enter the recovery key for "%s":
|
||||
recoveryKey.recover.correctKey=This recovery key is correct
|
||||
recoveryKey.recover.wrongKey=This recovery key belongs to a different vault
|
||||
recoveryKey.recover.invalidKey=This recovery key is not valid
|
||||
@@ -457,6 +460,13 @@ recoveryKey.recover.resetBtn=Reset
|
||||
recoveryKey.recover.resetSuccess.message=Password reset successful
|
||||
recoveryKey.recover.resetSuccess.description=You can unlock your vault with the new password.
|
||||
|
||||
# Convert Vault
|
||||
convertVault.title=Convert Vault
|
||||
convertVault.convert.convertBtn.before=Convert
|
||||
convertVault.convert.convertBtn.processing=Converting…
|
||||
convertVault.success.message=Conversion successful
|
||||
convertVault.hubToPassword.success.description=You can now unlock the vault with the chosen password without requiring Hub access.
|
||||
|
||||
# New Password
|
||||
newPassword.promptText=Enter a new password
|
||||
newPassword.reenterPassword=Confirm the new password
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.cryptomator.ui.common;
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.cryptomator.ui.convertvault;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.ui.changepassword.NewPasswordController;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
public class HubToPasswordConvertControllerTest {
|
||||
|
||||
@TempDir
|
||||
Path tmpDir;
|
||||
|
||||
Stage window;
|
||||
Vault vault;
|
||||
StringProperty recoveryKey;
|
||||
RecoveryKeyFactory recoveryKeyFactory;
|
||||
MasterkeyFileAccess masterkeyFileAccess;
|
||||
ExecutorService backgroundExecutorService;
|
||||
ResourceBundle resourceBundle;
|
||||
BooleanProperty isConverting;
|
||||
FxApplicationWindows appWindows;
|
||||
Lazy<Scene> successScene;
|
||||
|
||||
NewPasswordController newPasswordController;
|
||||
|
||||
HubToPasswordConvertController inTest;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
window = Mockito.mock(Stage.class);
|
||||
vault = Mockito.mock(Vault.class);
|
||||
recoveryKey = Mockito.mock(StringProperty.class);
|
||||
recoveryKeyFactory = Mockito.mock(RecoveryKeyFactory.class);
|
||||
masterkeyFileAccess = Mockito.mock(MasterkeyFileAccess.class);
|
||||
backgroundExecutorService = Mockito.mock(ExecutorService.class);
|
||||
resourceBundle = Mockito.mock(ResourceBundle.class);
|
||||
isConverting = Mockito.mock(BooleanProperty.class);
|
||||
appWindows = Mockito.mock(FxApplicationWindows.class);
|
||||
successScene = Mockito.mock(Lazy.class);
|
||||
newPasswordController = Mockito.mock(NewPasswordController.class);
|
||||
inTest = new HubToPasswordConvertController(window, successScene, appWindows, vault, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, backgroundExecutorService, resourceBundle);
|
||||
inTest.newPasswordController = newPasswordController;
|
||||
Mockito.when(vault.getPath()).thenReturn(tmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackupHubConfig() throws IOException {
|
||||
var configContent = "Hello Config!".getBytes();
|
||||
Path configPath = tmpDir.resolve(Constants.VAULTCONFIG_FILENAME);
|
||||
Files.write(configPath, configContent, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
|
||||
|
||||
inTest.backupHubConfig(configPath);
|
||||
Optional<Path> result = Files.list(tmpDir).filter(p -> {
|
||||
var fileName = p.getFileName().toString();
|
||||
return fileName.startsWith(Constants.VAULTCONFIG_FILENAME) && fileName.endsWith(Constants.MASTERKEY_BACKUP_SUFFIX);
|
||||
}).findAny();
|
||||
|
||||
Assertions.assertTrue(Files.exists(configPath));
|
||||
Assertions.assertTrue(result.isPresent());
|
||||
Assertions.assertArrayEquals(configContent, Files.readAllBytes(result.get()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@DisplayName("createPasswordConfig creates valid config with password key id")
|
||||
public void integrationTestCreatePasswordConfig(@TempDir Path tmpDir) throws NoSuchAlgorithmException, IOException {
|
||||
//prepare
|
||||
var csprng = SecureRandom.getInstanceStrong();
|
||||
var key = Masterkey.generate(csprng);
|
||||
var masterkeyPath = tmpDir.resolve("masterkey");
|
||||
MasterkeyFileAccess mkAccess = new MasterkeyFileAccess(Constants.PEPPER, csprng);
|
||||
mkAccess.persist(key, masterkeyPath, "");
|
||||
|
||||
var config = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_GCM).shorteningThreshold(42).build();
|
||||
var token = config.toToken("test", key.getEncoded());
|
||||
var hubConfig = VaultConfig.decode(token);
|
||||
var configCache = Mockito.mock(VaultConfigCache.class);
|
||||
Mockito.when(vault.getVaultConfigCache()).thenReturn(configCache);
|
||||
Mockito.when(configCache.get()).thenReturn(hubConfig);
|
||||
|
||||
var passwordConfigPath = tmpDir.resolve("passwordConfig");
|
||||
|
||||
inTest = new HubToPasswordConvertController(window, successScene, appWindows, vault, recoveryKey, recoveryKeyFactory, mkAccess, backgroundExecutorService, resourceBundle);
|
||||
|
||||
//execute
|
||||
Path result = inTest.createPasswordConfig(passwordConfigPath, masterkeyPath, Passphrase.copyOf(""));
|
||||
|
||||
//check
|
||||
AtomicReference<VaultConfig.UnverifiedVaultConfig> unverifiedCfg = new AtomicReference<>();
|
||||
AtomicReference<VaultConfig> cfg = new AtomicReference<>();
|
||||
Assertions.assertTrue(Files.exists(result));
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
var tmp = VaultConfig.decode(Files.readString(result, StandardCharsets.US_ASCII));
|
||||
unverifiedCfg.set(tmp);
|
||||
});
|
||||
Assertions.assertDoesNotThrow(() -> {
|
||||
var tmp = unverifiedCfg.get().verify(key.getEncoded(), config.getVaultVersion());
|
||||
cfg.set(tmp);
|
||||
});
|
||||
Assertions.assertAll( //
|
||||
() -> Assertions.assertEquals(config.getCipherCombo(), cfg.get().getCipherCombo()), //
|
||||
() -> Assertions.assertEquals(config.getVaultVersion(), cfg.get().getVaultVersion()), //
|
||||
() -> Assertions.assertEquals(config.getShorteningThreshold(), cfg.get().getShorteningThreshold()), //
|
||||
() -> Assertions.assertEquals(Constants.DEFAULT_KEY_ID, unverifiedCfg.get().getKeyId()));
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ConvertInternalTests {
|
||||
|
||||
|
||||
Passphrase passphrase = Mockito.mock(Passphrase.class);
|
||||
String actualRecoveryKey = "recoveryKey";
|
||||
HubToPasswordConvertController inSpy;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws IOException {
|
||||
inSpy = Mockito.spy(inTest);
|
||||
Mockito.when(newPasswordController.getNewPassword()).thenReturn(passphrase);
|
||||
Mockito.when(recoveryKey.get()).thenReturn(actualRecoveryKey);
|
||||
Mockito.doNothing().when(recoveryKeyFactory).newMasterkeyFileWithPassphrase(any(), anyString(), any());
|
||||
Mockito.doNothing().when(inSpy).backupHubConfig(any());
|
||||
Mockito.doNothing().when(passphrase).destroy();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConvertInternal() throws IOException {
|
||||
var passwordConfigContent = "Hello Config!".getBytes();
|
||||
Path passwordConfigPath = tmpDir.resolve("passwordConfig");
|
||||
Path configPath = tmpDir.resolve(Constants.VAULTCONFIG_FILENAME);
|
||||
Files.write(passwordConfigPath, passwordConfigContent, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE);
|
||||
Mockito.doReturn(passwordConfigPath).when(inSpy).createPasswordConfig(any(), any(), eq(passphrase));
|
||||
|
||||
inSpy.convertInternal();
|
||||
|
||||
var inOrder = Mockito.inOrder(inSpy, recoveryKeyFactory, passphrase);
|
||||
inOrder.verify(recoveryKeyFactory).newMasterkeyFileWithPassphrase(tmpDir, actualRecoveryKey, passphrase);
|
||||
inOrder.verify(inSpy).createPasswordConfig(any(), Mockito.any(), eq(passphrase));
|
||||
inOrder.verify(inSpy).backupHubConfig(configPath);
|
||||
inOrder.verify(passphrase).destroy();
|
||||
Assertions.assertArrayEquals(passwordConfigContent, Files.readAllBytes(configPath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertInternalWrapsCryptoException() throws IOException {
|
||||
Mockito.doThrow(new MasterkeyLoadingFailedException("yadda")).when(inSpy).createPasswordConfig(any(), any(), any());
|
||||
|
||||
Assertions.assertThrows(CompletionException.class, inSpy::convertInternal);
|
||||
|
||||
Mockito.verify(passphrase, times(1)).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertInternalWrapsIOException() throws IOException {
|
||||
Mockito.doReturn(Mockito.mock(Path.class)).when(inSpy).createPasswordConfig(any(), any(), eq(passphrase));
|
||||
Mockito.doThrow(new IOException("yudu")).when(inSpy).backupHubConfig(any());
|
||||
|
||||
Assertions.assertThrows(CompletionException.class, inSpy::convertInternal);
|
||||
|
||||
Mockito.verify(passphrase, times(1)).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertInternalNotWrapsIAE() throws IOException {
|
||||
Mockito.doThrow(new IllegalArgumentException("yolo")).when(recoveryKeyFactory).newMasterkeyFileWithPassphrase(any(), anyString(), any());
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, inSpy::convertInternal);
|
||||
|
||||
Mockito.verify(passphrase, times(1)).destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user