mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 18:21:26 +00:00
use new auth flow
talking directly to Authorization Server and Resource Server instead of web frontend
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -27,7 +27,7 @@
|
||||
<nonModularGroupIds>com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.1.0-beta9</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>2.1.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.0.0-rc1</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.0.0-beta2</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.0.0-beta2</cryptomator.integrations.mac.version>
|
||||
|
||||
@@ -14,6 +14,7 @@ public enum FxmlFile {
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
|
||||
HUB_P12("/fxml/hub_p12.fxml"), //
|
||||
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
|
||||
LOCK_FORCED("/fxml/lock_forced.fxml"), //
|
||||
|
||||
@@ -127,28 +127,14 @@ class AuthFlow implements AutoCloseable {
|
||||
.build();
|
||||
HttpResponse<InputStream> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
if (response.statusCode() == 200) {
|
||||
var json = parseBody(response);
|
||||
var json = HttpHelper.parseBody(response);
|
||||
return json.getAsJsonObject().get("access_token").getAsString();
|
||||
} else {
|
||||
LOG.error("Unexpected HTTP response {}: {}", response.statusCode(), readBody(response));
|
||||
LOG.error("Unexpected HTTP response {}: {}", response.statusCode(), HttpHelper.readBody(response));
|
||||
throw new IOException("Unexpected HTTP response code " + response.statusCode());
|
||||
}
|
||||
}
|
||||
|
||||
private String readBody(HttpResponse<InputStream> response) throws IOException {
|
||||
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
return CharStreams.toString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonElement parseBody(HttpResponse<InputStream> response) throws IOException {
|
||||
try (InputStream in = response.body(); Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
return JsonParser.parseReader(reader);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Failed to parse JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
private URI appendQueryParams(URI uri, Map<String, String> params) {
|
||||
var oldParams = Splitter.on("&").omitEmptyStrings().splitToStream(Strings.nullToEmpty(uri.getQuery()));
|
||||
var newParams = paramString(params);
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.Bindings;
|
||||
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.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
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 static final String JWT_KEY_AUTH_ENDPOINT = "authEndpoint";
|
||||
private static final String JWT_KEY_TOKEN_ENDPOINT = "tokenEndpoint";
|
||||
private static final String JWT_KEY_CLIENT_ID = "clientId";
|
||||
|
||||
private final Application application;
|
||||
private final Stage window;
|
||||
private final ExecutorService executor;
|
||||
private final Vault vault;
|
||||
private final AtomicReference<String> tokenRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> 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, @KeyLoading Vault vault, @Named("bearerToken") AtomicReference<String> tokenRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene, ErrorComponent.Builder errorComponent) {
|
||||
this.application = application;
|
||||
this.window = window;
|
||||
this.executor = executor;
|
||||
this.vault = vault;
|
||||
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);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
assert task == null;
|
||||
try {
|
||||
task = setupTask();
|
||||
task.setOnFailed(this::authFailed);
|
||||
task.setOnSucceeded(this::authSucceeded);
|
||||
executor.submit(task);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unreadable vault config", e);
|
||||
errorComponent.cause(e).window(window).build().showErrorScene();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void browse() {
|
||||
application.getHostServices().showDocument(authUri.get().toString());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
private AuthFlowTask setupTask() throws IOException {
|
||||
var authUri = URI.create(vault.getUnverifiedVaultConfig().get(JWT_KEY_AUTH_ENDPOINT).asString());
|
||||
var tokenUri = URI.create(vault.getUnverifiedVaultConfig().get(JWT_KEY_TOKEN_ENDPOINT).asString());
|
||||
var clientId = vault.getUnverifiedVaultConfig().get(JWT_KEY_CLIENT_ID).asString();
|
||||
return new AuthFlowTask(authUri, tokenUri, clientId, this::setAuthUri);
|
||||
}
|
||||
|
||||
private void setAuthUri(URI uri) {
|
||||
authUri.set(uri);
|
||||
browse();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void authSucceeded(WorkerStateEvent workerStateEvent) {
|
||||
tokenRef.set(task.getValue());
|
||||
window.requestFocus();
|
||||
window.setScene(receiveKeyScene.get());
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public StringBinding authHostProperty() {
|
||||
return authHost;
|
||||
}
|
||||
|
||||
public String getAuthHost() {
|
||||
var uri = authUri.get();
|
||||
if (uri == null) {
|
||||
return "";
|
||||
} else {
|
||||
return uri.getAuthority().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class AuthFlowTask extends Task<String> {
|
||||
|
||||
private final URI authUri;
|
||||
private final URI tokenUri;
|
||||
private final String clientId;
|
||||
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 AuthFlowTask(URI authUri, URI tokenUri, String clientId, Consumer<URI> redirectUriConsumer) {
|
||||
this.authUri = authUri;
|
||||
this.tokenUri = tokenUri;
|
||||
this.clientId = clientId;
|
||||
this.redirectUriConsumer = redirectUriConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
try (var authFlow = AuthFlow.init(authUri, tokenUri, clientId)) {
|
||||
return authFlow.run(uri -> Platform.runLater(() -> redirectUriConsumer.accept(uri)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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<EciesParams> {
|
||||
|
||||
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 EciesParams 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,120 +0,0 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
/**
|
||||
* A basic implementation for RFC 8252, Section 7.3:
|
||||
* <p>
|
||||
* We're spawning a local http server on a system-assigned high port and
|
||||
* use <code>http://127.0.0.1:{PORT}/success</code> as a redirect URI.
|
||||
* <p>
|
||||
* Furthermore, we can deliver a html response to inform the user that the
|
||||
* auth workflow finished and she can close the browser tab.
|
||||
*/
|
||||
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"}
|
||||
""";
|
||||
private static final String JSON_400 = """
|
||||
{"status": "missing param"}
|
||||
""";
|
||||
|
||||
private final Server server;
|
||||
private final ServerConnector connector;
|
||||
private final CallbackServlet servlet;
|
||||
|
||||
private AuthReceiver(Server server, ServerConnector connector, CallbackServlet servlet) {
|
||||
assert server.isRunning();
|
||||
this.server = server;
|
||||
this.connector = connector;
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
public URI getRedirectURL() {
|
||||
try {
|
||||
return new URI(REDIRECT_SCHEME, null, LOOPBACK_ADDR, connector.getLocalPort(), null, null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("URI constructed from well-formed components.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthReceiver start() throws Exception {
|
||||
var server = new Server();
|
||||
var context = new ServletContextHandler();
|
||||
|
||||
var corsFilter = new FilterHolder(new CrossOriginFilter());
|
||||
corsFilter.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); // TODO restrict to hub host
|
||||
context.addFilter(corsFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
var servlet = new CallbackServlet();
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
|
||||
var connector = new ServerConnector(server);
|
||||
connector.setPort(0);
|
||||
connector.setHost(LOOPBACK_ADDR);
|
||||
server.setConnectors(new Connector[]{connector});
|
||||
server.setHandler(context);
|
||||
server.start();
|
||||
return new AuthReceiver(server, connector, servlet);
|
||||
}
|
||||
|
||||
public EciesParams receive() throws InterruptedException {
|
||||
return servlet.receivedKeys.take();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
private static class CallbackServlet extends HttpServlet {
|
||||
|
||||
private final BlockingQueue<EciesParams> receivedKeys = new LinkedBlockingQueue<>();
|
||||
|
||||
// TODO change to POST?
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||
var m = req.getParameter("m"); // encrypted masterkey
|
||||
var epk = req.getParameter("epk"); // ephemeral public key
|
||||
byte[] response;
|
||||
if (m != null && epk != null) {
|
||||
res.setStatus(HttpServletResponse.SC_OK);
|
||||
response = JSON_200.getBytes(StandardCharsets.UTF_8);
|
||||
} else {
|
||||
res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
response = JSON_400.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
res.setContentType("application/json;charset=utf-8");
|
||||
res.setContentLength(response.length);
|
||||
res.getOutputStream().write(response);
|
||||
res.getOutputStream().flush();
|
||||
|
||||
// the following line might trigger a server shutdown,
|
||||
// so let's make sure the response is flushed first
|
||||
if (m != null && epk != null) {
|
||||
receivedKeys.add(new EciesParams(m, epk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
class HttpHelper {
|
||||
|
||||
public static String readBody(HttpResponse<InputStream> response) throws IOException {
|
||||
try (var in = response.body(); var reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
return CharStreams.toString(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonElement parseBody(HttpResponse<InputStream> response) throws IOException {
|
||||
try (InputStream in = response.body(); Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
return JsonParser.parseReader(reader);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Failed to parse JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.cryptomator.ui.keyloading.KeyLoading;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
@@ -26,7 +27,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
@Module
|
||||
public abstract class HubKeyLoadingModule {
|
||||
|
||||
public enum AuthFlow {
|
||||
public enum HubLoadingResult {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
CANCELLED
|
||||
@@ -39,23 +40,24 @@ public abstract class HubKeyLoadingModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("bearerToken")
|
||||
@KeyLoadingScoped
|
||||
static AtomicReference<EciesParams> provideAuthParamsRef() {
|
||||
static AtomicReference<String> provideBearerTokenRef() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static UserInteractionLock<AuthFlow> provideAuthFlowLock() {
|
||||
static AtomicReference<EciesParams> provideEciesParamsRef() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static UserInteractionLock<HubLoadingResult> provideResultLock() {
|
||||
return new UserInteractionLock<>(null);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static AtomicReference<URI> provideHubUri() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@KeyLoadingScoped
|
||||
@@ -75,10 +77,18 @@ public abstract class HubKeyLoadingModule {
|
||||
return fxmlLoaders.createScene(FxmlFile.HUB_P12);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
|
||||
@KeyLoadingScoped
|
||||
static Scene provideHubAuthFlowScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.HUB_AUTH_FLOW);
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.HUB_RECEIVE_KEY)
|
||||
@KeyLoadingScoped
|
||||
static Scene provideHubAuthScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||
static Scene provideHubReceiveKeyScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.HUB_RECEIVE_KEY);
|
||||
}
|
||||
|
||||
@@ -97,6 +107,11 @@ public abstract class HubKeyLoadingModule {
|
||||
@FxControllerKey(P12CreateController.class)
|
||||
abstract FxController bindP12CreateController(P12CreateController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AuthFlowController.class)
|
||||
abstract FxController bindAuthFlowController(AuthFlowController controller);
|
||||
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
|
||||
@@ -19,7 +19,6 @@ import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -33,28 +32,26 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> p12LoadingScene;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final AtomicReference<EciesParams> authParamsRef;
|
||||
private final AtomicReference<EciesParams> eciesParams;
|
||||
|
||||
@Inject
|
||||
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction, AtomicReference<URI> hubUriRef, AtomicReference<KeyPair> keyPairRef, AtomicReference<EciesParams> authParamsRef) {
|
||||
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction, AtomicReference<KeyPair> keyPairRef, AtomicReference<EciesParams> eciesParams) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.p12LoadingScene = p12LoadingScene;
|
||||
this.userInteraction = userInteraction;
|
||||
this.hubUriRef = hubUriRef;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.authParamsRef = authParamsRef;
|
||||
this.eciesParams = eciesParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
||||
hubUriRef.set(getHubUri(keyId));
|
||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||
try {
|
||||
return switch (auth()) {
|
||||
case SUCCESS -> EciesHelper.decryptMasterkey(keyPairRef.get(), authParamsRef.get());
|
||||
case SUCCESS -> EciesHelper.decryptMasterkey(keyPairRef.get(), eciesParams.get());
|
||||
case FAILED -> throw new MasterkeyLoadingFailedException("failed to load keypair");
|
||||
case CANCELLED -> throw new UnlockCancelledException("User cancelled auth workflow");
|
||||
};
|
||||
@@ -72,17 +69,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
private URI getHubUri(URI keyId) {
|
||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||
var hubUriScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
|
||||
try {
|
||||
return new URI(hubUriScheme, keyId.getSchemeSpecificPart(), keyId.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||
}
|
||||
}
|
||||
|
||||
private HubKeyLoadingModule.AuthFlow auth() throws InterruptedException {
|
||||
private HubKeyLoadingModule.HubLoadingResult auth() throws InterruptedException {
|
||||
Platform.runLater(() -> {
|
||||
window.setScene(p12LoadingScene.get());
|
||||
window.show();
|
||||
|
||||
@@ -20,10 +20,10 @@ public class P12Controller implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction;
|
||||
|
||||
@Inject
|
||||
public P12Controller(@KeyLoading Stage window, Environment env, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction) {
|
||||
public P12Controller(@KeyLoading Stage window, Environment env, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> userInteraction) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.userInteraction = userInteraction;
|
||||
@@ -34,7 +34,7 @@ public class P12Controller implements FxController {
|
||||
// if not already interacted, mark this workflow as cancelled:
|
||||
if (userInteraction.awaitingInteraction().get()) {
|
||||
LOG.debug("P12 loading cancelled by user.");
|
||||
userInteraction.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
|
||||
userInteraction.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public class P12CreateController implements FxController {
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final Lazy<Scene> receiveKeyScene;
|
||||
private final Lazy<Scene> authFlowScene;
|
||||
|
||||
private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty();
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled);
|
||||
@@ -48,11 +48,11 @@ public class P12CreateController implements FxController {
|
||||
public NewPasswordController newPasswordController;
|
||||
|
||||
@Inject
|
||||
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
|
||||
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.receiveKeyScene = receiveKeyScene;
|
||||
this.authFlowScene = authFlowScene;
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class P12CreateController implements FxController {
|
||||
var keyPair = P12AccessHelper.createNew(p12File, pw);
|
||||
setKeyPair(keyPair);
|
||||
LOG.debug("Created .p12 file {}", p12File);
|
||||
window.setScene(receiveKeyScene.get());
|
||||
window.setScene(authFlowScene.get());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load .p12 file.", e);
|
||||
// TODO
|
||||
|
||||
@@ -41,18 +41,18 @@ public class P12LoadController implements FxController {
|
||||
private final Stage window;
|
||||
private final Environment env;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final Lazy<Scene> receiveKeyScene;
|
||||
private final Lazy<Scene> authFlowScene;
|
||||
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, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
|
||||
public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene) {
|
||||
this.window = window;
|
||||
this.env = env;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.receiveKeyScene = receiveKeyScene;
|
||||
this.authFlowScene = authFlowScene;
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class P12LoadController implements FxController {
|
||||
var keyPair = P12AccessHelper.loadExisting(p12File, pw);
|
||||
setKeyPair(keyPair);
|
||||
LOG.debug("Loaded .p12 file {}", p12File);
|
||||
window.setScene(receiveKeyScene.get());
|
||||
window.setScene(authFlowScene.get());
|
||||
} catch (InvalidPassphraseException e) {
|
||||
LOG.warn("Invalid passphrase entered for .p12 file");
|
||||
Animations.createShakeWindowAnimation(window).playFromStart();
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.gson.JsonElement;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
@@ -11,17 +13,25 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.WorkerStateEvent;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -31,53 +41,88 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
public class ReceiveKeyController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReceiveKeyController.class);
|
||||
private static final String SCHEME_PREFIX = "hub+";
|
||||
|
||||
private final Application application;
|
||||
private final ExecutorService executor;
|
||||
private final Stage window;
|
||||
private final KeyPair keyPair;
|
||||
private final AtomicReference<EciesParams> authParamsRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
private final String bearerToken;
|
||||
private final AtomicReference<EciesParams> eciesParamsRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
private final ObjectProperty<URI> redirectUriRef;
|
||||
private final BooleanBinding ready;
|
||||
private final AuthReceiveTask receiveTask;
|
||||
private final URI vaultBaseUri;
|
||||
private final ObjectProperty<ReceiveKeyState> state = new SimpleObjectProperty<>(ReceiveKeyState.LOADING);
|
||||
private final HttpClient httpClient;
|
||||
|
||||
public TextField deviceName;
|
||||
|
||||
@Inject
|
||||
public ReceiveKeyController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, AtomicReference<EciesParams> authParamsRef, UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock, AtomicReference<URI> hubUriRef, ErrorComponent.Builder errorComponent) {
|
||||
this.application = application;
|
||||
this.executor = executor;
|
||||
public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, @Named("bearerToken") AtomicReference<String> tokenRef, AtomicReference<EciesParams> eciesParamsRef, UserInteractionLock<HubKeyLoadingModule.HubLoadingResult> result, ErrorComponent.Builder errorComponent) {
|
||||
this.window = window;
|
||||
this.keyPair = Objects.requireNonNull(keyPairRef.get());
|
||||
this.authParamsRef = authParamsRef;
|
||||
this.authFlowLock = authFlowLock;
|
||||
this.hubUriRef = hubUriRef;
|
||||
this.bearerToken = Objects.requireNonNull(tokenRef.get());
|
||||
this.eciesParamsRef = eciesParamsRef;
|
||||
this.result = result;
|
||||
this.errorComponent = errorComponent;
|
||||
this.redirectUriRef = new SimpleObjectProperty<>();
|
||||
this.ready = redirectUriRef.isNotNull();
|
||||
this.receiveTask = new AuthReceiveTask(redirectUriRef::set);
|
||||
this.vaultBaseUri = getVaultBaseUri(vault);
|
||||
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
|
||||
this.httpClient = HttpClient.newBuilder().executor(executor).build();
|
||||
// var deviceKey = BaseEncoding.base64Url().omitPadding().encode(keyPairRef.get().getPublic().getEncoded());
|
||||
// LOG.info("deviceKey {}", deviceKey);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
Preconditions.checkState(hubUriRef.get() != null);
|
||||
receiveTask.setOnSucceeded(this::receivedKey);
|
||||
receiveTask.setOnFailed(this::keyRetrievalFailed);
|
||||
executor.submit(receiveTask);
|
||||
var keyUri = appendPath(vaultBaseUri, "/keys/desktop-app-3000"); // TODO use actual device id
|
||||
var request = HttpRequest.newBuilder(keyUri) //
|
||||
.header("Authorization", "Bearer " + bearerToken) //
|
||||
.GET() //
|
||||
.build();
|
||||
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) //
|
||||
.whenCompleteAsync(this::loadedExistingKey, Platform::runLater);
|
||||
}
|
||||
|
||||
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 loadedExistingKey(HttpResponse<InputStream> response, Throwable error) {
|
||||
if (error != null) {
|
||||
retrievalFailed(error);
|
||||
} else {
|
||||
switch (response.statusCode()) {
|
||||
case 200 -> retrievalSucceeded(response);
|
||||
case 404 -> state.set(ReceiveKeyState.NEEDS_REGISTRATION);
|
||||
default -> retrievalFailed(new IOException("Unexpected response " + response.statusCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receivedKey(WorkerStateEvent workerStateEvent) {
|
||||
authParamsRef.set(Objects.requireNonNull(receiveTask.getValue()));
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.SUCCESS);
|
||||
window.close();
|
||||
@FXML
|
||||
public void register() {
|
||||
Preconditions.checkArgument(deviceName.textProperty().isNotEmpty().get(), "device name must not be empty");
|
||||
// var keyUri = appendPath(vaultBaseUri, "../../devices/desktop-app-3000");
|
||||
// var request = HttpRequest.newBuilder(keyUri) //
|
||||
// .header("Authorization", "Bearer " + bearerToken) //
|
||||
// .GET() //
|
||||
// .build();
|
||||
// httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) //
|
||||
// .whenCompleteAsync(this::loadedExistingKey, Platform::runLater);
|
||||
}
|
||||
|
||||
private void retrievalSucceeded(HttpResponse<InputStream> response) {
|
||||
try {
|
||||
var json = HttpHelper.parseBody(response);
|
||||
Preconditions.checkArgument(json.isJsonObject());
|
||||
Preconditions.checkArgument(json.getAsJsonObject().has("device_specific_masterkey"));
|
||||
Preconditions.checkArgument(json.getAsJsonObject().has("ephemeral_public_key"));
|
||||
var m = json.getAsJsonObject().get("device_specific_masterkey").getAsString();
|
||||
var epk = json.getAsJsonObject().get("ephemeral_public_key").getAsString();
|
||||
eciesParamsRef.set(new EciesParams(m, epk));
|
||||
result.interacted(HubKeyLoadingModule.HubLoadingResult.SUCCESS);
|
||||
window.close();
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
retrievalFailed(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void retrievalFailed(Throwable cause) {
|
||||
result.interacted(HubKeyLoadingModule.HubLoadingResult.FAILED);
|
||||
LOG.error("Key retrieval failed", cause);
|
||||
errorComponent.cause(cause).window(window).build().showErrorScene();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -86,44 +131,41 @@ public class ReceiveKeyController implements FxController {
|
||||
}
|
||||
|
||||
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()) {
|
||||
if (result.awaitingInteraction().get()) {
|
||||
LOG.debug("Authorization cancelled by user.");
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
|
||||
result.interacted(HubKeyLoadingModule.HubLoadingResult.CANCELLED);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void openBrowser() {
|
||||
assert ready.get();
|
||||
var hubUri = Objects.requireNonNull(hubUriRef.get());
|
||||
var redirectUri = Objects.requireNonNull(redirectUriRef.get());
|
||||
var sb = new StringBuilder(hubUri.toString());
|
||||
sb.append("?redirect_uri=").append(URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII));
|
||||
sb.append("&device_id=").append("desktop-app-3000");
|
||||
sb.append("&device_key=").append(BaseEncoding.base64Url().omitPadding().encode(keyPair.getPublic().getEncoded()));
|
||||
var url = sb.toString();
|
||||
application.getHostServices().showDocument(url);
|
||||
private static URI appendPath(URI base, String path) {
|
||||
try {
|
||||
var newPath = base.getPath() + path;
|
||||
return new URI(base.getScheme(), base.getAuthority(), newPath, base.getQuery(), base.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Can't append '" + path + "' to URI: " + base, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static URI getVaultBaseUri(Vault vault) {
|
||||
try {
|
||||
var kid = vault.getUnverifiedVaultConfig().getKeyId();
|
||||
assert kid.getScheme().startsWith(SCHEME_PREFIX);
|
||||
var hubUriScheme = kid.getScheme().substring(SCHEME_PREFIX.length());
|
||||
return new URI(hubUriScheme, kid.getSchemeSpecificPart(), kid.getFragment());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("URI constructed from params known to be valid", e);
|
||||
}
|
||||
}
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getHubUriHost() {
|
||||
var hubUri = hubUriRef.get();
|
||||
if (hubUri == null) {
|
||||
return null;
|
||||
} else {
|
||||
return hubUri.getHost();
|
||||
}
|
||||
public ObjectProperty<ReceiveKeyState> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public BooleanBinding readyProperty() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return ready.get();
|
||||
public ReceiveKeyState getState() {
|
||||
return state.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
public enum ReceiveKeyState {
|
||||
LOADING,
|
||||
NEEDS_REGISTRATION
|
||||
}
|
||||
42
src/main/resources/fxml/hub_auth_flow.fxml
Normal file
42
src/main/resources/fxml/hub_auth_flow.fxml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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.Hyperlink?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.keyloading.hub.AuthFlowController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<HBox spacing="12" VBox.vgrow="ALWAYS">
|
||||
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" cache="true">
|
||||
<Image url="@../img/bot/bot.png"/>
|
||||
</ImageView>
|
||||
<TextFlow visible="${!controller.authHost.empty}" managed="${!controller.authHost.empty}">
|
||||
<Text text="TODO: please login via " />
|
||||
<Hyperlink styleClass="hyperlink-underline" text="${controller.authHost}" onAction="#browse"/>
|
||||
</TextFlow>
|
||||
</HBox>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
|
||||
<?import org.cryptomator.ui.keyloading.hub.ReceiveKeyState?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.keyloading.hub.ReceiveKeyController"
|
||||
@@ -18,6 +18,10 @@
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<fx:define>
|
||||
<ReceiveKeyState fx:id="loading" fx:constant="LOADING" />
|
||||
<ReceiveKeyState fx:id="needsRegistration" fx:constant="NEEDS_REGISTRATION"/>
|
||||
</fx:define>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
@@ -26,17 +30,20 @@
|
||||
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" cache="true">
|
||||
<Image url="@../img/bot/bot.png"/>
|
||||
</ImageView>
|
||||
<TextFlow visible="${controller.ready}" managed="${controller.ready}">
|
||||
<Text text="TODO: please login via " />
|
||||
<Hyperlink styleClass="hyperlink-underline" text="${controller.hubUriHost}" onAction="#openBrowser"/>
|
||||
</TextFlow>
|
||||
<FontAwesome5Spinner glyphSize="12" visible="${!controller.ready}" managed="${!controller.ready}"/>
|
||||
|
||||
<FontAwesome5Spinner glyphSize="12" visible="${controller.state == loading}" managed="${controller.state == loading}"/>
|
||||
|
||||
<VBox spacing="6" visible="${controller.state == needsRegistration}" managed="${controller.state == needsRegistration}">
|
||||
<Label text="TODO: register device" labelFor="$locationTextField"/>
|
||||
<TextField fx:id="deviceName" promptText="TODO: device name" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
|
||||
<Button text="TODO: register device" ButtonBar.buttonData="NEXT_FORWARD" visible="${controller.state == needsRegistration}" managed="${controller.state == needsRegistration}" defaultButton="true" onAction="#register"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
@@ -11,7 +11,7 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
Cryptomator uses 46 third-party dependencies under the following licenses:
|
||||
Apache License v2.0:
|
||||
- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
|
||||
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
|
||||
@@ -33,7 +33,10 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:10.0.6 - https://eclipse.org/jetty/jetty-servlets)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
|
||||
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:10.0.6 - https://eclipse.org/jetty/jetty-webapp)
|
||||
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:10.0.6 - https://eclipse.org/jetty/jetty-xml)
|
||||
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
|
||||
BSD:
|
||||
- asm (org.ow2.asm:asm:7.1 - http://asm.ow2.org/)
|
||||
@@ -53,7 +56,10 @@ Cryptomator uses 43 third-party dependencies under the following licenses:
|
||||
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
|
||||
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
|
||||
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
|
||||
- Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:10.0.6 - https://eclipse.org/jetty/jetty-servlets)
|
||||
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
|
||||
- Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:10.0.6 - https://eclipse.org/jetty/jetty-webapp)
|
||||
- Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:10.0.6 - https://eclipse.org/jetty/jetty-xml)
|
||||
Eclipse Public License - v 1.0:
|
||||
- Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - http://logback.qos.ch/logback-classic)
|
||||
- Logback Core Module (ch.qos.logback:logback-core:1.2.3 - http://logback.qos.ch/logback-core)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
public class AuthReceiverTest {
|
||||
|
||||
static {
|
||||
System.setProperty("LOGLEVEL", "INFO");
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try (var receiver = AuthReceiver.start()) {
|
||||
System.out.println("Waiting on " + receiver.getRedirectURL());
|
||||
var token = receiver.receive();
|
||||
System.out.println("SUCCESS: " + token);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
System.out.println("CANCELLED");
|
||||
} catch (Exception e) {
|
||||
System.out.println("ERROR");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user