This commit is contained in:
crschnick
2026-02-28 15:11:06 +00:00
parent 29c5c10fa9
commit dd4c9c120e
15 changed files with 360 additions and 0 deletions
@@ -0,0 +1,90 @@
package io.xpipe.ext.base.identity;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.cred.SshIdentityStrategy;
import io.xpipe.app.cred.UsernameStrategy;
import io.xpipe.app.ext.ValidationException;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.secret.EncryptedValue;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.Validators;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.Value;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@SuperBuilder
@JsonTypeName("multiIdentity")
@Jacksonized
@Value
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MultiIdentityStore extends IdentityStore {
List<DataStoreEntryRef<IdentityStore>> identities;
public List<DataStoreEntryRef<IdentityStore>> getAvailableIdentities() {
return identities.stream().filter(ref -> ref != null && ref.get().getValidity().isUsable()).toList();
}
public Optional<DataStoreEntryRef<IdentityStore>> getSelected() {
UUID cached = AppCache.getNonNull("id-default-" + getSelfEntry().getUuid(), UUID.class, () -> null);
if (cached != null) {
var entry = DataStorage.get().getStoreEntryIfPresent(cached);
if (entry.isPresent() && entry.get().getValidity().isUsable() && getAvailableIdentities().contains(entry.get().ref())) {
return Optional.of(entry.get().ref());
}
}
var fallback = getAvailableIdentities().stream().findFirst();
if (fallback.isPresent()) {
return fallback;
}
return Optional.empty();
}
public void select(DataStoreEntryRef<IdentityStore> entry) {
if (!getAvailableIdentities().contains(entry)) {
return;
}
AppCache.update("id-default-" + getSelfEntry().getUuid(), entry.get().getUuid());
}
private DataStoreEntryRef<IdentityStore> getSelectedOrThrow() {
var found = getSelected();
if (found.isPresent()) {
return found.get();
}
throw ErrorEventFactory.expected(new IllegalStateException("No available identity for multi identity " + getSelfEntry().getName()));
}
@Override
public void checkComplete() throws Throwable {
getSelectedOrThrow();
}
public UsernameStrategy getUsername() {
return getSelectedOrThrow().getStore().getUsername();
}
@Override
public SecretRetrievalStrategy getPassword() {
return getSelectedOrThrow().getStore().getPassword();
}
@Override
public SshIdentityStrategy getSshIdentity() {
return getSelectedOrThrow().getStore().getSshIdentity();
}
}
@@ -0,0 +1,68 @@
package io.xpipe.ext.base.identity;
import io.xpipe.app.cred.NoIdentityStrategy;
import io.xpipe.app.cred.SshIdentityStrategyChoiceConfig;
import io.xpipe.app.ext.DataStore;
import io.xpipe.app.ext.GuiDialog;
import io.xpipe.app.hub.comp.StoreListChoiceComp;
import io.xpipe.app.hub.comp.StoreViewState;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.platform.OptionsChoiceBuilder;
import io.xpipe.app.secret.EncryptedValue;
import io.xpipe.app.secret.SecretNoneStrategy;
import io.xpipe.app.secret.SecretRetrievalStrategy;
import io.xpipe.app.secret.SecretStrategyChoiceConfig;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.DocumentationLink;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MultiIdentityStoreProvider extends IdentityStoreProvider {
@Override
public GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
MultiIdentityStore st = (MultiIdentityStore) store.getValue();
var identities = new SimpleListProperty<>(FXCollections.observableArrayList(st.getIdentities()));
return new OptionsBuilder()
.nameAndDescription("multiIdentityList")
.addComp(new StoreListChoiceComp<>(identities, IdentityStore.class,
ref -> !(ref.getStore() instanceof MultiIdentityStore) && !identities.contains(ref),
StoreViewState.get().getAllIdentitiesCategory()))
.bind(
() -> {
return MultiIdentityStore.builder()
.identities(identities)
.build();
},
store)
.buildDialog();
}
@Override
public DataStore defaultStore(DataStoreCategory category) {
return MultiIdentityStore.builder()
.identities(new ArrayList<>())
.build();
}
@Override
public String getId() {
return "multiIdentity";
}
@Override
public List<Class<?>> getStoreClasses() {
return List.of(MultiIdentityStore.class);
}
}
@@ -0,0 +1,89 @@
package io.xpipe.ext.base.identity;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.hub.action.HubBranchProvider;
import io.xpipe.app.hub.action.HubLeafProvider;
import io.xpipe.app.hub.action.HubMenuItemProvider;
import io.xpipe.app.hub.action.StoreActionCategory;
import io.xpipe.app.platform.LabelGraphic;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import java.util.List;
import java.util.stream.Collectors;
public class MultiIdentitySwitchBranchProvider implements HubBranchProvider<MultiIdentityStore> {
@Override
public boolean isMajor() {
return true;
}
@Override
public List<HubMenuItemProvider<?>> getChildren(DataStoreEntryRef<MultiIdentityStore> store) {
var selected = store.getStore().getSelected();
return store.getStore().getAvailableIdentities().stream()
.map(is -> {
return new IdentityProvider(is, selected.map(ref -> ref.equals(is)).orElse(false));
})
.collect(Collectors.toList());
}
@Override
public StoreActionCategory getCategory() {
return StoreActionCategory.CONFIGURATION;
}
@Override
public boolean isApplicable(DataStoreEntryRef<MultiIdentityStore> o) {
return o.getStore().getAvailableIdentities().size() > 1;
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<MultiIdentityStore> store) {
return AppI18n.observable("switchIdentity");
}
@Override
public LabelGraphic getIcon(DataStoreEntryRef<MultiIdentityStore> store) {
return new LabelGraphic.IconGraphic("mdi2f-format-list-group");
}
@Override
public Class<MultiIdentityStore> getApplicableClass() {
return MultiIdentityStore.class;
}
private static class IdentityProvider implements HubLeafProvider<MultiIdentityStore> {
private final DataStoreEntryRef<IdentityStore> identity;
private final boolean active;
private IdentityProvider(DataStoreEntryRef<IdentityStore> identity, boolean active) {
this.identity = identity;
this.active = active;
}
@Override
public void execute(DataStoreEntryRef<MultiIdentityStore> ref) {
ref.getStore().select(identity);
}
@Override
public ObservableValue<String> getName(DataStoreEntryRef<MultiIdentityStore> store) {
return new ReadOnlyStringWrapper((active ? "> " : "") + identity.get().getName());
}
@Override
public LabelGraphic getIcon(DataStoreEntryRef<MultiIdentityStore> store) {
return new LabelGraphic.ImageGraphic(identity.get().getEffectiveIconFile(), 16);
}
@Override
public Class<MultiIdentityStore> getApplicableClass() {
return MultiIdentityStore.class;
}
}
}
+2
View File
@@ -39,6 +39,7 @@ open module io.xpipe.ext.base {
AbstractHostCreationActionProvider,
HostAddressSwitchBranchProvider,
LocalIdentityConvertHubLeafProvider,
MultiIdentitySwitchBranchProvider,
RunBackgroundScriptActionProvider,
RunHubBatchScriptActionProvider,
RunHubScriptActionProvider,
@@ -68,6 +69,7 @@ open module io.xpipe.ext.base {
LocalIdentityStoreProvider,
SyncedIdentityStoreProvider,
PasswordManagerIdentityStoreProvider,
MultiIdentityStoreProvider,
AbstractHostStoreProvider;
provides DataStorageExtensionProvider with
ScriptDataStorageProvider;
Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
data-name="Layer 1"
viewBox="0 0 204.59 204.59"
version="1.1"
sodipodi:docname="multiIdentity_icon-dark.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><path
style="fill:#e98325;fill-opacity:1;stroke-width:0.647549"
d="m 137.49341,134.48676 -18.02556,-19.08771 -0.002,12.28612 h -16.44943 -0.3122 -9.677136 v 13.60523 h 9.678366 v 0 h 16.75995 l 0.002,12.28747 z"
id="path1-4" /><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.7891646"
inkscape:cx="127.99531"
inkscape:cy="122.79663"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><defs
id="defs856"><style
id="style854">.cls-1{fill:#ac55ff;}</style><style
id="style2411">.cls-1{fill:#606161;}</style></defs><title
id="title858">identity</title><path
class="cls-1"
d="M332.32,153.7H179.68a26,26,0,0,0-26,26V332.32a26,26,0,0,0,26,26H332.32a26,26,0,0,0,26-26V179.68A26,26,0,0,0,332.32,153.7Zm14,178.62a14,14,0,0,1-14,14H179.68a14,14,0,0,1-14-14V179.68a14,14,0,0,1,14-14H332.32a14,14,0,0,1,14,14Z"
id="path860"
style="fill:#d9d9d9;fill-opacity:1"
transform="translate(-153.7 -153.7)" /><metadata
id="metadata932"><rdf:RDF><cc:Work
rdf:about=""><dc:title>identity</dc:title></cc:Work></rdf:RDF></metadata><path
d="M 154.40997,126.10572 A 53.816715,53.816715 0 0 0 142.81203,108.91027 54.051264,54.051264 0 0 0 125.61659,97.312331 c -0.0576,-0.0288 -0.11512,-0.0432 -0.17268,-0.072 8.96466,-6.47527 14.7924,-17.02277 14.7924,-28.92289 0,-19.71361 -15.97234,-35.68595 -35.68595,-35.68595 -19.713613,0 -35.685953,15.97234 -35.685953,35.68595 0,11.90012 5.82775,22.44762 14.79241,28.93728 -0.0576,0.0288 -0.11512,0.0432 -0.17268,0.072 -6.44649,2.719609 -12.23107,6.619159 -17.19545,11.597939 a 54.051264,54.051264 0 0 0 -11.59793,17.19545 53.485756,53.485756 0 0 0 -4.2449,19.87189 1.1511597,1.1511597 0 0 0 1.15116,1.17994 h 8.6337 c 0.63313,0 1.13677,-0.50363 1.15116,-1.12238 0.28779,-11.10869 4.74853,-21.5123 12.63397,-29.39774 8.15885,-8.15884 18.99414,-12.64838 30.534513,-12.64838 11.54038,0 22.37567,4.48954 30.53451,12.64838 7.88545,7.88544 12.34619,18.28905 12.63398,29.39774 0.0144,0.63314 0.51802,1.12238 1.15116,1.12238 h 8.6337 a 1.1511597,1.1511597 0 0 0 1.15116,-1.17994 c -0.1439,-6.87818 -1.56846,-13.56929 -4.2449,-19.88628 z M 104.55036,93.067431 c -6.604773,0 -12.821043,-2.57572 -17.497623,-7.25231 -4.67659,-4.67659 -7.25231,-10.89285 -7.25231,-17.49763 0,-6.60478 2.57572,-12.82104 7.25231,-17.49762 4.67658,-4.67659 10.89285,-7.25231 17.497623,-7.25231 6.60478,0 12.82104,2.57572 17.49763,7.25231 4.67659,4.67658 7.25231,10.89284 7.25231,17.49762 0,6.60478 -2.57572,12.82104 -7.25231,17.49763 -4.67659,4.67659 -10.89285,7.25231 -17.49763,7.25231 z"
id="path1"
style="fill:#e98325;fill-opacity:1;stroke-width:0.143895" /><path
style="fill:#e98325;fill-opacity:1;stroke-width:0.653351"
d="m 66.172124,153.93532 18.350031,-19.08837 0.0012,12.28609 h 16.745855 0.31783 9.29408 v 13.60524 h -9.29529 v 0 H 84.523412 l -0.0012,12.28747 z"
id="path2" /></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
data-name="Layer 1"
viewBox="0 0 204.59 204.59"
version="1.1"
sodipodi:docname="multiIdentity_icon.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><path
style="fill:#d96700;fill-opacity:1;stroke-width:0.647549"
d="m 137.49341,134.48676 -18.02556,-19.08771 -0.002,12.28612 h -16.44943 -0.3122 -9.677136 v 13.60523 h 9.678366 v 0 h 16.75995 l 0.002,12.28747 z"
id="path1-4" /><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.9722372"
inkscape:cx="146.5341"
inkscape:cy="133.60462"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" /><defs
id="defs856"><style
id="style854">.cls-1{fill:#ac55ff;}</style><style
id="style2411">.cls-1{fill:#606161;}</style></defs><title
id="title858">identity</title><path
class="cls-1"
d="M332.32,153.7H179.68a26,26,0,0,0-26,26V332.32a26,26,0,0,0,26,26H332.32a26,26,0,0,0,26-26V179.68A26,26,0,0,0,332.32,153.7Zm14,178.62a14,14,0,0,1-14,14H179.68a14,14,0,0,1-14-14V179.68a14,14,0,0,1,14-14H332.32a14,14,0,0,1,14,14Z"
id="path860"
style="fill:#212121;fill-opacity:1"
transform="translate(-153.7 -153.7)" /><metadata
id="metadata932"><rdf:RDF><cc:Work
rdf:about=""><dc:title>identity</dc:title></cc:Work></rdf:RDF></metadata><path
d="M 154.40997,126.10572 A 53.816715,53.816715 0 0 0 142.81203,108.91027 54.051264,54.051264 0 0 0 125.61659,97.312331 c -0.0576,-0.0288 -0.11512,-0.0432 -0.17268,-0.072 8.96466,-6.47527 14.7924,-17.02277 14.7924,-28.92289 0,-19.71361 -15.97234,-35.68595 -35.68595,-35.68595 -19.713613,0 -35.685953,15.97234 -35.685953,35.68595 0,11.90012 5.82775,22.44762 14.79241,28.93728 -0.0576,0.0288 -0.11512,0.0432 -0.17268,0.072 -6.44649,2.719609 -12.23107,6.619159 -17.19545,11.597939 a 54.051264,54.051264 0 0 0 -11.59793,17.19545 53.485756,53.485756 0 0 0 -4.2449,19.87189 1.1511597,1.1511597 0 0 0 1.15116,1.17994 h 8.6337 c 0.63313,0 1.13677,-0.50363 1.15116,-1.12238 0.28779,-11.10869 4.74853,-21.5123 12.63397,-29.39774 8.15885,-8.15884 18.99414,-12.64838 30.534513,-12.64838 11.54038,0 22.37567,4.48954 30.53451,12.64838 7.88545,7.88544 12.34619,18.28905 12.63398,29.39774 0.0144,0.63314 0.51802,1.12238 1.15116,1.12238 h 8.6337 a 1.1511597,1.1511597 0 0 0 1.15116,-1.17994 c -0.1439,-6.87818 -1.56846,-13.56929 -4.2449,-19.88628 z M 104.55036,93.067431 c -6.604773,0 -12.821043,-2.57572 -17.497623,-7.25231 -4.67659,-4.67659 -7.25231,-10.89285 -7.25231,-17.49763 0,-6.60478 2.57572,-12.82104 7.25231,-17.49762 4.67658,-4.67659 10.89285,-7.25231 17.497623,-7.25231 6.60478,0 12.82104,2.57572 17.49763,7.25231 4.67659,4.67658 7.25231,10.89284 7.25231,17.49762 0,6.60478 -2.57572,12.82104 -7.25231,17.49763 -4.67659,4.67659 -10.89285,7.25231 -17.49763,7.25231 z"
id="path1"
style="fill:#d96700;fill-opacity:1;stroke-width:0.143895" /><path
style="fill:#d96700;fill-opacity:1;stroke-width:0.653351"
d="m 66.172124,153.93532 18.350031,-19.08837 0.0012,12.28609 h 16.745855 0.31783 9.29408 v 13.60524 h -9.29529 v 0 H 84.523412 l -0.0012,12.28747 z"
id="path2" /></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+5
View File
@@ -2016,3 +2016,8 @@ passwordManagerSshAgentSocketDescription=Override the default agent socket locat
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
multiIdentity.displayName=Multi identity
multiIdentity.displayDescription=Choose from multiple different identities to connect to a system
multiIdentityList=Identity list
multiIdentityListDescription=The list of identities to switch between
switchIdentity=Switch identity