mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
Feature/change password backend 2 (#1375)
* Add option to choose from available password backends on Linux Implements #1301
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum KeychainBackend {
|
||||
GNOME("preferences.general.keychainBackend.gnome", SystemUtils.IS_OS_LINUX), //
|
||||
KDE("preferences.general.keychainBackend.kde", SystemUtils.IS_OS_LINUX), //
|
||||
MAC_SYSTEM_KEYCHAIN("preferences.general.keychainBackend.macSystemKeychain", SystemUtils.IS_OS_MAC), //
|
||||
WIN_SYSTEM_KEYCHAIN("preferences.general.keychainBackend.winSystemKeychain", SystemUtils.IS_OS_WINDOWS);
|
||||
|
||||
public static KeychainBackend[] supportedBackends() {
|
||||
return Arrays.stream(values()).filter(KeychainBackend::isSupported).toArray(KeychainBackend[]::new);
|
||||
}
|
||||
|
||||
public static KeychainBackend defaultBackend() {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
return KeychainBackend.GNOME;
|
||||
} else { // SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS
|
||||
return Arrays.stream(KeychainBackend.supportedBackends()).findFirst().orElseThrow(IllegalStateException::new);
|
||||
}
|
||||
}
|
||||
|
||||
private final String configName;
|
||||
private final boolean isSupported;
|
||||
|
||||
KeychainBackend(String configName, boolean isSupported) {
|
||||
this.configName = configName;
|
||||
this.isSupported = isSupported;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return configName;
|
||||
}
|
||||
|
||||
public boolean isSupported() { return isSupported; }
|
||||
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public class Settings {
|
||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = KeychainBackend.defaultBackend();
|
||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||
private static final String DEFAULT_LICENSE_KEY = "";
|
||||
|
||||
@@ -49,6 +50,7 @@ public class Settings {
|
||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||
private final ObjectProperty<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
|
||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||
|
||||
@@ -68,6 +70,7 @@ public class Settings {
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
theme.addListener(this::somethingChanged);
|
||||
keychainBackend.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
}
|
||||
@@ -128,6 +131,8 @@ public class Settings {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ObjectProperty<KeychainBackend> keychainBackend() { return keychainBackend; }
|
||||
|
||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||
return userInterfaceOrientation;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||
out.name("theme").value(value.theme().get().name());
|
||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||
out.name("keychainBackend").value(value.keychainBackend().get().name());
|
||||
out.name("licenseKey").value(value.licenseKey().get());
|
||||
out.endObject();
|
||||
}
|
||||
@@ -69,6 +70,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
@@ -108,6 +110,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
}
|
||||
}
|
||||
|
||||
private KeychainBackend parseKeychainBackend(String backendName) {
|
||||
try {
|
||||
return KeychainBackend.valueOf(backendName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid keychain backend {}. Defaulting to {}.", backendName, Settings.DEFAULT_KEYCHAIN_BACKEND);
|
||||
return Settings.DEFAULT_KEYCHAIN_BACKEND;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||
try {
|
||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
interface KeychainAccessStrategy {
|
||||
public interface KeychainAccessStrategy {
|
||||
|
||||
/**
|
||||
* Associates a passphrase with a given key.
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.KeychainBackend;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -16,25 +20,65 @@ public class LinuxSystemKeychainAccess implements KeychainAccessStrategy {
|
||||
// the actual implementation is hidden in this delegate objects which are loaded via reflection,
|
||||
// as it depends on libraries that aren't necessarily available:
|
||||
private final Optional<KeychainAccessStrategy> delegate;
|
||||
private final Settings settings;
|
||||
private static EnumSet<KeychainBackend> availableKeychainBackends = EnumSet.noneOf(KeychainBackend.class);
|
||||
private static KeychainBackend backendActivated = null;
|
||||
private static boolean isGnomeKeyringAvailable;
|
||||
private static boolean isKdeWalletAvailable;
|
||||
|
||||
@Inject
|
||||
public LinuxSystemKeychainAccess() {
|
||||
public LinuxSystemKeychainAccess(Settings settings) {
|
||||
this.settings = settings;
|
||||
this.delegate = constructKeychainAccess();
|
||||
}
|
||||
|
||||
private static Optional<KeychainAccessStrategy> constructKeychainAccess() {
|
||||
try { // is gnome-keyring or kwallet installed?
|
||||
private Optional<KeychainAccessStrategy> constructKeychainAccess() {
|
||||
try { // find out which backends are available
|
||||
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
|
||||
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
if (instance.isSupported()) return Optional.of(instance);
|
||||
KeychainAccessStrategy gnomeKeyring = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
if (gnomeKeyring.isSupported()) {
|
||||
LinuxSystemKeychainAccess.availableKeychainBackends.add(KeychainBackend.GNOME);
|
||||
LinuxSystemKeychainAccess.isGnomeKeyringAvailable = true;
|
||||
}
|
||||
clazz = Class.forName("org.cryptomator.keychain.LinuxKDEWalletKeychainAccessImpl");
|
||||
instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
return Optional.of(instance);
|
||||
KeychainAccessStrategy kdeWallet = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
if (kdeWallet.isSupported()) {
|
||||
LinuxSystemKeychainAccess.availableKeychainBackends.add(KeychainBackend.KDE);
|
||||
LinuxSystemKeychainAccess.isKdeWalletAvailable = true;
|
||||
}
|
||||
|
||||
// load password backend setting as the preferred backend
|
||||
ObjectProperty<KeychainBackend> pwSetting = settings.keychainBackend();
|
||||
|
||||
// check for GNOME keyring first, as this gets precedence over
|
||||
// KDE wallet as the former was implemented first
|
||||
if (isGnomeKeyringAvailable && pwSetting.get().equals(KeychainBackend.GNOME)) {
|
||||
pwSetting.setValue(KeychainBackend.GNOME);
|
||||
LinuxSystemKeychainAccess.backendActivated = KeychainBackend.GNOME;
|
||||
return Optional.of(gnomeKeyring);
|
||||
}
|
||||
|
||||
if (isKdeWalletAvailable && pwSetting.get().equals(KeychainBackend.KDE)) {
|
||||
pwSetting.setValue(KeychainBackend.KDE);
|
||||
LinuxSystemKeychainAccess.backendActivated = KeychainBackend.KDE;
|
||||
return Optional.of(kdeWallet);
|
||||
}
|
||||
return Optional.empty();
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public static EnumSet<KeychainBackend> getAvailableKeychainBackends() {
|
||||
return availableKeychainBackends;
|
||||
}
|
||||
|
||||
public static KeychainBackend getBackendActivated() {
|
||||
return backendActivated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
|
||||
|
||||
@@ -13,16 +13,22 @@ import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.util.StringConverter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.KeychainBackend;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.keychain.KeychainAccessStrategy;
|
||||
import org.cryptomator.keychain.LinuxSystemKeychainAccess;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -41,7 +47,9 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
private Optional<KeychainAccessStrategy> keychain;
|
||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||
public ChoiceBox<KeychainBackend> keychainBackendChoiceBox;
|
||||
public CheckBox startHiddenCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
public CheckBox autoStartCheckbox;
|
||||
@@ -50,10 +58,11 @@ public class GeneralPreferencesController implements FxController {
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment) {
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, Optional<KeychainAccessStrategy> keychain, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment) {
|
||||
this.settings = settings;
|
||||
this.trayMenuSupported = trayMenuSupported;
|
||||
this.autoStartStrategy = autoStartStrategy;
|
||||
this.keychain = keychain;
|
||||
this.selectedTabProperty = selectedTabProperty;
|
||||
this.licenseHolder = licenseHolder;
|
||||
this.executor = executor;
|
||||
@@ -84,6 +93,16 @@ public class GeneralPreferencesController implements FxController {
|
||||
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
||||
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
||||
nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
|
||||
|
||||
keychainBackendChoiceBox.getItems().addAll(getAvailableBackends());
|
||||
if (keychain.isPresent() && SystemUtils.IS_OS_LINUX) {
|
||||
keychainBackendChoiceBox.setValue(LinuxSystemKeychainAccess.getBackendActivated());
|
||||
}
|
||||
if (keychain.isPresent() && (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS)) {
|
||||
keychainBackendChoiceBox.setValue(Arrays.stream(KeychainBackend.supportedBackends()).findFirst().orElseThrow(IllegalStateException::new));
|
||||
}
|
||||
keychainBackendChoiceBox.setConverter(new KeychainBackendConverter(resourceBundle));
|
||||
keychainBackendChoiceBox.valueProperty().bindBidirectional(settings.keychainBackend());
|
||||
}
|
||||
|
||||
public boolean isTrayMenuSupported() {
|
||||
@@ -153,6 +172,25 @@ public class GeneralPreferencesController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeychainBackendConverter extends StringConverter<KeychainBackend> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
KeychainBackendConverter(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(KeychainBackend impl) {
|
||||
return resourceBundle.getString(impl.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeychainBackend fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ToggleAutoStartTask extends Task<Void> {
|
||||
|
||||
private final AutoStartStrategy autoStart;
|
||||
@@ -176,4 +214,17 @@ public class GeneralPreferencesController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private KeychainBackend[] getAvailableBackends() {
|
||||
if (!keychain.isPresent()) {
|
||||
return new KeychainBackend[]{};
|
||||
}
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
EnumSet<KeychainBackend> backends = LinuxSystemKeychainAccess.getAvailableKeychainBackends();
|
||||
return backends.toArray(KeychainBackend[]::new);
|
||||
}
|
||||
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
|
||||
return KeychainBackend.supportedBackends();
|
||||
}
|
||||
return new KeychainBackend[]{};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
<Hyperlink styleClass="hyperlink-underline" text="%preferences.general.debugDirectory" onAction="#showLogfileDirectory"/>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<Label text="%preferences.general.keychainBackend"/>
|
||||
<ChoiceBox fx:id="keychainBackendChoiceBox"/>
|
||||
</HBox>
|
||||
|
||||
<CheckBox fx:id="autoStartCheckbox" text="%preferences.general.autoStart" visible="${controller.autoStartSupported}" managed="${controller.autoStartSupported}" onAction="#toggleAutoStart"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
@@ -143,6 +143,11 @@ preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
preferences.general.debugDirectory=Reveal log files
|
||||
preferences.general.autoStart=Launch Cryptomator on system start
|
||||
preferences.general.keychainBackend=Password backend
|
||||
preferences.general.keychainBackend.gnome=Gnome Keyring
|
||||
preferences.general.keychainBackend.kde=KDE KWallet
|
||||
preferences.general.keychainBackend.macSystemKeychain=macOS Keychain Access
|
||||
preferences.general.keychainBackend.winSystemKeychain=Windows Data Protection Keychain
|
||||
preferences.general.interfaceOrientation=Interface Orientation
|
||||
preferences.general.interfaceOrientation.ltr=Left to Right
|
||||
preferences.general.interfaceOrientation.rtl=Right to Left
|
||||
|
||||
Reference in New Issue
Block a user