diff --git a/app/src/main/java/io/xpipe/app/ext/PrefsHandler.java b/app/src/main/java/io/xpipe/app/ext/PrefsHandler.java index 19fa619c4..532e809a4 100644 --- a/app/src/main/java/io/xpipe/app/ext/PrefsHandler.java +++ b/app/src/main/java/io/xpipe/app/ext/PrefsHandler.java @@ -1,10 +1,11 @@ package io.xpipe.app.ext; +import com.fasterxml.jackson.databind.JavaType; import io.xpipe.app.comp.Comp; import javafx.beans.property.Property; public interface PrefsHandler { - void addSetting(String id, Class c, Property property, Comp comp, boolean requiresRestart); + void addSetting(String id, JavaType t, Property property, Comp comp, boolean requiresRestart); } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 37e01c5d4..f1740b0ed 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -1,5 +1,8 @@ package io.xpipe.app.prefs; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.SimpleType; +import com.fasterxml.jackson.databind.type.TypeFactory; import io.xpipe.app.comp.Comp; import io.xpipe.app.core.*; import io.xpipe.app.core.mode.OperationMode; @@ -551,8 +554,7 @@ public class AppPrefs { private T loadValue(AppPrefsStorageHandler handler, Mapping value) { T def = (T) value.getProperty().getValue(); Property property = (Property) value.getProperty(); - Class clazz = (Class) value.getValueClass(); - var val = handler.loadObject(value.getKey(), clazz, def); + var val = handler.loadObject(value.getKey(), value.getValueType(), def); property.setValue(val); return val; } @@ -607,28 +609,47 @@ public class AppPrefs { String key; Property property; - Class valueClass; + JavaType valueType; boolean vaultSpecific; boolean requiresRestart; String licenseFeatureId; public Mapping( - String key, Property property, Class valueClass, boolean vaultSpecific, boolean requiresRestart) { + String key, Property property, Class valueType, boolean vaultSpecific, boolean requiresRestart) { this.key = key; this.property = property; - this.valueClass = valueClass; + this.valueType = SimpleType.constructUnsafe(valueType); this.vaultSpecific = vaultSpecific; this.requiresRestart = requiresRestart; this.licenseFeatureId = null; } + + public Mapping( + String key, Property property, JavaType valueType, boolean vaultSpecific, boolean requiresRestart) { + this.key = key; + this.property = property; + this.valueType = valueType; + this.vaultSpecific = vaultSpecific; + this.requiresRestart = requiresRestart; + this.licenseFeatureId = null; + } + + + public static class MappingBuilder { + + MappingBuilder valueClass(Class clazz) { + this.valueType(TypeFactory.defaultInstance().constructType(clazz)); + return this; + } + } } @Getter private class PrefsHandlerImpl implements PrefsHandler { @Override - public void addSetting(String id, Class c, Property property, Comp comp, boolean requiresRestart) { - var m = new Mapping(id, property, c, false, requiresRestart); + public void addSetting(String id, JavaType t, Property property, Comp comp, boolean requiresRestart) { + var m = new Mapping(id, property, t, false, requiresRestart); customEntries.put(m, comp); mapping.add(m); } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java index 5cb4e2716..71d63eecf 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java @@ -1,5 +1,6 @@ package io.xpipe.app.prefs; +import com.fasterxml.jackson.databind.JavaType; import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; @@ -16,6 +17,7 @@ import org.apache.commons.io.FileUtils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import static io.xpipe.app.ext.PrefsChoiceValue.getAll; import static io.xpipe.app.ext.PrefsChoiceValue.getSupported; @@ -77,7 +79,7 @@ public class AppPrefsStorageHandler { } @SuppressWarnings("unchecked") - public T loadObject(String id, Class type, T defaultObject) { + public T loadObject(String id, JavaType type, T defaultObject) { var tree = getContent(id); if (tree == null) { TrackEvent.withDebug("Preferences value not found") @@ -87,10 +89,10 @@ public class AppPrefsStorageHandler { return defaultObject; } - if (PrefsChoiceValue.class.isAssignableFrom(type)) { - var all = getAll(type); + if (PrefsChoiceValue.class.isAssignableFrom(type.getRawClass())) { + List all = (List) getAll(type.getRawClass()); if (all != null) { - Class cast = (Class) type; + Class cast = (Class) type.getRawClass(); var in = tree.asText(); var found = all.stream() .filter(t -> ((PrefsChoiceValue) t).getId().equalsIgnoreCase(in)) diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageNode.java b/app/src/main/java/io/xpipe/app/storage/DataStorageNode.java index e16b4f9f4..7af72a7db 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageNode.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageNode.java @@ -45,7 +45,7 @@ public class DataStorageNode { } try { - var secret = JacksonMapper.getDefault().treeToValue(node, DataStorageSecret.class); + var secret = DataStorageSecret.deserialize(node); if (secret == null) { return new DataStorageNode(node, false, true, false); } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageSecret.java b/app/src/main/java/io/xpipe/app/storage/DataStorageSecret.java index 6f63b5ddf..13834e5e4 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageSecret.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageSecret.java @@ -1,8 +1,14 @@ package io.xpipe.app.storage; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.EncryptionToken; import io.xpipe.app.util.PasswordLockSecretValue; import io.xpipe.app.util.VaultKeySecretValue; +import io.xpipe.core.util.EncryptedSecretValue; import io.xpipe.core.util.JacksonMapper; import io.xpipe.core.util.SecretValue; @@ -10,11 +16,50 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.Value; import lombok.experimental.NonFinal; +import java.io.IOException; import java.util.Arrays; @Value public class DataStorageSecret { + public static DataStorageSecret deserialize(JsonNode tree) throws IOException { + if (!tree.isObject()) { + return null; + } + + var legacy = JacksonMapper.getDefault().treeToValue(tree, EncryptedSecretValue.class); + if (legacy != null) { + // Don't cache legacy node + return new DataStorageSecret(EncryptionToken.ofVaultKey(), null, legacy.inPlace()); + } + + var obj = (ObjectNode) tree; + if (!obj.has("secret")) { + return null; + } + + var secretTree = obj.required("secret"); + var secret = JacksonMapper.getDefault().treeToValue(secretTree, SecretValue.class); + if (secret == null) { + return null; + } + + var hadLock = AppPrefs.get().getLockCrypt().get() != null + && !AppPrefs.get().getLockCrypt().get().isEmpty(); + var tokenNode = obj.get("encryptedToken"); + var token = + tokenNode != null ? JacksonMapper.getDefault().treeToValue(tokenNode, EncryptionToken.class) : null; + if (token == null) { + var userToken = hadLock; + if (userToken && DataStorageUserHandler.getInstance().getActiveUser() == null) { + return null; + } + token = userToken ? EncryptionToken.ofUser() : EncryptionToken.ofVaultKey(); + } + + return new DataStorageSecret(token, secretTree, secret); + } + public static DataStorageSecret ofCurrentSecret(SecretValue internalSecret) { var handler = DataStorageUserHandler.getInstance(); return new DataStorageSecret( @@ -41,7 +86,7 @@ public class DataStorageSecret { this.internalSecret = internalSecret; } - public boolean requiresRewrite() { + public boolean requiresRewrite(boolean allowUserSecretKey) { var isVault = encryptedToken.isVault(); var isUser = encryptedToken.isUser(); var userHandler = DataStorageUserHandler.getInstance(); @@ -53,12 +98,17 @@ public class DataStorageSecret { return false; } + // We don't want to use the new user key + if (!allowUserSecretKey) { + return false; + } + return true; } var hasUserKey = userHandler.getActiveUser() != null; // Switch from vault to user - if (hasUserKey && isVault) { + if (hasUserKey && isVault && allowUserSecretKey) { return true; } // Switch from user to vault @@ -69,9 +119,9 @@ public class DataStorageSecret { return false; } - public JsonNode rewrite() { + public JsonNode rewrite(boolean allowUserSecretKey) { var handler = DataStorageUserHandler.getInstance(); - if (handler != null && handler.getActiveUser() != null && encryptedToken.isUser()) { + if (handler != null && handler.getActiveUser() != null && allowUserSecretKey) { var val = new PasswordLockSecretValue(getSecret()); originalNode = JacksonMapper.getDefault().valueToTree(val); encryptedToken = EncryptionToken.ofUser(); @@ -84,6 +134,24 @@ public class DataStorageSecret { return originalNode; } + public void serialize(JsonGenerator jgen, boolean allowUserSecretKey) throws IOException { + var mapper = JacksonMapper.getDefault(); + var tree = JsonNodeFactory.instance.objectNode(); + tree.set("encryptedToken", mapper.valueToTree(getEncryptedToken())); + + // Preserve same output if not changed + if (getOriginalNode() != null && !requiresRewrite(allowUserSecretKey)) { + tree.set("secret", getOriginalNode()); + jgen.writeTree(tree); + return; + } + + // Reencrypt + var val = rewrite(allowUserSecretKey); + tree.set("secret", val); + jgen.writeTree(tree); + } + public char[] getSecret() { return internalSecret != null ? internalSecret.getSecret() : new char[0]; } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index 96ed896a0..5bc33925b 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -391,9 +391,7 @@ public class StandardStorage extends DataStorage { } deleteLeftovers(); - if (dispose) { - dataStorageUserHandler.save(); - } + dataStorageUserHandler.save(); dataStorageSyncHandler.afterStorageSave(); if (dispose) { disposed = true; diff --git a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java index b9b15107b..62d7c22a4 100644 --- a/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/util/AppJacksonModule.java @@ -1,20 +1,19 @@ package io.xpipe.app.util; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.type.SimpleType; import io.xpipe.app.ext.LocalStore; -import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.*; import io.xpipe.app.terminal.ExternalTerminalType; -import io.xpipe.core.util.EncryptedSecretValue; import io.xpipe.core.util.JacksonMapper; -import io.xpipe.core.util.SecretValue; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.CharArrayReader; import java.io.IOException; import java.util.UUID; @@ -29,10 +28,10 @@ public class AppJacksonModule extends SimpleModule { addDeserializer(DataStoreEntryRef.class, new DataStoreEntryRefDeserializer()); addSerializer(ContextualFileReference.class, new LocalFileReferenceSerializer()); addDeserializer(ContextualFileReference.class, new LocalFileReferenceDeserializer()); - addSerializer(DataStorageSecret.class, new DataStoreSecretSerializer()); - addDeserializer(DataStorageSecret.class, new DataStoreSecretDeserializer()); addSerializer(ExternalTerminalType.class, new ExternalTerminalTypeSerializer()); addDeserializer(ExternalTerminalType.class, new ExternalTerminalTypeDeserializer()); + addSerializer(EncryptedValue.class, new EncryptedValueSerializer()); + addDeserializer(EncryptedValue.class, new EncryptedValueDeserializer()); context.addSerializers(_serializers); context.addDeserializers(_deserializers); @@ -76,69 +75,49 @@ public class AppJacksonModule extends SimpleModule { } } - public static class DataStoreSecretSerializer extends JsonSerializer { + @SuppressWarnings("all") + public static class EncryptedValueSerializer extends JsonSerializer { @Override - public void serialize(DataStorageSecret value, JsonGenerator jgen, SerializerProvider provider) + public void serialize(EncryptedValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - var mapper = JacksonMapper.getDefault(); - var tree = JsonNodeFactory.instance.objectNode(); - tree.set("encryptedToken", mapper.valueToTree(value.getEncryptedToken())); - - // Preserve same output if not changed - if (value.getOriginalNode() != null && !value.requiresRewrite()) { - tree.set("secret", value.getOriginalNode()); - jgen.writeTree(tree); - return; - } - - // Reencrypt - var val = value.rewrite(); - tree.set("secret", val); - jgen.writeTree(tree); + value.getSecret().serialize(jgen, value.allowUserSecretKey()); } } - public static class DataStoreSecretDeserializer extends JsonDeserializer { + @SuppressWarnings("all") + public static class EncryptedValueDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private boolean useCurrentSecretKey; + private Class type; @Override - public DataStorageSecret deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - var tree = JacksonMapper.getDefault().readTree(p); - if (!tree.isObject()) { - return null; - } + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { + JavaType wrapperType = property.getType(); + JavaType valueType = wrapperType.containedType(0); + var deserializer = new EncryptedValueDeserializer(); + var useCurrentSecretKey = !wrapperType.equals(SimpleType.constructUnsafe(EncryptedValue.VaultKey.class)); + deserializer.useCurrentSecretKey = useCurrentSecretKey; + deserializer.type = valueType.getRawClass(); + return deserializer; + } - var legacy = JacksonMapper.getDefault().treeToValue(tree, EncryptedSecretValue.class); - if (legacy != null) { - // Don't cache legacy node - return new DataStorageSecret(EncryptionToken.ofVaultKey(), null, legacy.inPlace()); - } - - var obj = (ObjectNode) tree; - if (!obj.has("secret")) { - return null; - } - - var secretTree = obj.required("secret"); - var secret = JacksonMapper.getDefault().treeToValue(secretTree, SecretValue.class); + @Override + public EncryptedValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Object value; + var secret = DataStorageSecret.deserialize(JacksonMapper.getDefault().readTree(p)); if (secret == null) { - return null; - } - - var hadLock = AppPrefs.get().getLockCrypt().get() != null - && !AppPrefs.get().getLockCrypt().get().isEmpty(); - var tokenNode = obj.get("encryptedToken"); - var token = - tokenNode != null ? JacksonMapper.getDefault().treeToValue(tokenNode, EncryptionToken.class) : null; - if (token == null) { - var migrateToUser = hadLock; - if (migrateToUser && DataStorageUserHandler.getInstance().getActiveUser() == null) { + var raw = JacksonMapper.getDefault().readValue(p, type); + if (raw != null) { + value = raw; + } else { return null; } - token = migrateToUser ? EncryptionToken.ofUser() : EncryptionToken.ofVaultKey(); + } else { + value = JacksonMapper.getDefault().readValue(new CharArrayReader(secret.getSecret()), type); } - - return new DataStorageSecret(token, secretTree, secret); + var perUser = useCurrentSecretKey || secret.getEncryptedToken().isUser(); + return perUser ? new EncryptedValue.CurrentKey<>(value, secret) : new EncryptedValue.VaultKey<>(value, secret); } } diff --git a/app/src/main/java/io/xpipe/app/util/EncryptedValue.java b/app/src/main/java/io/xpipe/app/util/EncryptedValue.java new file mode 100644 index 000000000..3500aa8ce --- /dev/null +++ b/app/src/main/java/io/xpipe/app/util/EncryptedValue.java @@ -0,0 +1,68 @@ +package io.xpipe.app.util; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.storage.DataStorageSecret; +import io.xpipe.app.storage.DataStorageUserHandler; +import io.xpipe.core.util.JacksonMapper; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.SneakyThrows; + +@AllArgsConstructor +@Getter +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = EncryptedValue.VaultKey.class), + @JsonSubTypes.Type(value = EncryptedValue.CurrentKey.class), +}) +public abstract class EncryptedValue { + + private final T value; + private final DataStorageSecret secret; + + public abstract boolean allowUserSecretKey(); + + @JsonTypeName("current") + public static class CurrentKey extends EncryptedValue { + + public CurrentKey(T value, DataStorageSecret secret) { + super(value, secret); + } + + @SneakyThrows + public static CurrentKey of(T value) { + var handler = DataStorageUserHandler.getInstance(); + var s = JacksonMapper.getDefault().writeValueAsString(value); + var secret = new VaultKeySecretValue(s.toCharArray()); + return new CurrentKey<>(value, DataStorageSecret.ofSecret(secret, + handler.getActiveUser() != null ? EncryptionToken.ofUser() : EncryptionToken.ofVaultKey())); + } + + @Override + public boolean allowUserSecretKey() { + return true; + } + } + + @JsonTypeName("vault") + public static class VaultKey extends EncryptedValue { + + public VaultKey(T value, DataStorageSecret secret) { + super(value, secret); + } + + @SneakyThrows + public static VaultKey of(T value) { + var s = JacksonMapper.getDefault().writeValueAsString(value); + var secret = new VaultKeySecretValue(s.toCharArray()); + return new VaultKey<>(value, DataStorageSecret.ofSecret(secret, EncryptionToken.ofVaultKey())); + } + + @Override + public boolean allowUserSecretKey() { + return false; + } + } +} diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java index 585780553..4aa58dee0 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java @@ -52,9 +52,9 @@ public interface SecretRetrievalStrategy { @Jacksonized class InPlace implements SecretRetrievalStrategy { - DataStorageSecret value; + InPlaceSecretValue value; - public InPlace(DataStorageSecret value) { + public InPlace(InPlaceSecretValue value) { this.value = value; } @@ -69,7 +69,7 @@ public interface SecretRetrievalStrategy { @Override public SecretQueryResult query(String prompt) { return new SecretQueryResult( - value != null ? value.getInternalSecret() : InPlaceSecretValue.of(""), + value, SecretQueryState.NORMAL); } diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java index ed819809a..394363f19 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java @@ -22,11 +22,11 @@ import java.util.List; public class SecretRetrievalStrategyHelper { - private static OptionsBuilder inPlace(Property p, boolean allowUserSecretKey) { + private static OptionsBuilder inPlace(Property p) { var original = p.getValue() != null ? p.getValue().getValue() : null; var secretProperty = new SimpleObjectProperty<>( p.getValue() != null && p.getValue().getValue() != null - ? p.getValue().getValue().getInternalSecret().inPlace() + ? p.getValue().getValue() : null); return new OptionsBuilder() .addComp(new SecretFieldComp(secretProperty, true), secretProperty) @@ -38,10 +38,7 @@ public class SecretRetrievalStrategyHelper { newSecret != null ? newSecret.getSecret() : new char[0], original != null ? original.getSecret() : new char[0]); var val = changed - ? (allowUserSecretKey - ? DataStorageSecret.ofCurrentSecret(secretProperty.getValue()) - : DataStorageSecret.ofSecret( - secretProperty.getValue(), EncryptionToken.ofVaultKey())) + ? secretProperty.getValue() : original; return new SecretRetrievalStrategy.InPlace(val); }, @@ -91,7 +88,7 @@ public class SecretRetrievalStrategyHelper { } public static OptionsBuilder comp( - Property s, boolean allowNone, boolean allowUserSecretKey) { + Property s, boolean allowNone) { SecretRetrievalStrategy strat = s.getValue(); var inPlace = new SimpleObjectProperty<>(strat instanceof SecretRetrievalStrategy.InPlace i ? i : null); var passwordManager = @@ -102,7 +99,7 @@ public class SecretRetrievalStrategyHelper { if (allowNone) { map.put(AppI18n.observable("app.none"), new OptionsBuilder()); } - map.put(AppI18n.observable("app.password"), inPlace(inPlace, allowUserSecretKey)); + map.put(AppI18n.observable("app.password"), inPlace(inPlace)); map.put(AppI18n.observable("app.passwordManager"), passwordManager(passwordManager)); map.put(AppI18n.observable("app.customCommand"), customCommand(customCommand)); map.put(AppI18n.observable("app.prompt"), new OptionsBuilder()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoice.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoice.java index b09c6122e..b12722be2 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoice.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityChoice.java @@ -2,6 +2,7 @@ package io.xpipe.ext.base.identity; import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.EncryptedValue; import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.SecretRetrievalStrategyHelper; @@ -58,10 +59,10 @@ public class IdentityChoice { var options = new OptionsBuilder() .nameAndDescription(userChoiceTranslationKey) .addComp(new IdentitySelectComp(ref, user, pass, identityStrategy, allowCustomUserInput), user) - .nonNullIf(inPlaceSelected.and(new SimpleBooleanProperty(requireUserInput))) + .nonNullIf(inPlaceSelected) .nameAndDescription(passwordChoiceTranslationKey) - .sub(SecretRetrievalStrategyHelper.comp(pass, true, true), pass) - .nonNullIf(inPlaceSelected.and(new SimpleBooleanProperty(requirePassword))) + .sub(SecretRetrievalStrategyHelper.comp(pass, true), pass) + .nonNullIf(inPlaceSelected) .disable(refSelected) .hide(refSelected) .name("keyAuthentication") @@ -72,8 +73,7 @@ public class IdentityChoice { gateway != null ? gateway : new SimpleObjectProperty<>(), identityStrategy, null, - false, - true), + false), identityStrategy) .nonNullIf(inPlaceSelected.and(new SimpleBooleanProperty(keyInput))) .disable(refSelected) @@ -89,8 +89,8 @@ public class IdentityChoice { return IdentityValue.InPlace.builder() .identityStore(LocalIdentityStore.builder() .username(user.get()) - .password(pass.get()) - .sshIdentity(identityStrategy.get()) + .password(EncryptedValue.CurrentKey.of(pass.get())) + .sshIdentity(EncryptedValue.CurrentKey.of(identityStrategy.get())) .build()) .build(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityMigrationDeserializer.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityMigrationDeserializer.java index 348c84e85..6bc2c282a 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityMigrationDeserializer.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityMigrationDeserializer.java @@ -71,8 +71,6 @@ public class IdentityMigrationDeserializer extends DelegatingDeserializer { identityStore.put("type", "localIdentity"); if (user != null && user.isTextual()) { identityStore.set("username", user); - } else { - int a = 0; } identityStore.set("password", password); identityStore.set("sshIdentity", identity); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java index 896d229f1..9bd735ca1 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentitySelectComp.java @@ -15,6 +15,7 @@ import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.EncryptedValue; import io.xpipe.app.util.PlatformThread; import io.xpipe.app.util.SecretRetrievalStrategy; @@ -63,13 +64,13 @@ public class IdentitySelectComp extends Comp> { var id = canSync ? SyncedIdentityStore.builder() .username(inPlaceUser.getValue()) - .password(password.getValue()) - .sshIdentity(identityStrategy.getValue()) + .password(EncryptedValue.VaultKey.of(password.getValue())) + .sshIdentity(EncryptedValue.VaultKey.of(identityStrategy.getValue())) .build() : LocalIdentityStore.builder() .username(inPlaceUser.getValue()) - .password(password.getValue()) - .sshIdentity(identityStrategy.getValue()) + .password(EncryptedValue.CurrentKey.of(password.getValue())) + .sshIdentity(EncryptedValue.CurrentKey.of(identityStrategy.getValue())) .build(); StoreCreationComp.showCreation( id, diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStore.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStore.java index cbd7c0c2a..ed115c568 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityStore.java @@ -16,16 +16,16 @@ import lombok.experimental.SuperBuilder; public abstract class IdentityStore implements SelfReferentialStore, DataStore { String username; - SecretRetrievalStrategy password; - SshIdentityStrategy sshIdentity; + + public abstract SecretRetrievalStrategy getPassword(); + + public abstract SshIdentityStrategy getSshIdentity(); @Override public void checkComplete() throws Throwable { - if (password != null) { - password.checkComplete(); - } - if (sshIdentity != null) { - sshIdentity.checkComplete(); - } + Validators.nonNull(getPassword()); + Validators.nonNull(getSshIdentity()); + getPassword().checkComplete(); + getSshIdentity().checkComplete(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityValue.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityValue.java index 9feb03a86..98fbcecb4 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityValue.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/IdentityValue.java @@ -21,7 +21,7 @@ public interface IdentityValue { return new InPlace(identityStore); } - void checkComplete(boolean requireUser, boolean requirePassword, boolean requireKey) throws Throwable; + void checkComplete(boolean requireUser) throws Throwable; IdentityStore unwrap(); @@ -36,24 +36,12 @@ public interface IdentityValue { LocalIdentityStore identityStore; @Override - public void checkComplete(boolean requireUser, boolean requirePassword, boolean requireKey) throws Throwable { + public void checkComplete(boolean requireUser) throws Throwable { Validators.nonNull(identityStore); - identityStore.checkComplete(); if (requireUser) { Validators.nonNull(identityStore.getUsername()); } - if (requirePassword) { - Validators.nonNull(identityStore.getPassword()); - } - if (identityStore.getPassword() != null) { - identityStore.getPassword().checkComplete(); - } - if (requireKey) { - Validators.nonNull(identityStore.getSshIdentity()); - } - if (identityStore.getSshIdentity() != null) { - identityStore.getSshIdentity().checkComplete(); - } + identityStore.checkComplete(); } @Override @@ -76,25 +64,13 @@ public interface IdentityValue { DataStoreEntryRef ref; @Override - public void checkComplete(boolean requireUser, boolean requirePassword, boolean requireKey) throws Throwable { + public void checkComplete(boolean requireUser) throws Throwable { Validators.nonNull(ref); Validators.isType(ref, IdentityStore.class); - ref.getStore().checkComplete(); if (requireUser) { Validators.nonNull(ref.getStore().getUsername()); } - if (requirePassword) { - Validators.nonNull(ref.getStore().getPassword()); - } - if (ref.getStore().getPassword() != null) { - ref.getStore().getPassword().checkComplete(); - } - if (requireKey) { - Validators.nonNull(ref.getStore().getSshIdentity()); - } - if (ref.getStore().getSshIdentity() != null) { - ref.getStore().getSshIdentity().checkComplete(); - } + ref.getStore().checkComplete(); } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertAction.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertAction.java index 3dd005a94..fde4748ed 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityConvertAction.java @@ -7,6 +7,7 @@ import io.xpipe.app.ext.DataStoreCreationCategory; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.EncryptedValue; import javafx.beans.value.ObservableValue; import lombok.Value; @@ -58,8 +59,9 @@ public class LocalIdentityConvertAction implements ActionProvider { var st = ref.getStore(); var synced = SyncedIdentityStore.builder() .username(st.getUsername()) - .password(st.getPassword()) - .sshIdentity(st.getSshIdentity()) + .password(EncryptedValue.VaultKey.of(st.getPassword())) + .sshIdentity(EncryptedValue.VaultKey.of(st.getSshIdentity())) + .perUser(false) .build(); StoreCreationComp.showCreation(synced, DataStoreCreationCategory.IDENTITY, dataStoreEntry -> {}, true); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStore.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStore.java index e0a1a20a9..bfac4d26d 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/LocalIdentityStore.java @@ -1,6 +1,10 @@ package io.xpipe.ext.base.identity; import com.fasterxml.jackson.annotation.JsonTypeName; +import io.xpipe.app.util.EncryptedValue; +import io.xpipe.app.util.SecretRetrievalStrategy; +import io.xpipe.app.util.Validators; +import io.xpipe.core.util.ValidationException; import lombok.EqualsAndHashCode; import lombok.ToString; import lombok.Value; @@ -13,4 +17,18 @@ import lombok.extern.jackson.Jacksonized; @Value @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class LocalIdentityStore extends IdentityStore {} +public class LocalIdentityStore extends IdentityStore { + + EncryptedValue.CurrentKey password; + EncryptedValue.CurrentKey sshIdentity; + + @Override + public SecretRetrievalStrategy getPassword() { + return password != null ? password.getValue() : null; + } + + @Override + public SshIdentityStrategy getSshIdentity() { + return sshIdentity != null ? sshIdentity.getValue() : null; + } +} 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 da66b01a4..407ce6ae8 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 @@ -4,6 +4,7 @@ import io.xpipe.app.comp.store.StoreEntryWrapper; import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.storage.*; +import io.xpipe.app.util.EncryptedValue; import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.SecretRetrievalStrategyHelper; import io.xpipe.core.store.DataStore; @@ -39,19 +40,19 @@ public class LocalIdentityStoreProvider extends IdentityStoreProvider { .addString(user) .name("passwordAuthentication") .description("passwordAuthenticationDescription") - .sub(SecretRetrievalStrategyHelper.comp(pass, true, true), pass) + .sub(SecretRetrievalStrategyHelper.comp(pass, true), pass) .name("keyAuthentication") .description("keyAuthenticationDescription") .longDescription("base:sshKey") .sub( - SshIdentityStrategyHelper.identity(new SimpleObjectProperty<>(), identity, null, false, true), + SshIdentityStrategyHelper.identity(new SimpleObjectProperty<>(), identity, null, false), identity) .bind( () -> { return LocalIdentityStore.builder() .username(user.get()) - .password(pass.get()) - .sshIdentity(identity.get()) + .password(EncryptedValue.CurrentKey.of(pass.get())) + .sshIdentity(EncryptedValue.CurrentKey.of(identity.get())) .build(); }, store) diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java index 76e5701e7..617496298 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SshIdentityStrategyHelper.java @@ -49,8 +49,7 @@ public class SshIdentityStrategyHelper { Property> proxy, Property fileProperty, Predicate perUserFile, - boolean allowSync, - boolean allowUserSecretKey) { + boolean allowSync) { var keyPath = new SimpleStringProperty( fileProperty.getValue() != null && fileProperty.getValue().getFile() != null ? fileProperty.getValue().getFile().toAbsoluteFilePath(null) @@ -77,7 +76,7 @@ public class SshIdentityStrategyHelper { .name("keyPassword") .description("sshConfigHost.identityPassphraseDescription") .sub( - SecretRetrievalStrategyHelper.comp(keyPasswordProperty, true, allowUserSecretKey), + SecretRetrievalStrategyHelper.comp(keyPasswordProperty, true), keyPasswordProperty) .nonNull() .bind( @@ -92,8 +91,7 @@ public class SshIdentityStrategyHelper { Property> proxy, Property strategyProperty, Predicate perUserFile, - boolean allowSync, - boolean allowUserSecretKey) { + boolean allowSync) { SshIdentityStrategy strat = strategyProperty.getValue(); var file = new SimpleObjectProperty<>( strat instanceof SshIdentityStrategy.File f @@ -110,7 +108,7 @@ public class SshIdentityStrategyHelper { map.put(AppI18n.observable("base.none"), new OptionsBuilder()); map.put( AppI18n.observable("base.keyFile"), - fileIdentity(proxy, file, perUserFile, allowSync, allowUserSecretKey)); + fileIdentity(proxy, file, perUserFile, allowSync)); map.put(AppI18n.observable("base.sshAgent"), agent(agent)); map.put(AppI18n.observable("base.pageant"), new OptionsBuilder()); map.put(gpgFeature.suffixObservable("base.gpgAgent"), new OptionsBuilder()); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStore.java b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStore.java index c8921ad59..d79d97376 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/identity/SyncedIdentityStore.java @@ -1,6 +1,9 @@ package io.xpipe.ext.base.identity; import io.xpipe.app.ext.UserScopeStore; +import io.xpipe.app.util.EncryptedValue; +import io.xpipe.app.util.SecretRetrievalStrategy; +import io.xpipe.app.util.Validators; import io.xpipe.core.util.ValidationException; import com.fasterxml.jackson.annotation.JsonTypeName; @@ -18,8 +21,20 @@ import lombok.extern.jackson.Jacksonized; @ToString(callSuper = true) public class SyncedIdentityStore extends IdentityStore implements UserScopeStore { + EncryptedValue password; + EncryptedValue sshIdentity; boolean perUser; + @Override + public SecretRetrievalStrategy getPassword() { + return password != null ? password.getValue() : null; + } + + @Override + public SshIdentityStrategy getSshIdentity() { + return sshIdentity != null ? sshIdentity.getValue() : null; + } + @Override public void checkComplete() throws Throwable { super.checkComplete(); 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 46ddde6b2..c07e4e7e2 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 @@ -5,6 +5,7 @@ import io.xpipe.app.core.AppI18n; import io.xpipe.app.ext.DataStoreCreationCategory; import io.xpipe.app.ext.GuiDialog; import io.xpipe.app.storage.*; +import io.xpipe.app.util.EncryptedValue; import io.xpipe.app.util.OptionsBuilder; import io.xpipe.app.util.SecretRetrievalStrategyHelper; import io.xpipe.app.util.Validator; @@ -59,13 +60,13 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider { .addString(user) .name("passwordAuthentication") .description("passwordAuthenticationDescription") - .sub(SecretRetrievalStrategyHelper.comp(pass, true, true), pass) + .sub(SecretRetrievalStrategyHelper.comp(pass, true), pass) .name("keyAuthentication") .description("keyAuthenticationDescription") .longDescription("base:sshKey") .sub( SshIdentityStrategyHelper.identity( - new SimpleObjectProperty<>(), identity, path -> perUser.get(), true, true), + new SimpleObjectProperty<>(), identity, path -> perUser.get(), true), identity) .check(val -> Validator.create(val, AppI18n.observable("keyNotSynced"), identity, i -> { var wrong = i instanceof SshIdentityStrategy.File f @@ -83,8 +84,8 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider { () -> { return SyncedIdentityStore.builder() .username(user.get()) - .password(pass.get()) - .sshIdentity(identity.get()) + .password(perUser.get() ? EncryptedValue.CurrentKey.of(pass.get()) : EncryptedValue.VaultKey.of(pass.get())) + .sshIdentity(perUser.get() ? EncryptedValue.CurrentKey.of(identity.get()) : EncryptedValue.VaultKey.of(identity.get())) .perUser(perUser.get()) .build(); }, diff --git a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java index 079f7dc22..5e25ae439 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/incus/IncusContainerStore.java @@ -54,7 +54,7 @@ public class IncusContainerStore install.checkComplete(); Validators.nonNull(containerName); if (identity != null) { - identity.checkComplete(false, false, false); + identity.checkComplete(false); } } diff --git a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java index 6e1217e75..2df9abb98 100644 --- a/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java +++ b/ext/system/src/main/java/io/xpipe/ext/system/lxd/LxdContainerStore.java @@ -52,7 +52,7 @@ public class LxdContainerStore cmd.checkComplete(); Validators.nonNull(containerName); if (identity != null) { - identity.checkComplete(false, false, false); + identity.checkComplete(false); } }