This commit is contained in:
crschnick
2025-05-27 13:26:23 +00:00
parent 9e2726b66f
commit dbbe2c96ed
25 changed files with 578 additions and 322 deletions
@@ -76,9 +76,13 @@ public class AppShellCheck {
@Override
protected boolean fallBackInstantly() {
var complex = ShellDialects.CMD.requiresScript(System.getenv("USERPROFILE")) ||
ShellDialects.CMD.requiresScript(System.getenv("TEMP"));
return complex;
// In theory, this prevents cmd issues with unsupported characters
// However, due to workarounds, this should still work
// Falling back to powershell would make it slower and introduce other potential issues
// var complex = ShellDialects.CMD.requiresScript(System.getenv("USERPROFILE")) ||
// ShellDialects.CMD.requiresScript(System.getenv("TEMP"));
// return complex;
return false;
}
};
};
@@ -2,6 +2,7 @@ package io.xpipe.app.password;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.CommandSupport;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl;
@@ -22,6 +23,16 @@ public class OnePasswordManager implements PasswordManager {
@Override
public String retrievePassword(String key) {
try {
CommandSupport.isInLocalPathOrThrow("1Password CLI", "op");
} catch (Exception e) {
ErrorEvent.fromThrowable(e)
.expected()
.link("https://developer.1password.com/docs/cli/get-started/")
.handle();
return null;
}
try {
var r = getOrStartShell()
.command(CommandBuilder.of()
@@ -23,6 +23,7 @@ public interface PasswordManager {
l.add(EnpassPasswordManager.class);
}
l.add(KeeperPasswordManager.class);
l.add(PsonoPasswordManager.class);
if (OsType.getLocal() == OsType.WINDOWS) {
l.add(WindowsCredentialManager.class);
}
@@ -0,0 +1,110 @@
package io.xpipe.app.password;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.comp.base.SecretFieldComp;
import io.xpipe.app.comp.base.TextFieldComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.*;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.InPlaceSecretValue;
import io.xpipe.core.util.SecretValue;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.jackson.Jacksonized;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
@Getter
@Builder
@ToString
@Jacksonized
@JsonTypeName("psono")
public class PsonoPasswordManager implements PasswordManager {
private final InPlaceSecretValue apiKey;
private final InPlaceSecretValue apiSecretKey;
private final String serverUrl;
public static OptionsBuilder createOptions(Property<PsonoPasswordManager> p) {
var apiKey = new SimpleObjectProperty<>(p.getValue().getApiKey());
var apiSecretKey = new SimpleObjectProperty<>(p.getValue().getApiSecretKey());
var serverUrl = new SimpleStringProperty(p.getValue().getServerUrl());
return new OptionsBuilder()
.nameAndDescription("psonoServerUrl")
.addComp(new TextFieldComp(serverUrl).apply(struc -> {
struc.get().setPromptText("https://www.psono.pw/server");
}).maxWidth(600), serverUrl)
.nameAndDescription("psonoApiKey")
.addComp(new SecretFieldComp(apiKey, false).maxWidth(600), apiKey)
.nameAndDescription("psonoApiSecretKey")
.addComp(new SecretFieldComp(apiSecretKey, false).maxWidth(600), apiSecretKey)
.bind(
() -> {
return PsonoPasswordManager.builder().apiKey(apiKey.get()).apiSecretKey(apiSecretKey.get()).serverUrl(serverUrl.get()).build();
},
p);
}
private static ShellControl SHELL;
private static synchronized ShellControl getOrStartShell() throws Exception {
if (SHELL == null) {
SHELL = ProcessControlProvider.get().createLocalProcessControl(true);
}
SHELL.start();
return SHELL;
}
@Override
public String retrievePassword(String key) {
try {
CommandSupport.isInLocalPathOrThrow("Psono CLI", "psonoci");
} catch (Exception e) {
ErrorEvent.fromThrowable(e)
.expected()
.link("https://doc.psono.com/user/psonoci/install.html")
.handle();
return null;
}
try {
var cmd = getOrStartShell()
.command(CommandBuilder.of()
.add("psonoci", "--api-key-id")
.addLiteral(apiKey.getSecretValue())
.add("--api-secret-key-hex")
.addLiteral(apiSecretKey.getSecretValue())
.add("--server-url")
.addLiteral(serverUrl)
.add("secret", "get")
.addLiteral(key)
.add("password"));
cmd.setSensitive();;
var r = cmd.readStdoutOrThrow();
return r;
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return null;
}
}
@Override
public String getKeyPlaceholder() {
return AppI18n.get("psonoPlaceholder");
}
}
@@ -10,6 +10,8 @@ import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellScript;
import io.xpipe.core.util.XPipeInstallation;
@@ -113,11 +115,17 @@ public class ChocoUpdater extends UpdateHandler {
var performedUpdate = new PerformedUpdate(p.getVersion(), p.getBody(), p.getVersion());
AppCache.update("performedUpdate", performedUpdate);
OperationMode.executeAfterShutdown(() -> {
var systemWide =
Files.exists(XPipeInstallation.getCurrentInstallationBasePath().resolve("system"));
var propertiesArguments = systemWide ? ", --install-arguments=\"'ALLUSERS=1'\"" : "";
TerminalLauncher.openDirectFallback("XPipe Updater", sc -> {
var pkg = "xpipe";
var commandToRun = "Start-Process -Wait -Verb runAs -FilePath choco -ArgumentList upgrade, " + pkg
+ ", -y" + propertiesArguments;
var powershell = ShellDialects.isPowershell(sc);
var powershellCommand = powershell ? "powershell -Command " + sc.getShellDialect().quoteArgument(commandToRun) : "powershell -Command " + commandToRun;
return ShellScript.lines(
"powershell -Command \"Start-Process -Verb runAs -FilePath choco -ArgumentList upgrade, " + pkg
+ "\"",
powershellCommand,
AppRestart.getTerminalRestartCommand());
});
});
@@ -1,6 +1,8 @@
package io.xpipe.app.util;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.augment.Augment;
import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.GuiDialog;
@@ -16,6 +18,8 @@ import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import atlantafx.base.controls.Spacer;
import javafx.scene.layout.VBox;
import net.synedra.validatorfx.Check;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
@@ -35,6 +39,7 @@ public class OptionsBuilder {
private final List<Check> allChecks = new ArrayList<>();
private final List<OptionsComp.Entry> entries = new ArrayList<>();
private final List<Property<?>> props = new ArrayList<>();
private final List<Augment<CompStructure<VBox>>> augments = new ArrayList<>();
private ObservableValue<String> name;
private ObservableValue<String> description;
@@ -53,6 +58,11 @@ public class OptionsBuilder {
this.allValidators.add(ownValidator);
}
public OptionsBuilder augment(Augment<CompStructure<VBox>> augment) {
this.augments.add(augment);
return this;
}
public Validator buildEffectiveValidator() {
return new ChainedValidator(allValidators);
}
@@ -459,7 +469,11 @@ public class OptionsBuilder {
public OptionsComp buildComp() {
finishCurrent();
return new OptionsComp(allChecks, entries);
var comp = new OptionsComp(allChecks, entries);
for (Augment<CompStructure<VBox>> augment : augments) {
comp.apply(augment);
}
return comp;
}
public Region build() {