diff --git a/pom.xml b/pom.xml index c216885d7..d9feb3fe5 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 31.1-jre 2.41 2.9.0 - 1.5.2 + 1.6.0 1.7.36 1.2.11 diff --git a/src/main/java/org/cryptomator/common/settings/UiTheme.java b/src/main/java/org/cryptomator/common/settings/UiTheme.java index 62fe714e4..24e73dad6 100644 --- a/src/main/java/org/cryptomator/common/settings/UiTheme.java +++ b/src/main/java/org/cryptomator/common/settings/UiTheme.java @@ -3,9 +3,9 @@ package org.cryptomator.common.settings; import org.apache.commons.lang3.SystemUtils; public enum UiTheme { - LIGHT("preferences.general.theme.light"), // - DARK("preferences.general.theme.dark"), // - AUTOMATIC("preferences.general.theme.automatic"); + LIGHT("preferences.interface.theme.light"), // + DARK("preferences.interface.theme.dark"), // + AUTOMATIC("preferences.interface.theme.automatic"); public static UiTheme[] applicableValues() { if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) { diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 90f391ec6..3cd5ec727 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -21,15 +21,18 @@ import javax.inject.Inject; import javax.inject.Singleton; import javafx.application.Application; import javafx.stage.Stage; +import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.Executors; @Singleton public class Cryptomator { + private static final long STARTUP_TIME = System.currentTimeMillis(); // DaggerCryptomatorComponent gets generated by Dagger. // Run Maven and include target/generated-sources/annotations in your IDE. - private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create(); + private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME); private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); private final LoggerConfiguration logConfig; @@ -50,6 +53,20 @@ public class Cryptomator { } public static void main(String[] args) { + var printVersion = Optional.ofNullable(args) // + .stream() //Streams either one element (the args-array) or zero elements + .flatMap(Arrays::stream) // + .anyMatch(arg -> "-v".equals(arg) || "--version".equals(arg)); + + if (printVersion) { + var appVer = System.getProperty("cryptomator.appVersion", "SNAPSHOT"); + var buildNumber = System.getProperty("cryptomator.buildNumber", "SNAPSHOT"); + + //Reduce noise for parsers by using System.out directly + System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber); + return; + } + int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); LOG.info("Exit {}", exitCode); System.exit(exitCode); // end remaining non-daemon threads. @@ -63,6 +80,7 @@ public class Cryptomator { */ private int run(String[] args) { logConfig.init(); + LOG.debug("Dagger graph initialized after {}ms", System.currentTimeMillis() - STARTUP_TIME); LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); supportedLanguages.applyPreferred(); @@ -112,7 +130,7 @@ public class Cryptomator { @Override public void start(Stage primaryStage) { - LOG.info("JavaFX application started."); + LOG.info("JavaFX runtime started after {}ms", System.currentTimeMillis() - STARTUP_TIME); FxApplicationComponent component = CRYPTOMATOR_COMPONENT.fxAppComponentBuilder() // .fxApplication(this) // .primaryStage(primaryStage) // diff --git a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java index b43c0eca0..8c6443717 100644 --- a/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java +++ b/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java @@ -1,10 +1,12 @@ package org.cryptomator.launcher; +import dagger.BindsInstance; import dagger.Component; import org.cryptomator.common.CommonsModule; import org.cryptomator.logging.LoggerModule; import org.cryptomator.ui.fxapp.FxApplicationComponent; +import javax.inject.Named; import javax.inject.Singleton; @Singleton @@ -15,4 +17,9 @@ public interface CryptomatorComponent { FxApplicationComponent.Builder fxAppComponentBuilder(); + @Component.Factory + interface Factory { + CryptomatorComponent create(@BindsInstance @Named("startupTime") long startupTime); + } + } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index b810a817f..3ddb7cba6 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; import javafx.application.Platform; @FxApplicationScoped @@ -14,6 +15,7 @@ public class FxApplication { private static final Logger LOG = LoggerFactory.getLogger(FxApplication.class); + private final long startupTime; private final Settings settings; private final AppLaunchEventHandler launchEventHandler; private final Lazy trayMenu; @@ -23,7 +25,8 @@ public class FxApplication { private final AutoUnlocker autoUnlocker; @Inject - FxApplication(Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { + FxApplication(@Named("startupTime") long startupTime, Settings settings, AppLaunchEventHandler launchEventHandler, Lazy trayMenu, FxApplicationWindows appWindows, FxApplicationStyle applicationStyle, FxApplicationTerminator applicationTerminator, AutoUnlocker autoUnlocker) { + this.startupTime = startupTime; this.settings = settings; this.launchEventHandler = launchEventHandler; this.trayMenu = trayMenu; @@ -58,6 +61,10 @@ public class FxApplication { stage.setIconified(true); } } + LOG.debug("Main window initialized after {}ms", System.currentTimeMillis() - startupTime); + }).exceptionally(error -> { + LOG.error("Failed to show main window", error); + return null; }); launchEventHandler.startHandlingLaunchEvents(); diff --git a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java index 50826f4c4..d33e919b6 100644 --- a/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java @@ -1,38 +1,26 @@ package org.cryptomator.ui.preferences; -import com.google.common.base.Strings; import org.cryptomator.common.Environment; -import org.cryptomator.common.LicenseHolder; import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.UiTheme; import org.cryptomator.integrations.autostart.AutoStartProvider; import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException; import org.cryptomator.integrations.keychain.KeychainAccessProvider; -import org.cryptomator.launcher.SupportedLanguages; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.application.Application; import javafx.beans.binding.Bindings; -import javafx.beans.property.ObjectProperty; -import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; -import javafx.geometry.NodeOrientation; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; -import javafx.scene.control.RadioButton; -import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; import javafx.stage.Stage; import javafx.util.StringConverter; import java.util.List; -import java.util.Locale; import java.util.Optional; -import java.util.ResourceBundle; @PreferencesScoped public class GeneralPreferencesController implements FxController { @@ -41,39 +29,23 @@ public class GeneralPreferencesController implements FxController { private final Stage window; private final Settings settings; - private final boolean trayMenuInitialized; - private final boolean trayMenuSupported; private final Optional autoStartProvider; - private final ObjectProperty selectedTabProperty; - private final LicenseHolder licenseHolder; - private final ResourceBundle resourceBundle; private final Application application; private final Environment environment; private final List keychainAccessProviders; private final FxApplicationWindows appWindows; - public ChoiceBox themeChoiceBox; public ChoiceBox keychainBackendChoiceBox; - public CheckBox showMinimizeButtonCheckbox; - public CheckBox showTrayIconCheckbox; public CheckBox startHiddenCheckbox; - public ChoiceBox preferredLanguageChoiceBox; public CheckBox debugModeCheckbox; public CheckBox autoStartCheckbox; public ToggleGroup nodeOrientation; - public RadioButton nodeOrientationLtr; - public RadioButton nodeOrientationRtl; @Inject - GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, TrayMenuComponent trayMenu, Optional autoStartProvider, List keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle, Application application, Environment environment, FxApplicationWindows appWindows) { + GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, Optional autoStartProvider, List keychainAccessProviders, Application application, Environment environment, FxApplicationWindows appWindows) { this.window = window; this.settings = settings; - this.trayMenuInitialized = trayMenu.isInitialized(); - this.trayMenuSupported = trayMenu.isSupported(); this.autoStartProvider = autoStartProvider; this.keychainAccessProviders = keychainAccessProviders; - this.selectedTabProperty = selectedTabProperty; - this.licenseHolder = licenseHolder; - this.resourceBundle = resourceBundle; this.application = application; this.environment = environment; this.appWindows = appWindows; @@ -81,32 +53,12 @@ public class GeneralPreferencesController implements FxController { @FXML public void initialize() { - themeChoiceBox.getItems().addAll(UiTheme.applicableValues()); - if (!themeChoiceBox.getItems().contains(settings.theme().get())) { - settings.theme().set(UiTheme.LIGHT); - } - themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); - themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); - - showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton()); - - showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon()); - startHiddenCheckbox.selectedProperty().bindBidirectional(settings.startHidden()); - preferredLanguageChoiceBox.getItems().add(null); - preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS); - preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); - preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); - debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode()); autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled())); - nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); - nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); - nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); - var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders); keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders); keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider().get())); @@ -114,29 +66,10 @@ public class GeneralPreferencesController implements FxController { Bindings.bindBidirectional(settings.keychainProvider(), keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter); } - - public boolean isTrayMenuInitialized() { - return trayMenuInitialized; - } - - public boolean isTrayMenuSupported() { - return trayMenuSupported; - } - public boolean isAutoStartSupported() { return autoStartProvider.isPresent(); } - private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { - if (nodeOrientationLtr.equals(newValue)) { - settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); - } else if (nodeOrientationRtl.equals(newValue)) { - settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); - } else { - LOG.warn("Unexpected toggle option {}", newValue); - } - } - @FXML public void toggleAutoStart() { autoStartProvider.ifPresent(autoStart -> { @@ -155,16 +88,6 @@ public class GeneralPreferencesController implements FxController { }); } - public LicenseHolder getLicenseHolder() { - return licenseHolder; - } - - - @FXML - public void showContributeTab() { - selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE); - } - @FXML public void showLogfileDirectory() { environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString())); @@ -172,53 +95,7 @@ public class GeneralPreferencesController implements FxController { /* Helper classes */ - private static class UiThemeConverter extends StringConverter { - - private final ResourceBundle resourceBundle; - - UiThemeConverter(ResourceBundle resourceBundle) { - this.resourceBundle = resourceBundle; - } - - @Override - public String toString(UiTheme impl) { - return resourceBundle.getString(impl.getDisplayName()); - } - - @Override - public UiTheme fromString(String string) { - throw new UnsupportedOperationException(); - } - - } - - private static class LanguageTagConverter extends StringConverter { - - private final ResourceBundle resourceBundle; - - LanguageTagConverter(ResourceBundle resourceBundle) { - this.resourceBundle = resourceBundle; - } - - @Override - public String toString(String tag) { - if (tag == null) { - return resourceBundle.getString("preferences.general.language.auto"); - } else { - var locale = Locale.forLanguageTag(tag); - var lang = locale.getDisplayLanguage(locale); - var region = locale.getDisplayCountry(locale); - return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")"); - } - } - - @Override - public String fromString(String displayLanguage) { - throw new UnsupportedOperationException(); - } - } - - private class KeychainProviderDisplayNameConverter extends StringConverter { + private static class KeychainProviderDisplayNameConverter extends StringConverter { @Override public String toString(KeychainAccessProvider provider) { diff --git a/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java new file mode 100644 index 000000000..dd463b9e3 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/preferences/InterfacePreferencesController.java @@ -0,0 +1,156 @@ +package org.cryptomator.ui.preferences; + +import com.google.common.base.Strings; +import org.cryptomator.common.LicenseHolder; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.UiTheme; +import org.cryptomator.launcher.SupportedLanguages; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.traymenu.TrayMenuComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.geometry.NodeOrientation; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.util.StringConverter; +import java.util.Locale; +import java.util.ResourceBundle; + +@PreferencesScoped +public class InterfacePreferencesController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(InterfacePreferencesController.class); + + private final Settings settings; + private final boolean trayMenuInitialized; + private final boolean trayMenuSupported; + private final ObjectProperty selectedTabProperty; + private final LicenseHolder licenseHolder; + private final ResourceBundle resourceBundle; + public ChoiceBox themeChoiceBox; + public CheckBox showMinimizeButtonCheckbox; + public CheckBox showTrayIconCheckbox; + public ChoiceBox preferredLanguageChoiceBox; + public ToggleGroup nodeOrientation; + public RadioButton nodeOrientationLtr; + public RadioButton nodeOrientationRtl; + + @Inject + InterfacePreferencesController(Settings settings, TrayMenuComponent trayMenu, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ResourceBundle resourceBundle) { + this.settings = settings; + this.trayMenuInitialized = trayMenu.isInitialized(); + this.trayMenuSupported = trayMenu.isSupported(); + this.selectedTabProperty = selectedTabProperty; + this.licenseHolder = licenseHolder; + this.resourceBundle = resourceBundle; + } + + @FXML + public void initialize() { + themeChoiceBox.getItems().addAll(UiTheme.applicableValues()); + if (!themeChoiceBox.getItems().contains(settings.theme().get())) { + settings.theme().set(UiTheme.LIGHT); + } + themeChoiceBox.valueProperty().bindBidirectional(settings.theme()); + themeChoiceBox.setConverter(new UiThemeConverter(resourceBundle)); + + showMinimizeButtonCheckbox.selectedProperty().bindBidirectional(settings.showMinimizeButton()); + + showTrayIconCheckbox.selectedProperty().bindBidirectional(settings.showTrayIcon()); + + preferredLanguageChoiceBox.getItems().add(null); + preferredLanguageChoiceBox.getItems().addAll(SupportedLanguages.LANGUAGAE_TAGS); + preferredLanguageChoiceBox.valueProperty().bindBidirectional(settings.languageProperty()); + preferredLanguageChoiceBox.setConverter(new LanguageTagConverter(resourceBundle)); + + nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT); + nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT); + nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation); + } + + + public boolean isTrayMenuInitialized() { + return trayMenuInitialized; + } + + public boolean isTrayMenuSupported() { + return trayMenuSupported; + } + + private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (nodeOrientationLtr.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.LEFT_TO_RIGHT); + } else if (nodeOrientationRtl.equals(newValue)) { + settings.userInterfaceOrientation().set(NodeOrientation.RIGHT_TO_LEFT); + } else { + LOG.warn("Unexpected toggle option {}", newValue); + } + } + + public LicenseHolder getLicenseHolder() { + return licenseHolder; + } + + + @FXML + public void showContributeTab() { + selectedTabProperty.set(SelectedPreferencesTab.CONTRIBUTE); + } + + /* Helper classes */ + + private static class UiThemeConverter extends StringConverter { + + private final ResourceBundle resourceBundle; + + UiThemeConverter(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + @Override + public String toString(UiTheme impl) { + return resourceBundle.getString(impl.getDisplayName()); + } + + @Override + public UiTheme fromString(String string) { + throw new UnsupportedOperationException(); + } + + } + + private static class LanguageTagConverter extends StringConverter { + + private final ResourceBundle resourceBundle; + + LanguageTagConverter(ResourceBundle resourceBundle) { + this.resourceBundle = resourceBundle; + } + + @Override + public String toString(String tag) { + if (tag == null) { + return resourceBundle.getString("preferences.interface.language.auto"); + } else { + var locale = Locale.forLanguageTag(tag); + var lang = locale.getDisplayLanguage(locale); + var region = locale.getDisplayCountry(locale); + return lang + (Strings.isNullOrEmpty(region) ? "" : " (" + region + ")"); + } + } + + @Override + public String fromString(String displayLanguage) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java index 276794753..caaaf7d80 100644 --- a/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java +++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java @@ -24,6 +24,7 @@ public class PreferencesController implements FxController { private final BooleanBinding updateAvailable; public TabPane tabPane; public Tab generalTab; + public Tab interfaceTab; public Tab volumeTab; public Tab updatesTab; public Tab contributeTab; @@ -50,10 +51,11 @@ public class PreferencesController implements FxController { private Tab getTabToSelect(SelectedPreferencesTab selectedTab) { return switch (selectedTab) { - case UPDATES -> updatesTab; - case VOLUME -> volumeTab; - case CONTRIBUTE -> contributeTab; case GENERAL -> generalTab; + case INTERFACE -> interfaceTab; + case VOLUME -> volumeTab; + case UPDATES -> updatesTab; + case CONTRIBUTE -> contributeTab; case ABOUT -> aboutTab; case ANY -> updateAvailable.get() ? updatesTab : generalTab; }; diff --git a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java index 189666591..858f8f1d8 100644 --- a/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java +++ b/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java @@ -64,6 +64,11 @@ abstract class PreferencesModule { @FxControllerKey(GeneralPreferencesController.class) abstract FxController bindGeneralPreferencesController(GeneralPreferencesController controller); + @Binds + @IntoMap + @FxControllerKey(InterfacePreferencesController.class) + abstract FxController bindInterfacePreferencesController(InterfacePreferencesController controller); + @Binds @IntoMap @FxControllerKey(UpdatesPreferencesController.class) diff --git a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java index 892d16a8c..00b519493 100644 --- a/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java +++ b/src/main/java/org/cryptomator/ui/preferences/SelectedPreferencesTab.java @@ -11,6 +11,11 @@ public enum SelectedPreferencesTab { */ GENERAL, + /** + * Show interface tab + */ + INTERFACE, + /** * Show volume tab */ diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java index 311f5746e..a1180dd9f 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java @@ -7,6 +7,7 @@ import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.jetbrains.annotations.Nullable; import javax.inject.Inject; import javax.inject.Singleton; @@ -16,6 +17,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collection; +import java.util.function.Predicate; import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; @@ -102,12 +104,29 @@ public class RecoveryKeyFactory { * @return true if this seems to be a legitimate recovery key */ public boolean validateRecoveryKey(String recoveryKey) { + return validateRecoveryKey(recoveryKey, null); + } + + /** + * Checks whether a String is a syntactically correct recovery key with a valid checksum and passes the extended validation. + * + * @param recoveryKey A word sequence which might be a recovery key + * @param extendedValidation Additional verification of the decoded key (optional) + * @return true if this seems to be a legitimate recovery key and passes the extended validation + */ + public boolean validateRecoveryKey(String recoveryKey, @Nullable Predicate extendedValidation) { + byte[] key = new byte[0]; try { - byte[] key = decodeRecoveryKey(recoveryKey); - Arrays.fill(key, (byte) 0x00); - return true; + key = decodeRecoveryKey(recoveryKey); + if (extendedValidation != null) { + return extendedValidation.test(key); + } else { + return true; + } } catch (IllegalArgumentException e) { return false; + } finally { + Arrays.fill(key, (byte) 0x00); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index b30167e73..0522e3a8d 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -4,7 +4,9 @@ import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; +import org.cryptomator.common.Nullable; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -22,12 +24,25 @@ import javafx.beans.property.StringProperty; import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; +import java.io.IOException; import java.util.Map; import java.util.ResourceBundle; @Module abstract class RecoveryKeyModule { + @Provides + @Nullable + @RecoveryKeyWindow + @RecoveryKeyScoped + static VaultConfig.UnverifiedVaultConfig vaultConfig(@RecoveryKeyWindow Vault vault) { + try { + return vault.getVaultConfigCache().get(); + } catch (IOException e) { + return null; + } + } + @Provides @RecoveryKeyWindow @RecoveryKeyScoped diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 7e40a833b..e05a73169 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -3,10 +3,16 @@ package org.cryptomator.ui.recoverykey; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import dagger.Lazy; +import org.cryptomator.common.Nullable; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.VaultConfigLoadException; +import org.cryptomator.cryptofs.VaultKeyInvalidException; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.beans.binding.Bindings; @@ -24,10 +30,12 @@ import java.util.Optional; @RecoveryKeyScoped public class RecoveryKeyRecoverController implements FxController { - private final static CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' ')); + private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); + private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' ')); private final Stage window; private final Vault vault; + private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; private final StringProperty recoveryKey; private final RecoveryKeyFactory recoveryKeyFactory; private final BooleanBinding validRecoveryKey; @@ -37,9 +45,10 @@ public class RecoveryKeyRecoverController implements FxController { public TextArea textarea; @Inject - public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene) { + public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene) { this.window = window; this.vault = vault; + this.unverifiedVaultConfig = unverifiedVaultConfig; this.recoveryKey = recoveryKey; this.recoveryKeyFactory = recoveryKeyFactory; this.validRecoveryKey = Bindings.createBooleanBinding(this::isValidRecoveryKey, recoveryKey); @@ -96,6 +105,20 @@ public class RecoveryKeyRecoverController implements FxController { window.setScene(resetPasswordScene.get()); } + private boolean checkKeyAgainstVaultConfig(byte[] key) { + try { + var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion()); + LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId()); + return true; + } catch (VaultKeyInvalidException e) { + LOG.debug("Provided recovery key does not match vault config signature."); + return false; + } catch (VaultConfigLoadException e) { + LOG.error("Failed to parse vault config", e); + return false; + } + } + /* Getter/Setter */ public Vault getVault() { @@ -107,7 +130,11 @@ public class RecoveryKeyRecoverController implements FxController { } public boolean isValidRecoveryKey() { - return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get()); + if (unverifiedVaultConfig != null) { + return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), this::checkKeyAgainstVaultConfig); + } else { + return recoveryKeyFactory.validateRecoveryKey(recoveryKey.get()); + } } public TextFormatter getRecoveryKeyTextFormatter() { diff --git a/src/main/resources/fxml/preferences.fxml b/src/main/resources/fxml/preferences.fxml index 4eaf3d45c..c96eb5395 100644 --- a/src/main/resources/fxml/preferences.fxml +++ b/src/main/resources/fxml/preferences.fxml @@ -9,7 +9,7 @@ fx:controller="org.cryptomator.ui.preferences.PreferencesController" minWidth="-Infinity" maxWidth="-Infinity" - prefWidth="500" + prefWidth="650" tabMinWidth="60" tabClosingPolicy="UNAVAILABLE" tabDragPolicy="FIXED"> @@ -22,6 +22,14 @@ + + + + + + + + diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml index cfa8ec010..ce2a31e4e 100644 --- a/src/main/resources/fxml/preferences_about.fxml +++ b/src/main/resources/fxml/preferences_about.fxml @@ -11,9 +11,9 @@ + spacing="24"> - + diff --git a/src/main/resources/fxml/preferences_contribute.fxml b/src/main/resources/fxml/preferences_contribute.fxml index bfe91d0eb..55d40b495 100644 --- a/src/main/resources/fxml/preferences_contribute.fxml +++ b/src/main/resources/fxml/preferences_contribute.fxml @@ -13,9 +13,9 @@ + spacing="24"> - + diff --git a/src/main/resources/fxml/preferences_general.fxml b/src/main/resources/fxml/preferences_general.fxml index 1e3be74c6..6d2b68447 100644 --- a/src/main/resources/fxml/preferences_general.fxml +++ b/src/main/resources/fxml/preferences_general.fxml @@ -5,54 +5,35 @@ - + + spacing="12"> - + - - - - - - - - - + - - - - - - - - - + - + + + + + + diff --git a/src/main/resources/fxml/preferences_interface.fxml b/src/main/resources/fxml/preferences_interface.fxml new file mode 100644 index 000000000..6cb9b7d73 --- /dev/null +++ b/src/main/resources/fxml/preferences_interface.fxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/preferences_updates.fxml b/src/main/resources/fxml/preferences_updates.fxml index 0b176dfb3..3156d1c3c 100644 --- a/src/main/resources/fxml/preferences_updates.fxml +++ b/src/main/resources/fxml/preferences_updates.fxml @@ -11,19 +11,19 @@ + spacing="12"> - + - +