This commit is contained in:
crschnick
2026-02-28 10:43:06 +00:00
parent 080f7578fb
commit 9da592fc28
18 changed files with 130 additions and 93 deletions
@@ -14,6 +14,7 @@ import io.xpipe.app.pwman.PasswordManagerKeyConfiguration;
import io.xpipe.core.KeyValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
@@ -45,7 +46,7 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy {
return AppI18n.get("passwordManagerEmpty");
}
if (!pwman.getKeyConfiguration().supportsAgent()) {
if (!pwman.getKeyConfiguration().useAgent()) {
return AppI18n.get("passwordManagerNoAgentSupport");
}
@@ -67,9 +68,10 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy {
return new OptionsBuilder()
.nameAndDescription("passwordManagerSshKeyConfig")
.addComp(pwmanDisplay)
.hide(pwmanProp.isNull())
.hide(Bindings.or(pwmanProp.isNull(), new ReadOnlyBooleanWrapper(!config.isAllowPasswordAgentKeyChoice())))
.nameAndDescription("publicKey")
.addComp(new SshAgentKeyListComp(config.getFileSystem(), p, publicKey), publicKey)
.hide(!config.isAllowPasswordAgentKeyChoice())
.nameAndDescription("forwardAgent")
.addToggle(forward)
.nonNull()
@@ -86,7 +88,7 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy {
private PasswordManagerKeyConfiguration getConfig() {
var pwman = AppPrefs.get().passwordManager().getValue();
return pwman != null && pwman.getKeyConfiguration() != null && pwman.getKeyConfiguration().supportsAgent() ? pwman.getKeyConfiguration() : null;
return pwman != null && pwman.getKeyConfiguration() != null && pwman.getKeyConfiguration().useAgent() ? pwman.getKeyConfiguration() : null;
}
@Override
@@ -19,6 +19,10 @@ public class SshAgentKeyList {
String name;
}
public static void validate(DataStoreEntryRef<ShellStore> ref, SshIdentityStrategy strategy, String publicKey) {
}
public static List<Entry> listAgentIdentities(DataStoreEntryRef<ShellStore> ref, SshIdentityStrategy strategy) throws Exception {
var session = ref.getStore().getOrStartSession();
strategy.prepareParent(session);
@@ -17,5 +17,6 @@ public class SshIdentityStrategyChoiceConfig {
Supplier<Boolean> perUserKeyFileCheck;
boolean allowKeyFileSync;
boolean allowAgentForward;
boolean allowPasswordAgentKeyChoice;
ObservableValue<DataStoreEntryRef<ShellStore>> fileSystem;
}
@@ -27,6 +27,10 @@ import java.util.UUID;
public interface DataStoreProvider {
default boolean allowCreation() {
return true;
}
default boolean showIncompleteInfo() {
return false;
}
@@ -122,7 +122,8 @@ public class StoreCreationDialog {
name,
prov,
base,
dataStoreProvider -> (category != null && category.equals(dataStoreProvider.getCreationCategory()))
dataStoreProvider -> (category != null && dataStoreProvider.allowCreation() &&
category.equals(dataStoreProvider.getCreationCategory()))
|| dataStoreProvider.equals(prov),
consumer,
false,
@@ -153,7 +153,7 @@ public class StoreCreationMenu {
});
int lastOrder = providers.getFirst().getOrderPriority();
for (io.xpipe.app.ext.DataStoreProvider dataStoreProvider : providers) {
for (var dataStoreProvider : providers) {
if (dataStoreProvider.getOrderPriority() != lastOrder) {
menu.getItems().add(new SeparatorMenuItem());
lastOrder = dataStoreProvider.getOrderPriority();
@@ -167,6 +167,7 @@ public class StoreCreationMenu {
StoreCreationDialog.showCreation(dataStoreProvider, category);
event.consume();
});
item.setDisable(!dataStoreProvider.allowCreation());
menu.getItems().add(item);
}
return menu;
@@ -61,7 +61,7 @@ public class BitwardenPasswordManager implements PasswordManager {
@SuppressWarnings("unused")
public static OptionsBuilder createOptions(Property<BitwardenPasswordManager> p) {
var agentStrategy = new SimpleObjectProperty<>(p.getValue().keyStrategy);
var keyStrategy = new SimpleObjectProperty<>(p.getValue().keyStrategy);
AtomicReference<Region> button = new AtomicReference<>();
var testButton = new ButtonComp(AppI18n.observable("sync"), new FontIcon("mdi2r-refresh"), () -> {
@@ -76,18 +76,18 @@ public class BitwardenPasswordManager implements PasswordManager {
testButton.apply(struc -> button.set(struc));
testButton.padding(new Insets(6, 10, 6, 6));
var agentStrategyChoice = OptionsChoiceBuilder.builder()
var keyStrategyChoice = OptionsChoiceBuilder.builder()
.allowNull(true)
.available(List.of(PasswordManagerKeyStrategy.Agent.class))
.property(agentStrategy)
.property(keyStrategy)
.build();
return new OptionsBuilder()
.addComp(testButton)
.nameAndDescription("passwordManagerKeyStrategy")
.sub(agentStrategyChoice.build(), agentStrategy)
.sub(keyStrategyChoice.build(), keyStrategy)
.bind(() -> {
return BitwardenPasswordManager.builder().keyStrategy(agentStrategy.getValue()).build();
return BitwardenPasswordManager.builder().keyStrategy(keyStrategy.getValue()).build();
}, p);
}
@@ -260,26 +260,6 @@ public class BitwardenPasswordManager implements PasswordManager {
@Override
public PasswordManagerKeyConfiguration getKeyConfiguration() {
return new PasswordManagerKeyConfiguration() {
@Override
public boolean supportsInlineSshKeys() {
return true;
}
@Override
public boolean supportsAgent() {
return keyStrategy != null;
}
@Override
public boolean supportsJoinedEntries() {
return false;
}
@Override
public SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward) {
return keyStrategy.getSshIdentityStrategy(publicKey, forward);
}
};
return PasswordManagerKeyConfiguration.of(true, false, keyStrategy);
}
}
@@ -51,7 +51,7 @@ public class KeePassXcPasswordManager implements PasswordManager {
@SuppressWarnings("unused")
public static OptionsBuilder createOptions(Property<KeePassXcPasswordManager> p) {
var keyStrategy = new SimpleObjectProperty<>(p.getValue().getKeyStrategy());
var agentStrategyChoice = OptionsChoiceBuilder.builder()
var keyStrategyChoice = OptionsChoiceBuilder.builder()
.allowNull(true)
.available(List.of(PasswordManagerKeyStrategy.KeePassXcOpenSshAgent.class, PasswordManagerKeyStrategy.KeePassXcPageant.class))
.property(keyStrategy)
@@ -95,7 +95,7 @@ public class KeePassXcPasswordManager implements PasswordManager {
.hide(Bindings.isEmpty(prop))
.addProperty(prop)
.nameAndDescription("passwordManagerKeyStrategy")
.sub(agentStrategyChoice.build(), keyStrategy)
.sub(keyStrategyChoice.build(), keyStrategy)
.bind(
() -> {
return new KeePassXcPasswordManager(prop, keyStrategy.getValue());
@@ -404,7 +404,7 @@ public class KeeperPasswordManager implements PasswordManager {
@SuppressWarnings("unused")
public static OptionsBuilder createOptions(Property<KeeperPasswordManager> p) {
var agentStrategy = new SimpleObjectProperty<>(p.getValue().getKeyStrategy());
var keyStrategy = new SimpleObjectProperty<>(p.getValue().getKeyStrategy());
var mfa = new SimpleObjectProperty<>(p.getValue().getTwoFactorAuth() != null ? p.getValue().getTwoFactorAuth() : new KeeperAuth.None());
var choice = OptionsChoiceBuilder.builder()
@@ -412,10 +412,10 @@ public class KeeperPasswordManager implements PasswordManager {
.available(KeeperAuth.getClasses())
.property(mfa)
.build();
var agentStrategyChoice = OptionsChoiceBuilder.builder()
var keyStrategyChoice = OptionsChoiceBuilder.builder()
.allowNull(true)
.available(List.of(PasswordManagerKeyStrategy.Agent.class))
.property(agentStrategy)
.available(List.of(PasswordManagerKeyStrategy.Inline.class, PasswordManagerKeyStrategy.Agent.class))
.property(keyStrategy)
.build();
@@ -423,11 +423,12 @@ public class KeeperPasswordManager implements PasswordManager {
.nameAndDescription("keeper2fa")
.sub(choice.build(), mfa)
.nameAndDescription("passwordManagerKeyStrategy")
.sub(agentStrategyChoice.build(), agentStrategy)
.sub(keyStrategyChoice.build(), keyStrategy)
.bind(
() -> {
return KeeperPasswordManager.builder()
.twoFactorAuth(mfa.get())
.keyStrategy(keyStrategy.get())
.build();
},
p);
@@ -39,19 +39,19 @@ public class OnePasswordManager implements PasswordManager {
@SuppressWarnings("unused")
public static OptionsBuilder createOptions(Property<OnePasswordManager> p) {
var agentStrategy = new SimpleObjectProperty<>(p.getValue().getKeyStrategy());
var keyStrategy = new SimpleObjectProperty<>(p.getValue().getKeyStrategy());
var agentStrategyChoice = OptionsChoiceBuilder.builder()
var keyStrategyChoice = OptionsChoiceBuilder.builder()
.allowNull(true)
.available(List.of(PasswordManagerKeyStrategy.Agent.class))
.property(agentStrategy)
.property(keyStrategy)
.build();
return new OptionsBuilder()
.nameAndDescription("passwordManagerKeyStrategy")
.sub(agentStrategyChoice.build(), agentStrategy)
.sub(keyStrategyChoice.build(), keyStrategy)
.bind(() -> {
return OnePasswordManager.builder().keyStrategy(agentStrategy.getValue()).build();
return OnePasswordManager.builder().keyStrategy(keyStrategy.getValue()).build();
}, p);
}
@@ -7,18 +7,13 @@ public interface PasswordManagerKeyConfiguration {
static PasswordManagerKeyConfiguration of(boolean inline, boolean joined, PasswordManagerKeyStrategy strategy) {
return new PasswordManagerKeyConfiguration() {
@Override
public boolean supportsInlineSshKeys() {
return inline;
public boolean useInline() {
return (strategy == null || !strategy.useAgent()) && inline && joined;
}
@Override
public boolean supportsAgent() {
return strategy != null;
}
@Override
public boolean supportsJoinedEntries() {
return joined;
public boolean useAgent() {
return strategy != null && strategy.useAgent();
}
@Override
@@ -31,17 +26,12 @@ public interface PasswordManagerKeyConfiguration {
static PasswordManagerKeyConfiguration none() {
return new PasswordManagerKeyConfiguration() {
@Override
public boolean supportsInlineSshKeys() {
public boolean useInline() {
return false;
}
@Override
public boolean supportsAgent() {
return false;
}
@Override
public boolean supportsJoinedEntries() {
public boolean useAgent() {
return false;
}
@@ -52,11 +42,9 @@ public interface PasswordManagerKeyConfiguration {
};
}
boolean supportsInlineSshKeys();
boolean useInline();
boolean supportsAgent();
boolean supportsJoinedEntries();
boolean useAgent();
SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward);
}
@@ -38,6 +38,11 @@ public interface PasswordManagerKeyStrategy {
return "inlineKey";
}
@Override
public boolean useAgent() {
return false;
}
@Override
public SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward) {
return null;
@@ -52,6 +57,11 @@ public interface PasswordManagerKeyStrategy {
FilePath customSocket;
@Override
public boolean useAgent() {
return true;
}
@SuppressWarnings("unused")
public static String getOptionsNameKey() {
return "keyAgent";
@@ -134,6 +144,11 @@ public interface PasswordManagerKeyStrategy {
property);
}
@Override
public boolean useAgent() {
return true;
}
@Override
public SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward) {
return OpenSshAgentStrategy.builder().build();
@@ -157,12 +172,19 @@ public interface PasswordManagerKeyStrategy {
property);
}
@Override
public boolean useAgent() {
return true;
}
@Override
public SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward) {
return PageantStrategy.builder().build();
}
}
boolean useAgent();
SshIdentityStrategy getSshIdentityStrategy(String publicKey, boolean forward);
static List<Class<?>> getClasses() {
@@ -152,6 +152,7 @@ public class IdentityChoiceBuilder {
.allowAgentForward(allowAgentForward)
.allowKeyFileSync(true)
.perUserKeyFileCheck(() -> false)
.allowPasswordAgentKeyChoice(true)
.fileSystem(fileSystem)
.build();
@@ -42,6 +42,7 @@ public class LocalIdentityStoreProvider extends IdentityStoreProvider {
var sshIdentityChoiceConfig = SshIdentityStrategyChoiceConfig.builder()
.allowAgentForward(true)
.allowKeyFileSync(false)
.allowPasswordAgentKeyChoice(true)
.perUserKeyFileCheck(() -> false)
.build();
@@ -51,7 +51,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
return true;
}
private PasswordManager.Result retrieveCredentials() {
private PasswordManager.Result retrieve() {
if (!checkOutdatedOrRefresh()) {
var r = getCache("result", PasswordManager.Result.class, null);
if (r != null) {
@@ -79,6 +79,18 @@ public class PasswordManagerIdentityStore extends IdentityStore
new UnsupportedOperationException("Identity " + key + " does not provide a username"));
}
if (sshKey != null) {
var pwman = AppPrefs.get().passwordManager().getValue();
if (pwman.getKeyConfiguration().useInline() && r.getSshKey() == null) {
throw ErrorEventFactory.expected(
new UnsupportedOperationException("Identity " + key + " does not provide an SSH key"));
}
if (pwman.getKeyConfiguration().useAgent()) {
SshAgentKeyList.validate();
}
}
setCache("lastQueried", Instant.now());
setCache("result", r);
@@ -87,7 +99,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
public UsernameStrategy getUsername() {
return new UsernameStrategy.Dynamic(() -> {
var r = retrieveCredentials();
var r = retrieve();
var effective = r != null && r.getCredentials() != null && r.getCredentials().getUsername() != null ? r.getCredentials().getUsername() : "unknown";
return effective;
});
@@ -102,7 +114,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
return new SecretQuery() {
@Override
public SecretQueryResult query(String prompt) {
var r = retrieveCredentials();
var r = retrieve();
if (r == null || r.getCredentials() == null || r.getCredentials().getPassword() == null) {
return new SecretQueryResult(null, SecretQueryState.RETRIEVAL_FAILURE);
}
@@ -138,15 +150,15 @@ public class PasswordManagerIdentityStore extends IdentityStore
}
var strat = r.getKeyConfiguration();
if (strat == null || (!strat.supportsInlineSshKeys() && !strat.supportsAgent())) {
if (strat == null || (!strat.useInline() && !strat.useAgent())) {
return def;
}
if (strat.supportsInlineSshKeys() && strat.supportsJoinedEntries()) {
if (strat.useInline()) {
return new SshIdentityStrategy() {
@Override
public void prepareParent(ShellControl parent) throws Exception {
var r = retrieveCredentials();
var r = retrieve();
if (r == null || r.getSshKey() == null || r.getSshKey().getPrivateKey() == null) {
return;
}
@@ -157,7 +169,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
@Override
public void buildCommand(CommandBuilder builder) {
var r = retrieveCredentials();
var r = retrieve();
if (r == null || r.getSshKey() == null || r.getSshKey().getPrivateKey() == null) {
return;
}
@@ -168,7 +180,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
@Override
public List<KeyValue> configOptions(ShellControl sc) throws Exception {
var r = retrieveCredentials();
var r = retrieve();
if (r == null || r.getSshKey() == null || r.getSshKey().getPrivateKey() == null) {
return List.of();
}
@@ -179,7 +191,7 @@ public class PasswordManagerIdentityStore extends IdentityStore
@Override
public String getPublicKey() {
var r = retrieveCredentials();
var r = retrieve();
if (r == null || r.getSshKey() == null || r.getSshKey().getPublicKey() == null) {
return null;
}
@@ -189,17 +201,23 @@ public class PasswordManagerIdentityStore extends IdentityStore
};
}
var agentStrat = strat.getSshIdentityStrategy(null, false);
if (strat.useAgent() && sshKey != null) {
return sshKey;
}
return new NoIdentityStrategy();
}
@Override
public void checkComplete() throws Throwable {
Validators.nonNull(key);
if (sshKey != null) {
sshKey.checkComplete();
}
}
@Override
public void validate() {
retrieveCredentials();
retrieve();
}
}
@@ -1,30 +1,28 @@
package io.xpipe.ext.base.identity;
import io.xpipe.app.cred.SshIdentityStrategy;
import io.xpipe.app.cred.PasswordManagerAgentStrategy;
import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig;
import io.xpipe.app.ext.DataStore;
import io.xpipe.app.ext.DataStoreCreationCategory;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.OptionsChoiceBuilder;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.PasswordManagerTestComp;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStorageUserHandler;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.*;
import java.util.List;
public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider {
@Override
public DataStoreCreationCategory getCreationCategory() {
return AppPrefs.get().passwordManager().getValue() != null ? DataStoreCreationCategory.IDENTITY : null;
public boolean allowCreation() {
return AppPrefs.get().passwordManager().getValue() != null;
}
@Override
@@ -35,21 +33,30 @@ public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider
var sshKey = new SimpleObjectProperty<>(st.getSshKey());
var perUser = new SimpleBooleanProperty(st.isPerUser());
var pwMan = AppPrefs.get().passwordManager().getValue();
var showKeyChoice = pwMan == null || !pwMan.getKeyConfiguration().useInline();
var sshIdentityChoiceConfig = SshIdentityStrategyChoiceConfig.builder()
.allowAgentForward(true)
.allowKeyFileSync(true)
.allowPasswordAgentKeyChoice(showKeyChoice)
.perUserKeyFileCheck(() -> false)
.fileSystem(new ReadOnlyObjectWrapper<>(DataStorage.get().local().ref()))
.build();
var sshKeyChoice = OptionsChoiceBuilder.builder().allowNull(true)
.available(SshIdentityStrategy.getClasses()).property(sshKey).build();
.customConfiguration(sshIdentityChoiceConfig)
.available(List.of(PasswordManagerAgentStrategy.class)).property(sshKey).build();
var hideSshKeyChoice = Bindings.createBooleanBinding(() -> {
var pwman = AppPrefs.get().passwordManager().getValue();
var strat = pwman.getKeyConfiguration();
return !strat.supportsAgent() ||
(strat.supportsJoinedEntries() && strat.supportsInlineSshKeys());
});
return !strat.useAgent() && !strat.useInline();
}, AppPrefs.get().passwordManager());
var comp = new PasswordManagerTestComp(key, false);
var testComp = new PasswordManagerTestComp(key, false);
return new OptionsBuilder()
.nameAndDescription("passwordManagerKey")
.addComp(comp.hgrow(), key)
.addComp(testComp.hgrow(), key)
.nonNull()
.nameAndDescription("sshKey")
.nameAndDescription("passwordManagerIdentityAgentKey")
.sub(sshKeyChoice.build(), sshKey)
.hide(hideSshKeyChoice)
.nameAndDescription(
@@ -8,6 +8,7 @@ import io.xpipe.app.hub.comp.StoreEntryWrapper;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.OptionsChoiceBuilder;
import io.xpipe.app.platform.Validator;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.VaultAuthentication;
import io.xpipe.app.secret.EncryptedValue;
import io.xpipe.app.secret.SecretNoneStrategy;
@@ -29,8 +30,8 @@ import java.util.UUID;
public class SyncedIdentityStoreProvider extends IdentityStoreProvider {
@Override
public DataStoreCreationCategory getCreationCategory() {
return DataStorage.get().supportsSync() ? DataStoreCreationCategory.IDENTITY : null;
public boolean allowCreation() {
return DataStorage.get().supportsSync();
}
@Override
@@ -75,6 +76,7 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider {
var sshIdentityChoiceConfig = SshIdentityStrategyChoiceConfig.builder()
.allowAgentForward(true)
.allowKeyFileSync(true)
.allowPasswordAgentKeyChoice(true)
.perUserKeyFileCheck(() -> perUser.get())
.build();
+6 -2
View File
@@ -1454,7 +1454,8 @@ terminalPromptConfiguration=Terminal prompt configuration
terminalPromptConfig=Config file
terminalPromptConfigDescription=The custom config file to apply to the prompt. This config will be automatically set up on the target system when the terminal is initialized and used as the default prompt config.\n\nIf you want to use the existing default config file on each system, you can leave this field empty.
passwordManagerKey=Password manager key
passwordManagerKeyDescription=The password manager identifier of the secret
#force
passwordManagerKeyDescription=The password manager identifier of the credentials entry
passwordManagerAgent=Password manager agent
dockerComposeProject.displayName=Docker compose project
dockerComposeProject.displayDescription=Group containers of a compose project together
@@ -1532,7 +1533,8 @@ customVncCommandDescription=The custom command to execute to launch VNC sessions
vncConnections=VNC connections
passwordManagerIdentity=Password manager identity
passwordManagerIdentity.displayName=Password manager identity
passwordManagerIdentity.displayDescription=Retrieve username and password of an identity from your password manager
#force
passwordManagerIdentity.displayDescription=Retrieve credentials of an identity from your password manager
passwordCopied=Connection password copied to clipboard
errorOccurred=Error occurred
actionMacro.displayName=Action macro
@@ -2010,3 +2012,5 @@ keePassXcPageant=Use Pageant
passwordManagerSshAgentSocket=SSH agent socket
passwordManagerSshAgentSocketDescription=Override the default agent socket location that should be used.
passwordManagerSshKeysNotSupported=The current password manager configuration does not support retrieving SSH keys
passwordManagerIdentityAgentKey=Additional SSH key
passwordManagerIdentityAgentKeyDescription=The SSH key to use from the password manager SSH agent