Use CompletableFuture instead of UserInteractionLock + AtomicReference

This commit is contained in:
Sebastian Stenzel
2021-12-15 17:19:59 +01:00
parent 157bef5df6
commit 25b9722019
6 changed files with 42 additions and 90 deletions

View File

@@ -1,15 +1,12 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
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;
@@ -24,29 +21,27 @@ import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class AuthFlowController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(AuthFlowController.class);
private final Application application;
private final Stage window;
private final ExecutorService executor;
private final String deviceId;
private final HubConfig hubConfig;
private final AtomicReference<String> tokenRef;
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
private final CompletableFuture<JWEObject> result;
private final Lazy<Scene> receiveKeyScene;
private final ErrorComponent.Builder errorComponent;
private final ObjectProperty<URI> authUri;
private final StringBinding authHost;
private AuthFlowTask task;
@Inject
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene, ErrorComponent.Builder errorComponent) {
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
this.application = application;
this.window = window;
this.executor = executor;
@@ -55,7 +50,6 @@ public class AuthFlowController implements FxController {
this.tokenRef = tokenRef;
this.result = result;
this.receiveKeyScene = receiveKeyScene;
this.errorComponent = errorComponent;
this.authUri = new SimpleObjectProperty<>();
this.authHost = Bindings.createStringBinding(this::getAuthHost, authUri);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -64,7 +58,7 @@ public class AuthFlowController implements FxController {
@FXML
public void initialize() {
assert task == null;
task = new AuthFlowTask(hubConfig, new AuthFlowContext(deviceId), this::setAuthUri);;
task = new AuthFlowTask(hubConfig, new AuthFlowContext(deviceId), this::setAuthUri);
task.setOnFailed(this::authFailed);
task.setOnSucceeded(this::authSucceeded);
executor.submit(task);
@@ -88,11 +82,7 @@ public class AuthFlowController implements FxController {
private void windowClosed(WindowEvent windowEvent) {
// stop server, if it is still running
task.cancel();
// if not already interacted, mark this workflow as cancelled:
if (result.awaitingInteraction().get()) {
LOG.debug("Authorization cancelled by user.");
result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
}
result.cancel(true);
}
private void authSucceeded(WorkerStateEvent workerStateEvent) {
@@ -102,11 +92,9 @@ public class AuthFlowController implements FxController {
}
private void authFailed(WorkerStateEvent workerStateEvent) {
result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
window.requestFocus();
var exception = workerStateEvent.getSource().getException();
LOG.error("Authentication failed", exception);
errorComponent.cause(exception).window(window).build().showErrorScene();
result.completeExceptionally(exception);
}
/* Getter/Setter */

View File

@@ -17,7 +17,6 @@ import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.PasswordStrengthUtil;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
@@ -28,17 +27,12 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
@Module
public abstract class HubKeyLoadingModule {
public enum HubLoadingResult {
SUCCESS,
FAILED,
CANCELLED
}
@Provides
@KeyLoadingScoped
static HubConfig provideHubConfig(@KeyLoading Vault vault) {
@@ -67,14 +61,8 @@ public abstract class HubKeyLoadingModule {
@Provides
@KeyLoadingScoped
static AtomicReference<JWEObject> provideJweRef() {
return new AtomicReference<>();
}
@Provides
@KeyLoadingScoped
static UserInteractionLock<HubLoadingResult> provideResultLock() {
return new UserInteractionLock<>(null);
static CompletableFuture<JWEObject> provideResult() {
return new CompletableFuture<>();
}
@Binds

View File

@@ -8,7 +8,6 @@ import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
@@ -19,8 +18,9 @@ import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.net.URI;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@KeyLoading
public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
@@ -31,37 +31,37 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
private final Stage window;
private final Lazy<Scene> authFlowScene;
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction;
private final CompletableFuture<JWEObject> result;
private final DeviceKey deviceKey;
private final AtomicReference<JWEObject> jweRef;
@Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction, DeviceKey deviceKey, AtomicReference<JWEObject> jweRef) {
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey) {
this.window = window;
this.authFlowScene = authFlowScene;
this.userInteraction = userInteraction;
this.result = result;
this.deviceKey = deviceKey;
this.jweRef = jweRef;
}
@Override
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
try {
return switch (auth()) {
case SUCCESS -> JWEHelper.decrypt(jweRef.get(), deviceKey.get().getPrivate());
case FAILED -> throw new MasterkeyLoadingFailedException("failed to load keypair");
case CANCELLED -> throw new UnlockCancelledException("User cancelled auth workflow");
};
startAuthFlow();
var jwe = result.get();
return JWEHelper.decrypt(jwe, deviceKey.get().getPrivate());
} catch (DeviceKey.DeviceKeyRetrievalException e) {
throw new MasterkeyLoadingFailedException("Failed to create or load device key pair", e);
throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
} catch (CancellationException e) {
throw new UnlockCancelledException("User cancelled auth workflow", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UnlockCancelledException("Loading interrupted", e);
} catch (ExecutionException e) {
throw new MasterkeyLoadingFailedException("Failed to retrieve key", e);
}
}
private HubKeyLoadingModule.HubLoadingResult auth() throws InterruptedException {
private void startAuthFlow() {
Platform.runLater(() -> {
window.setScene(authFlowScene.get());
window.show();
@@ -73,7 +73,6 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
window.centerOnScreen();
}
});
return userInteraction.awaitInteraction();
}
}

View File

@@ -3,11 +3,9 @@ package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.eclipse.jetty.io.RuntimeIOException;
@@ -31,6 +29,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.text.ParseException;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@@ -43,24 +42,20 @@ public class ReceiveKeyController implements FxController {
private final Stage window;
private final String deviceId;
private final String bearerToken;
private final AtomicReference<JWEObject> jweRef;
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
private final CompletableFuture<JWEObject> result;
private final Lazy<Scene> registerDeviceScene;
private final Lazy<Scene> unauthorizedScene;
private final ErrorComponent.Builder errorComponent;
private final URI vaultBaseUri;
private final HttpClient httpClient;
@Inject
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, AtomicReference<JWEObject> jweRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene, ErrorComponent.Builder errorComponent) {
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy<Scene> registerDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy<Scene> unauthorizedScene) {
this.window = window;
this.deviceId = deviceId;
this.bearerToken = Objects.requireNonNull(tokenRef.get());
this.jweRef = jweRef;
this.result = result;
this.registerDeviceScene = registerDeviceScene;
this.unauthorizedScene = unauthorizedScene;
this.errorComponent = errorComponent;
this.vaultBaseUri = getVaultBaseUri(vault);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
this.httpClient = HttpClient.newBuilder().executor(executor).build();
@@ -94,8 +89,7 @@ public class ReceiveKeyController implements FxController {
private void retrievalSucceeded(HttpResponse<InputStream> response) throws IOException {
try {
var string = HttpHelper.readBody(response);
jweRef.set(JWEObject.parse(string));
result.interacted(HubKeyLoadingModule.HubLoadingResult.SUCCESS);
result.complete(JWEObject.parse(string));
window.close();
} catch (ParseException e) {
throw new IOException("Failed to parse JWE", e);
@@ -111,9 +105,7 @@ public class ReceiveKeyController implements FxController {
}
private Void retrievalFailed(Throwable cause) {
result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
LOG.error("Key retrieval failed", cause);
errorComponent.cause(cause).window(window).build().showErrorScene();
result.completeExceptionally(cause);
return null;
}
@@ -123,11 +115,7 @@ public class ReceiveKeyController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (result.awaitingInteraction().get()) {
LOG.debug("Authorization cancelled by user.");
result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
}
result.cancel(true);
}
private static URI appendPath(URI base, String path) {

View File

@@ -5,10 +5,10 @@ import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.common.settings.DeviceKey;
import org.cryptomator.cryptolib.common.P384KeyPair;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
@@ -27,6 +27,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@@ -41,14 +42,14 @@ public class RegisterDeviceController implements FxController {
private final String bearerToken;
private final String deviceId;
private final P384KeyPair keyPair;
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
private final CompletableFuture<JWEObject> result;
private final DecodedJWT jwt;
private final HttpClient httpClient;
public TextField deviceNameField;
@Inject
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @Named("bearerToken") AtomicReference<String> bearerToken) {
public RegisterDeviceController(@KeyLoading Stage window, ExecutorService executor, HubConfig hubConfig, @Named("deviceId") String deviceId, DeviceKey deviceKey, CompletableFuture<JWEObject> result, @Named("bearerToken") AtomicReference<String> bearerToken) {
this.window = window;
this.hubConfig = hubConfig;
this.deviceId = deviceId;
@@ -84,9 +85,7 @@ public class RegisterDeviceController implements FxController {
}
private Void registrationFailed(Throwable cause) {
result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
LOG.error("Key retrieval failed", cause);
// TODO errorComponent.cause(cause).window(window).build().showErrorScene();
result.completeExceptionally(cause);
return null;
}
@@ -96,10 +95,7 @@ public class RegisterDeviceController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (result.awaitingInteraction().get()) {
result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
}
result.cancel(true);
}
/* Getter */

View File

@@ -1,27 +1,24 @@
package org.cryptomator.ui.keyloading.hub;
import com.nimbusds.jose.JWEObject;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
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 javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.util.concurrent.CompletableFuture;
@KeyLoadingScoped
public class UnauthorizedDeviceController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(UnauthorizedDeviceController.class);
private final Stage window;
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
private final CompletableFuture<JWEObject> result;
@Inject
public UnauthorizedDeviceController(@KeyLoading Stage window, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result) {
public UnauthorizedDeviceController(@KeyLoading Stage window, CompletableFuture<JWEObject> result) {
this.window = window;
this.result = result;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
@@ -33,10 +30,6 @@ public class UnauthorizedDeviceController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (result.awaitingInteraction().get()) {
LOG.debug("Authorization cancelled. Device not authorized.");
result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
}
result.cancel(true);
}
}