diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java index fd622e299..d36c8e4b5 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java @@ -11,6 +11,7 @@ import dagger.Provides; import dagger.multibindings.ElementsIntoSet; import org.cryptomator.common.JniModule; +import javax.inject.Singleton; import java.util.Optional; import java.util.Set; @@ -24,6 +25,7 @@ public class KeychainModule { } @Provides + @Singleton public Optional provideSupportedKeychain(Set keychainAccessStrategies) { return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/launcher/AutoUnlocker.java b/main/ui/src/main/java/org/cryptomator/ui/launcher/AutoUnlocker.java new file mode 100644 index 000000000..21b3d5a6c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/launcher/AutoUnlocker.java @@ -0,0 +1,74 @@ +package org.cryptomator.ui.launcher; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.common.vaults.Volume; +import org.cryptomator.keychain.KeychainAccess; +import org.cryptomator.keychain.KeychainAccessException; +import org.cryptomator.keychain.KeychainModule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.CharBuffer; +import java.util.Arrays; +import java.util.Optional; + +@Singleton +class AutoUnlocker { + + private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class); + + private final ObservableList vaults; + private final Optional keychain; + + @Inject + AutoUnlocker(ObservableList vaults, Optional keychain) { + this.vaults = vaults; + this.keychain = keychain; + } + + /** + * Attempts to unlock all vaults that have been configured for auto unlock. + * If an attempt fails (i.e. because the stored password is wrong) it will be silently skipped. + */ + public void autoUnlock() { + if (!keychain.isPresent()) { + LOG.info("No system keychain found. Skipping auto unlock."); + return; + } + // TODO: do async + vaults.filtered(v -> v.getVaultSettings().unlockAfterStartup().get()).forEach(this::autoUnlock); + } + + private void autoUnlock(Vault vault) { + if (vault.getState() != VaultState.LOCKED) { + LOG.warn("Can't unlock vault {} due to its state {}", vault.getDisplayablePath(), vault.getState()); + return; + } + assert keychain.isPresent(); + char[] storedPw = null; + try { + storedPw = keychain.get().loadPassphrase(vault.getId()); + if (storedPw == null) { + LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath()); + } else { + vault.unlock(CharBuffer.wrap(storedPw)); + // TODO + // Platform.runLater(() -> vault.setState(VaultState.UNLOCKED)); + LOG.info("Unlocked vault {}", vault.getDisplayablePath()); + } + } catch (IOException | Volume.VolumeException | KeychainAccessException e) { + LOG.error("Auto unlock failed.", e); + } finally { + if (storedPw != null) { + Arrays.fill(storedPw, ' '); + } + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java b/main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java index 4eab204ac..0fd9425d0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java +++ b/main/ui/src/main/java/org/cryptomator/ui/launcher/UiLauncher.java @@ -27,14 +27,16 @@ public class UiLauncher { private final TrayMenuComponent.Builder trayComponent; private final FxApplicationStarter fxApplicationStarter; private final AppLaunchEventHandler launchEventHandler; + private final AutoUnlocker autoUnlocker; private final Optional macFunctions; @Inject - public UiLauncher(Settings settings, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, Optional macFunctions) { + public UiLauncher(Settings settings, TrayMenuComponent.Builder trayComponent, FxApplicationStarter fxApplicationStarter, AppLaunchEventHandler launchEventHandler, AutoUnlocker autoUnlocker, Optional macFunctions) { this.settings = settings; this.trayComponent = trayComponent; this.fxApplicationStarter = fxApplicationStarter; this.launchEventHandler = launchEventHandler; + this.autoUnlocker = autoUnlocker; this.macFunctions = macFunctions; } @@ -57,6 +59,9 @@ public class UiLauncher { // register app reopen listener Desktop.getDesktop().addAppEventListener((AppReopenedListener) e -> showMainWindowAsync(hasTrayIcon)); + + // auto unlock - no shit! + autoUnlocker.autoUnlock(); launchEventHandler.startHandlingLaunchEvents(hasTrayIcon); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java index d6f66c7c4..5acc37b9e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.vaultoptions; import javafx.fxml.FXML; +import javafx.scene.control.CheckBox; import javafx.stage.Stage; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.changepassword.ChangePasswordComponent; @@ -18,6 +19,7 @@ public class GeneralVaultOptionsController implements FxController { private final ChangePasswordComponent.Builder changePasswordWindow; private final RecoveryKeyComponent.Builder recoveryKeyWindow; private final RecoverVaultComponent.Builder recoverVaultWindow; + public CheckBox unlockOnStartupCheckbox; @Inject GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, RecoverVaultComponent.Builder recoverVaultWindow) { @@ -27,6 +29,11 @@ public class GeneralVaultOptionsController implements FxController { this.recoveryKeyWindow = recoveryKeyWindow; this.recoverVaultWindow = recoverVaultWindow; } + + @FXML + public void initialize() { + unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup()); + } @FXML public void changePassword() { @@ -42,5 +49,4 @@ public class GeneralVaultOptionsController implements FxController { public void showRecoverVaultDialogue(){ recoverVaultWindow.vault(vault).owner(window).build().showRecoverVaultWindow(); } - } diff --git a/main/ui/src/main/resources/fxml/vault_options_general.fxml b/main/ui/src/main/resources/fxml/vault_options_general.fxml index 76bbee0b8..dc35c6e2a 100644 --- a/main/ui/src/main/resources/fxml/vault_options_general.fxml +++ b/main/ui/src/main/resources/fxml/vault_options_general.fxml @@ -3,6 +3,7 @@ +