diff --git a/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java b/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java index f8bd2734d..22a0d8e54 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/OptionsComp.java @@ -38,12 +38,10 @@ public class OptionsComp extends RegionBuilder { private final List entries; private final List checks; - private final boolean autoFocus; - public OptionsComp(List entries, List checks, boolean autoFocus) { + public OptionsComp(List entries, List checks) { this.entries = entries; this.checks = checks; - this.autoFocus = autoFocus; } @Override @@ -244,10 +242,6 @@ public class OptionsComp extends RegionBuilder { return; } - if (!autoFocus) { - return; - } - var failed = checks.stream() .filter(check -> check.getValidationResult().getMessages().size() > 0) .findFirst(); diff --git a/app/src/main/java/io/xpipe/app/cred/PasswordManagerAgentStrategy.java b/app/src/main/java/io/xpipe/app/cred/PasswordManagerAgentStrategy.java index 8b132eeaa..ce22e87ca 100644 --- a/app/src/main/java/io/xpipe/app/cred/PasswordManagerAgentStrategy.java +++ b/app/src/main/java/io/xpipe/app/cred/PasswordManagerAgentStrategy.java @@ -7,11 +7,13 @@ import io.xpipe.app.comp.base.LabelComp; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.ValidationException; import io.xpipe.app.platform.OptionsBuilder; +import io.xpipe.app.platform.Validator; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.process.CommandBuilder; import io.xpipe.app.process.ShellControl; import io.xpipe.app.pwman.PasswordManagerKeyConfiguration; import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.util.Validators; import io.xpipe.core.KeyValue; import javafx.beans.binding.Bindings; import javafx.beans.property.Property; @@ -27,7 +29,7 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; -@JsonTypeName("passwordManager") +@JsonTypeName("passwordManagerAgent") @Value @Jacksonized @Builder @@ -36,8 +38,6 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy { @SuppressWarnings("unused") public static OptionsBuilder createOptions( Property p, SshIdentityStrategyChoiceConfig config) { - var forward = - new SimpleBooleanProperty(p.getValue() != null && p.getValue().isForwardAgent()); var identifier = new SimpleStringProperty(p.getValue() != null ? p.getValue().getIdentifier() : null); @@ -69,22 +69,18 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy { return new OptionsBuilder() .nameAndDescription("passwordManagerSshKeyConfig") .addComp(pwmanDisplay) - .hide(Bindings.or(pwmanProp.isNull(), new ReadOnlyBooleanWrapper(!config.isAllowPasswordAgentKeyChoice()))) + .hide(pwmanProp.isNull()) .nameAndDescription(useKeyName() ? "agentKeyName" : "publicKey") .addComp(new SshAgentKeyListComp(config.getFileSystem(), p, identifier, useKeyName()), identifier) - .hide(!config.isAllowPasswordAgentKeyChoice()) - .nameAndDescription("forwardAgent") - .addToggle(forward) .nonNull() .hide(!config.isAllowAgentForward()) .bind( () -> { - return new PasswordManagerAgentStrategy(forward.get(), identifier.get()); + return new PasswordManagerAgentStrategy(identifier.get()); }, p); } - boolean forwardAgent; String identifier; private static PasswordManagerKeyConfiguration getConfig() { @@ -99,6 +95,7 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy { @Override public void checkComplete() throws ValidationException { + Validators.nonNull(identifier); var config = getConfig(); if (config == null) { throw new ValidationException(AppI18n.get("passwordManagerSshKeysNotSupported")); @@ -127,7 +124,7 @@ public class PasswordManagerAgentStrategy implements SshIdentityStrategy { public List configOptions(ShellControl sc) throws Exception { var config = getConfig(); if (config != null) { - var strat = config.getSshIdentityStrategy(getPublicKeyStrategy().retrievePublicKey(), forwardAgent); + var strat = config.getSshIdentityStrategy(getPublicKeyStrategy().retrievePublicKey(), false); return strat.configOptions(sc); } else { return List.of(); diff --git a/app/src/main/java/io/xpipe/app/cred/SshAgentKeyListComp.java b/app/src/main/java/io/xpipe/app/cred/SshAgentKeyListComp.java index 081653621..92ea7d82d 100644 --- a/app/src/main/java/io/xpipe/app/cred/SshAgentKeyListComp.java +++ b/app/src/main/java/io/xpipe/app/cred/SshAgentKeyListComp.java @@ -70,6 +70,7 @@ public class SshAgentKeyListComp extends SimpleRegionBuilder { popover.hide(); e.consume(); }); + entryButton.setMinWidth(600); content.getChildren().add(entryButton); } popover.setContentNode(content); diff --git a/app/src/main/java/io/xpipe/app/cred/SshIdentityStrategyChoiceConfig.java b/app/src/main/java/io/xpipe/app/cred/SshIdentityStrategyChoiceConfig.java index 7357f9005..dead1746e 100644 --- a/app/src/main/java/io/xpipe/app/cred/SshIdentityStrategyChoiceConfig.java +++ b/app/src/main/java/io/xpipe/app/cred/SshIdentityStrategyChoiceConfig.java @@ -17,6 +17,5 @@ public class SshIdentityStrategyChoiceConfig { Supplier perUserKeyFileCheck; boolean allowKeyFileSync; boolean allowAgentForward; - boolean allowPasswordAgentKeyChoice; ObservableValue> fileSystem; } diff --git a/app/src/main/java/io/xpipe/app/platform/OptionsBuilder.java b/app/src/main/java/io/xpipe/app/platform/OptionsBuilder.java index a8ffbd679..c6a810ef9 100644 --- a/app/src/main/java/io/xpipe/app/platform/OptionsBuilder.java +++ b/app/src/main/java/io/xpipe/app/platform/OptionsBuilder.java @@ -84,11 +84,6 @@ public class OptionsBuilder { return this; } - public OptionsBuilder disableAutoFocus() { - focusEnabled = false; - return this; - } - public OptionsBuilder() { this.ownValidator = new SimpleValidator(); this.allValidators.add(ownValidator); @@ -496,7 +491,7 @@ public class OptionsBuilder { public OptionsComp buildComp() { finishCurrent(); - var comp = new OptionsComp(entries, focusFirstIncomplete ? allChecks : List.of(), focusEnabled); + var comp = new OptionsComp(entries, focusFirstIncomplete ? allChecks : List.of()); return comp; } diff --git a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java index 932c2cc72..aca5b5071 100644 --- a/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/BitwardenPasswordManager.java @@ -84,7 +84,6 @@ public class BitwardenPasswordManager implements PasswordManager { .build(); return new OptionsBuilder() - .disableAutoFocus() .addComp(syncButton) .nameAndDescription("passwordManagerTest") .addComp(new PasswordManagerTestComp(true)) diff --git a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java index 9aba33a85..22f9f772b 100644 --- a/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/DashlanePasswordManager.java @@ -37,7 +37,6 @@ public class DashlanePasswordManager implements PasswordManager { @SuppressWarnings("unused") public static OptionsBuilder createOptions(Property p) { return new OptionsBuilder() - .disableAutoFocus() .nameAndDescription("passwordManagerTest") .addComp(new PasswordManagerTestComp(true)); } diff --git a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java index 246c8ea90..ceacb6706 100644 --- a/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/KeeperPasswordManager.java @@ -66,7 +66,7 @@ public class KeeperPasswordManager implements PasswordManager { return values; } - String constructKeeperInput(KeeperPasswordManager passwordManager) throws Exception; + String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) throws Exception; Duration getCacheDuration(); @@ -105,11 +105,13 @@ public class KeeperPasswordManager implements PasswordManager { return index; } - private void sendInitialSms() throws Exception { + private boolean sendInitialSms(SecretValue password) throws Exception { var sc = getOrStartShell(); var b = CommandBuilder.of() .add(getExecutable(), "get") - .addLiteral("test"); + .addLiteral("xpipe-test") + .add("--password") + .addLiteral(password.getSecretValue()); var file = sc.getSystemTemporaryDirectory().join("keeper" + Math.abs(new Random().nextInt()) + ".txt"); var input = """ @@ -119,20 +121,30 @@ public class KeeperPasswordManager implements PasswordManager { """; sc.view().writeTextFile(file, input); - var fullCommand = CommandBuilder.of() + var fullB = CommandBuilder.of() .add(sc.getShellDialect() == ShellDialects.CMD ? "type" : "cat") .addFile(file) .add("|") .add(b); - sc.command(fullCommand).sensitive().execute(); + + var command = sc.command(fullB); + command.killOnTimeout(CountDown.of().start(30_000)); + command.sensitive(); + var success = command.executeAndCheck(); + // A fail indicates the query went through but the entry was not found + if (!success) { + return false; + } else { + return true; + } } @Override - public String constructKeeperInput(KeeperPasswordManager passwordManager) throws Exception { - sendInitialSms(); + public String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) throws Exception { + var sent = sendInitialSms(password); var index = getTotpDurationIndex(); - if (passwordManager.isHasCompletedRequestInSession() && index > 0) { + if (!sent || (passwordManager.isHasCompletedRequestInSession() && index > 0)) { var input = """ 1 @@ -215,7 +227,7 @@ public class KeeperPasswordManager implements PasswordManager { } @Override - public String constructKeeperInput(KeeperPasswordManager passwordManager) { + public String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) { var index = getTotpDurationIndex(); if (passwordManager.isHasCompletedRequestInSession() && index > 0) { var input = """ @@ -284,7 +296,7 @@ public class KeeperPasswordManager implements PasswordManager { class SecurityKey implements KeeperAuth { @Override - public String constructKeeperInput(KeeperPasswordManager passwordManager) { + public String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) { var input = """ 1 @@ -337,7 +349,7 @@ public class KeeperPasswordManager implements PasswordManager { } @Override - public String constructKeeperInput(KeeperPasswordManager passwordManager) { + public String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) { var input = """ 1 @@ -369,7 +381,7 @@ public class KeeperPasswordManager implements PasswordManager { } @Override - public String constructKeeperInput(KeeperPasswordManager passwordManager) { + public String constructKeeperInput(KeeperPasswordManager passwordManager, SecretValue password) { var input = """ 1 @@ -493,7 +505,7 @@ public class KeeperPasswordManager implements PasswordManager { FilePath file = sc.getSystemTemporaryDirectory().join("keeper" + Math.abs(new Random().nextInt()) + ".txt"); var effectiveTwoFactor = twoFactorAuth != null ? twoFactorAuth : new KeeperAuth.None(); - var input = effectiveTwoFactor.constructKeeperInput(this); + var input = effectiveTwoFactor.constructKeeperInput(this, r); if (input == null) { return null; } diff --git a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java index e91fcd167..6ef6cdd63 100644 --- a/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/LastpassPasswordManager.java @@ -32,7 +32,6 @@ public class LastpassPasswordManager implements PasswordManager { @SuppressWarnings("unused") public static OptionsBuilder createOptions(Property p) { return new OptionsBuilder() - .disableAutoFocus() .nameAndDescription("passwordManagerTest") .addComp(new PasswordManagerTestComp(true)); } diff --git a/app/src/main/java/io/xpipe/app/pwman/OnePasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/OnePasswordManager.java index 1fd26d195..b9b5cdf6f 100644 --- a/app/src/main/java/io/xpipe/app/pwman/OnePasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/OnePasswordManager.java @@ -55,7 +55,6 @@ public class OnePasswordManager implements PasswordManager { .build(); return new OptionsBuilder() - .disableAutoFocus() .nameAndDescription("onePasswordManagerAccount") .addString(account) .hide(account.isNull().and(availableAccounts.emptyProperty())) diff --git a/app/src/main/java/io/xpipe/app/pwman/PassboltPasswordManager.java b/app/src/main/java/io/xpipe/app/pwman/PassboltPasswordManager.java index 8f0940df3..220d32be0 100644 --- a/app/src/main/java/io/xpipe/app/pwman/PassboltPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/PassboltPasswordManager.java @@ -67,7 +67,6 @@ public class PassboltPasswordManager implements PasswordManager { chooser.setPrompt(new ReadOnlyObjectWrapper<>(FilePath.of("passbolt_private.asc"))); return new OptionsBuilder() - .disableAutoFocus() .nameAndDescription("passboltServerUrl") .addComp( new TextFieldComp(serverUrl) diff --git a/app/src/main/java/io/xpipe/app/pwman/PasswordManagerKeyStrategy.java b/app/src/main/java/io/xpipe/app/pwman/PasswordManagerKeyStrategy.java index 30d647a83..ad66ee9ef 100644 --- a/app/src/main/java/io/xpipe/app/pwman/PasswordManagerKeyStrategy.java +++ b/app/src/main/java/io/xpipe/app/pwman/PasswordManagerKeyStrategy.java @@ -189,10 +189,10 @@ public interface PasswordManagerKeyStrategy { static List> getClasses() { var l = new ArrayList>(); - l.add(Inline.class); l.add(Agent.class); l.add(KeePassXcOpenSshAgent.class); l.add(KeePassXcPageant.class); + l.add(Inline.class); return l; } } diff --git a/app/src/main/java/io/xpipe/app/pwman/WindowsCredentialManager.java b/app/src/main/java/io/xpipe/app/pwman/WindowsCredentialManager.java index 32f170441..276315469 100644 --- a/app/src/main/java/io/xpipe/app/pwman/WindowsCredentialManager.java +++ b/app/src/main/java/io/xpipe/app/pwman/WindowsCredentialManager.java @@ -23,7 +23,6 @@ public class WindowsCredentialManager implements PasswordManager { @SuppressWarnings("unused") public static OptionsBuilder createOptions(Property p) { return new OptionsBuilder() - .disableAutoFocus() .nameAndDescription("passwordManagerTest") .addComp(new PasswordManagerTestComp(true)); } diff --git a/app/src/main/resources/io/xpipe/app/resources/style/style.css b/app/src/main/resources/io/xpipe/app/resources/style/style.css index 659fb126f..51951b0cc 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/style.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/style.css @@ -234,3 +234,7 @@ .ikonli-font-icon.graphic.terminal-dock-button { -fx-icon-color: -color-accent-fg; } + +.text-field:focused { + -fx-prompt-text-fill: -color-fg-subtle; +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java index 29460a035..246b68cc6 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoiceBuilder.java @@ -152,7 +152,6 @@ public class IdentityChoiceBuilder { .allowAgentForward(allowAgentForward) .allowKeyFileSync(true) .perUserKeyFileCheck(() -> false) - .allowPasswordAgentKeyChoice(true) .fileSystem(fileSystem) .build(); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java index febc294c8..2bd1c5d96 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStoreProvider.java @@ -42,7 +42,6 @@ public class LocalIdentityStoreProvider extends IdentityStoreProvider { var sshIdentityChoiceConfig = SshIdentityStrategyChoiceConfig.builder() .allowAgentForward(true) .allowKeyFileSync(false) - .allowPasswordAgentKeyChoice(true) .perUserKeyFileCheck(() -> false) .build(); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java index 0d2fbc270..13b5250ac 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/PasswordManagerIdentityStoreProvider.java @@ -17,9 +17,24 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.*; import java.util.List; +import java.util.UUID; public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider { + @Override + public UUID getTargetCategory(DataStore store, UUID target) { + PasswordManagerIdentityStore st = (PasswordManagerIdentityStore) store; + if (!st.isPerUser()) { + return target; + } + + var cat = DataStorage.get().getStoreCategoryIfPresent(target).orElseThrow(); + var inSynced = DataStorage.get().getCategoryParentHierarchy(cat).stream() + .anyMatch(dataStoreCategory -> + dataStoreCategory.getUuid().equals(DataStorage.SYNCED_IDENTITIES_CATEGORY_UUID)); + return inSynced ? target : DataStorage.SYNCED_IDENTITIES_CATEGORY_UUID; + } + @Override public boolean allowCreation() { return AppPrefs.get().passwordManager().getValue() != null; @@ -33,12 +48,9 @@ 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(); @@ -48,7 +60,7 @@ public class PasswordManagerIdentityStoreProvider extends IdentityStoreProvider var hideSshKeyChoice = Bindings.createBooleanBinding(() -> { var pwman = AppPrefs.get().passwordManager().getValue(); var strat = pwman.getKeyConfiguration(); - return !strat.useAgent() && !strat.useInline(); + return strat.useInline(); }, AppPrefs.get().passwordManager()); var testComp = new PasswordManagerTestComp(key, false); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java index 0773173d4..91613e41a 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStoreProvider.java @@ -76,7 +76,6 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider { var sshIdentityChoiceConfig = SshIdentityStrategyChoiceConfig.builder() .allowAgentForward(true) .allowKeyFileSync(true) - .allowPasswordAgentKeyChoice(true) .perUserKeyFileCheck(() -> perUser.get()) .build(); diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 0b2d7428b..c96ef9c67 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1639,7 +1639,7 @@ sshAgentSocketDescription=The custom socket to use to communicate with the SSH a publicKey=Key selector publicKeyDescription=The optional public key to force the agent to only offer the matching private key agentKeyName=Key selector -agentKeyNameDescription=The optional key name to force the agent to only offer the matching key +agentKeyNameDescription=The name of the key entry in the password manager actions=Actions hcloudServer.displayName=Hetzner cloud server hcloudServer.displayDescription=Access a server hosted on Hetzner cloud via SSH