From 54bfea677966515682cfe4f05a9831e88ee5a573 Mon Sep 17 00:00:00 2001 From: crschnick Date: Wed, 28 May 2025 10:23:40 +0000 Subject: [PATCH] Fixes --- .../java/io/xpipe/app/core/mode/BaseMode.java | 4 +-- .../app/password/DashlanePasswordManager.java | 9 ++++-- .../app/password/EnpassPasswordManager.java | 6 ++-- ...ger.java => KeePassXcPasswordManager.java} | 12 +++---- .../app/password/KeeperPasswordManager.java | 32 +++++++++++++++++-- .../app/password/LastpassPasswordManager.java | 23 +++++++++++-- .../xpipe/app/password/PasswordManager.java | 3 +- .../app/password/PsonoPasswordManager.java | 13 ++++---- .../io/xpipe/core/process/CommandControl.java | 2 -- 9 files changed, 78 insertions(+), 26 deletions(-) rename app/src/main/java/io/xpipe/app/password/{KeePassXcManager.java => KeePassXcPasswordManager.java} (94%) diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index 12d3a54be..0f27195e7 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -15,7 +15,7 @@ import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.icon.SystemIconManager; import io.xpipe.app.issue.TrackEvent; -import io.xpipe.app.password.KeePassXcManager; +import io.xpipe.app.password.KeePassXcPasswordManager; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.resources.*; import io.xpipe.app.storage.DataStorage; @@ -180,7 +180,7 @@ public class BaseMode extends OperationMode { ProcessControlProvider.get().reset(); AppPrefs.reset(); AppBeaconServer.reset(); - KeePassXcManager.reset(); + KeePassXcPasswordManager.reset(); StoreViewState.reset(); AppLayoutModel.reset(); AppTheme.reset(); diff --git a/app/src/main/java/io/xpipe/app/password/DashlanePasswordManager.java b/app/src/main/java/io/xpipe/app/password/DashlanePasswordManager.java index ad6a1a463..aa2711464 100644 --- a/app/src/main/java/io/xpipe/app/password/DashlanePasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/DashlanePasswordManager.java @@ -9,6 +9,8 @@ import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellScript; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.util.InPlaceSecretValue; +import io.xpipe.core.util.JacksonMapper; @JsonTypeName("dashlane") public class DashlanePasswordManager implements PasswordManager { @@ -45,10 +47,13 @@ public class DashlanePasswordManager implements PasswordManager { } var out = sc.command(CommandBuilder.of() - .add("dcli", "password", "--output", "console") + .add("dcli", "password", "--output", "console", "-o", "json") .addLiteral(key)) .readStdoutOrThrow(); - return null; + var tree = JacksonMapper.getDefault().readTree(out); + var login = tree.get("login"); + var password = tree.get("password"); + return new CredentialResult(login != null ? login.asText() : null, password != null ? InPlaceSecretValue.of(password.asText()) : null); } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); return null; diff --git a/app/src/main/java/io/xpipe/app/password/EnpassPasswordManager.java b/app/src/main/java/io/xpipe/app/password/EnpassPasswordManager.java index 9a1e9ccac..dfaa970e7 100644 --- a/app/src/main/java/io/xpipe/app/password/EnpassPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/EnpassPasswordManager.java @@ -13,6 +13,7 @@ import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellScript; import io.xpipe.core.store.FilePath; +import io.xpipe.core.util.InPlaceSecretValue; import io.xpipe.core.util.JacksonMapper; import javafx.application.Platform; import javafx.beans.property.Property; @@ -135,8 +136,9 @@ public class EnpassPasswordManager implements PasswordManager { throw ErrorEvent.expected(new IllegalArgumentException("Ambiguous item name, multiple password entries match: " + String.join(", ", matches))); } - var secret = json.get(0).get("password").asText(); - return null; + var login = json.get(0).required("login").asText(); + var secret = json.get(0).required("password").asText(); + return new CredentialResult(!login.isEmpty() ? login : null, !secret.isEmpty() ? InPlaceSecretValue.of(secret) : null); } } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); diff --git a/app/src/main/java/io/xpipe/app/password/KeePassXcManager.java b/app/src/main/java/io/xpipe/app/password/KeePassXcPasswordManager.java similarity index 94% rename from app/src/main/java/io/xpipe/app/password/KeePassXcManager.java rename to app/src/main/java/io/xpipe/app/password/KeePassXcPasswordManager.java index 38136524f..3a9d3aea1 100644 --- a/app/src/main/java/io/xpipe/app/password/KeePassXcManager.java +++ b/app/src/main/java/io/xpipe/app/password/KeePassXcPasswordManager.java @@ -28,13 +28,13 @@ import java.util.regex.Pattern; @ToString @Jacksonized @JsonTypeName("keePassXc") -public class KeePassXcManager implements PasswordManager { +public class KeePassXcPasswordManager implements PasswordManager { private static KeePassXcProxyClient client; private final KeePassXcAssociationKey associationKey; - public static OptionsBuilder createOptions(Property p) { + public static OptionsBuilder createOptions(Property p) { var prop = new SimpleObjectProperty(); p.subscribe(keePassXcManager -> { prop.set(keePassXcManager != null ? keePassXcManager.getAssociationKey() : null); @@ -63,7 +63,7 @@ public class KeePassXcManager implements PasswordManager { .addProperty(prop) .bind( () -> { - return new KeePassXcManager(prop.getValue()); + return new KeePassXcPasswordManager(prop.getValue()); }, p); } @@ -114,7 +114,7 @@ public class KeePassXcManager implements PasswordManager { c.exchangeKeys(); var pref = AppPrefs.get().passwordManager(); KeePassXcAssociationKey cached = - pref.getValue() instanceof KeePassXcManager kpm ? kpm.getAssociationKey() : null; + pref.getValue() instanceof KeePassXcPasswordManager kpm ? kpm.getAssociationKey() : null; if (cached != null) { c.useExistingAssociationKey(cached); try { @@ -128,7 +128,7 @@ public class KeePassXcManager implements PasswordManager { if (cached == null) { c.associate(); c.testAssociation(); - if (pref.getValue() instanceof KeePassXcManager kpm + if (pref.getValue() instanceof KeePassXcPasswordManager kpm && !c.getAssociationKey().equals(kpm.getAssociationKey())) { AppPrefs.get() .setFromExternal( @@ -192,7 +192,7 @@ public class KeePassXcManager implements PasswordManager { @Override public CredentialResult retrieveCredentials(String key) { try { - return KeePassXcManager.receive(key); + return KeePassXcPasswordManager.receive(key); } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); return null; diff --git a/app/src/main/java/io/xpipe/app/password/KeeperPasswordManager.java b/app/src/main/java/io/xpipe/app/password/KeeperPasswordManager.java index 63ca6e23d..a7be82963 100644 --- a/app/src/main/java/io/xpipe/app/password/KeeperPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/KeeperPasswordManager.java @@ -1,5 +1,6 @@ package io.xpipe.app.password; +import com.fasterxml.jackson.databind.JsonNode; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.terminal.TerminalLauncher; @@ -9,6 +10,8 @@ import io.xpipe.app.util.SecretRetrievalStrategy; import io.xpipe.core.process.*; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.util.InPlaceSecretValue; +import io.xpipe.core.util.JacksonMapper; import java.util.UUID; @@ -67,10 +70,35 @@ public class KeeperPasswordManager implements PasswordManager { var out = sc.command(CommandBuilder.of() .add(getExecutable(sc), "get") .addLiteral(key) - .add("--format", "password", "--unmask", "--password") + .add("--format", "json", "--unmask") + .add("--password") .addLiteral(r.getSecretValue())) .readStdoutOrThrow(); - return null; + var tree = JacksonMapper.getDefault().readTree(out); + var fields = tree.required("fields"); + if (!fields.isArray()) { + return null; + } + + String login = null; + String password = null; + for (JsonNode field : fields) { + var type = field.required("type").asText(); + if (type.equals("login")) { + var v = field.required("value"); + if (v.size() > 0) { + login = v.get(0).asText(); + } + } + if (type.equals("password")) { + var v = field.required("value"); + if (v.size() > 0) { + password = v.get(0).asText(); + } + } + } + + return new CredentialResult(login, password != null ? InPlaceSecretValue.of(password) : null); } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); return null; diff --git a/app/src/main/java/io/xpipe/app/password/LastpassPasswordManager.java b/app/src/main/java/io/xpipe/app/password/LastpassPasswordManager.java index ecbcf8c3b..866daaee8 100644 --- a/app/src/main/java/io/xpipe/app/password/LastpassPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/LastpassPasswordManager.java @@ -7,6 +7,10 @@ import io.xpipe.app.util.*; import io.xpipe.core.process.*; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.core.util.InPlaceSecretValue; +import io.xpipe.core.util.JacksonMapper; + +import java.util.ArrayList; @JsonTypeName("lastpass") public class LastpassPasswordManager implements PasswordManager { @@ -47,10 +51,25 @@ public class LastpassPasswordManager implements PasswordManager { var out = sc.command(CommandBuilder.of() .add("lpass", "show") - .add("--fixed-strings", "--password") + .add("--fixed-strings", "--json") .addLiteral(key)) .readStdoutOrThrow(); - return null; + var tree = JacksonMapper.getDefault().readTree(out); + + if (tree.size() > 1) { + var matches = new ArrayList(); + tree.iterator().forEachRemaining(item -> { + var title = item.get("name"); + if (title != null) { + matches.add(title.asText()); + } + }); + throw ErrorEvent.expected(new IllegalArgumentException("Ambiguous item name, multiple password entries match: " + String.join(", ", matches))); + } + + var username = tree.get(0).required("username").asText(); + var password = tree.get(0).required("password").asText(); + return new CredentialResult(!username.isEmpty() ? username : null, !password.isEmpty() ? InPlaceSecretValue.of(password) : null); } catch (Exception ex) { ErrorEvent.fromThrowable(ex).handle(); return null; diff --git a/app/src/main/java/io/xpipe/app/password/PasswordManager.java b/app/src/main/java/io/xpipe/app/password/PasswordManager.java index d0b80aa44..6b8808fee 100644 --- a/app/src/main/java/io/xpipe/app/password/PasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/PasswordManager.java @@ -2,7 +2,6 @@ package io.xpipe.app.password; import io.xpipe.core.process.OsType; import io.xpipe.core.util.SecretValue; -import io.xpipe.core.util.ValidationException; import com.fasterxml.jackson.annotation.JsonTypeInfo; import lombok.Value; @@ -16,7 +15,7 @@ public interface PasswordManager { static List> getClasses() { var l = new ArrayList>(); l.add(OnePasswordManager.class); - l.add(KeePassXcManager.class); + l.add(KeePassXcPasswordManager.class); l.add(BitwardenPasswordManager.class); l.add(DashlanePasswordManager.class); if (OsType.getLocal() != OsType.WINDOWS) { diff --git a/app/src/main/java/io/xpipe/app/password/PsonoPasswordManager.java b/app/src/main/java/io/xpipe/app/password/PsonoPasswordManager.java index eb0880f8e..05f04db55 100644 --- a/app/src/main/java/io/xpipe/app/password/PsonoPasswordManager.java +++ b/app/src/main/java/io/xpipe/app/password/PsonoPasswordManager.java @@ -84,20 +84,21 @@ public class PsonoPasswordManager implements PasswordManager { } try { + getOrStartShell().view().setSensitiveEnvironmentVariable("PSONO_CI_API_KEY_ID", apiKey.getSecretValue()); + getOrStartShell().view().setSensitiveEnvironmentVariable("PSONO_CI_API_SECRET_KEY_HEX", apiSecretKey.getSecretValue()); var cmd = getOrStartShell() .command(CommandBuilder.of() - .add("psonoci", "--api-key-id") - .addLiteral(apiKey.getSecretValue()) - .add("--api-secret-key-hex") - .addLiteral(apiSecretKey.getSecretValue()) + .add("psonoci") .add("--server-url") .addLiteral(serverUrl) .add("secret", "get") .addLiteral(key) .add("json")); - cmd.setSensitive();; + cmd.setSensitive(); var r = JacksonMapper.getDefault().readTree(cmd.readStdoutOrThrow()); - return null; + var username = r.required("username"); + var password = r.required("password"); + return new CredentialResult(username.isNull() ? null : username.asText(), password.isNull() ? null : InPlaceSecretValue.of(password.asText())); } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); return null; diff --git a/core/src/main/java/io/xpipe/core/process/CommandControl.java b/core/src/main/java/io/xpipe/core/process/CommandControl.java index 5bb1513a7..36ca4cf8a 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandControl.java +++ b/core/src/main/java/io/xpipe/core/process/CommandControl.java @@ -77,8 +77,6 @@ public interface CommandControl extends ProcessControl { String readStdoutOrThrow() throws Exception; - SecretValue readStdoutSecretOrThrow() throws Exception; - Optional readStdoutIfPossible() throws Exception; default boolean discardAndCheckExit() throws ProcessOutputException {