From fa1b00d69b67a96636a0f5ae73b42c0f368e767e Mon Sep 17 00:00:00 2001 From: apriestman Date: Fri, 12 Mar 2021 09:57:56 -0500 Subject: [PATCH] Add context menu to merge hosts. Fixed capitalization on csv menu option. --- .../sleuthkit/autopsy/datamodel/HostNode.java | 24 ++--- .../datamodel/hosts/Bundle.properties-MERGED | 5 + .../datamodel/hosts/MergeHostAction.java | 76 +++++++++++++++ .../datamodel/hosts/MergeHostMenuAction.java | 94 +++++++++++++++++++ .../directorytree/Bundle.properties-MERGED | 2 +- .../directorytree/ExportCSVAction.java | 2 +- 6 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostMenuAction.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java index 44cb4fad5f..72b65a89c2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Optional; @@ -40,6 +41,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.HostsChangedEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.hosts.AssociatePersonsMenuAction; +import org.sleuthkit.autopsy.datamodel.hosts.MergeHostMenuAction; import org.sleuthkit.autopsy.datamodel.hosts.RemoveParentPersonAction; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Host; @@ -287,10 +289,13 @@ public class HostNode extends DisplayableItemNode { "HostNode_actions_removeFromPerson=Remove from person ({0})"}) public Action[] getActions(boolean context) { - Optional parent = Optional.empty(); - + List actionsList = new ArrayList<>(); + // if there is a host, then provide actions if (this.host != null) { + + // Add the appropriate Person action + Optional parent; try { parent = Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getPerson(this.host); } catch (NoCurrentCaseException | TskCoreException ex) { @@ -300,17 +305,14 @@ public class HostNode extends DisplayableItemNode { // if there is a parent, only give option to remove parent person. if (parent.isPresent()) { - return new Action[]{ - new RemoveParentPersonAction(this.host, parent.get()), - null - }; + actionsList.add(new RemoveParentPersonAction(this.host, parent.get())); } else { - return new Action[]{ - new AssociatePersonsMenuAction(this.host), - null - }; + actionsList.add(new AssociatePersonsMenuAction(this.host)); } + + // Add option to merge hosts + actionsList.add(new MergeHostMenuAction(this.host)); } - return new Action[0]; + return actionsList.toArray(new Action[actionsList.size()]); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/hosts/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/Bundle.properties-MERGED index 452a98a54d..4555d4ed24 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/hosts/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/Bundle.properties-MERGED @@ -31,6 +31,11 @@ AddEditHostDialog.okButton.text=OK AddEditHostDialog.cancelButton.text=Cancel AddEditHostDialog.inputTextField.text=jTextField1 ManageHostsDialog_title_text=Manage Hosts +# {0} - sourceHostName +# {1} - destHostName +MergeHostAction_onError_description=There was an error merging host {0} into host {1}. +MergeHostAction_onError_title=Error Merging Hosts +MergeHostMenuAction_menuTitle=Merge Into Other Host OpenHostsAction_displayName=Hosts # {0} - personName RemoveParentPersonAction_menuTitle=Remove from Person ({0}) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostAction.java b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostAction.java new file mode 100644 index 0000000000..b242ddae52 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostAction.java @@ -0,0 +1,76 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel.hosts; + +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Menu action to merge a host into another host. + */ +@Messages({ + "MergeHostAction_onError_title=Error Merging Hosts", + "# {0} - sourceHostName", + "# {1} - destHostName", + "MergeHostAction_onError_description=There was an error merging host {0} into host {1}.",}) +public class MergeHostAction extends AbstractAction { + + private static final Logger logger = Logger.getLogger(MergeHostAction.class.getName()); + + private final Host sourceHost; + private final Host destHost; + + /** + * Main constructor. + * + * @param sourceHost The source host. + * @param destHost The destination host. + */ + public MergeHostAction(Host sourceHost, Host destHost) { + super(destHost.getName()); + + this.sourceHost = sourceHost; + this.destHost = destHost; + } + + @Override + public void actionPerformed(ActionEvent e) { + try { + Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().mergeHosts(sourceHost, destHost); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to merge host: %s into host: %s", sourceHost.getName(), destHost.getName()), ex); + + JOptionPane.showMessageDialog( + WindowManager.getDefault().getMainWindow(), + Bundle.MergeHostAction_onError_description(sourceHost, destHost), + Bundle.MergeHostAction_onError_title(), + JOptionPane.WARNING_MESSAGE); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostMenuAction.java b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostMenuAction.java new file mode 100644 index 0000000000..637c33cc88 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/hosts/MergeHostMenuAction.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel.hosts; + +import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; +import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * JMenu item to show a menu allowing the selected host to be merged into another host. + */ +@Messages({ + "MergeHostMenuAction_menuTitle=Merge Into Other Host",}) +public class MergeHostMenuAction extends AbstractAction implements Presenter.Popup { + + private static final Logger logger = Logger.getLogger(MergeHostMenuAction.class.getName()); + + private final Host sourceHost; + + /** + * Main constructor. + * + * @param host The original host. + */ + public MergeHostMenuAction(Host host) { + super(""); + this.sourceHost = host; + } + + @Override + @SuppressWarnings("NoopMethodInAbstractClass") + public void actionPerformed(ActionEvent event) { + } + + @Override + public JMenuItem getPopupPresenter() { + JMenu menu = new JMenu(Bundle.MergeHostMenuAction_menuTitle()); + + // Get a list of all other hosts + List otherHosts = Collections.emptyList(); + try { + otherHosts = Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getHosts(); + otherHosts.remove(sourceHost); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, "Error getting hosts for case.", ex); + } + + // If there are no other hosts, disable the menu item. Otherwise add + // the other hosts to the menu. + if (otherHosts.isEmpty()) { + menu.setEnabled(false); + } else { + menu.setEnabled(true); + otherHosts.stream() + .filter(p -> p != null && p.getName() != null) + .sorted((a, b) -> a.getName().compareToIgnoreCase(b.getName())) + .map(p -> new JMenuItem(new MergeHostAction(sourceHost, p))) + .forEach(menu::add); + } + + return menu; + } + +} + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 7e96d8fdaa..c648387016 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -14,7 +14,7 @@ ExportCSV.saveNodesToCSV.empty=No data to export # {0} - Output file ExportCSV.saveNodesToCSV.fileExists=File {0} already exists ExportCSV.saveNodesToCSV.noCurrentCase=No open case available -ExportCSV.title.text=Export selected rows to CSV +ExportCSV.title.text=Export Selected Rows to CSV ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened. ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch. ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java index 7761e82231..7b312211c4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExportCSVAction.java @@ -85,7 +85,7 @@ public final class ExportCSVAction extends AbstractAction { /** * Private constructor for the action. */ - @NbBundle.Messages({"ExportCSV.title.text=Export selected rows to CSV"}) + @NbBundle.Messages({"ExportCSV.title.text=Export Selected Rows to CSV"}) private ExportCSVAction() { super(Bundle.ExportCSV_title_text()); }