Parcourir la source

Merge pull request #3289 from cryptomator/feature/share-vault

Feature: Introduce 'Share Vault' Functionality
mindmonk il y a 1 an
Parent
commit
fa26fa9dee

+ 1 - 0
src/main/java/org/cryptomator/ui/common/FxmlFile.java

@@ -45,6 +45,7 @@ public enum FxmlFile {
 	RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
 	RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
 	REMOVE_VAULT("/fxml/remove_vault.fxml"), //
+	SHARE_VAULT("/fxml/share_vault.fxml"), //
 	UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
 	UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
 	UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //

+ 1 - 0
src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java

@@ -47,6 +47,7 @@ public enum FontAwesome5Icon {
 	QUESTION_CIRCLE("\uf059"), //
 	REDO("\uF01E"), //
 	SEARCH("\uF002"), //
+	SHARE("\uF064"), //
 	SPINNER("\uF110"), //
 	STETHOSCOPE("\uF0f1"), //
 	SYNC("\uF021"), //

+ 12 - 1
src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java

@@ -13,6 +13,7 @@ import org.cryptomator.ui.lock.LockComponent;
 import org.cryptomator.ui.mainwindow.MainWindowComponent;
 import org.cryptomator.ui.preferences.PreferencesComponent;
 import org.cryptomator.ui.quit.QuitComponent;
+import org.cryptomator.ui.sharevault.ShareVaultComponent;
 import org.cryptomator.ui.traymenu.TrayMenuComponent;
 import org.cryptomator.ui.unlock.UnlockComponent;
 import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -22,7 +23,17 @@ import javafx.scene.image.Image;
 import java.io.IOException;
 import java.io.InputStream;
 
-@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
+@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
+		MainWindowComponent.class, //
+		PreferencesComponent.class, //
+		VaultOptionsComponent.class, //
+		UnlockComponent.class, //
+		LockComponent.class, //
+		QuitComponent.class, //
+		ErrorComponent.class, //
+		HealthCheckComponent.class, //
+		UpdateReminderComponent.class, //
+		ShareVaultComponent.class})
 abstract class FxApplicationModule {
 
 	private static Image createImageFromResource(String resourceName) throws IOException {

+ 8 - 0
src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java

@@ -11,6 +11,7 @@ import org.cryptomator.ui.mainwindow.MainWindowComponent;
 import org.cryptomator.ui.preferences.PreferencesComponent;
 import org.cryptomator.ui.preferences.SelectedPreferencesTab;
 import org.cryptomator.ui.quit.QuitComponent;
+import org.cryptomator.ui.sharevault.ShareVaultComponent;
 import org.cryptomator.ui.unlock.UnlockComponent;
 import org.cryptomator.ui.unlock.UnlockWorkflow;
 import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -51,6 +52,7 @@ public class FxApplicationWindows {
 	private final ErrorComponent.Factory errorWindowFactory;
 	private final ExecutorService executor;
 	private final VaultOptionsComponent.Factory vaultOptionsWindow;
+	private final ShareVaultComponent.Factory shareVaultWindow;
 	private final FilteredList<Window> visibleWindows;
 
 	@Inject
@@ -64,6 +66,7 @@ public class FxApplicationWindows {
 								LockComponent.Factory lockWorkflowFactory, //
 								ErrorComponent.Factory errorWindowFactory, //
 								VaultOptionsComponent.Factory vaultOptionsWindow, //
+								ShareVaultComponent.Factory shareVaultWindow, //
 								ExecutorService executor) {
 		this.primaryStage = primaryStage;
 		this.trayIntegration = trayIntegration;
@@ -76,6 +79,7 @@ public class FxApplicationWindows {
 		this.errorWindowFactory = errorWindowFactory;
 		this.executor = executor;
 		this.vaultOptionsWindow = vaultOptionsWindow;
+		this.shareVaultWindow = shareVaultWindow;
 		this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
 	}
 
@@ -122,6 +126,10 @@ public class FxApplicationWindows {
 		return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
 	}
 
+	public void showShareVaultWindow(Vault vault) {
+		CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater);
+	}
+
 	public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
 		return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
 	}

+ 5 - 0
src/main/java/org/cryptomator/ui/mainwindow/VaultDetailLockedController.java

@@ -44,6 +44,11 @@ public class VaultDetailLockedController implements FxController {
 		appWindows.startUnlockWorkflow(vault.get(), mainWindow);
 	}
 
+	@FXML
+	public void share() {
+		appWindows.showShareVaultWindow(vault.get());
+	}
+
 	@FXML
 	public void showVaultOptions() {
 		vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);

+ 34 - 0
src/main/java/org/cryptomator/ui/sharevault/ShareVaultComponent.java

@@ -0,0 +1,34 @@
+package org.cryptomator.ui.sharevault;
+
+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 javafx.scene.Scene;
+import javafx.stage.Stage;
+
+@ShareVaultScoped
+@Subcomponent(modules = {ShareVaultModule.class})
+public interface ShareVaultComponent {
+
+	@ShareVaultWindow
+	Stage window();
+
+	@FxmlScene(FxmlFile.SHARE_VAULT)
+	Lazy<Scene> scene();
+
+	default void showShareVaultWindow(){
+		Stage stage = window();
+		stage.setScene(scene().get());
+		stage.show();
+	}
+
+	@Subcomponent.Factory
+	interface Factory {
+		ShareVaultComponent create(@BindsInstance @ShareVaultWindow Vault vault);
+	}
+
+}

+ 76 - 0
src/main/java/org/cryptomator/ui/sharevault/ShareVaultController.java

@@ -0,0 +1,76 @@
+package org.cryptomator.ui.sharevault;
+
+import dagger.Lazy;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
+
+import javax.inject.Inject;
+import javafx.application.Application;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+@ShareVaultScoped
+public class ShareVaultController implements FxController {
+
+	private static final String SCHEME_PREFIX = "hub+";
+	private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
+	private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/en/latest/security/best-practices/#sharing-of-vaults";
+
+	private final Stage window;
+	private final Lazy<Application> application;
+	private final Vault vault;
+	private final Boolean hubVault;
+
+	@Inject
+	ShareVaultController(@ShareVaultWindow Stage window, //
+						 Lazy<Application> application, //
+						 @ShareVaultWindow Vault vault) {
+		this.window = window;
+		this.application = application;
+		this.vault = vault;
+		var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
+		this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
+	}
+
+	@FXML
+	public void close() {
+		window.close();
+	}
+
+	@FXML
+	public void visitHub() {
+		application.get().getHostServices().showDocument(VISIT_HUB_URL);
+	}
+
+	@FXML
+	public void openHub() {
+		application.get().getHostServices().showDocument(getHubUri(vault).toString());
+	}
+
+	@FXML
+	public void visitBestPractices() {
+		application.get().getHostServices().showDocument(BEST_PRACTICES_URL);
+	}
+
+	private static URI getHubUri(Vault vault) {
+		try {
+			var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
+			assert keyID.getScheme().startsWith(SCHEME_PREFIX);
+			return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
+		} catch (IOException e) {
+			throw new UncheckedIOException(e);
+		} catch (URISyntaxException e) {
+			throw new IllegalStateException("URI constructed from params known to be valid", e);
+		}
+	}
+
+	public boolean isHubVault() {
+		return hubVault;
+	}
+
+}

+ 54 - 0
src/main/java/org/cryptomator/ui/sharevault/ShareVaultModule.java

@@ -0,0 +1,54 @@
+package org.cryptomator.ui.sharevault;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoMap;
+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 javax.inject.Provider;
+import javafx.scene.Scene;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+@Module
+abstract class ShareVaultModule {
+
+	@Provides
+	@ShareVaultWindow
+	@ShareVaultScoped
+	static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+		return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
+	}
+
+	@Provides
+	@ShareVaultWindow
+	@ShareVaultScoped
+	static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
+		Stage stage = factory.create();
+		stage.setResizable(false);
+		stage.initModality(Modality.APPLICATION_MODAL);
+		stage.setTitle(resourceBundle.getString("shareVault.title"));
+		return stage;
+	}
+
+	@Provides
+	@FxmlScene(FxmlFile.SHARE_VAULT)
+	@ShareVaultScoped
+	static Scene provideShareVaultScene(@ShareVaultWindow FxmlLoaderFactory fxmlLoaders) {
+		return fxmlLoaders.createScene(FxmlFile.SHARE_VAULT);
+	}
+
+	@Binds
+	@IntoMap
+	@FxControllerKey(ShareVaultController.class)
+	abstract FxController bindShareVaultController(ShareVaultController controller);
+}

+ 13 - 0
src/main/java/org/cryptomator/ui/sharevault/ShareVaultScoped.java

@@ -0,0 +1,13 @@
+package org.cryptomator.ui.sharevault;
+
+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 ShareVaultScoped {
+
+}

+ 14 - 0
src/main/java/org/cryptomator/ui/sharevault/ShareVaultWindow.java

@@ -0,0 +1,14 @@
+package org.cryptomator.ui.sharevault;
+
+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  ShareVaultWindow {
+
+}

+ 13 - 0
src/main/resources/css/dark_theme.css

@@ -936,3 +936,16 @@
 	-fx-padding: 0.5px;
 	-fx-background-color: CONTROL_BORDER_NORMAL;
 }
+
+/*******************************************************************************
+ *                                                                             *
+ * Ad box                                                                   *
+ *                                                                             *
+ ******************************************************************************/
+
+.ad-box {
+	-fx-padding: 12px;
+	-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+	-fx-background-insets: 0, 1px;
+	-fx-background-radius: 4px;
+}

+ 13 - 0
src/main/resources/css/light_theme.css

@@ -935,3 +935,16 @@
 	-fx-padding: 0.5px;
 	-fx-background-color: CONTROL_BORDER_NORMAL;
 }
+
+/*******************************************************************************
+ *                                                                             *
+ * Ad box                                                                   *
+ *                                                                             *
+ ******************************************************************************/
+
+.ad-box {
+	-fx-padding: 12px;
+	-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
+	-fx-background-insets: 0, 1px;
+	-fx-background-radius: 4px;
+}

+ 108 - 0
src/main/resources/fxml/share_vault.fxml

@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.Region?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.StackPane?>
+<?import javafx.scene.shape.Circle?>
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import javafx.scene.image.ImageView?>
+<?import javafx.scene.image.Image?>
+<?import javafx.scene.control.Hyperlink?>
+<?import javafx.scene.control.Tooltip?>
+<?import javafx.scene.Group?>
+<HBox xmlns:fx="http://javafx.com/fxml"
+	  xmlns="http://javafx.com/javafx"
+	  fx:controller="org.cryptomator.ui.sharevault.ShareVaultController"
+	  prefWidth="540"
+	  spacing="12">
+	<padding>
+		<Insets topRightBottomLeft="12"/>
+	</padding>
+	<Group>
+		<StackPane>
+			<padding>
+				<Insets topRightBottomLeft="6"/>
+			</padding>
+			<Circle styleClass="glyph-icon-primary" radius="24"/>
+			<FontAwesome5IconView styleClass="glyph-icon-white" glyph="INFO" glyphSize="24"/>
+		</StackPane>
+	</Group>
+	<VBox>
+		<VBox HBox.hgrow="ALWAYS" visible="${controller.hubVault}" managed="${controller.hubVault}">
+			<Label text="%shareVault.hub.message" styleClass="label-large" wrapText="true">
+				<padding>
+					<Insets bottom="6" top="6"/>
+				</padding>
+			</Label>
+			<Label text="%shareVault.hub.description" wrapText="true"/>
+			<VBox>
+				<padding>
+					<Insets left="6"/>
+				</padding>
+				<Label text="%shareVault.hub.instruction.1" wrapText="true"/>
+				<Label text="%shareVault.hub.instruction.2" wrapText="true"/>
+			</VBox>
+		</VBox>
+		<VBox HBox.hgrow="ALWAYS" visible="${!controller.hubVault}" managed="${!controller.hubVault}">
+			<Label text="%shareVault.message" styleClass="label-large" wrapText="true">
+				<padding>
+					<Insets bottom="6" top="6"/>
+				</padding>
+			</Label>
+			<Label text="%shareVault.description" wrapText="true"/>
+			<VBox>
+				<padding>
+					<Insets left="6"/>
+				</padding>
+				<Label text="%shareVault.instruction.1" wrapText="true"/>
+				<Label text="%shareVault.instruction.2" wrapText="true"/>
+			</VBox>
+			<Region minHeight="6"/>
+			<HBox spacing="6">
+				<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#visitBestPractices">
+					<graphic>
+						<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
+					</graphic>
+					<tooltip>
+						<Tooltip text="%shareVault.docsTooltip" showDelay="100ms"/>
+					</tooltip>
+				</Hyperlink>
+				<Label text="%shareVault.remarkBestPractices" wrapText="true"/>
+			</HBox>
+			<Region minHeight="12"/>
+			<HBox alignment="CENTER_LEFT" spacing="6" styleClass="ad-box">
+				<VBox spacing="6" alignment="CENTER_LEFT">
+					<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
+						<Image url="@../img/hub_logo.png"/>
+					</ImageView>
+					<Label text="%shareVault.hubAd.description" style="-fx-font-weight: bold;" wrapText="true"/>
+					<VBox spacing="6" alignment="CENTER_LEFT">
+						<padding>
+							<Insets left="6"/>
+						</padding>
+						<Label text="%shareVault.hubAd.keyManagement" wrapText="true"/>
+						<Label text="%shareVault.hubAd.authentication" wrapText="true"/>
+						<Label text="%shareVault.hubAd.encryption" wrapText="true"/>
+					</VBox>
+				</VBox>
+				<Region HBox.hgrow="ALWAYS"/>
+				<ImageView HBox.hgrow="ALWAYS" fitWidth="180" preserveRatio="true" cache="true">
+					<Image url="@../img/group-magic.png"/>
+				</ImageView>
+			</HBox>
+		</VBox>
+		<Region VBox.vgrow="ALWAYS" minHeight="18"/>
+		<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
+			<buttons>
+				<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#close"/>
+				<Button text="%shareVault.hub.openHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#openHub" visible="${controller.hubVault}" managed="${controller.hubVault}"/>
+				<Button text="%shareVault.visitHub" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#visitHub" visible="${!controller.hubVault}" managed="${!controller.hubVault}"/>
+			</buttons>
+		</ButtonBar>
+	</VBox>
+</HBox>

+ 6 - 3
src/main/resources/fxml/vault_detail_locked.fxml

@@ -24,14 +24,17 @@
 				<FontAwesome5IconView glyph="KEY" glyphSize="15"/>
 			</graphic>
 		</Button>
-		<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
+		<Hyperlink text="%main.vaultDetail.passwordSavedInKeychain" visible="${controller.passwordSaved}" managed="${controller.passwordSaved}" onAction="#showKeyVaultOptions">
 			<graphic>
 				<FontAwesome5IconView glyph="LOCK"/>
 			</graphic>
 		</Hyperlink>
-
+		<Button text="%main.vaultDetail.share" minWidth="120" onAction="#share">
+			<graphic>
+				<FontAwesome5IconView glyph="SHARE" glyphSize="15"/>
+			</graphic>
+		</Button>
 		<Region VBox.vgrow="ALWAYS"/>
-
 		<HBox alignment="BOTTOM_RIGHT">
 			<Button text="%main.vaultDetail.optionsBtn" minWidth="120" onAction="#showVaultOptions">
 				<graphic>

+ 22 - 1
src/main/resources/i18n/strings.properties

@@ -393,6 +393,7 @@ main.vaultDetail.unlockBtn=Unlock…
 main.vaultDetail.unlockNowBtn=Unlock Now
 main.vaultDetail.optionsBtn=Vault Options
 main.vaultDetail.passwordSavedInKeychain=Password saved
+main.vaultDetail.share=Share…
 ### Unlocked
 main.vaultDetail.unlockedStatus=UNLOCKED
 main.vaultDetail.accessLocation=Your vault's contents are accessible here:
@@ -530,4 +531,24 @@ updateReminder.message=Check for Updates?
 updateReminder.description=Stay updated with new features, bug fixes, and security improvements. We recommend to automatically check for updates.
 updateReminder.notNow=Not Now
 updateReminder.yesOnce=Yes, Once
-updateReminder.yesAutomatically=Yes, Automatically
+updateReminder.yesAutomatically=Yes, Automatically
+
+# Share Vault
+shareVault.title=Share Vault
+shareVault.message=Would you like to share your vault with others?
+shareVault.description=Always be careful when sharing your vault with other people. In short, follow these steps:
+shareVault.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
+shareVault.instruction.2=2. Share the vault password in a secure way.
+shareVault.remarkBestPractices=For more information, check out the best practices suggestions in our docs.
+shareVault.docsTooltip=Open the documentation to learn more about sharing of vaults.
+shareVault.hubAd.description=The secure way to work in teams
+shareVault.hubAd.keyManagement=• Zero-knowledge key management
+shareVault.hubAd.authentication=• Strong authentication
+shareVault.hubAd.encryption=• End-to-end encryption
+shareVault.visitHub=Visit Cryptomator Hub
+
+shareVault.hub.message=How to share a Hub vault
+shareVault.hub.description=In order to share the vault content with another team member, you have to perform two steps:
+shareVault.hub.instruction.1=1. Share access of the encrypted vault folder via cloud storage.
+shareVault.hub.instruction.2=2. Grant access to team member in Cryptomator Hub.
+shareVault.hub.openHub=Open Cryptomator Hub

BIN
src/main/resources/img/group-magic.png


BIN
src/main/resources/img/group-magic@2x.png


BIN
src/main/resources/img/hub_logo.png


BIN
src/main/resources/img/hub_logo@2x.png