mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 20:21:27 +00:00
keep window open while waiting for http callback
This commit is contained in:
@@ -9,23 +9,26 @@ module org.cryptomator.desktop {
|
||||
requires org.cryptomator.frontend.fuse;
|
||||
requires org.cryptomator.frontend.webdav;
|
||||
requires org.cryptomator.integrations.api;
|
||||
// jdk:
|
||||
requires java.desktop;
|
||||
requires java.net.http;
|
||||
requires javafx.base;
|
||||
requires javafx.graphics;
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires com.tobiasdiez.easybind;
|
||||
requires jdk.crypto.ec;
|
||||
// 3rd party:
|
||||
requires com.auth0.jwt;
|
||||
requires com.google.common;
|
||||
requires com.google.gson;
|
||||
requires com.nulabinc.zxcvbn;
|
||||
requires org.slf4j;
|
||||
requires org.apache.commons.lang3;
|
||||
requires org.eclipse.jetty.server;
|
||||
requires com.tobiasdiez.easybind;
|
||||
requires dagger;
|
||||
requires com.auth0.jwt;
|
||||
requires org.slf4j;
|
||||
requires org.bouncycastle.provider;
|
||||
requires org.bouncycastle.pkix;
|
||||
requires org.apache.commons.lang3;
|
||||
requires org.eclipse.jetty.server;
|
||||
|
||||
/* TODO: filename-based modules: */
|
||||
requires static javax.inject; /* ugly dagger/guava crap */
|
||||
|
||||
@@ -15,6 +15,7 @@ public enum FxmlFile {
|
||||
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
HUB_P12("/fxml/hub_p12.fxml"), //
|
||||
HUB_AUTH("/fxml/hub_auth.fxml"), //
|
||||
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
||||
LOCK_FAILED("/fxml/lock_failed.fxml"), //
|
||||
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
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.application.Application;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.WorkerStateEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@KeyLoadingScoped
|
||||
public class AuthController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthController.class);
|
||||
|
||||
private final Application application;
|
||||
private final ExecutorService executor;
|
||||
private final Stage window;
|
||||
private final KeyPair keyPair;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
private final ObjectProperty<URI> redirectUriRef;
|
||||
private final ObjectBinding<URI> authUri;
|
||||
private final StringBinding authUriHost;
|
||||
private final BooleanBinding ready;
|
||||
private final AuthReceiveTask receiveTask;
|
||||
|
||||
@Inject
|
||||
public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock, AtomicReference<URI> hubUriRef, ErrorComponent.Builder errorComponent) {
|
||||
this.application = application;
|
||||
this.executor = executor;
|
||||
this.window = window;
|
||||
this.keyPair = Objects.requireNonNull(keyPairRef.get());
|
||||
this.authFlowLock = authFlowLock;
|
||||
this.hubUriRef = hubUriRef;
|
||||
this.errorComponent = errorComponent;
|
||||
this.redirectUriRef = new SimpleObjectProperty<>();
|
||||
this.authUri = Bindings.createObjectBinding(this::getAuthUri, redirectUriRef);
|
||||
this.authUriHost = Bindings.createStringBinding(this::getAuthUriHost, authUri);
|
||||
this.ready = authUri.isNotNull();
|
||||
this.receiveTask = new AuthReceiveTask(redirectUriRef::set);
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
Preconditions.checkState(hubUriRef.get() != null);
|
||||
receiveTask.setOnSucceeded(this::receivedKey);
|
||||
receiveTask.setOnFailed(this::keyRetrievalFailed);
|
||||
executor.submit(receiveTask);
|
||||
}
|
||||
|
||||
private void keyRetrievalFailed(WorkerStateEvent workerStateEvent) {
|
||||
LOG.error("Cryptomator Hub login failed with error", receiveTask.getException());
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.FAILED);
|
||||
errorComponent.cause(receiveTask.getException()).window(window).build().showErrorScene();
|
||||
}
|
||||
|
||||
private void receivedKey(WorkerStateEvent workerStateEvent) {
|
||||
var authParams = receiveTask.getValue();
|
||||
LOG.info("Cryptomator Hub login succeeded: {} encrypted with {}", authParams, keyPair.getPublic());
|
||||
// TODO decrypt and return masterkey
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.SUCCESS);
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
private void windowClosed(WindowEvent windowEvent) {
|
||||
// stop server, if it is still running
|
||||
receiveTask.cancel();
|
||||
// if not already interacted, mark this workflow as cancelled:
|
||||
if (authFlowLock.awaitingInteraction().get()) {
|
||||
LOG.debug("Authorization cancelled by user.");
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void openBrowser() {
|
||||
assert getAuthUri() != null;
|
||||
application.getHostServices().showDocument(getAuthUri().toString());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ObjectBinding<URI> authUriProperty() {
|
||||
return authUri;
|
||||
}
|
||||
|
||||
public URI getAuthUri() {
|
||||
var hubUri = hubUriRef.get();
|
||||
var redirectUri = redirectUriRef.get();
|
||||
if (hubUri == null || redirectUri == null) {
|
||||
return null;
|
||||
}
|
||||
var redirectParam = "redirect_uri=" + URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII);
|
||||
try {
|
||||
return new URI(hubUri.getScheme(), hubUri.getAuthority(), hubUri.getPath(), redirectParam, null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||
}
|
||||
}
|
||||
|
||||
public StringBinding authUriHostProperty() {
|
||||
return authUriHost;
|
||||
}
|
||||
|
||||
public String getAuthUriHost() {
|
||||
var authUri = getAuthUri();
|
||||
if (authUri == null) {
|
||||
return null;
|
||||
} else {
|
||||
return authUri.getHost();
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanBinding readyProperty() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return ready.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
/**
|
||||
* Parameters required to decrypt the masterkey:
|
||||
* <ul>
|
||||
* <li><code>m</code> Encrypted Masterkey (Base64-encoded)</li>
|
||||
* <li><code>epk</code> Ephemeral Public Key (TODO: PEM-encoded?)</li>
|
||||
* </ul>
|
||||
*/
|
||||
record AuthParams(String m, String epk) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class AuthReceiveTask extends Task<AuthParams> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthReceiveTask.class);
|
||||
|
||||
private final Consumer<URI> redirectUriConsumer;
|
||||
|
||||
/**
|
||||
* Spawns a server and waits for the redirectUri to be called.
|
||||
*
|
||||
* @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started
|
||||
*/
|
||||
public AuthReceiveTask(Consumer<URI> redirectUriConsumer) {
|
||||
this.redirectUriConsumer = redirectUriConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuthParams call() throws Exception {
|
||||
try (var receiver = AuthReceiver.start()) {
|
||||
var redirectUri = receiver.getRedirectURL();
|
||||
Platform.runLater(() -> redirectUriConsumer.accept(redirectUri));
|
||||
LOG.debug("Waiting for key on {}", redirectUri);
|
||||
return receiver.receive();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
@@ -13,19 +12,8 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.TransferQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A basic implementation for RFC 8252, Section 7.3:
|
||||
@@ -41,11 +29,11 @@ class AuthReceiver implements AutoCloseable {
|
||||
private static final String REDIRECT_SCHEME = "http";
|
||||
private static final String LOOPBACK_ADDR = "127.0.0.1";
|
||||
private static final String JSON_200 = """
|
||||
{"status": "success"}
|
||||
""";
|
||||
{"status": "success"}
|
||||
""";
|
||||
private static final String JSON_400 = """
|
||||
{"status": "missing param key"}
|
||||
""";
|
||||
{"status": "missing param"}
|
||||
""";
|
||||
|
||||
private final Server server;
|
||||
private final ServerConnector connector;
|
||||
@@ -78,7 +66,7 @@ class AuthReceiver implements AutoCloseable {
|
||||
return new AuthReceiver(server, connector, handler);
|
||||
}
|
||||
|
||||
public String receive() throws InterruptedException {
|
||||
public AuthParams receive() throws InterruptedException {
|
||||
return handler.receivedKeys.take();
|
||||
}
|
||||
|
||||
@@ -89,14 +77,15 @@ class AuthReceiver implements AutoCloseable {
|
||||
|
||||
private static class Handler extends AbstractHandler {
|
||||
|
||||
private final BlockingQueue<String> receivedKeys = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<AuthParams> receivedKeys = new LinkedBlockingQueue<>();
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||
baseRequest.setHandled(true);
|
||||
var key = req.getParameter("key");
|
||||
var m = req.getParameter("m"); // encrypted masterkey
|
||||
var epk = req.getParameter("epk"); // ephemeral public key
|
||||
byte[] response;
|
||||
if (key != null) {
|
||||
if (m != null && epk != null) {
|
||||
res.setStatus(HttpServletResponse.SC_OK);
|
||||
response = JSON_200.getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
@@ -110,8 +99,8 @@ class AuthReceiver implements AutoCloseable {
|
||||
|
||||
// the following line might trigger a server shutdown,
|
||||
// so let's make sure the response is flushed first
|
||||
if (key != null) {
|
||||
receivedKeys.add(key);
|
||||
if (m != null && epk != null) {
|
||||
receivedKeys.add(new AuthParams(m, epk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -25,10 +26,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Module
|
||||
public abstract class HubKeyLoadingModule {
|
||||
|
||||
public enum P12KeyLoading {
|
||||
LOADED,
|
||||
CREATED,
|
||||
CANCELED
|
||||
public enum AuthFlow {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -39,10 +40,16 @@ public abstract class HubKeyLoadingModule {
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static UserInteractionLock<P12KeyLoading> provideP12KeyLoadingLock() {
|
||||
static UserInteractionLock<AuthFlow> provideAuthFlowLock() {
|
||||
return new UserInteractionLock<>(null);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static AtomicReference<URI> provideHubUri() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@KeyLoadingScoped
|
||||
@@ -62,6 +69,13 @@ public abstract class HubKeyLoadingModule {
|
||||
return fxmlLoaders.createScene(FxmlFile.HUB_P12);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.HUB_AUTH)
|
||||
@KeyLoadingScoped
|
||||
static Scene provideHubAuthScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.HUB_AUTH);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(P12Controller.class)
|
||||
@@ -84,4 +98,9 @@ public abstract class HubKeyLoadingModule {
|
||||
return new NewPasswordController(resourceBundle, strengthRater);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AuthController.class)
|
||||
abstract FxController bindAuthController(AuthController controller);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
@@ -14,17 +13,13 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.unlock.UnlockCancelledException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@KeyLoading
|
||||
@@ -34,55 +29,48 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
|
||||
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
|
||||
|
||||
private final Application application;
|
||||
private final ExecutorService executor;
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> p12LoadingScene;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
|
||||
@Inject
|
||||
public HubKeyLoadingStrategy(Application application, ExecutorService executor, @KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock, AtomicReference<KeyPair> keyPairRef) {
|
||||
this.application = application;
|
||||
this.executor = executor;
|
||||
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction, AtomicReference<URI> hubUriRef) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.p12LoadingScene = p12LoadingScene;
|
||||
this.p12LoadingLock = p12LoadingLock;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.userInteraction = userInteraction;
|
||||
this.hubUriRef = hubUriRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||
hubUriRef.set(getHubUri(keyId));
|
||||
try {
|
||||
loadP12();
|
||||
LOG.info("keypair loaded {}", keyPairRef.get().getPublic());
|
||||
var task = new ReceiveEncryptedMasterkeyTask(redirectUri -> {
|
||||
openBrowser(keyId, redirectUri);
|
||||
});
|
||||
executor.submit(task);
|
||||
throw new UnlockCancelledException("not yet implemented"); // TODO
|
||||
switch (auth()) {
|
||||
case SUCCESS -> LOG.debug("TODO success"); // TODO return key
|
||||
//case FAILED -> LOG.error("failed to load keypair");
|
||||
case CANCELLED -> throw new UnlockCancelledException("User cancelled auth workflow");
|
||||
}
|
||||
throw new UnlockCancelledException("not yet implemented"); // TODO remove
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new UnlockCancelledException("Loading interrupted", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void openBrowser(URI keyId, URI redirectUri) {
|
||||
private URI getHubUri(URI keyId) {
|
||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||
var httpScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
|
||||
var redirectParam = "redirect_uri="+ URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII);
|
||||
var hubUriScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
|
||||
try {
|
||||
var uri = new URI(httpScheme, keyId.getAuthority(), keyId.getPath(), redirectParam, null);
|
||||
application.getHostServices().showDocument(uri.toString());
|
||||
return new URI(hubUriScheme, keyId.getSchemeSpecificPart(), null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||
}
|
||||
}
|
||||
|
||||
private HubKeyLoadingModule.P12KeyLoading loadP12() throws InterruptedException {
|
||||
private HubKeyLoadingModule.AuthFlow auth() throws InterruptedException {
|
||||
Platform.runLater(() -> {
|
||||
window.setScene(p12LoadingScene.get());
|
||||
window.show();
|
||||
@@ -94,7 +82,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
window.centerOnScreen();
|
||||
}
|
||||
});
|
||||
return p12LoadingLock.awaitInteraction();
|
||||
return userInteraction.awaitInteraction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class P12AccessHelper {
|
||||
keyGen.initialize(new ECGenParameterSpec(EC_CURVE_NAME));
|
||||
return keyGen;
|
||||
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
|
||||
throw new IllegalStateException("secp256r1 curve not supported");
|
||||
throw new IllegalStateException(EC_CURVE_NAME + " curve not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,17 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.common.Destroyables;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
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.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@KeyLoadingScoped
|
||||
public class P12Controller implements FxController {
|
||||
@@ -38,21 +20,21 @@ public class P12Controller implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
|
||||
|
||||
@Inject
|
||||
public P12Controller(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
|
||||
public P12Controller(@KeyLoading Stage window, Environment env, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.p12LoadingLock = p12LoadingLock;
|
||||
this.window.setOnHiding(this::windowClosed);
|
||||
this.userInteraction = userInteraction;
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
private void windowClosed(WindowEvent windowEvent) {
|
||||
// if not already interacted, mark this workflow as cancelled:
|
||||
if (p12LoadingLock.awaitingInteraction().get()) {
|
||||
LOG.debug("P12 loading canceled by user.");
|
||||
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.CANCELED);
|
||||
if (userInteraction.awaitingInteraction().get()) {
|
||||
LOG.debug("P12 loading cancelled by user.");
|
||||
userInteraction.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.cryptolib.common.Destroyables;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
@@ -19,8 +22,10 @@ import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
@@ -35,25 +40,27 @@ public class P12CreateController implements FxController {
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
|
||||
private final Lazy<Scene> authScene;
|
||||
|
||||
private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled);
|
||||
private final BooleanProperty readyToCreate = new SimpleBooleanProperty();
|
||||
|
||||
public NewPasswordController newPasswordController;
|
||||
|
||||
|
||||
@Inject
|
||||
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
|
||||
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy<Scene> authScene) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.p12LoadingLock = p12LoadingLock;
|
||||
this.authScene = authScene;
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
readyToCreate.bind(newPasswordController.goodPasswordProperty());
|
||||
newPasswordController.passwordField.requestFocus();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -61,6 +68,11 @@ public class P12CreateController implements FxController {
|
||||
window.close();
|
||||
}
|
||||
|
||||
private void windowClosed(WindowEvent windowEvent) {
|
||||
newPasswordController.passwordField.wipe();
|
||||
newPasswordController.reenterField.wipe();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void create() {
|
||||
Preconditions.checkState(newPasswordController.goodPasswordProperty().get());
|
||||
@@ -70,15 +82,12 @@ public class P12CreateController implements FxController {
|
||||
var keyPair = P12AccessHelper.createNew(p12File, pw);
|
||||
setKeyPair(keyPair);
|
||||
LOG.debug("Created .p12 file {}", p12File);
|
||||
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.CREATED);
|
||||
window.close();
|
||||
window.setScene(authScene.get());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load .p12 file.", e);
|
||||
// TODO
|
||||
} finally {
|
||||
Arrays.fill(pw, '\0');
|
||||
newPasswordController.passwordField.wipe();
|
||||
newPasswordController.reenterField.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import dagger.Lazy;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.common.Destroyables;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
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.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
@@ -20,8 +23,10 @@ import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -37,18 +42,24 @@ public class P12LoadController implements FxController {
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
|
||||
private final Lazy<Scene> authScene;
|
||||
private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled);
|
||||
|
||||
public NiceSecurePasswordField passwordField;
|
||||
|
||||
@Inject
|
||||
public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
|
||||
public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy<Scene> authScene) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.p12LoadingLock = p12LoadingLock;
|
||||
this.authScene = authScene;
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -56,6 +67,10 @@ public class P12LoadController implements FxController {
|
||||
window.close();
|
||||
}
|
||||
|
||||
private void windowClosed(WindowEvent windowEvent) {
|
||||
passwordField.wipe();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void load() {
|
||||
char[] pw = passwordField.copyChars();
|
||||
@@ -64,8 +79,7 @@ public class P12LoadController implements FxController {
|
||||
var keyPair = P12AccessHelper.loadExisting(p12File, pw);
|
||||
setKeyPair(keyPair);
|
||||
LOG.debug("Loaded .p12 file {}", p12File);
|
||||
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.LOADED);
|
||||
window.close();
|
||||
window.setScene(authScene.get());
|
||||
} catch (InvalidPassphraseException e) {
|
||||
LOG.warn("Invalid passphrase entered for .p12 file");
|
||||
Animations.createShakeWindowAnimation(window).playFromStart();
|
||||
@@ -75,7 +89,6 @@ public class P12LoadController implements FxController {
|
||||
// TODO
|
||||
} finally {
|
||||
Arrays.fill(pw, '\0');
|
||||
passwordField.wipe();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class ReceiveEncryptedMasterkeyTask extends Task<byte[]> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReceiveEncryptedMasterkeyTask.class);
|
||||
|
||||
private final Consumer<URI> redirectUriConsumer;
|
||||
|
||||
public ReceiveEncryptedMasterkeyTask(Consumer<URI> redirectUriConsumer) {
|
||||
this.redirectUriConsumer = redirectUriConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] call() throws Exception {
|
||||
try (var receiver = AuthReceiver.start()) {
|
||||
var redirectUri = receiver.getRedirectURL();
|
||||
LOG.debug("Waiting for key on {}", redirectUri);
|
||||
redirectUriConsumer.accept(redirectUri);
|
||||
var token = receiver.receive();
|
||||
return BaseEncoding.base64Url().decode(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,12 +35,12 @@ public abstract class MasterkeyFileLoadingModule {
|
||||
|
||||
public enum PasswordEntry {
|
||||
PASSWORD_ENTERED,
|
||||
CANCELED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
public enum MasterkeyFileProvision {
|
||||
MASTERKEYFILE_PROVIDED,
|
||||
CANCELED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -95,7 +95,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
if (filePath.get() == null) {
|
||||
return switch (askUserForMasterkeyFilePath()) {
|
||||
case MASTERKEYFILE_PROVIDED -> filePath.get();
|
||||
case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
|
||||
case CANCELLED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
|
||||
};
|
||||
} else {
|
||||
return filePath.get();
|
||||
@@ -121,7 +121,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
|
||||
if (password.get() == null) {
|
||||
return switch (askForPassphrase()) {
|
||||
case PASSWORD_ENTERED -> CharBuffer.wrap(password.get());
|
||||
case CANCELED -> throw new UnlockCancelledException("Password entry cancelled.");
|
||||
case CANCELLED -> throw new UnlockCancelledException("Password entry cancelled.");
|
||||
};
|
||||
} else {
|
||||
// e.g. pre-filled from keychain or previous unlock attempt
|
||||
|
||||
@@ -143,8 +143,8 @@ 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 cancelled by user.");
|
||||
passwordEntryLock.interacted(PasswordEntry.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ public class SelectMasterkeyFileController implements FxController {
|
||||
private void windowClosed(WindowEvent windowEvent) {
|
||||
// if not already interacted, mark this workflow as cancelled:
|
||||
if (masterkeyFileProvisionLock.awaitingInteraction().get()) {
|
||||
LOG.debug("Unlock canceled by user.");
|
||||
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED);
|
||||
LOG.debug("Unlock cancelled by user.");
|
||||
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user