diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/SettableErrorValidator.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/SettableErrorValidator.java index 96e7d65baa..9ec735d539 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/SettableErrorValidator.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/controls/validator/SettableErrorValidator.java @@ -35,6 +35,12 @@ public SettableErrorValidator() { isInvalid.addListener(new WeakChangeListener<>(isInvalidListener)); } + public SettableErrorValidator(String message) { + super(message); + + isInvalid.addListener(new WeakChangeListener<>(isInvalidListener)); + } + @Override protected void eval() { hasErrors.set(getIsInvalid()); @@ -49,7 +55,6 @@ public BooleanProperty isInvalidProperty() { } public void setIsInvalid(boolean isInvalid) { - log.error("setInvalid {}", isInvalid); this.isInvalid.set(isInvalid); eval(); } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/moderator/ReportToModeratorTable.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/moderator/ReportToModeratorTable.java index b258fa8639..25f86fa1df 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/moderator/ReportToModeratorTable.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/moderator/ReportToModeratorTable.java @@ -43,6 +43,7 @@ import bisq.support.moderator.ReportToModeratorMessage; import bisq.user.profile.UserProfile; import bisq.user.profile.UserProfileService; +import de.jensd.fx.fontawesome.AwesomeIcon; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; @@ -296,7 +297,7 @@ protected void updateItem(ListItem item, boolean empty) { private Callback, TableCell> getMessageCellFactory() { return column -> new TableCell<>() { private final Label message = new Label(); - private final Button icon = BisqIconButton.createCopyIconButton(); + private final Button icon = BisqIconButton.createIconButton(AwesomeIcon.EXTERNAL_LINK); private final HBox hBox = new HBox(message, icon); private final BisqTooltip tooltip = new BisqTooltip(BisqTooltip.Style.DARK); @@ -317,7 +318,13 @@ protected void updateItem(ListItem item, boolean empty) { tooltip.setText(item.getMessage()); message.setTooltip(tooltip); - icon.setOnAction(e -> ClipboardUtil.copyToClipboard(item.getMessage())); + // icon.setOnAction(e -> ClipboardUtil.copyToClipboard(item.getMessage())); + icon.setOnAction(e -> new Popup() + .headline(Res.get("authorizedRole.moderator.table.message.popup.headline")) + .information(item.getMessage()) + .actionButtonText(Res.get("action.copyToClipboard")) + .onAction(()-> ClipboardUtil.copyToClipboard(item.getMessage())) + .show()); setGraphic(hBox); } else { icon.setOnAction(null); diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerController.java index bf0127eb16..b8a8ec5444 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerController.java @@ -35,6 +35,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import java.security.KeyPair; + @Slf4j public class ReleaseManagerController implements Controller { @Getter @@ -63,6 +65,11 @@ public void onActivate() { .to(releaseNotificationsService.getReleaseNotifications()); model.getActionButtonDisabled().bind(model.getReleaseNotes().isEmpty().or(model.getVersion().isEmpty())); + + + KeyPair keyPair = userIdentityService.getSelectedUserIdentity().getIdentity().getKeyBundle().getKeyPair(); + releaseNotificationsService.getReleaseNotifications().forEach(releaseNotification -> + releaseManagerService.republishReleaseNotification(releaseNotification, keyPair)); } @Override diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerView.java index 9a11f481dd..b03a250c0e 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/release_manager/ReleaseManagerView.java @@ -64,6 +64,10 @@ public ReleaseManagerView(ReleaseManagerModel model, ReleaseManagerController co isPreReleaseCheckBox = new CheckBox(Res.get("authorizedRole.releaseManager.isPreRelease")); isLauncherUpdateCheckBox = new CheckBox(Res.get("authorizedRole.releaseManager.isLauncherUpdate")); + // For now there is no support for jar based updates so we hide that checkbox + isLauncherUpdateCheckBox.setManaged(false); + isLauncherUpdateCheckBox.setVisible(false); + sendButton = new Button(Res.get("authorizedRole.releaseManager.send")); sendButton.setDefaultButton(true); sendButton.setAlignment(Pos.BOTTOM_RIGHT); @@ -77,7 +81,7 @@ public ReleaseManagerView(ReleaseManagerModel model, ReleaseManagerController co VBox.setMargin(sendButton, new Insets(10, 0, 0, 0)); VBox.setMargin(isPreReleaseCheckBox, new Insets(10, 0, 0, 0)); VBox.setMargin(roleInfo, new Insets(20, 0, 0, 0)); - this.root.getChildren().addAll(headline, + root.getChildren().addAll(headline, releaseNotes, version, isPreReleaseCheckBox, diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerController.java index 678d56fe64..48e7070850 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerController.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerController.java @@ -46,6 +46,7 @@ import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import java.security.KeyPair; import java.util.Optional; import java.util.stream.Collectors; @@ -62,7 +63,7 @@ public class SecurityManagerController implements Controller { private final DifficultyAdjustmentService difficultyAdjustmentService; private Pin userIdentityPin, alertsPin, bondedRoleSetPin, difficultyAdjustmentListItemsPin; private Subscription messagePin, requireVersionForTradingPin, minVersionPin, selectedBondedRolePin, - difficultyAdjustmentPin; + difficultyAdjustmentPin, bannedAccountDataPin; public SecurityManagerController(ServiceProvider serviceProvider) { securityManagerService = serviceProvider.getSupportService().getSecurityManagerService(); @@ -102,10 +103,20 @@ public void onActivate() { .map(SecurityManagerView.DifficultyAdjustmentListItem::new) .to(difficultyAdjustmentService.getAuthorizedDifficultyAdjustmentDataSet()); - model.getDifficultyAdjustmentFactor().set(difficultyAdjustmentService.getMostRecentValueOrDefault().get()); - difficultyAdjustmentPin = EasyBind.subscribe(model.getDifficultyAdjustmentFactor(), difficultyAdjustmentFactor -> - model.getDifficultyAdjustmentFactorButtonDisabled().set(difficultyAdjustmentFactor == null || - !isValidDifficultyAdjustmentFactor(difficultyAdjustmentFactor.doubleValue()))); + double difficultyAdjustmentFactor = difficultyAdjustmentService.getMostRecentValueOrDefault().get(); + model.getDifficultyAdjustmentFactor().set(difficultyAdjustmentFactor); + difficultyAdjustmentPin = EasyBind.subscribe(model.getDifficultyAdjustmentFactor(), factor -> + model.getDifficultyAdjustmentFactorButtonDisabled().set(factor == null || + !isValidDifficultyAdjustmentFactor(factor.doubleValue()))); + bannedAccountDataPin = EasyBind.subscribe(model.getBannedAccountData(), e -> updateSendButtonDisabled()); + + KeyPair keyPair = userIdentityService.getSelectedUserIdentity().getIdentity().getKeyBundle().getKeyPair(); + alertService.getAuthorizedAlertDataSet().forEach(authorizedAlert -> + securityManagerService.rePublishAlert(authorizedAlert, keyPair)); + + if (difficultyAdjustmentFactor != NetworkLoad.DEFAULT_DIFFICULTY_ADJUSTMENT) { + securityManagerService.publishDifficultyAdjustment(difficultyAdjustmentFactor); + } } @Override @@ -119,6 +130,7 @@ public void onDeactivate() { minVersionPin.unsubscribe(); selectedBondedRolePin.unsubscribe(); difficultyAdjustmentPin.unsubscribe(); + bannedAccountDataPin.unsubscribe(); } void onSelectAlertType(AlertType alertType) { @@ -140,6 +152,12 @@ void onSendAlert() { new Popup().warning(Res.get("authorizedRole.securityManager.alert.message.tooLong")).show(); return; } + String bannedAccountData = model.getBannedAccountData().get(); + if (bannedAccountData != null && bannedAccountData.length() > AuthorizedAlertData.MAX_BANNED_ACCOUNT_DATA_LENGTH) { + new Popup().warning(Res.get("authorizedRole.securityManager.bannedAccounts.data.tooLong")).show(); + return; + } + SecurityManagerView.BondedRoleListItem bondedRoleListItem = model.getSelectedBondedRoleListItem().get(); Optional bannedRole = bondedRoleListItem == null ? Optional.empty() : Optional.ofNullable(bondedRoleListItem.getBondedRole().getAuthorizedBondedRole()); @@ -149,24 +167,22 @@ void onSendAlert() { model.getHaltTrading().get(), model.getRequireVersionForTrading().get(), StringUtils.toOptional(model.getMinVersion().get()), - bannedRole) + bannedRole, + StringUtils.toOptional(bannedAccountData)) .whenComplete((result, throwable) -> UIThread.run(() -> { if (throwable != null) { new Popup().error(throwable).show(); - } else { - model.getSelectedAlertType().set(null); - model.getHeadline().set(null); - model.getMessage().set(null); - model.getHaltTrading().set(false); - model.getRequireVersionForTrading().set(false); - model.getMinVersion().set(null); - model.getSelectedBondedRoleListItem().set(null); } - })); - } - boolean isRemoveDifficultyAdjustmentButtonVisible(AuthorizedAlertData authorizedAlertData) { - return userIdentityService.getSelectedUserIdentity().getId().equals(authorizedAlertData.getSecurityManagerProfileId()); + model.getSelectedAlertType().set(null); + model.getHeadline().set(null); + model.getMessage().set(null); + model.getHaltTrading().set(false); + model.getRequireVersionForTrading().set(false); + model.getMinVersion().set(null); + model.getSelectedBondedRoleListItem().set(null); + model.getBannedAccountData().set(null); + })); } void onRemoveAlert(AuthorizedAlertData authorizedAlertData) { @@ -174,33 +190,6 @@ void onRemoveAlert(AuthorizedAlertData authorizedAlertData) { securityManagerService.removeAlert(authorizedAlertData, userIdentity.getNetworkIdWithKeyPair().getKeyPair()); } - String getBondedRoleDisplayString(BondedRole bondedRole) { - AuthorizedBondedRole authorizedBondedRole = bondedRole.getAuthorizedBondedRole(); - String roleType = authorizedBondedRole.getBondedRoleType().getDisplayString().toUpperCase(); - String profileId = authorizedBondedRole.getProfileId(); - String nickNameOrBondName = userProfileService.findUserProfile(profileId) - .map(UserProfile::getNickName) - .orElse(authorizedBondedRole.getBondUserName()); - Optional addresses = authorizedBondedRole.getAddressByTransportTypeMap() - .map(e -> e.values().stream() - .map(Address::getFullAddress) - .collect(Collectors.joining(", "))); - if (addresses.isPresent()) { - return Res.get("authorizedRole.securityManager.selectedBondedNode", roleType, nickNameOrBondName, profileId, addresses.get()); - } else { - return Res.get("authorizedRole.securityManager.selectedBondedRole", roleType, nickNameOrBondName, profileId); - } - } - - String getBannedBondedRoleDisplayString(AuthorizedBondedRole authorizedBondedRole) { - String roleType = authorizedBondedRole.getBondedRoleType().getDisplayString(); - String profileId = authorizedBondedRole.getProfileId(); - String nickNameOrBondName = userProfileService.findUserProfile(profileId) - .map(UserProfile::getNickName) - .orElse(authorizedBondedRole.getBondUserName()); - return Res.get("authorizedRole.securityManager.alert.table.bannedRole.value", roleType, nickNameOrBondName, profileId); - } - void onPublishDifficultyAdjustmentFactor() { double difficultyAdjustmentFactor = model.getDifficultyAdjustmentFactor().get(); if (isValidDifficultyAdjustmentFactor(difficultyAdjustmentFactor)) { @@ -215,21 +204,50 @@ void onPublishDifficultyAdjustmentFactor() { } } - private static boolean isValidDifficultyAdjustmentFactor(double difficultyAdjustmentFactor) { - return difficultyAdjustmentFactor >= 0 && difficultyAdjustmentFactor <= NetworkLoad.MAX_DIFFICULTY_ADJUSTMENT; + void onRemoveDifficultyAdjustmentListItem(SecurityManagerView.DifficultyAdjustmentListItem item) { + UserIdentity userIdentity = userIdentityService.getSelectedUserIdentity(); + securityManagerService.removeDifficultyAdjustment(item.getData(), userIdentity.getNetworkIdWithKeyPair().getKeyPair()); + model.getDifficultyAdjustmentFactor().set(difficultyAdjustmentService.getMostRecentValueOrDefault().get()); } + boolean isRemoveDifficultyAdjustmentButtonVisible(AuthorizedDifficultyAdjustmentData data) { return userIdentityService.getSelectedUserIdentity().getId().equals(data.getSecurityManagerProfileId()); } - void onRemoveDifficultyAdjustmentListItem(SecurityManagerView.DifficultyAdjustmentListItem item) { - UserIdentity userIdentity = userIdentityService.getSelectedUserIdentity(); - securityManagerService.removeDifficultyAdjustment(item.getData(), userIdentity.getNetworkIdWithKeyPair().getKeyPair()); - model.getDifficultyAdjustmentFactor().set(difficultyAdjustmentService.getMostRecentValueOrDefault().get()); + boolean isRemoveDifficultyAdjustmentButtonVisible(AuthorizedAlertData authorizedAlertData) { + return userIdentityService.getSelectedUserIdentity().getId().equals(authorizedAlertData.getSecurityManagerProfileId()); + } + + String getBondedRoleDisplayString(BondedRole bondedRole) { + AuthorizedBondedRole authorizedBondedRole = bondedRole.getAuthorizedBondedRole(); + String roleType = authorizedBondedRole.getBondedRoleType().getDisplayString().toUpperCase(); + String profileId = authorizedBondedRole.getProfileId(); + String nickNameOrBondName = userProfileService.findUserProfile(profileId) + .map(UserProfile::getNickName) + .orElse(authorizedBondedRole.getBondUserName()); + Optional addresses = authorizedBondedRole.getAddressByTransportTypeMap() + .map(e -> e.values().stream() + .map(Address::getFullAddress) + .collect(Collectors.joining(", "))); + return addresses.map(address -> Res.get("authorizedRole.securityManager.selectedBondedNode", roleType, nickNameOrBondName, profileId, address)) + .orElseGet(() -> Res.get("authorizedRole.securityManager.selectedBondedRole", roleType, nickNameOrBondName, profileId)); + } + + String getBannedBondedRoleDisplayString(AuthorizedBondedRole authorizedBondedRole) { + String roleType = authorizedBondedRole.getBondedRoleType().getDisplayString(); + String profileId = authorizedBondedRole.getProfileId(); + String nickNameOrBondName = userProfileService.findUserProfile(profileId) + .map(UserProfile::getNickName) + .orElse(authorizedBondedRole.getBondUserName()); + return Res.get("authorizedRole.securityManager.alert.table.bannedRole.value", roleType, nickNameOrBondName, profileId); } private void applySelectAlertType(AlertType alertType) { + model.getAlertsVisible().set(false); + model.getBondedRoleSelectionVisible().set(false); + model.getBannedAccountDataVisible().set(false); + model.getSelectedAlertType().set(alertType); switch (alertType) { case INFO: @@ -238,9 +256,13 @@ private void applySelectAlertType(AlertType alertType) { model.getRequireVersionForTrading().set(false); model.getMinVersion().set(null); model.getSelectedBondedRoleListItem().set(null); + model.getAlertsVisible().set(true); + model.getBannedAccountData().set(null); break; case EMERGENCY: model.getSelectedBondedRoleListItem().set(null); + model.getAlertsVisible().set(true); + model.getBannedAccountData().set(null); break; case BAN: model.getHaltTrading().set(false); @@ -248,6 +270,11 @@ private void applySelectAlertType(AlertType alertType) { model.getMinVersion().set(null); model.getHeadline().set(null); model.getMessage().set(null); + model.getBondedRoleSelectionVisible().set(true); + model.getBannedAccountData().set(null); + break; + case BANNED_ACCOUNT_DATA: + model.getBannedAccountDataVisible().set(true); break; } model.getActionButtonText().set(Res.get("authorizedRole.securityManager.actionButton." + alertType.name())); @@ -273,6 +300,15 @@ private void updateSendButtonDisabled() { case BAN: model.getActionButtonDisabled().set(model.getSelectedBondedRoleListItem().get() == null); break; + case BANNED_ACCOUNT_DATA: + boolean isBannedAccountDataEmpty = StringUtils.isEmpty(model.getBannedAccountData().get()); + model.getActionButtonDisabled().set(isBannedAccountDataEmpty); + break; } } + + private static boolean isValidDifficultyAdjustmentFactor(double difficultyAdjustmentFactor) { + return difficultyAdjustmentFactor >= 0 && difficultyAdjustmentFactor <= NetworkLoad.MAX_DIFFICULTY_ADJUSTMENT; + } + } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerModel.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerModel.java index 67ccace4c4..f7d691db6a 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerModel.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerModel.java @@ -48,6 +48,10 @@ public class SecurityManagerModel implements Model { private final StringProperty minVersion = new SimpleStringProperty(); private final BooleanProperty haltTrading = new SimpleBooleanProperty(); private final BooleanProperty requireVersionForTrading = new SimpleBooleanProperty(); + private final BooleanProperty alertsVisible = new SimpleBooleanProperty(); + private final BooleanProperty bondedRoleSelectionVisible = new SimpleBooleanProperty(); + private final BooleanProperty bannedAccountDataVisible = new SimpleBooleanProperty(); + private final StringProperty bannedAccountData = new SimpleStringProperty(); private final ObservableList alertListItems = FXCollections.observableArrayList(); private final SortedList sortedAlertListItems = new SortedList<>(alertListItems); diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java index 686ec9969e..c1cecd4fc8 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/authorized_role/security_manager/SecurityManagerView.java @@ -21,9 +21,13 @@ import bisq.bonded_roles.security_manager.alert.AlertType; import bisq.bonded_roles.security_manager.alert.AuthorizedAlertData; import bisq.bonded_roles.security_manager.difficulty_adjustment.AuthorizedDifficultyAdjustmentData; +import bisq.common.util.StringUtils; import bisq.desktop.common.converters.Converters; +import bisq.desktop.common.utils.ClipboardUtil; import bisq.desktop.common.view.View; +import bisq.desktop.components.containers.Spacer; import bisq.desktop.components.controls.AutoCompleteComboBox; +import bisq.desktop.components.controls.BisqIconButton; import bisq.desktop.components.controls.MaterialTextArea; import bisq.desktop.components.controls.MaterialTextField; import bisq.desktop.components.controls.validator.NumberValidator; @@ -36,6 +40,7 @@ import bisq.network.p2p.node.network_load.NetworkLoad; import bisq.presentation.formatters.BooleanFormatter; import bisq.presentation.formatters.DateFormatter; +import de.jensd.fx.fontawesome.AwesomeIcon; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -62,7 +67,7 @@ public class SecurityManagerView extends View alertTypeSelection; private final AutoCompleteComboBox bondedRoleSelection; @@ -124,6 +129,9 @@ public BondedRoleListItem fromString(String string) { requireVersionForTradingHBox = new HBox(20, requireVersionForTradingCheckBox, minVersion); requireVersionForTradingHBox.setAlignment(Pos.CENTER_LEFT); + bannedAccountData = new MaterialTextArea(Res.get("authorizedRole.securityManager.bannedAccounts.data"), + Res.get("authorizedRole.securityManager.bannedAccounts.data.prompt")); + sendAlertButton = new Button(); sendAlertButton.setDefaultButton(true); sendAlertButton.setAlignment(Pos.BOTTOM_RIGHT); @@ -164,6 +172,7 @@ public BondedRoleListItem fromString(String string) { alertTypeSelection, headline, message, haltTradingCheckBox, requireVersionForTradingHBox, bondedRoleSelection, + bannedAccountData, sendAlertButton, alertTableView, difficultyAdjustmentHeadline, difficultyAdjustmentFactor, difficultyAdjustmentButton, @@ -186,16 +195,21 @@ protected void onViewAttached() { minVersion.textProperty().bindBidirectional(model.getMinVersion()); minVersion.disableProperty().bind(requireVersionForTradingCheckBox.selectedProperty().not()); difficultyAdjustmentButton.disableProperty().bind(model.getDifficultyAdjustmentFactorButtonDisabled()); - bondedRoleSelection.visibleProperty().bind(model.getSelectedAlertType().isEqualTo(AlertType.BAN)); - bondedRoleSelection.managedProperty().bind(bondedRoleSelection.visibleProperty()); + bondedRoleSelection.visibleProperty().bind(model.getBondedRoleSelectionVisible()); + bondedRoleSelection.managedProperty().bind(model.getBondedRoleSelectionVisible()); headline.textProperty().bindBidirectional(model.getHeadline()); - headline.visibleProperty().bind(bondedRoleSelection.visibleProperty().not()); - headline.managedProperty().bind(headline.visibleProperty()); + headline.visibleProperty().bind(model.getAlertsVisible()); + headline.managedProperty().bind(model.getAlertsVisible()); message.textProperty().bindBidirectional(model.getMessage()); - message.visibleProperty().bind(bondedRoleSelection.visibleProperty().not()); - message.managedProperty().bind(message.visibleProperty()); + message.visibleProperty().bind(model.getAlertsVisible()); + message.managedProperty().bind(model.getAlertsVisible()); + + bannedAccountData.textProperty().bindBidirectional(model.getBannedAccountData()); + bannedAccountData.visibleProperty().bind(model.getBannedAccountDataVisible()); + bannedAccountData.managedProperty().bind(model.getBannedAccountDataVisible()); + sendAlertButton.textProperty().bind(model.getActionButtonText()); sendAlertButton.disableProperty().bind(model.getActionButtonDisabled()); @@ -252,6 +266,10 @@ protected void onViewDetached() { message.visibleProperty().unbind(); message.managedProperty().unbind(); + bannedAccountData.textProperty().unbindBidirectional(model.getBannedAccountData()); + bannedAccountData.visibleProperty().unbind(); + bannedAccountData.managedProperty().unbind(); + sendAlertButton.textProperty().unbind(); sendAlertButton.disableProperty().unbind(); haltTradingCheckBox.selectedProperty().unbindBidirectional(model.getHaltTrading()); @@ -323,6 +341,12 @@ private void configAlertTableView() { .valueSupplier(AlertListItem::getBondedRoleDisplayString) .tooltipSupplier(AlertListItem::getBondedRoleDisplayString) .build()); + alertTableView.getColumns().add(new BisqTableColumn.Builder() + .title(Res.get("authorizedRole.securityManager.alert.table.bannedAccountData")) + .minWidth(200) + .comparator(Comparator.comparing(AlertListItem::getBannedAccountData)) + .setCellFactory(getBannedAccountDataCellFactory()) + .build()); alertTableView.getColumns().add(new BisqTableColumn.Builder() .isSortable(false) .minWidth(200) @@ -332,6 +356,35 @@ private void configAlertTableView() { .build()); } + private Callback, TableCell> getBannedAccountDataCellFactory() { + return column -> new TableCell<>() { + private final Label label = new Label(); + private final BisqIconButton copyButton = new BisqIconButton(); + private final HBox hBox = new HBox(5, label, Spacer.fillHBox(), copyButton); + + { + copyButton.setIcon(AwesomeIcon.COPY); + copyButton.setAlignment(Pos.TOP_RIGHT); + label.setAlignment(Pos.CENTER_LEFT); + hBox.setAlignment(Pos.CENTER_LEFT); + } + + @Override + protected void updateItem(AlertListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + label.setText(StringUtils.truncate(item.getBannedAccountData(), 25)); + copyButton.setOnAction(e -> ClipboardUtil.copyToClipboard(item.getBannedAccountData())); + setGraphic(hBox); + } else { + copyButton.setOnAction(null); + setGraphic(null); + } + } + }; + } + private Callback, TableCell> getRemoveAlertCellFactory() { return column -> new TableCell<>() { private final Button button = new Button(Res.get("data.remove")); @@ -401,7 +454,8 @@ public static class AlertListItem implements DateTableItem { private final AuthorizedAlertData authorizedAlertData; private final long date; - private final String dateString, timeString, alertType, message, haltTrading, requireVersionForTrading, minVersion, bondedRoleDisplayString; + private final String dateString, timeString, alertType, message, haltTrading, requireVersionForTrading, + minVersion, bondedRoleDisplayString, bannedAccountData; public AlertListItem(AuthorizedAlertData authorizedAlertData, SecurityManagerController controller) { this.authorizedAlertData = authorizedAlertData; @@ -414,6 +468,7 @@ public AlertListItem(AuthorizedAlertData authorizedAlertData, SecurityManagerCon haltTrading = BooleanFormatter.toYesNo(this.authorizedAlertData.isHaltTrading()); requireVersionForTrading = BooleanFormatter.toYesNo(this.authorizedAlertData.isRequireVersionForTrading()); bondedRoleDisplayString = authorizedAlertData.getBannedRole().map(controller::getBannedBondedRoleDisplayString).orElse(""); + bannedAccountData = this.authorizedAlertData.getBannedAccountData().orElse(""); } } diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/states/BuyerState2a.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/states/BuyerState2a.java index 2168c6782b..ff2a00cda7 100644 --- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/states/BuyerState2a.java +++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/bisq_easy/open_trades/trade_state/states/BuyerState2a.java @@ -17,25 +17,37 @@ package bisq.desktop.main.content.bisq_easy.open_trades.trade_state.states; +import bisq.bisq_easy.BisqEasyService; import bisq.chat.bisq_easy.open_trades.BisqEasyOpenTradeChannel; import bisq.desktop.ServiceProvider; import bisq.desktop.common.utils.ClipboardUtil; import bisq.desktop.components.controls.MaterialTextArea; import bisq.desktop.components.controls.MaterialTextField; import bisq.desktop.components.controls.WrappingText; +import bisq.desktop.components.controls.validator.SettableErrorValidator; +import bisq.desktop.components.overlay.Popup; import bisq.i18n.Res; +import bisq.support.mediation.MediationRequestService; +import bisq.support.moderator.ModeratorService; import bisq.trade.bisq_easy.BisqEasyTrade; +import bisq.user.profile.UserProfile; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Insets; import javafx.scene.control.Button; import javafx.scene.layout.VBox; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import static bisq.chat.ChatChannelDomain.BISQ_EASY_OPEN_TRADES; + @Slf4j public class BuyerState2a extends BaseState { private final Controller controller; - public BuyerState2a(ServiceProvider serviceProvider, BisqEasyTrade bisqEasyTrade, BisqEasyOpenTradeChannel channel) { + public BuyerState2a(ServiceProvider serviceProvider, + BisqEasyTrade bisqEasyTrade, + BisqEasyOpenTradeChannel channel) { controller = new Controller(serviceProvider, bisqEasyTrade, channel); } @@ -44,8 +56,18 @@ public View getView() { } private static class Controller extends BaseState.Controller { - private Controller(ServiceProvider serviceProvider, BisqEasyTrade bisqEasyTrade, BisqEasyOpenTradeChannel channel) { + private final BisqEasyService bisqEasyService; + private final MediationRequestService mediationRequestService; + private final ModeratorService moderatorService; + + private Controller(ServiceProvider serviceProvider, + BisqEasyTrade bisqEasyTrade, + BisqEasyOpenTradeChannel channel) { super(serviceProvider, bisqEasyTrade, channel); + + bisqEasyService = serviceProvider.getBisqEasyService(); + mediationRequestService = serviceProvider.getSupportService().getMediationRequestService(); + moderatorService = serviceProvider.getSupportService().getModeratorService(); } @Override @@ -61,6 +83,27 @@ protected View createView() { @Override public void onActivate() { super.onActivate(); + BisqEasyTrade bisqEasyTrade = model.getBisqEasyTrade(); + String sellersAccountData = bisqEasyTrade.getPaymentAccountData().get(); + if (bisqEasyService.isAccountDataBanned(sellersAccountData)) { + model.getConfirmFiatSentButtonDisabled().set(true); + model.getAccountDataBannedValidator().setIsInvalid(true); + + UserProfile peer = model.getChannel().getPeer(); + String peerUserName = peer.getUserName(); + + // Report to moderator + String message = "Account data of " + peerUserName + " is banned: " + sellersAccountData; + moderatorService.reportUserProfile(peer, message, BISQ_EASY_OPEN_TRADES); + + // We reject the trade to avoid the banned user can continue + bisqEasyTradeService.cancelTrade(bisqEasyTrade); + + new Popup().warning(Res.get("bisqEasy.tradeState.info.buyer.phase2a.accountDataBanned.popup.warning")).show(); + } else { + model.getConfirmFiatSentButtonDisabled().set(false); + model.getAccountDataBannedValidator().setIsInvalid(false); + } } @Override @@ -77,6 +120,9 @@ private void onConfirmFiatSent() { @Getter private static class Model extends BaseState.Model { + private final BooleanProperty confirmFiatSentButtonDisabled = new SimpleBooleanProperty(); + private final SettableErrorValidator accountDataBannedValidator = new SettableErrorValidator(Res.get("bisqEasy.tradeState.info.buyer.phase2a.accountDataBannedError")); + protected Model(BisqEasyTrade bisqEasyTrade, BisqEasyOpenTradeChannel channel) { super(bisqEasyTrade, channel); } @@ -96,6 +142,7 @@ private View(Model model, Controller controller) { quoteAmount = FormUtils.getTextField(Res.get("bisqEasy.tradeState.info.buyer.phase2a.quoteAmount"), "", false); account = FormUtils.addTextArea(Res.get("bisqEasy.tradeState.info.buyer.phase2a.sellersAccount"), "", false); account.setHelpText(Res.get("bisqEasy.tradeState.info.buyer.phase2a.reasonForPaymentInfo")); + account.setValidator(model.getAccountDataBannedValidator()); confirmFiatSentButton = new Button(); confirmFiatSentButton.setDefaultButton(true); @@ -119,12 +166,14 @@ protected void onViewAttached() { account.validate(); confirmFiatSentButton.setText(Res.get("bisqEasy.tradeState.info.buyer.phase2a.confirmFiatSent", model.getFormattedQuoteAmount())); confirmFiatSentButton.setOnAction(e -> controller.onConfirmFiatSent()); + confirmFiatSentButton.disableProperty().bind(model.getConfirmFiatSentButtonDisabled()); } @Override protected void onViewDetached() { super.onViewDetached(); + confirmFiatSentButton.disableProperty().unbind(); confirmFiatSentButton.setOnAction(null); quoteAmount.getIconButton().setOnAction(null); } diff --git a/bisq-easy/src/main/java/bisq/bisq_easy/BisqEasyService.java b/bisq-easy/src/main/java/bisq/bisq_easy/BisqEasyService.java index 44c5a1d766..80fd608ecc 100644 --- a/bisq-easy/src/main/java/bisq/bisq_easy/BisqEasyService.java +++ b/bisq-easy/src/main/java/bisq/bisq_easy/BisqEasyService.java @@ -20,10 +20,14 @@ import bisq.account.AccountService; import bisq.bonded_roles.BondedRolesService; import bisq.bonded_roles.market_price.MarketPriceService; +import bisq.bonded_roles.security_manager.alert.AlertService; +import bisq.bonded_roles.security_manager.alert.AlertType; +import bisq.bonded_roles.security_manager.alert.AuthorizedAlertData; import bisq.chat.ChatService; import bisq.common.application.Service; import bisq.common.currency.MarketRepository; import bisq.common.observable.Pin; +import bisq.common.observable.collection.CollectionObserver; import bisq.common.util.CompletableFutureUtils; import bisq.contract.ContractService; import bisq.identity.IdentityService; @@ -41,10 +45,12 @@ import bisq.user.UserService; import bisq.user.identity.UserIdentity; import bisq.user.identity.UserIdentityService; +import com.google.common.annotations.VisibleForTesting; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.util.Collection; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -70,8 +76,11 @@ public class BisqEasyService implements Service { private final UserIdentityService userIdentityService; private final BisqEasyNotificationsService bisqEasyNotificationsService; private final MarketPriceService marketPriceService; + private final AlertService alertService; + + private final Set bannedAccountDataSet = new HashSet<>(); private Pin difficultyAdjustmentFactorPin, ignoreDiffAdjustmentFromSecManagerPin, - mostRecentDiffAdjustmentValueOrDefaultPin, selectedMarketPin; + mostRecentDiffAdjustmentValueOrDefaultPin, selectedMarketPin, authorizedAlertDataSetPin; public BisqEasyService(PersistenceService persistenceService, SecurityService securityService, @@ -103,6 +112,7 @@ public BisqEasyService(PersistenceService persistenceService, this.systemNotificationService = systemNotificationService; this.tradeService = tradeService; userIdentityService = userService.getUserIdentityService(); + alertService = bondedRolesService.getAlertService(); bisqEasyNotificationsService = new BisqEasyNotificationsService(chatService.getChatNotificationService(), supportService.getMediatorService(), @@ -132,6 +142,29 @@ public CompletableFuture initialize() { } }); + authorizedAlertDataSetPin = alertService.getAuthorizedAlertDataSet().addObserver(new CollectionObserver<>() { + @Override + public void add(AuthorizedAlertData authorizedAlertData) { + if (authorizedAlertData.getAlertType() == AlertType.BANNED_ACCOUNT_DATA) { + authorizedAlertData.getBannedAccountData().ifPresent(BisqEasyService.this.bannedAccountDataSet::add); + } + } + + @Override + public void remove(Object element) { + if (element instanceof AuthorizedAlertData authorizedAlertData) { + if (authorizedAlertData.getAlertType() == AlertType.BANNED_ACCOUNT_DATA) { + authorizedAlertData.getBannedAccountData().ifPresent(BisqEasyService.this.bannedAccountDataSet::remove); + } + } + } + + @Override + public void clear() { + BisqEasyService.this.bannedAccountDataSet.clear(); + } + }); + return bisqEasyNotificationsService.initialize(); } @@ -142,6 +175,7 @@ public CompletableFuture shutdown() { ignoreDiffAdjustmentFromSecManagerPin.unbind(); mostRecentDiffAdjustmentValueOrDefaultPin.unbind(); selectedMarketPin.unbind(); + authorizedAlertDataSetPin.unbind(); } return getStorePendingMessagesInMailboxFuture() @@ -206,4 +240,20 @@ private void applyDifficultyAdjustmentFactor() { } }); } + + public boolean isAccountDataBanned(String sellersAccountData) { + return isAccountDataBanned(bannedAccountDataSet, sellersAccountData); + } + + @VisibleForTesting + static boolean isAccountDataBanned(Set bannedAccountDataSet, String sellersAccountData) { + // Format is account data of a user separated with |, and then comma separated attributes like name and account number + return bannedAccountDataSet.stream() + .flatMap(data -> Stream.of(data.split("\\|"))) + .flatMap(account -> Stream.of(account.split(","))) + .anyMatch(attribute -> { + String trimmed = attribute.trim(); + return !trimmed.isEmpty() && sellersAccountData.contains(trimmed); + }); + } } diff --git a/bisq-easy/src/test/java/bisq/bisq_easy/BisqEasyServiceTest.java b/bisq-easy/src/test/java/bisq/bisq_easy/BisqEasyServiceTest.java new file mode 100644 index 0000000000..4a07ec20aa --- /dev/null +++ b/bisq-easy/src/test/java/bisq/bisq_easy/BisqEasyServiceTest.java @@ -0,0 +1,74 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.bisq_easy; + +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + + +public class BisqEasyServiceTest { + @Test + public void testIsAccountDataBanned() { + Set bannedAccountDataSet = new HashSet<>(); + String sellersAccountData; + + sellersAccountData=""; + assertFalse(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of(" | , "); + sellersAccountData="Name: Peter Tosh\nIBAN:123456789"; + assertFalse(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + sellersAccountData="abc"; + assertFalse(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + sellersAccountData="Name: Peter Tosh\nIBAN:123456789, BIC:3333"; + assertFalse(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertFalse(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Peter Tosh,123456789"); + sellersAccountData="Name: Peter Tosh\nIBAN:123456789, BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Peter Tosh,123456789"); + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Paula Jam, 3333"); + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Paula Jam , 3333"); + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Tim Burns,98989|123456789|"); + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + + bannedAccountDataSet = Set.of("Tim Burns,98989| Tosh |"); + sellersAccountData="Name: Peter Tosh, Paula Jam\nIBAN:123456789 | BIC:3333"; + assertTrue(BisqEasyService.isAccountDataBanned(bannedAccountDataSet, sellersAccountData)); + } +} diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AlertType.java b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AlertType.java index 03d1cc6d19..9bc9975090 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AlertType.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AlertType.java @@ -24,7 +24,8 @@ public enum AlertType implements ProtoEnum { INFO, WARN, EMERGENCY, - BAN; + BAN, + BANNED_ACCOUNT_DATA; @Override public bisq.bonded_roles.protobuf.AlertType toProtoEnum() { diff --git a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java index 629c1bb392..134911dbf4 100644 --- a/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java +++ b/bonded-roles/src/main/java/bisq/bonded_roles/security_manager/alert/AuthorizedAlertData.java @@ -37,8 +37,7 @@ import java.util.Optional; import java.util.Set; -import static bisq.network.p2p.services.data.storage.MetaData.HIGH_PRIORITY; -import static bisq.network.p2p.services.data.storage.MetaData.TTL_30_DAYS; +import static bisq.network.p2p.services.data.storage.MetaData.*; @Slf4j @ToString @@ -47,6 +46,7 @@ public final class AuthorizedAlertData implements AuthorizedDistributedData { private static final int VERSION = 1; public final static int MAX_MESSAGE_LENGTH = 1000; + public final static int MAX_BANNED_ACCOUNT_DATA_LENGTH = 10_000; // MetaData is transient as it will be used indirectly by low level network classes. Only some low level network classes write the metaData to their protobuf representations. private transient final MetaData metaData = new MetaData(TTL_30_DAYS, HIGH_PRIORITY, getClass().getSimpleName()); @@ -72,6 +72,9 @@ public final class AuthorizedAlertData implements AuthorizedDistributedData { @EqualsAndHashCode.Exclude private final boolean staticPublicKeysProvided; + // Added in version 2.1.3 + private final Optional bannedAccountData; + public AuthorizedAlertData(String id, long date, AlertType alertType, @@ -82,7 +85,8 @@ public AuthorizedAlertData(String id, Optional minVersion, Optional bannedRole, String securityManagerProfileId, - boolean staticPublicKeysProvided) { + boolean staticPublicKeysProvided, + Optional bannedAccountData) { this(VERSION, id, date, @@ -94,7 +98,8 @@ public AuthorizedAlertData(String id, minVersion, bannedRole, securityManagerProfileId, - staticPublicKeysProvided); + staticPublicKeysProvided, + bannedAccountData); } public AuthorizedAlertData(int version, @@ -108,7 +113,8 @@ public AuthorizedAlertData(int version, Optional minVersion, Optional bannedRole, String securityManagerProfileId, - boolean staticPublicKeysProvided) { + boolean staticPublicKeysProvided, + Optional bannedAccountData) { this.version = version; this.id = id; this.date = date; @@ -121,6 +127,7 @@ public AuthorizedAlertData(int version, this.bannedRole = bannedRole; this.securityManagerProfileId = securityManagerProfileId; this.staticPublicKeysProvided = staticPublicKeysProvided; + this.bannedAccountData = bannedAccountData; verify(); } @@ -132,6 +139,7 @@ public void verify() { NetworkDataValidation.validateText(message, MAX_MESSAGE_LENGTH); minVersion.ifPresent(NetworkDataValidation::validateVersion); NetworkDataValidation.validateProfileId(securityManagerProfileId); + NetworkDataValidation.validateText(bannedAccountData, MAX_BANNED_ACCOUNT_DATA_LENGTH); } @Override @@ -158,6 +166,7 @@ public bisq.bonded_roles.protobuf.AuthorizedAlertData.Builder getBuilder(boolean }); minVersion.ifPresent(builder::setMinVersion); bannedRole.ifPresent(authorizedBondedRole -> builder.setBannedRole(authorizedBondedRole.toProto(serializeForHash))); + bannedAccountData.ifPresent(builder::setBannedAccountData); return builder; } @@ -180,8 +189,9 @@ public static AuthorizedAlertData fromProto(bisq.bonded_roles.protobuf.Authorize proto.hasMinVersion() ? Optional.of(proto.getMinVersion()) : Optional.empty(), proto.hasBannedRole() ? Optional.of(AuthorizedBondedRole.fromProto(proto.getBannedRole())) : Optional.empty(), proto.getSecurityManagerProfileId(), - proto.getStaticPublicKeysProvided() - ); + proto.getStaticPublicKeysProvided(), + proto.hasBannedAccountData() ? Optional.of(proto.getBannedAccountData()) : Optional.empty() + ); } private static Optional getDefaultHeadline(AlertType alertType) { diff --git a/bonded-roles/src/main/proto/bonded_roles.proto b/bonded-roles/src/main/proto/bonded_roles.proto index 10da66f25d..9c3f8e11c4 100644 --- a/bonded-roles/src/main/proto/bonded_roles.proto +++ b/bonded-roles/src/main/proto/bonded_roles.proto @@ -57,6 +57,7 @@ enum AlertType { ALERTTYPE_WARN = 2; ALERTTYPE_EMERGENCY = 3; ALERTTYPE_BAN = 4; + ALERTTYPE_BANNED_ACCOUNT_DATA = 5; } message AuthorizedAlertData { @@ -72,6 +73,7 @@ message AuthorizedAlertData { bool staticPublicKeysProvided = 10; optional string headline = 11; sint32 version = 12; + optional string bannedAccountData = 13; } message ReleaseNotification { diff --git a/i18n/src/main/resources/authorized_role.properties b/i18n/src/main/resources/authorized_role.properties index fdc234f0b3..c07151607d 100644 --- a/i18n/src/main/resources/authorized_role.properties +++ b/i18n/src/main/resources/authorized_role.properties @@ -80,6 +80,8 @@ authorizedRole.moderator.table.ban=Ban authorizedRole.moderator.table.contact=Contact authorizedRole.moderator.table.delete=Delete +authorizedRole.moderator.table.message.popup.headline=Reporters message + authorizedRole.moderator.bannedUserProfile.table.headline=Banned user profiles authorizedRole.moderator.bannedUserProfile.table.userProfile=User profile authorizedRole.moderator.bannedUserProfile.table.contact=Contact @@ -111,6 +113,10 @@ authorizedRole.securityManager.emergency.requireVersionForTrading.version.prompt authorizedRole.securityManager.selectBondedRole=Select bonded role authorizedRole.securityManager.selectedBondedRole={0}: Nickname: {1}, Profile ID: {2} authorizedRole.securityManager.selectedBondedNode={0}: Nickname: {1}, Profile ID: {2}, Address: {3} +authorizedRole.securityManager.bannedAccounts.data=Banned account data +authorizedRole.securityManager.bannedAccounts.data.prompt=Account data of scammers separated with |, and comma separated attributes like name and account id/number.\n\ + E.g. Pablo Escobar, 123456|Joe Biden, sleepyJoe@wh.gov.us +authorizedRole.securityManager.bannedAccounts.data.tooLong=Account data is longer than 10 000 characters authorizedRole.securityManager.alert.table.headline=Alerts authorizedRole.securityManager.alert.table.alertType=Type @@ -120,6 +126,8 @@ authorizedRole.securityManager.alert.table.requireVersionForTrading=Require min. authorizedRole.securityManager.alert.table.minVersion=Min. version authorizedRole.securityManager.alert.table.bannedRole=Banned role authorizedRole.securityManager.alert.table.bannedRole.value={0}: {1} ({2}) +authorizedRole.securityManager.alert.table.bannedAccountData=Banned account data + # suppress inspection "UnusedProperty" authorizedRole.securityManager.actionButton.INFO=Send info alert # suppress inspection "UnusedProperty" @@ -129,6 +137,8 @@ authorizedRole.securityManager.actionButton.EMERGENCY=Send emergency alert # suppress inspection "UnusedProperty" authorizedRole.securityManager.actionButton.BAN=Ban bonded role # suppress inspection "UnusedProperty" +authorizedRole.securityManager.actionButton.BANNED_ACCOUNT_DATA=Ban account data +# suppress inspection "UnusedProperty" authorizedRole.securityManager.alertType.INFO=Info # suppress inspection "UnusedProperty" authorizedRole.securityManager.alertType.WARN=Warning @@ -136,6 +146,8 @@ authorizedRole.securityManager.alertType.WARN=Warning authorizedRole.securityManager.alertType.EMERGENCY=Emergency # suppress inspection "UnusedProperty" authorizedRole.securityManager.alertType.BAN=Ban role +# suppress inspection "UnusedProperty" +authorizedRole.securityManager.alertType.BANNED_ACCOUNT_DATA=Ban account data ################################################################################ diff --git a/i18n/src/main/resources/bisq_easy.properties b/i18n/src/main/resources/bisq_easy.properties index 4424aeb5a8..128ffd6a98 100644 --- a/i18n/src/main/resources/bisq_easy.properties +++ b/i18n/src/main/resources/bisq_easy.properties @@ -844,6 +844,12 @@ bisqEasy.tradeState.info.buyer.phase2a.sellersAccount=Payment account of seller bisqEasy.tradeState.info.buyer.phase2a.reasonForPaymentInfo=Please leave the 'Reason for payment' field empty, in case you make a bank transfer bisqEasy.tradeState.info.buyer.phase2a.confirmFiatSent=Confirm payment of {0} bisqEasy.tradeState.info.buyer.phase2a.tradeLogMessage={0} initiated the {1} payment +bisqEasy.tradeState.info.buyer.phase2a.accountDataBannedError=The seller's account data has been used in fraudulent activities +bisqEasy.tradeState.info.buyer.phase2a.accountDataBanned.popup.warning=The seller's account data has been banned due to fraudulent activities.\n\n\ + Do not send any money to this account!\n\n\ + The peer got reported to the moderator and will soon get banned from the network.\n\n\ + For your protection, this trade has been marked as canceled. You may now safely close the trade.\n\n\ + If you have any questions or need further assistance, please visit the support chat. bisqEasy.tradeState.info.buyer.phase2b.headline=Wait for the seller to confirm receipt of payment bisqEasy.tradeState.info.buyer.phase2b.info=Once the seller has received your payment of {0}, they will start the Bitcoin transfer to your provided {1}. diff --git a/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java b/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java index 8d24b0df07..52f55b97bb 100644 --- a/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java +++ b/support/src/main/java/bisq/support/release_manager/ReleaseManagerService.java @@ -68,6 +68,7 @@ public ReleaseManagerService(Config config, /////////////////////////////////////////////////////////////////////////////////////////////////// // Service + /////////////////////////////////////////////////////////////////////////////////////////////////// @Override @@ -83,6 +84,7 @@ public CompletableFuture shutdown() { /////////////////////////////////////////////////////////////////////////////////////////////////// // API + /////////////////////////////////////////////////////////////////////////////////////////////////// public CompletableFuture publishReleaseNotification(boolean isPreRelease, @@ -117,7 +119,14 @@ public CompletableFuture publishReleaseNotification(boolean isPreReleas .thenApply(broadCastDataResult -> true); } - public CompletableFuture removeReleaseNotification(ReleaseNotification releaseNotification, KeyPair ownerKeyPair) { + public CompletableFuture republishReleaseNotification(ReleaseNotification releaseNotification, + KeyPair ownerKeyPair) { + return networkService.publishAuthorizedData(releaseNotification, ownerKeyPair) + .thenApply(broadCastDataResult -> true); + } + + public CompletableFuture removeReleaseNotification(ReleaseNotification releaseNotification, + KeyPair ownerKeyPair) { return networkService.removeAuthorizedData(releaseNotification, ownerKeyPair, ownerKeyPair.getPublic()) diff --git a/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java b/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java index c54fc2d860..cd217f3255 100644 --- a/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java +++ b/support/src/main/java/bisq/support/security_manager/SecurityManagerService.java @@ -19,7 +19,6 @@ import bisq.bonded_roles.BondedRolesService; import bisq.bonded_roles.bonded_role.AuthorizedBondedRole; -import bisq.bonded_roles.bonded_role.AuthorizedBondedRolesService; import bisq.bonded_roles.security_manager.alert.AlertType; import bisq.bonded_roles.security_manager.alert.AuthorizedAlertData; import bisq.bonded_roles.security_manager.difficulty_adjustment.AuthorizedDifficultyAdjustmentData; @@ -71,6 +70,7 @@ public SecurityManagerService(Config config, /////////////////////////////////////////////////////////////////////////////////////////////////// // Service + /////////////////////////////////////////////////////////////////////////////////////////////////// @Override @@ -86,6 +86,7 @@ public CompletableFuture shutdown() { /////////////////////////////////////////////////////////////////////////////////////////////////// // API + /////////////////////////////////////////////////////////////////////////////////////////////////// public CompletableFuture publishAlert(AlertType alertType, @@ -94,7 +95,9 @@ public CompletableFuture publishAlert(AlertType alertType, boolean haltTrading, boolean requireVersionForTrading, Optional minVersion, - Optional bannedRole) { + Optional bannedRole, + Optional bannedAccountData + ) { UserIdentity userIdentity = userIdentityService.getSelectedUserIdentity(); String securityManagerProfileId = userIdentity.getId(); KeyPair keyPair = userIdentity.getIdentity().getKeyBundle().getKeyPair(); @@ -108,7 +111,8 @@ public CompletableFuture publishAlert(AlertType alertType, minVersion, bannedRole, securityManagerProfileId, - staticPublicKeysProvided); + staticPublicKeysProvided, + bannedAccountData); // Can be removed once there are no pre 2.1.0 versions out there anymore AuthorizedAlertData oldVersion = new AuthorizedAlertData(0, @@ -122,13 +126,19 @@ public CompletableFuture publishAlert(AlertType alertType, authorizedAlertData.getMinVersion(), authorizedAlertData.getBannedRole(), authorizedAlertData.getSecurityManagerProfileId(), - authorizedAlertData.isStaticPublicKeysProvided()); + authorizedAlertData.isStaticPublicKeysProvided(), + authorizedAlertData.getBannedAccountData()); networkService.publishAuthorizedData(oldVersion, keyPair); return networkService.publishAuthorizedData(authorizedAlertData, keyPair) .thenApply(broadCastDataResult -> true); } + public CompletableFuture rePublishAlert(AuthorizedAlertData authorizedAlertData, KeyPair ownerKeyPair) { + return networkService.publishAuthorizedData(authorizedAlertData, ownerKeyPair) + .thenApply(broadCastDataResult -> true); + } + public CompletableFuture removeAlert(AuthorizedAlertData authorizedAlertData, KeyPair ownerKeyPair) { return networkService.removeAuthorizedData(authorizedAlertData, ownerKeyPair, ownerKeyPair.getPublic()) .thenApply(broadCastDataResult -> true); @@ -155,7 +165,8 @@ public CompletableFuture publishDifficultyAdjustment(double difficultyA .thenApply(broadCastDataResult -> true); } - public CompletableFuture removeDifficultyAdjustment(AuthorizedDifficultyAdjustmentData data, KeyPair ownerKeyPair) { + public CompletableFuture removeDifficultyAdjustment(AuthorizedDifficultyAdjustmentData data, + KeyPair ownerKeyPair) { return networkService.removeAuthorizedData(data, ownerKeyPair, ownerKeyPair.getPublic()) .thenApply(broadCastDataResult -> true); }