From 57821b74743ec602fa1cd0e4e00d1d8b9c9a9079 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Fri, 28 Jan 2022 00:03:13 -0600 Subject: [PATCH] Clear account payload info & chats from closed trades & disputes. --- .../bisq/core/support/dispute/Dispute.java | 29 ++++++++++++- .../core/support/dispute/DisputeManager.java | 13 ++++++ .../arbitration/ArbitrationManager.java | 1 + .../dispute/mediation/MediationManager.java | 1 + .../support/dispute/refund/RefundManager.java | 1 + .../core/trade/ClosedTradableManager.java | 28 +++++++++++++ .../core/trade/model/bisq_v1/Contract.java | 24 +++++++++++ .../bisq/core/trade/model/bisq_v1/Trade.java | 28 +++++++++++++ .../main/java/bisq/core/user/Preferences.java | 12 +++++- .../bisq/core/user/PreferencesPayload.java | 3 ++ .../resources/i18n/displayStrings.properties | 10 +++++ .../presentation/SettingsPresentation.java | 1 + .../desktop/main/settings/SettingsView.java | 6 +++ .../settings/preferences/PreferencesView.java | 41 +++++++++++++++++-- proto/src/main/proto/pb.proto | 1 + 15 files changed, 193 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/support/dispute/Dispute.java b/core/src/main/java/bisq/core/support/dispute/Dispute.java index c721d0287c..4258d843ba 100644 --- a/core/src/main/java/bisq/core/support/dispute/Dispute.java +++ b/core/src/main/java/bisq/core/support/dispute/Dispute.java @@ -100,7 +100,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { private final PubKeyRing traderPubKeyRing; private final long tradeDate; private final long tradePeriodEnd; - private final Contract contract; + private Contract contract; @Nullable private final byte[] contractHash; @Nullable @@ -111,7 +111,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload { private final String depositTxId; @Nullable private final String payoutTxId; - private final String contractAsJson; + private String contractAsJson; @Nullable private final String makerContractSignature; @Nullable @@ -351,6 +351,31 @@ public final class Dispute implements NetworkPayload, PersistablePayload { } } + public boolean removeAllChatMessages() { + if (chatMessages.size() > 0) { + chatMessages.clear(); + return true; + } + return false; + } + + public void maybeClearSensitiveData() { + String change = ""; + if (contract.maybeClearSensitiveData()) { + change += "contract;"; + } + String edited = contract.sanitizeContractAsJson(contractAsJson); + if (!edited.equals(contractAsJson)) { + contractAsJson = edited; + change += "contractAsJson;"; + } + if (removeAllChatMessages()) { + change += "chat messages;"; + } + if (change.length() > 0) { + log.info("cleared sensitive data from {} of dispute for trade {}", change, Utilities.getShortId(getTradeId())); + } + } /////////////////////////////////////////////////////////////////////////////////////////// // Setters diff --git a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java index 68e0a351bd..f8f0824767 100644 --- a/core/src/main/java/bisq/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/bisq/core/support/dispute/DisputeManager.java @@ -66,6 +66,8 @@ import javafx.collections.ObservableList; import java.security.KeyPair; +import java.time.Instant; + import java.util.Date; import java.util.List; import java.util.Optional; @@ -282,6 +284,8 @@ public abstract class DisputeManager> extends Sup log.error(disputeReplayException.toString()); validationExceptions.add(disputeReplayException); }); + + maybeClearSensitiveData(); } public boolean isTrader(Dispute dispute) { @@ -298,6 +302,15 @@ public abstract class DisputeManager> extends Sup return disputeList.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny(); } + public void maybeClearSensitiveData() { + log.info("{} checking closed disputes eligibility for having sensitive data cleared", super.getClass().getSimpleName()); + Instant safeDate = closedTradableManager.getSafeDateForSensitiveDataClearing(); + getDisputeList().getList().stream() + .filter(e -> e.isClosed()) + .filter(e -> e.getTradeDate().toInstant().isBefore(safeDate)) + .forEach(Dispute::maybeClearSensitiveData); + requestPersistence(); + } /////////////////////////////////////////////////////////////////////////////////////////// // Message handler diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java index 30a6940d14..b879011989 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/ArbitrationManager.java @@ -338,6 +338,7 @@ public final class ArbitrationManager extends DisputeManager } sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null); + maybeClearSensitiveData(); requestPersistence(); } diff --git a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java index 914887d5aa..b2c477d052 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/RefundManager.java @@ -224,6 +224,7 @@ public final class RefundManager extends DisputeManager { openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer())); } + maybeClearSensitiveData(); requestPersistence(); } diff --git a/core/src/main/java/bisq/core/trade/ClosedTradableManager.java b/core/src/main/java/bisq/core/trade/ClosedTradableManager.java index 0eb00bf5f8..83a56f25cc 100644 --- a/core/src/main/java/bisq/core/trade/ClosedTradableManager.java +++ b/core/src/main/java/bisq/core/trade/ClosedTradableManager.java @@ -49,9 +49,12 @@ import com.google.common.collect.ImmutableList; import javafx.collections.ObservableList; +import java.time.Instant; + import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -120,10 +123,12 @@ public class ClosedTradableManager implements PersistedDataHost { public void onAllServicesInitialized() { cleanupMailboxMessagesService.handleTrades(getClosedTrades()); + maybeClearSensitiveData(); } public void add(Tradable tradable) { if (closedTradables.add(tradable)) { + maybeClearSensitiveData(); requestPersistence(); } } @@ -153,6 +158,29 @@ public class ClosedTradableManager implements PersistedDataHost { return closedTradables.stream().filter(e -> e.getId().equals(id)).findFirst(); } + public void maybeClearSensitiveData() { + log.info("checking closed trades eligibility for having sensitive data cleared"); + closedTradables.stream() + .filter(e -> e instanceof Trade) + .map(e -> (Trade) e) + .filter(e -> canTradeHaveSensitiveDataCleared(e.getId())) + .forEach(Trade::maybeClearSensitiveData); + requestPersistence(); + } + + public boolean canTradeHaveSensitiveDataCleared(String tradeId) { + Instant safeDate = getSafeDateForSensitiveDataClearing(); + return closedTradables.stream() + .filter(e -> e.getId().equals(tradeId)) + .filter(e -> e.getDate().toInstant().isBefore(safeDate)) + .count() > 0; + } + + public Instant getSafeDateForSensitiveDataClearing() { + return Instant.ofEpochSecond(Instant.now().getEpochSecond() + - TimeUnit.DAYS.toSeconds(preferences.getClearDataAfterDays())); + } + public Stream getTradesStreamWithFundsLockedIn() { return getClosedTrades().stream() .filter(Trade::isFundsLockedIn); diff --git a/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java index 473d90af0d..4f939e36b2 100644 --- a/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/Contract.java @@ -350,6 +350,30 @@ public final class Contract implements NetworkPayload { return isBuyerMakerAndSellerTaker() == isMyRoleBuyer(myPubKeyRing); } + public boolean maybeClearSensitiveData() { + boolean changed = false; + if (makerPaymentAccountPayload != null) { + makerPaymentAccountPayload = null; + changed = true; + } + if (takerPaymentAccountPayload != null) { + takerPaymentAccountPayload = null; + changed = true; + } + return changed; + } + + // edits a contract json string, removing the payment account payloads + public static String sanitizeContractAsJson(String contractAsJson) { + return contractAsJson + .replaceAll( + "\"takerPaymentAccountPayload\": \\{[^}]*}", + "\"takerPaymentAccountPayload\": null") + .replaceAll( + "\"makerPaymentAccountPayload\": \\{[^}]*}", + "\"makerPaymentAccountPayload\": null"); + } + public void printDiff(@Nullable String peersContractAsJson) { String json = JsonUtil.objectToJson(this); String diff = StringUtils.difference(json, peersContractAsJson); diff --git a/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java b/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java index 977f036c88..5a37ce1c01 100644 --- a/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java +++ b/core/src/main/java/bisq/core/trade/model/bisq_v1/Trade.java @@ -689,6 +689,14 @@ public abstract class Trade extends TradeModel { } } + public boolean removeAllChatMessages() { + if (chatMessages.size() > 0) { + chatMessages.clear(); + return true; + } + return false; + } + public boolean mediationResultAppliedPenaltyToSeller() { // If mediated payout is same or more then normal payout we enable otherwise a penalty was applied // by mediators and we keep the confirm disabled to avoid that the seller can complete the trade @@ -698,6 +706,26 @@ public abstract class Trade extends TradeModel { return payoutAmountFromMediation < normalPayoutAmount; } + public void maybeClearSensitiveData() { + String change = ""; + if (contract != null && contract.maybeClearSensitiveData()) { + change += "contract;"; + } + if (contractAsJson != null) { + String edited = contract.sanitizeContractAsJson(contractAsJson); + if (!edited.equals(contractAsJson)) { + contractAsJson = edited; + change += "contractAsJson;"; + } + } + if (removeAllChatMessages()) { + change += "chat messages;"; + } + if (change.length() > 0) { + log.info("cleared sensitive data from {} of trade {}", change, getShortId()); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // TradeModel implementation diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 5aa62366c0..1cff31f2e2 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -145,7 +145,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid )); public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; - + public static final int CLEAR_DATA_AFTER_DAYS_INITIAL = 99999; // feature effectively disabled until user agrees to settings notification + public static final int CLEAR_DATA_AFTER_DAYS_DEFAULT = 20; // used when user has agreed to settings notification // payload is initialized so the default values are available for Property initialization. @Setter @@ -355,6 +356,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid setIgnoreDustThreshold(600); } + if (prefPayload.getClearDataAfterDays() < 1) { + setClearDataAfterDays(Preferences.CLEAR_DATA_AFTER_DAYS_INITIAL); + } + // For users from old versions the 4 flags a false but we want to have it true by default // PhoneKeyAndToken is also null so we can use that to enable the flags if (prefPayload.getPhoneKeyAndToken() == null) { @@ -785,6 +790,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid requestPersistence(); } + public void setClearDataAfterDays(int value) { + prefPayload.setClearDataAfterDays(value); + requestPersistence(); + } + public void setShowOffersMatchingMyAccounts(boolean value) { prefPayload.setShowOffersMatchingMyAccounts(value); requestPersistence(); diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index ffa943f8ca..c7b5e1650d 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -121,6 +121,7 @@ public final class PreferencesPayload implements PersistableEnvelope { private String takeOfferSelectedPaymentAccountId; private double buyerSecurityDepositAsPercent = getDefaultBuyerSecurityDepositAsPercent(); private int ignoreDustThreshold = 600; + private int clearDataAfterDays = Preferences.CLEAR_DATA_AFTER_DAYS_INITIAL; private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent(); private int blockNotifyPort; private boolean tacAcceptedV120; @@ -192,6 +193,7 @@ public final class PreferencesPayload implements PersistableEnvelope { .setIsDaoFullNode(isDaoFullNode) .setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent) .setIgnoreDustThreshold(ignoreDustThreshold) + .setClearDataAfterDays(clearDataAfterDays) .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) .setBlockNotifyPort(blockNotifyPort) .setTacAcceptedV120(tacAcceptedV120) @@ -290,6 +292,7 @@ public final class PreferencesPayload implements PersistableEnvelope { proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(), proto.getBuyerSecurityDepositAsPercent(), proto.getIgnoreDustThreshold(), + proto.getClearDataAfterDays(), proto.getBuyerSecurityDepositAsPercentForCrypto(), proto.getBlockNotifyPort(), proto.getTacAcceptedV120(), diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 677958fe3d..35ccc6d7f3 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1324,6 +1324,7 @@ setting.preferences.txFeeMin=Transaction fee must be at least {0} satoshis/vbyte setting.preferences.txFeeTooLarge=Your input is above any reasonable value (>5000 satoshis/vbyte). Transaction fee is usually in the range of 50-400 satoshis/vbyte. setting.preferences.ignorePeers=Ignored peers [onion address:port] setting.preferences.ignoreDustThreshold=Min. non-dust output value +setting.preferences.clearDataAfterDays=Clear sensitive data after (days) setting.preferences.currenciesInList=Currencies in market price feed list setting.preferences.prefCurrency=Preferred currency setting.preferences.displayFiat=Display national currencies @@ -1386,6 +1387,15 @@ settings.preferences.editCustomExplorer.name=Name settings.preferences.editCustomExplorer.txUrl=Transaction URL settings.preferences.editCustomExplorer.addressUrl=Address URL +setting.info.headline=New data-privacy feature +settings.preferences.sensitiveDataRemoval.msg=To protect the privacy of yourself and other traders, Bisq intends to \ + remove payment account details from old trades. This is particularly important for fiat trades which may include bank \ + account details. Read more about this at [HYPERLINK:https://bisq.wiki/Data_Privacy].\n\n\ + The threshold for data removal can be configured on this screen via the field "Clear sensitive data after (days)". \ + It is recommended to set it as low as possible, for example 20 days. That means trades from more than 20 \ + days ago will have payment account details cleared, as long as they are finished. Finished trades are ones which \ + are found in the Portfolio / History tab. + settings.net.btcHeader=Bitcoin network settings.net.p2pHeader=Bisq network settings.net.onionAddressLabel=My onion address diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java index cd845c7975..73508b0538 100644 --- a/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java +++ b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java @@ -58,5 +58,6 @@ public class SettingsPresentation { } public void setup() { + showNotification.set(preferences.showAgain(SETTINGS_NEWS)); } } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java index 5ba17c62f8..440bd58d20 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java @@ -24,11 +24,14 @@ import bisq.desktop.common.view.FxmlView; import bisq.desktop.common.view.View; import bisq.desktop.common.view.ViewLoader; import bisq.desktop.main.MainView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.presentation.SettingsPresentation; import bisq.desktop.main.settings.about.AboutView; import bisq.desktop.main.settings.network.NetworkSettingsView; import bisq.desktop.main.settings.preferences.PreferencesView; import bisq.core.locale.Res; +import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import javax.inject.Inject; @@ -85,6 +88,9 @@ public class SettingsView extends ActivatableView { @Override protected void activate() { + // Hide new badge if user saw this section + preferences.dontShowAgain(SettingsPresentation.SETTINGS_NEWS, true); + root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); navigation.addListener(navigationListener); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 5b92c76697..551afe75d0 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -127,7 +127,7 @@ public class PreferencesView extends ActivatableViewAndModel tradeCurrencies; private InputTextField deviationInputTextField, bsqAverageTrimThresholdTextField; private ChangeListener deviationListener, bsqAverageTrimThresholdListener, ignoreTradersListListener, ignoreDustThresholdListener, - rpcUserListener, rpcPwListener, blockNotifyPortListener, + rpcUserListener, rpcPwListener, blockNotifyPortListener, clearDataAfterDaysListener, autoConfTradeLimitListener, autoConfServiceAddressListener; private ChangeListener deviationFocusedListener, bsqAverageTrimThresholdFocusedListener; private ChangeListener useCustomFeeCheckboxListener; @@ -225,6 +225,22 @@ public class PreferencesView extends ActivatableViewAndModel { + DontShowAgainLookup.dontShowAgain(key, true); + // user has acknowledged, enable the feature with a reasonable default value + preferences.setClearDataAfterDays(preferences.CLEAR_DATA_AFTER_DAYS_DEFAULT); + clearDataAfterDaysInputTextField.setText(String.valueOf(preferences.getClearDataAfterDays())); + }) + .closeButtonText(Res.get("shared.cancel")) + .show(); + } + // We want to have it updated in case an asset got removed allCryptoCurrencies = FXCollections.observableArrayList(CurrencyUtil.getActiveSortedCryptoCurrencies(assetService, filterManager)); allCryptoCurrencies.removeAll(cryptoCurrencies); @@ -250,7 +266,7 @@ public class PreferencesView extends ActivatableViewAndModel { + try { + int value = Integer.parseInt(newValue); + if (!newValue.equals(oldValue)) { + preferences.setClearDataAfterDays(value); + } + } catch (Throwable ignore) { + } + }; + if (displayStandbyModeFeature) { // AvoidStandbyModeService feature works only on OSX & Windows avoidStandbyMode = addSlideToggleButton(root, ++gridRow, @@ -825,6 +857,7 @@ public class PreferencesView extends ActivatableViewAndModel referralIdInputTextField.setText(referralId)); referralIdInputTextField.setPromptText(Res.get("setting.preferences.refererId.prompt"));*/ ignoreDustThresholdInputTextField.setText(String.valueOf(preferences.getIgnoreDustThreshold())); + clearDataAfterDaysInputTextField.setText(String.valueOf(preferences.getClearDataAfterDays())); userLanguageComboBox.setItems(languageCodes); userLanguageComboBox.getSelectionModel().select(preferences.getUserLanguage()); userLanguageComboBox.setConverter(new StringConverter<>() { @@ -889,6 +922,7 @@ public class PreferencesView extends ActivatableViewAndModel