From 0994e7bb3921e517071878f4c50d818b4eb1dfc6 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 9 Mar 2015 09:56:25 +0100 Subject: [PATCH] Show warning dialog, if MAC check failed. --- .../org/cryptomator/webdav/WebDavServer.java | 18 +- .../AbstractEncryptedNode.java | 2 +- .../jackrabbit/CryptoWarningHandler.java | 19 ++ .../jackrabbit/DavResourceFactoryImpl.java | 13 +- .../{resources => }/EncryptedDir.java | 4 +- .../{resources => }/EncryptedFile.java | 11 +- .../{resources => }/EncryptedFilePart.java | 8 +- .../{resources => }/FileTimeUtils.java | 2 +- .../{resources => }/HttpHeaderProperty.java | 2 +- .../{resources => }/NonExistingNode.java | 4 +- .../{resources => }/ResourcePathUtils.java | 4 +- .../webdav/jackrabbit/WebDavServlet.java | 8 +- .../org/cryptomator/ui/MainApplication.java | 6 +- .../ui/controllers/MacWarningsController.java | 33 ++++ .../ui/controllers/MainController.java | 51 ++++++ .../java/org/cryptomator/ui/model/Vault.java | 16 +- .../org/cryptomator/ui/util/FXThreads.java | 5 + .../ui/util/ObservableSetAggregator.java | 44 +++++ .../ui/util/ObservableSetOnMainThread.java | 163 ++++++++++++++++++ .../ui/util/mount/AbstractWebDavMount.java | 10 ++ .../ui/util/mount/FallbackWebDavMounter.java | 2 +- .../ui/util/mount/LinuxGvfsWebDavMounter.java | 2 +- .../ui/util/mount/MacOsXWebDavMounter.java | 2 +- .../ui/util/mount/WebDavMount.java | 5 +- .../ui/util/mount/WindowsWebDavMounter.java | 2 +- .../main/resources/fxml/change_password.fxml | 10 +- .../src/main/resources/fxml/initialize.fxml | 14 +- .../src/main/resources/fxml/mac_warnings.fxml | 28 +++ main/ui/src/main/resources/fxml/main.fxml | 1 - main/ui/src/main/resources/fxml/unlock.fxml | 12 +- main/ui/src/main/resources/fxml/unlocked.fxml | 11 +- main/ui/src/main/resources/fxml/welcome.fxml | 8 +- .../main/resources/localization.properties | 7 +- 33 files changed, 446 insertions(+), 81 deletions(-) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/AbstractEncryptedNode.java (99%) create mode 100644 main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/EncryptedDir.java (98%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/EncryptedFile.java (93%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/EncryptedFilePart.java (95%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/FileTimeUtils.java (95%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/HttpHeaderProperty.java (88%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/NonExistingNode.java (94%) rename main/core/src/main/java/org/cryptomator/webdav/jackrabbit/{resources => }/ResourcePathUtils.java (90%) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetAggregator.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetOnMainThread.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/mount/AbstractWebDavMount.java create mode 100644 main/ui/src/main/resources/fxml/mac_warnings.fxml diff --git a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java index 6ae5ade87..a0ff29ba3 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java +++ b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java @@ -11,6 +11,7 @@ package org.cryptomator.webdav; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; +import java.util.Collection; import java.util.UUID; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -83,11 +84,13 @@ public final class WebDavServer { /** * @param workDir Path of encrypted folder. * @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams. + * @param failingMacCollection A (observable, thread-safe) collection, to which the names of resources are written, whose MAC + * authentication fails. * @param name The name of the folder. Must be non-empty and only contain any of * _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 * @return servlet */ - public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, String name) { + public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, final Collection failingMacCollection, final String name) { try { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("name empty"); @@ -98,7 +101,7 @@ public final class WebDavServer { final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString() + "/" + name, null, null); final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, uri.getRawPath(), ServletContextHandler.SESSIONS); - final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor); + final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor, failingMacCollection); servletContext.addServlet(servlet, "/*"); servletCollection.mapContexts(); @@ -110,8 +113,8 @@ public final class WebDavServer { } } - private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor) { - final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor)); + private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor, final Collection failingMacCollection) { + final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor, failingMacCollection)); result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir); return result; } @@ -123,7 +126,7 @@ public final class WebDavServer { /** * Exposes implementation-specific methods to other modules. */ - public class ServletLifeCycleAdapter { + public class ServletLifeCycleAdapter implements AutoCloseable { private final LifeCycle lifecycle; private final URI servletUri; @@ -161,6 +164,11 @@ public final class WebDavServer { return servletUri; } + @Override + public void close() throws Exception { + this.stop(); + } + } } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/AbstractEncryptedNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java similarity index 99% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/AbstractEncryptedNode.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java index 903703236..f760c445b 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/AbstractEncryptedNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.io.IOException; import java.nio.file.AtomicMoveNotSupportedException; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java new file mode 100644 index 000000000..841c9565d --- /dev/null +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java @@ -0,0 +1,19 @@ +package org.cryptomator.webdav.jackrabbit; + +import java.util.Collection; + +class CryptoWarningHandler { + + private final Collection resourcesWithInvalidMac; + + public CryptoWarningHandler(Collection resourcesWithInvalidMac) { + this.resourcesWithInvalidMac = resourcesWithInvalidMac; + } + + public void macAuthFailed(String resourceName) { + if (!resourcesWithInvalidMac.contains(resourceName)) { + resourcesWithInvalidMac.add(resourceName); + } + } + +} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java index d37fd41d8..330f488b8 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavResourceFactoryImpl.java @@ -23,20 +23,17 @@ import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.SimpleLockManager; import org.cryptomator.crypto.Cryptor; -import org.cryptomator.webdav.jackrabbit.resources.EncryptedDir; -import org.cryptomator.webdav.jackrabbit.resources.EncryptedFile; -import org.cryptomator.webdav.jackrabbit.resources.EncryptedFilePart; -import org.cryptomator.webdav.jackrabbit.resources.NonExistingNode; -import org.cryptomator.webdav.jackrabbit.resources.ResourcePathUtils; import org.eclipse.jetty.http.HttpHeader; class DavResourceFactoryImpl implements DavResourceFactory { private final LockManager lockManager = new SimpleLockManager(); private final Cryptor cryptor; + private final CryptoWarningHandler cryptoWarningHandler; - DavResourceFactoryImpl(Cryptor cryptor) { + DavResourceFactoryImpl(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) { this.cryptor = cryptor; + this.cryptoWarningHandler = cryptoWarningHandler; } @Override @@ -70,11 +67,11 @@ class DavResourceFactoryImpl implements DavResourceFactory { } private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request) { - return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor); + return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, cryptoWarningHandler); } private EncryptedFile createFile(DavResourceLocator locator, DavSession session) { - return new EncryptedFile(this, locator, session, lockManager, cryptor); + return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler); } private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session) { diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedDir.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java similarity index 98% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedDir.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java index afcc3f578..a773b0b15 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedDir.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.io.IOException; import java.nio.channels.SeekableByteChannel; @@ -42,7 +42,7 @@ import org.cryptomator.webdav.exceptions.IORuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class EncryptedDir extends AbstractEncryptedNode { +class EncryptedDir extends AbstractEncryptedNode { private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java similarity index 93% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java index 2cd0369fe..5b5b3aa4b 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.io.EOFException; import java.io.IOException; @@ -36,12 +36,15 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class EncryptedFile extends AbstractEncryptedNode { +class EncryptedFile extends AbstractEncryptedNode { private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class); - public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { + private final CryptoWarningHandler cryptoWarningHandler; + + public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) { super(factory, locator, session, lockManager, cryptor); + this.cryptoWarningHandler = cryptoWarningHandler; } @Override @@ -81,7 +84,7 @@ public class EncryptedFile extends AbstractEncryptedNode { } catch (EOFException e) { LOG.warn("Unexpected end of stream (possibly client hung up)."); } catch (MacAuthenticationFailedException e) { - LOG.warn("MAC authentication failed, file content {} might be compromised.", getLocator().getResourcePath()); + cryptoWarningHandler.macAuthFailed(getLocator().getResourcePath()); } catch (DecryptFailedException e) { throw new IOException("Error decrypting file " + path.toString(), e); } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java similarity index 95% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java index b3c27fb18..98b67c3e2 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java @@ -1,4 +1,4 @@ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.io.EOFException; import java.io.IOException; @@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory; * * @see {@link https://tools.ietf.org/html/rfc7233#section-4} */ -public class EncryptedFilePart extends EncryptedFile { +class EncryptedFilePart extends EncryptedFile { private static final Logger LOG = LoggerFactory.getLogger(EncryptedFilePart.class); private static final String BYTE_UNIT_PREFIX = "bytes="; @@ -49,8 +49,8 @@ public class EncryptedFilePart extends EncryptedFile { private final Set> requestedContentRanges = new HashSet>(); - public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor) { - super(factory, locator, session, lockManager, cryptor); + public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler) { + super(factory, locator, session, lockManager, cryptor, cryptoWarningHandler); final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); if (rangeHeader == null) { throw new IllegalArgumentException("HTTP request doesn't contain a range header"); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/FileTimeUtils.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java similarity index 95% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/FileTimeUtils.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java index 583527242..10588325b 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/FileTimeUtils.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.nio.file.attribute.FileTime; import java.time.Instant; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/HttpHeaderProperty.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java similarity index 88% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/HttpHeaderProperty.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java index 1be9c7575..d27bc8be1 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/HttpHeaderProperty.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java @@ -1,4 +1,4 @@ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import org.apache.jackrabbit.webdav.property.AbstractDavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/NonExistingNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java similarity index 94% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/NonExistingNode.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java index 4461dfa4b..f58f20a2c 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/NonExistingNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.io.IOException; @@ -21,7 +21,7 @@ import org.apache.jackrabbit.webdav.io.OutputContext; import org.apache.jackrabbit.webdav.lock.LockManager; import org.cryptomator.crypto.Cryptor; -public class NonExistingNode extends AbstractEncryptedNode { +class NonExistingNode extends AbstractEncryptedNode { public NonExistingNode(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { super(factory, locator, session, lockManager, cryptor); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/ResourcePathUtils.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java similarity index 90% rename from main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/ResourcePathUtils.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java index 6450c6984..71f65fcd3 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/ResourcePathUtils.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/ResourcePathUtils.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.webdav.jackrabbit.resources; +package org.cryptomator.webdav.jackrabbit; import java.nio.file.FileSystems; import java.nio.file.Path; @@ -14,7 +14,7 @@ import java.nio.file.Path; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceLocator; -public final class ResourcePathUtils { +final class ResourcePathUtils { private ResourcePathUtils() { throw new IllegalStateException("not instantiable"); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java index 4379216f9..afb5c54f9 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -8,6 +8,8 @@ ******************************************************************************/ package org.cryptomator.webdav.jackrabbit; +import java.util.Collection; + import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -27,10 +29,12 @@ public class WebDavServlet extends AbstractWebdavServlet { private DavLocatorFactory davLocatorFactory; private DavResourceFactory davResourceFactory; private final Cryptor cryptor; + private final CryptoWarningHandler cryptoWarningHandler; - public WebDavServlet(final Cryptor cryptor) { + public WebDavServlet(final Cryptor cryptor, final Collection failingMacCollection) { super(); this.cryptor = cryptor; + this.cryptoWarningHandler = new CryptoWarningHandler(failingMacCollection); } @Override @@ -42,7 +46,7 @@ public class WebDavServlet extends AbstractWebdavServlet { final String fsRoot = config.getInitParameter(CFG_FS_ROOT); this.davLocatorFactory = new DavLocatorFactoryImpl(fsRoot, cryptor); - this.davResourceFactory = new DavResourceFactoryImpl(cryptor); + this.davResourceFactory = new DavResourceFactoryImpl(cryptor, cryptoWarningHandler); } @Override diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java index 2df84fc49..7889951e3 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java @@ -53,11 +53,7 @@ public class MainApplication extends Application { } private static Injector getInjector() { - try { - return Guice.createInjector(new MainModule()); - } catch (Exception e) { - throw e; - } + return Guice.createInjector(new MainModule()); } public MainApplication(Injector injector) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java new file mode 100644 index 000000000..77c409409 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java @@ -0,0 +1,33 @@ +package org.cryptomator.ui.controllers; + +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.stage.Stage; + +public class MacWarningsController { + + @FXML + private ListView warningsList; + + private Stage stage; + + @FXML + private void hideWindow(ActionEvent event) { + stage.hide(); + } + + public void setMacWarnings(ObservableList macWarnings) { + this.warningsList.setItems(macWarnings); + } + + public Stage getStage() { + return stage; + } + + public void setStage(Stage stage) { + this.stage = stage; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index fb4d79fa3..17890b531 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -13,21 +13,25 @@ import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.ResourceBundle; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.geometry.Side; import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.control.ContextMenu; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; @@ -47,6 +51,7 @@ import org.cryptomator.ui.controls.DirectoryListCell; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.model.VaultFactory; import org.cryptomator.ui.settings.Settings; +import org.cryptomator.ui.util.ObservableSetAggregator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,6 +84,9 @@ public class MainController implements Initializable, InitializationListener, Un private final ControllerFactory controllerFactory; private final Settings settings; private final VaultFactory vaultFactoy; + private final ObservableList aggregatedMacWarnings; + private final SetChangeListener macWarningsAggregator; + private final AtomicBoolean macWarningsWindowVisible; private ResourceBundle rb; @@ -88,6 +96,9 @@ public class MainController implements Initializable, InitializationListener, Un this.controllerFactory = controllerFactory; this.settings = settings; this.vaultFactoy = vaultFactoy; + this.aggregatedMacWarnings = FXCollections.observableList(new ArrayList<>()); + this.macWarningsAggregator = new ObservableSetAggregator<>(this.aggregatedMacWarnings); + this.macWarningsWindowVisible = new AtomicBoolean(); } @Override @@ -98,6 +109,8 @@ public class MainController implements Initializable, InitializationListener, Un vaultList.setItems(items); vaultList.setCellFactory(this::createDirecoryListCell); vaultList.getSelectionModel().getSelectedItems().addListener(this::selectedVaultDidChange); + + aggregatedMacWarnings.addListener(this::macWarningsDidChange); } @FXML @@ -216,6 +229,12 @@ public class MainController implements Initializable, InitializationListener, Un showChangePasswordView(selectedVault); } + private void macWarningsDidChange(ListChangeListener.Change change) { + if (aggregatedMacWarnings.size() > 0) { + Platform.runLater(this::showMacWarningsWindow); + } + } + // **************************************** // Subcontroller for right panel // **************************************** @@ -270,6 +289,7 @@ public class MainController implements Initializable, InitializationListener, Un @Override public void didUnlock(UnlockController ctrl) { + ctrl.getVault().getNamesOfResourcesWithInvalidMac().addListener(this.macWarningsAggregator); showUnlockedView(ctrl.getVault()); Platform.setImplicitExit(false); } @@ -282,6 +302,7 @@ public class MainController implements Initializable, InitializationListener, Un @Override public void didLock(UnlockedController ctrl) { + ctrl.getVault().getNamesOfResourcesWithInvalidMac().removeListener(this.macWarningsAggregator); showUnlockView(ctrl.getVault()); if (getUnlockedDirectories().isEmpty()) { Platform.setImplicitExit(true); @@ -299,6 +320,36 @@ public class MainController implements Initializable, InitializationListener, Un showUnlockView(ctrl.getVault()); } + private void showMacWarningsWindow() { + if (macWarningsWindowVisible.getAndSet(true) == false) { + try { + final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/mac_warnings.fxml"), rb); + loader.setControllerFactory(controllerFactory); + + final Parent root = loader.load(); + final Stage stage = new Stage(); + stage.setTitle(rb.getString("macWarnings.windowTitle")); + stage.setScene(new Scene(root)); + stage.sizeToScene(); + stage.setResizable(false); + stage.setOnHidden(this::onHideMacWarningsWindow); + + final MacWarningsController ctrl = loader.getController(); + ctrl.setMacWarnings(this.aggregatedMacWarnings); + ctrl.setStage(stage); + + stage.show(); + } catch (IOException e) { + throw new IllegalStateException("Failed to load fxml file.", e); + } + } + } + + private void onHideMacWarningsWindow(WindowEvent event) { + macWarningsWindowVisible.set(false); + aggregatedMacWarnings.clear(); + } + /* Convenience */ public Collection getDirectories() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 1e831c9e3..db11db34c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -10,11 +10,14 @@ import java.util.Optional; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; import org.apache.commons.lang3.StringUtils; import org.cryptomator.crypto.Cryptor; import org.cryptomator.ui.util.DeferredClosable; import org.cryptomator.ui.util.DeferredCloser; +import org.cryptomator.ui.util.FXThreads; import org.cryptomator.ui.util.mount.CommandFailedException; import org.cryptomator.ui.util.mount.WebDavMount; import org.cryptomator.ui.util.mount.WebDavMounter; @@ -38,6 +41,7 @@ public class Vault implements Serializable { private final WebDavMounter mounter; private final DeferredCloser closer; private final ObjectProperty unlocked = new SimpleObjectProperty(this, "unlocked", Boolean.FALSE); + private final ObservableSet namesOfResourcesWithInvalidMac = FXThreads.observableSetOnMainThread(FXCollections.observableSet()); private String mountName; private DeferredClosable webDavServlet = DeferredClosable.empty(); @@ -70,13 +74,14 @@ public class Vault implements Serializable { } public synchronized boolean startServer() { + namesOfResourcesWithInvalidMac.clear(); Optional o = webDavServlet.get(); if (o.isPresent() && o.get().isRunning()) { return false; } - ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, getMountName()); + ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, namesOfResourcesWithInvalidMac, mountName); if (servlet.start()) { - webDavServlet = closer.closeLater(servlet, ServletLifeCycleAdapter::stop); + webDavServlet = closer.closeLater(servlet); return true; } return false; @@ -86,6 +91,7 @@ public class Vault implements Serializable { unmount(); webDavServlet.close(); cryptor.swipeSensitiveData(); + namesOfResourcesWithInvalidMac.clear(); } public boolean mount() { @@ -94,7 +100,7 @@ public class Vault implements Serializable { return false; } try { - webDavMount = closer.closeLater(mounter.mount(o.get().getServletUri(), getMountName()), WebDavMount::unmount); + webDavMount = closer.closeLater(mounter.mount(o.get().getServletUri(), mountName)); return true; } catch (CommandFailedException e) { LOG.warn("mount failed", e); @@ -139,6 +145,10 @@ public class Vault implements Serializable { return mountName; } + public ObservableSet getNamesOfResourcesWithInvalidMac() { + return namesOfResourcesWithInvalidMac; + } + /** * Tries to form a similar string using the regular latin alphabet. * diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java b/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java index 05497db91..a1d58635c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java @@ -15,6 +15,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javafx.application.Platform; +import javafx.collections.ObservableSet; /** * Use this utility class to spawn background tasks and wait for them to finish.
@@ -118,4 +119,8 @@ public final class FXThreads { void taskFailed(Throwable t); } + public static ObservableSet observableSetOnMainThread(ObservableSet set) { + return new ObservableSetOnMainThread(set); + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetAggregator.java b/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetAggregator.java new file mode 100644 index 000000000..68fff9e79 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetAggregator.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 cryptomator.org + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial implementation + ******************************************************************************/ +package org.cryptomator.ui.util; + +import java.util.Collection; + +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; + +/** + * From the moment on, this aggregator is added as an observer to one or many {@link ObservableSet}s, change-events will be passed through + * to the given aggregation. + */ +public class ObservableSetAggregator implements SetChangeListener { + + private final Collection aggregation; + + /** + * @param aggregation Set to which elements from observed subsets shall be added. + */ + public ObservableSetAggregator(final Collection aggregation) { + this.aggregation = aggregation; + } + + @Override + public void onChanged(Change change) { + if (change.getSet() == aggregation) { + // break cycle if aggregator observes aggregation + return; + } + if (change.wasAdded()) { + aggregation.add(change.getElementAdded()); + } else if (change.wasRemoved()) { + aggregation.remove(change.getElementRemoved()); + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetOnMainThread.java b/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetOnMainThread.java new file mode 100644 index 000000000..64754042c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/ObservableSetOnMainThread.java @@ -0,0 +1,163 @@ +package org.cryptomator.ui.util; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; + +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; +import javafx.collections.SetChangeListener.Change; + +class ObservableSetOnMainThread implements ObservableSet { + + private final ObservableSet set; + private final Collection invalidationListeners; + private final Collection> setChangeListeners; + + public ObservableSetOnMainThread(ObservableSet set) { + this.set = set; + this.invalidationListeners = new HashSet<>(); + this.setChangeListeners = new HashSet<>(); + this.set.addListener(this::invalidated); + this.set.addListener(this::onChanged); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return set.contains(o); + } + + @Override + public Iterator iterator() { + return set.iterator(); + } + + @Override + public Object[] toArray() { + return set.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return set.toArray(a); + } + + @Override + public boolean add(E e) { + return set.add(e); + } + + @Override + public boolean remove(Object o) { + return set.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return set.addAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + @Override + public void clear() { + set.clear(); + } + + private void invalidated(Observable observable) { + Platform.runLater(() -> { + for (InvalidationListener listener : invalidationListeners) { + listener.invalidated(this); + } + }); + } + + @Override + public void addListener(InvalidationListener listener) { + invalidationListeners.add(listener); + } + + @Override + public void removeListener(InvalidationListener listener) { + invalidationListeners.remove(listener); + } + + private void onChanged(Change change) { + final Change c = new SetChange(this, change.getElementAdded(), change.getElementRemoved()); + Platform.runLater(() -> { + for (SetChangeListener listener : setChangeListeners) { + listener.onChanged(c); + } + }); + } + + @Override + public void addListener(SetChangeListener listener) { + setChangeListeners.add(listener); + } + + @Override + public void removeListener(SetChangeListener listener) { + setChangeListeners.add(listener); + } + + private class SetChange extends SetChangeListener.Change { + + private final E added; + private final E removed; + + public SetChange(ObservableSet set, E added, E removed) { + super(set); + this.added = added; + this.removed = removed; + } + + @Override + public boolean wasAdded() { + return added != null; + } + + @Override + public boolean wasRemoved() { + return removed != null; + } + + @Override + public E getElementAdded() { + return added; + } + + @Override + public E getElementRemoved() { + return removed; + } + + } + +} \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/AbstractWebDavMount.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/AbstractWebDavMount.java new file mode 100644 index 000000000..6ff446117 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/AbstractWebDavMount.java @@ -0,0 +1,10 @@ +package org.cryptomator.ui.util.mount; + +abstract class AbstractWebDavMount implements WebDavMount { + + @Override + public void close() throws Exception { + this.unmount(); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/FallbackWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/FallbackWebDavMounter.java index c3168ec06..6b95390ac 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/FallbackWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/FallbackWebDavMounter.java @@ -30,7 +30,7 @@ final class FallbackWebDavMounter implements WebDavMounterStrategy { @Override public WebDavMount mount(URI uri, String name) { displayMountInstructions(); - return new WebDavMount() { + return new AbstractWebDavMount() { @Override public void unmount() { displayUnmountInstructions(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java index 39a1e1ddb..4dddee38f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java @@ -48,7 +48,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy { "gvfs-mount -u \"dav:$DAV_SSP\"") .addEnv("DAV_SSP", uri.getRawSchemeSpecificPart()); mountScript.execute(); - return new WebDavMount() { + return new AbstractWebDavMount() { @Override public void unmount() throws CommandFailedException { unmountScript.execute(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java index 727e397d1..a9bf24b23 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java @@ -41,7 +41,7 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy { "umount $MOUNT_PATH") .addEnv("MOUNT_PATH", path); mountScript.execute(); - return new WebDavMount() { + return new AbstractWebDavMount() { @Override public void unmount() throws CommandFailedException { unmountScript.execute(); diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMount.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMount.java index 17a2104da..7df043421 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMount.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMount.java @@ -8,13 +8,12 @@ ******************************************************************************/ package org.cryptomator.ui.util.mount; - /** * A mounted webdav share. * * @author Markus Kreusch */ -public interface WebDavMount { +public interface WebDavMount extends AutoCloseable { /** * Unmounts this {@code WebDavMount}. @@ -22,5 +21,5 @@ public interface WebDavMount { * @throws CommandFailedException if the unmount operation fails */ void unmount() throws CommandFailedException; - + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java index d5ab03aa5..5a29df7c5 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java @@ -54,7 +54,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy { final CommandResult mountResult = mountScript.execute(30, TimeUnit.SECONDS); final String driveLetter = getDriveLetter(mountResult.getStdOut()); final Script unmountScript = fromLines("net use " + driveLetter + " /delete").addEnv("DRIVE_LETTER", driveLetter); - return new WebDavMount() { + return new AbstractWebDavMount() { @Override public void unmount() throws CommandFailedException { unmountScript.execute(); diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml index ebf2aa1de..f29b657ea 100644 --- a/main/ui/src/main/resources/fxml/change_password.fxml +++ b/main/ui/src/main/resources/fxml/change_password.fxml @@ -7,16 +7,16 @@ Contributors: Sebastian Stenzel - initial API and implementation --> - - - - - + + + + + diff --git a/main/ui/src/main/resources/fxml/initialize.fxml b/main/ui/src/main/resources/fxml/initialize.fxml index 36dbfa7ec..a543ac961 100644 --- a/main/ui/src/main/resources/fxml/initialize.fxml +++ b/main/ui/src/main/resources/fxml/initialize.fxml @@ -7,16 +7,14 @@ Contributors: Sebastian Stenzel - initial API and implementation --> - - - - - - + - - + + + + + diff --git a/main/ui/src/main/resources/fxml/mac_warnings.fxml b/main/ui/src/main/resources/fxml/mac_warnings.fxml new file mode 100644 index 000000000..4d5958993 --- /dev/null +++ b/main/ui/src/main/resources/fxml/mac_warnings.fxml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + +