diff --git a/dist/win/bundle/resources/winfsp-download.url b/dist/win/bundle/resources/winfsp-download.url index 6bb0c7daf..d29e5e1a5 100644 --- a/dist/win/bundle/resources/winfsp-download.url +++ b/dist/win/bundle/resources/winfsp-download.url @@ -1,2 +1,2 @@ [InternetShortcut] -URL=https://github.com/winfsp/winfsp/releases/download/v1.12/winfsp-1.12.22301.msi \ No newline at end of file +URL=https://github.com/winfsp/winfsp/releases/download/v1.12.22339/winfsp-1.12.22339.msi \ No newline at end of file diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index e4dbd9b2c..de77f17b5 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -316,18 +316,29 @@ public class Vault { /** * Gets from the cleartext path its ciphertext counterpart. - * The cleartext path has to start from the vault root (by starting with "/"). * * @return Local os path to the ciphertext resource * @throws IOException if an I/O error occurs + * @throws IllegalStateException if the vault is not unlocked */ - public Path getCiphertextPath(String cleartextPath) throws IOException { - if (!cleartextPath.startsWith("/")) { - throw new IllegalArgumentException("Input path must be absolute from vault root by starting with \"/\"."); + public Path getCiphertextPath(Path cleartextPath) throws IOException { + if (!state.getValue().equals(VaultState.Value.UNLOCKED)) { + throw new IllegalStateException("Vault is not unlocked"); } var fs = cryptoFileSystem.get(); - var cryptoPath = fs.getPath(cleartextPath); - return fs.getCiphertextPath(cryptoPath); + var osPathSeparator = cleartextPath.getFileSystem().getSeparator(); + var cryptoFsPathSeparator = fs.getSeparator(); + + if (getMountPoint() instanceof Mountpoint.WithPath mp) { + var absoluteCryptoFsPath = cryptoFsPathSeparator + mp.path().relativize(cleartextPath).toString(); + if (!cryptoFsPathSeparator.equals(osPathSeparator)) { + absoluteCryptoFsPath = absoluteCryptoFsPath.replace(osPathSeparator, cryptoFsPathSeparator); + } + var cryptoPath = fs.getPath(absoluteCryptoFsPath); + return fs.getCiphertextPath(cryptoPath); + } else { + throw new UnsupportedOperationException("URI mount points not supported."); + } } public VaultConfigCache getVaultConfigCache() { diff --git a/src/main/java/org/cryptomator/ui/common/ErrorController.java b/src/main/java/org/cryptomator/ui/common/ErrorController.java index 3b5e08ffb..15d2ee41f 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorController.java +++ b/src/main/java/org/cryptomator/ui/common/ErrorController.java @@ -29,9 +29,13 @@ public class ErrorController implements FxController { OS: %s / %s App: %s / %s - + + + + + """; private final Application application; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java index 0e19590a9..12fdfd692 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnlockedController.java @@ -166,12 +166,7 @@ public class VaultDetailUnlockedController implements FxController { return Optional.empty(); } try { - var accessPoint = mountPoint.getValue(); - var cleartextPath = path.toString().substring(accessPoint.length()); - if (!cleartextPath.startsWith("/")) { - cleartextPath = "/" + cleartextPath; - } - return Optional.of(vault.get().getCiphertextPath(cleartextPath)); + return Optional.of(vault.get().getCiphertextPath(path)); } catch (IOException e) { LOG.warn("Unable to get ciphertext path from path: {}", path, e); return Optional.empty(); diff --git a/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java b/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java index e483c265b..ccfbd3ad5 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/WelcomeController.java @@ -16,7 +16,7 @@ import javafx.fxml.FXML; public class WelcomeController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class); - private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.6/desktop/getting-started/"; + private static final String GETTING_STARTED_URI = "https://docs.cryptomator.org/en/1.7/desktop/getting-started/"; private final Application application; private final BooleanBinding noVaultPresent; diff --git a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java index 2cfe14551..893ef3577 100644 --- a/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java +++ b/src/main/java/org/cryptomator/ui/migration/MigrationImpossibleController.java @@ -10,7 +10,7 @@ import javafx.stage.Stage; public class MigrationImpossibleController implements FxController { - private static final String HELP_URI = "https://docs.cryptomator.org/en/1.6/help/manual-migration/"; + private static final String HELP_URI = "https://docs.cryptomator.org/en/1.7/help/manual-migration/"; private final Application application; private final Stage window; diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 61cdebcd9..9082d7311 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -4,6 +4,7 @@ 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; @@ -15,9 +16,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; +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.Scene; import javafx.scene.control.TextArea; @@ -38,11 +40,16 @@ public class RecoveryKeyRecoverController implements FxController { private final Vault vault; private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; private final StringProperty recoveryKey; + private final ObservableValue recoveryKeyCorrect; + private final ObservableValue recoveryKeyWrong; + private final ObservableValue recoveryKeyInvalid; private final RecoveryKeyFactory recoveryKeyFactory; - private final BooleanBinding validRecoveryKey; + private final ObjectProperty recoveryKeyState; private final Lazy resetPasswordScene; private final AutoCompleter autoCompleter; + private volatile boolean isWrongKey; + public TextArea textarea; @Inject @@ -53,14 +60,18 @@ public class RecoveryKeyRecoverController implements FxController { this.unverifiedVaultConfig = unverifiedVaultConfig; this.recoveryKey = recoveryKey; this.recoveryKeyFactory = recoveryKeyFactory; - this.validRecoveryKey = Bindings.createBooleanBinding(this::isValidRecoveryKey, recoveryKey); 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) { @@ -107,6 +118,12 @@ 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()); @@ -114,6 +131,7 @@ public class RecoveryKeyRecoverController implements FxController { 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); @@ -121,25 +139,64 @@ public class RecoveryKeyRecoverController implements FxController { } } + 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 BooleanBinding validRecoveryKeyProperty() { - return validRecoveryKey; - } - - public boolean isValidRecoveryKey() { - if (unverifiedVaultConfig != null) { - return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), this::checkKeyAgainstVaultConfig); - } else { - return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get()); - } - } - public TextFormatter getRecoveryKeyTextFormatter() { return new TextFormatter<>(this::filterTextChange); } + + public ObservableValue recoveryKeyInvalidProperty() { + return recoveryKeyInvalid; + } + + public boolean isRecoveryKeyInvalid() { + return recoveryKeyInvalid.getValue(); + } + + public ObservableValue recoveryKeyCorrectProperty() { + return recoveryKeyCorrect; + } + + public boolean isRecoveryKeyCorrect() { + return recoveryKeyCorrect.getValue(); + } + + public ObservableValue 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; + } } diff --git a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java index 389449d55..cc91d1afd 100644 --- a/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java +++ b/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertController.java @@ -15,7 +15,7 @@ import java.io.UncheckedIOException; @WrongFileAlertScoped public class WrongFileAlertController implements FxController { - private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.6/desktop/accessing-vaults/"; + private static final String DOCUMENTATION_URI = "https://docs.cryptomator.org/en/1.7/desktop/accessing-vaults/"; private final Application app; private final Stage window; diff --git a/src/main/resources/fxml/recoverykey_recover.fxml b/src/main/resources/fxml/recoverykey_recover.fxml index 89c54b5a4..e35905825 100644 --- a/src/main/resources/fxml/recoverykey_recover.fxml +++ b/src/main/resources/fxml/recoverykey_recover.fxml @@ -9,6 +9,8 @@ + + - - + + + + + + @@ -38,7 +56,7 @@