Forráskód Böngészése

move passphrase entry to subcomponent
and use CompletableFuture instead of UserInteractionLock + AtomicReference

Sebastian Stenzel 3 éve
szülő
commit
0bece0f591

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

@@ -42,7 +42,7 @@ public enum FxmlFile {
 		this.ressourcePathString = ressourcePathString;
 	}
 
-	String getRessourcePathString() {
+	public String getRessourcePathString() {
 		return ressourcePathString;
 	}
 }

+ 1 - 1
src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java

@@ -26,7 +26,7 @@ abstract class KeyLoadingModule {
 	@Provides
 	@KeyLoading
 	@KeyLoadingScoped
-	static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
+	static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
 		try {
 			String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme();
 			var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));

+ 0 - 62
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java

@@ -1,62 +0,0 @@
-package org.cryptomator.ui.keyloading.masterkeyfile;
-
-import org.cryptomator.common.keychain.KeychainManager;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.integrations.keychain.KeychainAccessException;
-import org.cryptomator.ui.keyloading.KeyLoading;
-import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import java.nio.CharBuffer;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-@KeyLoadingScoped
-class MasterkeyFileLoadingFinisher {
-
-	private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class);
-
-	private final Vault vault;
-	private final Optional<char[]> storedPassword;
-	private final AtomicReference<char[]> enteredPassword;
-	private final AtomicBoolean shouldSavePassword;
-	private final KeychainManager keychain;
-
-	@Inject
-	MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional<char[]> storedPassword, AtomicReference<char[]> enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) {
-		this.vault = vault;
-		this.storedPassword = storedPassword;
-		this.enteredPassword = enteredPassword;
-		this.shouldSavePassword = shouldSavePassword;
-		this.keychain = keychain;
-	}
-
-	public void cleanup(boolean successfullyUnlocked) {
-		if (successfullyUnlocked && shouldSavePassword.get()) {
-			savePasswordToSystemkeychain();
-		}
-		wipePassword(storedPassword.orElse(null));
-		wipePassword(enteredPassword.getAndSet(null));
-	}
-
-	private void savePasswordToSystemkeychain() {
-		if (keychain.isSupported()) {
-			try {
-				keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(enteredPassword.get()));
-			} catch (KeychainAccessException e) {
-				LOG.error("Failed to store passphrase in system keychain.", e);
-			}
-		}
-	}
-
-	private void wipePassword(char[] pw) {
-		if (pw != null) {
-			Arrays.fill(pw, ' ');
-		}
-	}
-}

+ 1 - 38
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java

@@ -25,30 +25,18 @@ import javax.inject.Named;
 import javafx.scene.Scene;
 import java.nio.file.Path;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
-@Module(subcomponents = {ForgetPasswordComponent.class})
+@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class})
 public abstract class MasterkeyFileLoadingModule {
 
 	private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class);
 
-	public enum PasswordEntry {
-		PASSWORD_ENTERED,
-		CANCELED
-	}
-
 	public enum MasterkeyFileProvision {
 		MASTERKEYFILE_PROVIDED,
 		CANCELED
 	}
 
-	@Provides
-	@KeyLoadingScoped
-	static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
-		return new UserInteractionLock<>(null);
-	}
-
 	@Provides
 	@KeyLoadingScoped
 	static UserInteractionLock<MasterkeyFileProvision> provideMasterkeyFileProvisionLock() {
@@ -77,26 +65,6 @@ public abstract class MasterkeyFileLoadingModule {
 		return new AtomicReference<>();
 	}
 
-	@Provides
-	@KeyLoadingScoped
-	static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
-		return new AtomicReference<>(storedPassword.orElse(null));
-	}
-
-	@Provides
-	@Named("savePassword")
-	@KeyLoadingScoped
-	static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
-		return new AtomicBoolean(storedPassword.isPresent());
-	}
-
-	@Provides
-	@FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD)
-	@KeyLoadingScoped
-	static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
-		return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
-	}
-
 	@Provides
 	@FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE)
 	@KeyLoadingScoped
@@ -104,11 +72,6 @@ public abstract class MasterkeyFileLoadingModule {
 		return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
 	}
 
-	@Binds
-	@IntoMap
-	@FxControllerKey(PassphraseEntryController.class)
-	abstract FxController bindUnlockController(PassphraseEntryController controller);
-
 	@Binds
 	@IntoMap
 	@FxControllerKey(SelectMasterkeyFileController.class)

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 50 - 31
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java


+ 35 - 0
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java

@@ -0,0 +1,35 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+import org.cryptomator.common.Nullable;
+import org.cryptomator.ui.common.Animations;
+
+import javax.inject.Named;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+@PassphraseEntryScoped
+@Subcomponent(modules = {PassphraseEntryModule.class})
+public interface PassphraseEntryComponent {
+
+	@PassphraseEntryScoped
+	Scene passphraseEntryScene();
+
+	@PassphraseEntryScoped
+	CompletableFuture<PassphraseEntryResult> result();
+
+	@Subcomponent.Builder
+	interface Builder {
+
+		@BindsInstance
+		PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") char[] savedPassword);
+
+		PassphraseEntryComponent build();
+	}
+
+}

+ 31 - 49
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java

@@ -1,16 +1,13 @@
 package org.cryptomator.ui.keyloading.masterkeyfile;
 
+import org.cryptomator.common.Nullable;
 import org.cryptomator.common.keychain.KeychainManager;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.common.UserInteractionLock;
 import org.cryptomator.ui.common.WeakBindings;
-import org.cryptomator.ui.controls.FontAwesome5IconView;
 import org.cryptomator.ui.controls.NiceSecurePasswordField;
 import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
 import org.cryptomator.ui.keyloading.KeyLoading;
-import org.cryptomator.ui.keyloading.KeyLoadingScoped;
-import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,8 +18,8 @@ import javafx.animation.Interpolator;
 import javafx.animation.KeyFrame;
 import javafx.animation.KeyValue;
 import javafx.animation.Timeline;
+import javafx.application.Platform;
 import javafx.beans.binding.Bindings;
-import javafx.beans.binding.BooleanBinding;
 import javafx.beans.binding.ObjectBinding;
 import javafx.beans.binding.StringBinding;
 import javafx.beans.property.BooleanProperty;
@@ -37,33 +34,27 @@ import javafx.scene.transform.Translate;
 import javafx.stage.Stage;
 import javafx.stage.WindowEvent;
 import javafx.util.Duration;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CompletableFuture;
 
-@KeyLoadingScoped
+@PassphraseEntryScoped
 public class PassphraseEntryController implements FxController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class);
 
 	private final Stage window;
 	private final Vault vault;
-	private final AtomicReference<char[]> password;
-	private final AtomicBoolean savePassword;
-	private final Optional<char[]> savedPassword;
-	private final UserInteractionLock<PasswordEntry> passwordEntryLock;
+	private final CompletableFuture<PassphraseEntryResult> result;
+	private final char[] savedPassword;
 	private final ForgetPasswordComponent.Builder forgetPassword;
 	private final KeychainManager keychain;
-	private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
-	private final BooleanBinding userInteractionDisabled;
-	private final BooleanProperty unlockButtonDisabled;
 	private final StringBinding vaultName;
+	private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
+	private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress);
+	private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
 
 	/* FXML */
 	public NiceSecurePasswordField passwordField;
 	public CheckBox savePasswordCheckbox;
-	public FontAwesome5IconView unlockInProgressView;
 	public ImageView face;
 	public ImageView leftArm;
 	public ImageView rightArm;
@@ -72,29 +63,25 @@ public class PassphraseEntryController implements FxController {
 	public Animation unlockAnimation;
 
 	@Inject
-	public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
+	public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") char[] savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
 		this.window = window;
 		this.vault = vault;
-		this.password = password;
-		this.savePassword = savePassword;
+		this.result = result;
 		this.savedPassword = savedPassword;
-		this.passwordEntryLock = passwordEntryLock;
 		this.forgetPassword = forgetPassword;
 		this.keychain = keychain;
-		this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction());
-		this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not();
-		this.unlockButtonDisabled = new SimpleBooleanProperty();
 		this.vaultName = WeakBindings.bindString(vault.displayNameProperty());
-		this.window.setOnHiding(this::windowClosed);
+		window.setOnHiding(this::windowClosed);
+		result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater);
 	}
 
 	@FXML
 	public void initialize() {
-		savePasswordCheckbox.setSelected(savedPassword.isPresent());
-		if (password.get() != null) {
-			passwordField.setPassword(password.get());
+		if (savedPassword != null) {
+			savePasswordCheckbox.setSelected(true);
+			passwordField.setPassword(savedPassword);
 		}
-		unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty()));
+		unlockButtonDisabled.bind(unlockInProgress.or(passwordField.textProperty().isEmpty()));
 
 		var leftArmTranslation = new Translate(24, 0);
 		var leftArmRotation = new Rotate(60, 16, 30, 0);
@@ -132,7 +119,7 @@ public class PassphraseEntryController implements FxController {
 				new KeyFrame(Duration.millis(1000), faceVisible) //
 		);
 
-		passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation());
+		result.whenCompleteAsync((r, t) -> stopUnlockAnimation());
 	}
 
 	@FXML
@@ -141,26 +128,20 @@ public class PassphraseEntryController implements FxController {
 	}
 
 	private void windowClosed(WindowEvent windowEvent) {
-		// if not already interacted, mark this workflow as cancelled:
-		if (passwordEntryLock.awaitingInteraction().get()) {
-			LOG.debug("Unlock canceled by user.");
-			passwordEntryLock.interacted(PasswordEntry.CANCELED);
-		}
+		LOG.debug("Unlock canceled by user.");
+		result.cancel(true);
 	}
 
 	@FXML
 	public void unlock() {
 		LOG.trace("UnlockController.unlock()");
+		unlockInProgress.set(true);
 		CharSequence pwFieldContents = passwordField.getCharacters();
-		char[] newPw = new char[pwFieldContents.length()];
+		char[] pw = new char[pwFieldContents.length()];
 		for (int i = 0; i < pwFieldContents.length(); i++) {
-			newPw[i] = pwFieldContents.charAt(i);
-		}
-		char[] oldPw = password.getAndSet(newPw);
-		if (oldPw != null) {
-			Arrays.fill(oldPw, ' ');
+			pw[i] = pwFieldContents.charAt(i);
 		}
-		passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED);
+		result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()));
 		startUnlockAnimation();
 	}
 
@@ -184,8 +165,7 @@ public class PassphraseEntryController implements FxController {
 
 	@FXML
 	private void didClickSavePasswordCheckbox() {
-		savePassword.set(savePasswordCheckbox.isSelected());
-		if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) {
+		if (!savePasswordCheckbox.isSelected() && savedPassword != null) {
 			forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten));
 		}
 	}
@@ -205,15 +185,15 @@ public class PassphraseEntryController implements FxController {
 	}
 
 	public ContentDisplay getUnlockButtonContentDisplay() {
-		return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT;
+		return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
 	}
 
-	public BooleanBinding userInteractionDisabledProperty() {
-		return userInteractionDisabled;
+	public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
+		return unlockInProgress;
 	}
 
 	public boolean isUserInteractionDisabled() {
-		return userInteractionDisabled.get();
+		return unlockInProgress.get();
 	}
 
 	public ReadOnlyBooleanProperty unlockButtonDisabledProperty() {
@@ -227,4 +207,6 @@ public class PassphraseEntryController implements FxController {
 	public boolean isKeychainAccessAvailable() {
 		return keychain.isSupported();
 	}
+
+
 }

+ 41 - 0
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java

@@ -0,0 +1,41 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+import dagger.Module;
+import dagger.Provides;
+import org.cryptomator.ui.common.DefaultSceneFactory;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlLoaderFactory;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.ResourceBundle;
+import java.util.concurrent.CompletableFuture;
+
+@Module
+abstract class PassphraseEntryModule {
+
+	@Provides
+	@PassphraseEntryScoped
+	static CompletableFuture<PassphraseEntryResult> provideResult() {
+		return new CompletableFuture<>();
+	}
+
+	@Provides
+	@PassphraseEntryScoped
+	static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+		// TODO: simplify FxmlLoaderFactory
+		try {
+			var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_ENTER_PASSWORD.getRessourcePathString());
+			var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller);
+			Parent root = loader.load();
+			return sceneFactory.apply(root);
+		} catch (IOException e) {
+			throw new UncheckedIOException("Failed to load UnlockScene", e);
+		}
+	}
+
+
+}

+ 6 - 0
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java

@@ -0,0 +1,6 @@
+package org.cryptomator.ui.keyloading.masterkeyfile;
+
+// TODO needs to be public due to Dagger -.-
+public record PassphraseEntryResult(char[] passphrase, boolean savePassphrase) {
+
+}

+ 13 - 0
src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java

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