/******************************************************************************* * 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.model; import java.nio.CharBuffer; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; import org.cryptomator.keychain.KeychainAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class AutoUnlocker { private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class); private static final int NAP_TIME_MILLIS = 500; private final Optional<KeychainAccess> keychainAccess; private final VaultList vaults; private final ExecutorService executor; @Inject public AutoUnlocker(Optional<KeychainAccess> keychainAccess, VaultList vaults, ExecutorService executor) { this.keychainAccess = keychainAccess; this.vaults = vaults; this.executor = executor; } public void unlockAllSilently() { Collection<Vault> vaultsToUnlock = vaults.stream().filter(this::shouldUnlockAfterStartup).collect(Collectors.toList()); if (keychainAccess.isPresent() && !vaultsToUnlock.isEmpty()) { executor.submit(() -> unlockAll(vaultsToUnlock)); } } private boolean shouldUnlockAfterStartup(Vault vault) { return vault.getVaultSettings().unlockAfterStartup().get(); } private void unlockAll(Collection<Vault> vaults) { try { Iterator<Vault> iterator = vaults.iterator(); assert iterator.hasNext() : "vaults must not be empty"; unlockSilently(iterator.next()); while (iterator.hasNext()) { Thread.sleep(NAP_TIME_MILLIS); unlockSilently(iterator.next()); } } catch (InterruptedException e) { LOG.warn("Auto unlock thread interrupted."); Thread.currentThread().interrupt(); } } private void unlockSilently(Vault vault) { char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId()); if (storedPw == null) { LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath()); } try { vault.unlock(CharBuffer.wrap(storedPw)); mountSilently(vault); } catch (CryptoException e) { LOG.error("Auto unlock failed.", e); } finally { Arrays.fill(storedPw, ' '); } } private void mountSilently(Vault unlockedVault) { if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) { return; } try { unlockedVault.mount(); revealSilently(unlockedVault); } catch (CommandFailedException e) { LOG.error("Auto unlock succeded, but mounting the drive failed.", e); } } private void revealSilently(Vault mountedVault) { if (!mountedVault.getVaultSettings().revealAfterMount().get()) { return; } try { mountedVault.reveal(); } catch (CommandFailedException e) { LOG.error("Auto unlock succeded, but revealing the drive failed.", e); } } }