mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-21 15:34:27 +00:00
if linked hub vault is alread present unlock/reveal
Signed-off-by: Armin Schrenk <armin.schrenk@skymatic.de>
This commit is contained in:
@@ -9,12 +9,14 @@ import org.cryptomator.launcher.OpenHubVaultEvent;
|
||||
import org.cryptomator.launcher.RevealRunningEvent;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.dialogs.Dialogs;
|
||||
import org.cryptomator.ui.keyloading.hub.HubVaults;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -34,16 +36,18 @@ class AppLaunchEventHandler {
|
||||
private final ExecutorService executorService;
|
||||
private final FxApplicationWindows appWindows;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final VaultService vaultService;
|
||||
private final Stage primaryStage;
|
||||
private final Dialogs dialogs;
|
||||
|
||||
@Inject
|
||||
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) {
|
||||
public AppLaunchEventHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExecutorService executorService, FxApplicationWindows appWindows, VaultListManager vaultListManager, ObservableList<Vault> vaults, VaultService vaultService, @PrimaryStage Stage primaryStage, Dialogs dialogs) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
this.executorService = executorService;
|
||||
this.appWindows = appWindows;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.vaults = vaults;
|
||||
this.vaultService = vaultService;
|
||||
this.primaryStage = primaryStage;
|
||||
this.dialogs = dialogs;
|
||||
@@ -69,11 +73,29 @@ class AppLaunchEventHandler {
|
||||
switch (event) {
|
||||
case RevealRunningEvent _ -> appWindows.showMainWindow();
|
||||
case OpenFileEvent openFileEvent -> openFileEvent.pathsToOpen().forEach(this::openPotentialVault);
|
||||
// TODO: show the hub vault open flow, see docs/hub-vault-open-deeplink-plan.md
|
||||
case OpenHubVaultEvent openHubVaultEvent -> {
|
||||
LOG.info("Received request to open hub vault {}.", openHubVaultEvent.vaultId());
|
||||
appWindows.showMainWindow();
|
||||
}
|
||||
case OpenHubVaultEvent openHubVaultEvent -> openHubVault(openHubVaultEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a hub vault is set up on this machine is a purely local question - hub manages the vault's key, not where
|
||||
* it lives. Only if it is not set up here do we need to ask hub about it.
|
||||
*/
|
||||
private void openHubVault(OpenHubVaultEvent event) {
|
||||
var existing = HubVaults.findByVaultId(vaults, event.vaultId());
|
||||
if (existing.isPresent()) {
|
||||
var vault = existing.get();
|
||||
Platform.runLater(() -> {
|
||||
if (vault.isUnlocked()) {
|
||||
vaultService.reveal(vault);
|
||||
} else if (vault.isLocked()) {
|
||||
appWindows.startUnlockWorkflow(vault, null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//TODO: authenticate, ask hub for the vault's details and offer to add it, see docs/hub-vault-open-deeplink-plan.md
|
||||
LOG.info("Hub vault {} is not set up on this machine.", event.vaultId());
|
||||
appWindows.showMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.VaultConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Locates Hub vaults among the vaults set up on this machine.
|
||||
* <p>
|
||||
* Hub manages a vault's key, not where it lives or how it is laid out, so the local vault list is the only place that
|
||||
* can answer whether a given Hub vault is already set up here.
|
||||
*/
|
||||
public final class HubVaults {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HubVaults.class);
|
||||
|
||||
private HubVaults() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Hub vault with the given id.
|
||||
* <p>
|
||||
* A vault whose config cannot be read - e.g. because it sits on storage that is currently unavailable - is skipped
|
||||
* rather than failing the lookup: one unreachable vault must not prevent finding a different one.
|
||||
*
|
||||
* @param vaults the vaults set up on this machine
|
||||
* @param hubVaultId the vault's id within its Hub instance
|
||||
* @return the local vault, or empty if none of them is that Hub vault
|
||||
*/
|
||||
public static Optional<Vault> findByVaultId(Collection<Vault> vaults, UUID hubVaultId) {
|
||||
return vaults.stream() //
|
||||
.filter(vault -> hasVaultId(vault, hubVaultId)) //
|
||||
.findAny();
|
||||
}
|
||||
|
||||
private static boolean hasVaultId(Vault vault, UUID hubVaultId) {
|
||||
try {
|
||||
return hasVaultId(vault.getVaultConfigCache().get(), hubVaultId);
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Skipping vault {} while looking for hub vault {}, its config is not readable.", vault.getPath(), hubVaultId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasVaultId(VaultConfig.UnverifiedVaultConfig config, UUID hubVaultId) {
|
||||
var keyIdScheme = config.getKeyId().getScheme();
|
||||
if (keyIdScheme == null || !keyIdScheme.startsWith(HubKeyLoadingStrategy.SCHEME_PREFIX)) {
|
||||
return false; //not a hub vault, so it cannot be the one we are looking for
|
||||
}
|
||||
return hubVaultId.toString().equalsIgnoreCase(config.allegedVaultId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -32,7 +33,7 @@ public class AppLaunchEventHandlerTest {
|
||||
queue = new LinkedBlockingQueue<>();
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
appWindows = mock(FxApplicationWindows.class);
|
||||
handler = new AppLaunchEventHandler(queue, executor, appWindows, mock(VaultListManager.class), mock(VaultService.class), mock(Stage.class), mock(Dialogs.class));
|
||||
handler = new AppLaunchEventHandler(queue, executor, appWindows, mock(VaultListManager.class), FXCollections.observableArrayList(), mock(VaultService.class), mock(Stage.class), mock(Dialogs.class));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultConfigCache;
|
||||
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class HubVaultsTest {
|
||||
|
||||
private static final UUID VAULT_ID = UUID.fromString("d3a1f0b2-7c4e-4a1d-9f3b-2e5c6a7b8c9d");
|
||||
private static final UUID OTHER_VAULT_ID = UUID.fromString("11111111-2222-3333-4444-555555555555");
|
||||
|
||||
@Test
|
||||
@DisplayName("the hub vault with the requested id is found")
|
||||
public void testFindsMatchingVault() throws IOException {
|
||||
var wanted = hubVault(VAULT_ID);
|
||||
var vaults = List.of(hubVault(OTHER_VAULT_ID), wanted);
|
||||
|
||||
Assertions.assertEquals(Optional.of(wanted), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("no vault with the requested id yields empty")
|
||||
public void testNoMatch() throws IOException {
|
||||
var vaults = List.of(hubVault(OTHER_VAULT_ID));
|
||||
|
||||
Assertions.assertEquals(Optional.empty(), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a password vault never matches, even carrying the same id")
|
||||
public void testIgnoresPasswordVault() throws IOException {
|
||||
var vaults = List.of(vault("masterkeyfile:masterkey.cryptomator", VAULT_ID));
|
||||
|
||||
Assertions.assertEquals(Optional.empty(), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("a vault whose config cannot be read is skipped, not fatal")
|
||||
public void testSkipsUnreadableVault() throws IOException {
|
||||
// e.g. a vault on a network drive that is currently offline - it must not hide a vault further down the list
|
||||
var wanted = hubVault(VAULT_ID);
|
||||
var vaults = List.of(unreadableVault(), wanted);
|
||||
|
||||
Assertions.assertEquals(Optional.of(wanted), HubVaults.findByVaultId(vaults, VAULT_ID));
|
||||
}
|
||||
|
||||
|
||||
// setup/mock provider
|
||||
|
||||
private static Vault hubVault(UUID vaultId) throws IOException {
|
||||
return vault("hub+https://hub.example.com/api/vaults/" + vaultId, vaultId);
|
||||
}
|
||||
|
||||
private static Vault vault(String keyId, UUID vaultId) throws IOException {
|
||||
var configCache = mock(VaultConfigCache.class);
|
||||
var config = mockConfig(keyId, vaultId);
|
||||
when(configCache.get()).thenReturn(config);
|
||||
return vaultWith(configCache);
|
||||
}
|
||||
|
||||
private static Vault unreadableVault() throws IOException {
|
||||
var configCache = mock(VaultConfigCache.class);
|
||||
when(configCache.get()).thenThrow(new IOException("vault directory unavailable"));
|
||||
return vaultWith(configCache);
|
||||
}
|
||||
|
||||
private static Vault vaultWith(VaultConfigCache configCache) {
|
||||
var vault = mock(Vault.class);
|
||||
when(vault.getVaultConfigCache()).thenReturn(configCache);
|
||||
return vault;
|
||||
}
|
||||
|
||||
private static UnverifiedVaultConfig mockConfig(String keyId, UUID vaultId) {
|
||||
var mock = mock(UnverifiedVaultConfig.class);
|
||||
when(mock.getKeyId()).thenReturn(URI.create(keyId));
|
||||
when(mock.allegedVaultId()).thenReturn(vaultId.toString());
|
||||
return mock;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user