diff --git a/Core/build.xml b/Core/build.xml index a2e901c3a9..968bec5b92 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -110,11 +110,11 @@ - - + + - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index 23d25aab2e..eb10fd7c47 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -181,7 +181,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { /** * Create a list of the DataSourceProcessors which should exist as options * on this panel. The default Autopsy DataSourceProcessors will appear at - * the beggining of the list in the same order. + * the beginning of the list in the same order. * * @return dspList a list of DataSourceProcessors which can be chose in this * panel @@ -200,6 +200,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { dspList.add(LocalDiskDSProcessor.getType()); dspList.add(LocalFilesDSProcessor.getType()); dspList.add(RawDSProcessor.getType()); + dspList.add(LogicalImagerDSProcessor.getType()); // now add any addtional DSPs that haven't already been added for (String dspType : datasourceProcessorsMap.keySet()) { if (!dspList.contains(dspType)) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index ef41f8c70f..2683f71d6d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -238,6 +238,13 @@ OpenMultiUserCasePanel.cancelButton.text=Cancel OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case... OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name +LogicalImagerPanel.jLabel1.text=Insert external drive +LogicalImagerPanel.scanButton.text=Scan +LogicalImagerPanel.jLabel6.text=Or, pick a Logical Imager folder +LogicalImagerPanel.browseButton.text=Browse +LogicalImagerPanel.topLabel.text=Import Autopsy Imager Results +LogicalImagerPanel.selectDriveLabel.text=Select Drive +LogicalImagerPanel.messageLabel.text=Error/Status message UnpackagePortableCaseDialog.desc2Label.text=Portable Case Report Module. UnpackagePortableCaseDialog.desc1Label.text=Unpackage a portable case so it can be opened in Autopsy. Portable cases are created through the UnpackagePortableCaseDialog.exitButton.text=Exit @@ -252,4 +259,4 @@ UnpackagePortableCaseProgressDialog.cancelButton.text=Cancel UnpackagePortableCaseProgressDialog.okButton.text=OK UnpackagePortableCaseProgressDialog.resultLabel.text=resultLabel UnpackagePortableCaseDialog.extractLabel.text=Folder to extract to: -UnpackagePortableCaseDialog.caseLabel.text=Portable Case: +UnpackagePortableCaseDialog.caseLabel.text=Portable Case: \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 48d6c84258..4150e5729e 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -168,6 +168,18 @@ LogicalEvidenceFilePanel.pathValidation.getOpenCase.Error=Warning: Exception whi LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=Only files with the .l01 file extension are supported here. LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01) LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders +LogicalImagerDSProcessor.dataSourceType=Autopsy Imager +LogicalImagerPanel.imageTable.columnModel.title0=Hostname +LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date +LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images +LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1} +LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS +LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images +LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found +LogicalImagerPanel.messageLabel.noImageSelected=No image selected +LogicalImagerPanel.messageLabel.scanningExternalDrives=Scanning external drives for sparse_image.vhd ... +LogicalImagerPanel.messageLabel.selectedImage=Selected folder +LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive Menu/Case/OpenRecentCase=Open Recent Case CTL_CaseDeleteAction=Delete Case OpenIDE-Module-Name=Case @@ -440,6 +452,13 @@ OpenMultiUserCasePanel.cancelButton.text=Cancel OpenMultiUserCasePanel.openSingleUserCaseButton.text=Open Single-User Case... OpenMultiUserCasePanel.openSelectedCaseButton.text=Open Selected Case OpenMultiUserCasePanel.searchLabel.text=Select any case and start typing to search by case name +LogicalImagerPanel.jLabel1.text=Insert external drive +LogicalImagerPanel.scanButton.text=Scan +LogicalImagerPanel.jLabel6.text=Or, pick a Logical Imager folder +LogicalImagerPanel.browseButton.text=Browse +LogicalImagerPanel.topLabel.text=Import Autopsy Imager Results +LogicalImagerPanel.selectDriveLabel.text=Select Drive +LogicalImagerPanel.messageLabel.text=Error/Status message UnpackagePortableCaseDialog.desc2Label.text=Portable Case Report Module. UnpackagePortableCaseDialog.desc1Label.text=Unpackage a portable case so it can be opened in Autopsy. Portable cases are created through the UnpackagePortableCaseDialog.exitButton.text=Exit diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form index 2274ccb9a7..0c634afc24 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form @@ -116,7 +116,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java new file mode 100644 index 0000000000..ea67c48290 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerDSProcessor.java @@ -0,0 +1,165 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 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.casemodule; + +import java.nio.file.Path; +import javax.swing.JPanel; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.lookup.ServiceProviders; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; + +/** + * A Logical Imager data source processor that implements the DataSourceProcessor service + * provider interface to allow integration with the add data source wizard. It + * also provides a run method overload to allow it to be used independently of + * the wizard. + */ +@ServiceProviders(value={ + @ServiceProvider(service=DataSourceProcessor.class)} +) +public class LogicalImagerDSProcessor implements DataSourceProcessor { + + private final LogicalImagerPanel configPanel; + + /* + * Constructs a Logical Imager data source processor that implements the + * DataSourceProcessor service provider interface to allow integration with + * the add data source wizard. It also provides a run method overload to + * allow it to be used independently of the wizard. + */ + public LogicalImagerDSProcessor() { + configPanel = LogicalImagerPanel.createInstance(LogicalImagerDSProcessor.class.getName()); + } + + /** + * Gets a string that describes the type of data sources this processor is + * able to add to the case database. The string is suitable for display in a + * type selection UI component (e.g., a combo box). + * + * @return A data source type display string for this data source processor. + */ + @Messages({"LogicalImagerDSProcessor.dataSourceType=Autopsy Imager"}) + public static String getType() { + return Bundle.LogicalImagerDSProcessor_dataSourceType(); + } + + /** + * Gets a string that describes the type of data sources this processor is + * able to add to the case database. The string is suitable for display in a + * type selection UI component (e.g., a combo box). + * + * @return A data source type display string for this data source processor. + */ + @Override + public String getDataSourceType() { + return Bundle.LogicalImagerDSProcessor_dataSourceType(); + } + + /** + * Gets the panel that allows a user to select a data source and do any + * configuration required by the data source. The panel is less than 544 + * pixels wide and less than 173 pixels high. + * + * @return A selection and configuration panel for this data source + * processor. + */ + @Override + public JPanel getPanel() { + configPanel.reset(); + return configPanel; + } + + /** + * Indicates whether the settings in the selection and configuration panel + * are valid and complete. + * + * @return True if the settings are valid and complete and the processor is + * ready to have its run method called, false otherwise. + */ + @Override + public boolean isPanelValid() { + return configPanel.validatePanel(); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Returns as soon as the background task is started. + * The background task uses a callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + @Override + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + configPanel.storeSettings(); + Path dirPath = configPanel.getImageDirPath(); + System.out.println("Choosen directory " + dirPath.toString()); + // TODO: process the data source in 5011 + } + + /** + * Adds a "Logical Imager" data source to the case database using a background task in + * a separate thread and the given settings instead of those provided by the + * selection and configuration panel. Returns as soon as the background task + * is started and uses the callback object to signal task completion and + * return results. + * + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is + * intended to be unique across multiple cases + * (e.g., a UUID). + * @param imageFilePath Path to the image file. + * @param timeZone The time zone to use when processing dates + * and times for the image, obtained from + * java.util.TimeZone.getID. + * @param chunkSize The maximum size of each chunk of the raw + * data source as it is divided up into virtual + * unallocated space files. + * @param progressMonitor Progress monitor for reporting progress + * during processing. + * @param callback Callback to call when processing is done. + */ + private void run(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddImageTask addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback); + new Thread(addImageTask).start(); + } + + @Override + public void cancel() { + } + + /** + * Resets the selection and configuration panel for this data source + * processor. + */ + @Override + public void reset() { + configPanel.reset(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form new file mode 100644 index 0000000000..d89ee47202 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.form @@ -0,0 +1,233 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java new file mode 100644 index 0000000000..44d50d0be5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalImagerPanel.java @@ -0,0 +1,583 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2019 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.casemodule; + +import java.awt.Color; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileSystemView; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableModel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; + +/** + * Panel for adding an logical image file from drive letters. Allows the user + * to select a file. + */ +@Messages({ + "LogicalImagerPanel.messageLabel.selectedImage=Selected folder", + "LogicalImagerPanel.messageLabel.noImageSelected=No image selected", + "LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images", + "LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive", +}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class LogicalImagerPanel extends JPanel implements DocumentListener { + + private static final long serialVersionUID = 1L; + private static final String SPARSE_IMAGE_VHD = "sparse_image.vhd"; //NON-NLS + private static final String SELECTED_IMAGE = Bundle.LogicalImagerPanel_messageLabel_selectedImage(); + private static final String NO_IMAGE_SELECTED = Bundle.LogicalImagerPanel_messageLabel_noImageSelected(); + private static final String DRIVE_HAS_NO_IMAGES = Bundle.LogicalImagerPanel_messageLabel_driveHasNoImages(); + private static final String[] EMPTY_LIST_DATA = {}; + + private final JFileChooser fileChooser = new JFileChooser(); + private final Pattern regex = Pattern.compile("Logical_Imager_(.+)_(\\d{4})(\\d{2})(\\d{2})_(\\d{2})_(\\d{2})_(\\d{2})"); + private final String contextName; + private Path choosenImageDirPath; + private TableModel imageTableModel; + + /** + * Creates new form LogicalImagerPanel + * + * @param context A string context name used to read/store last + * used settings. + */ + private LogicalImagerPanel(String context) { + this.contextName = context; + initComponents(); + clearImageTable(); + } + + /** + * Creates and returns an instance of a LogicalImagerPanel. + * + * @param context A string context name used to read/store last + * used settings. + * + * @return instance of the LogicalImagerPanel + */ + @Messages({ + "LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images" + }) + public static synchronized LogicalImagerPanel createInstance(String context) { + LogicalImagerPanel instance = new LogicalImagerPanel(context); + // post-constructor initialization of listener support without leaking references of uninitialized objects + instance.messageLabel.setText(Bundle.LogicalImagerPanel_messageLabel_clickScanOrBrowse()); + instance.imageTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + return instance; + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + topLabel = new javax.swing.JLabel(); + jLabel1 = new javax.swing.JLabel(); + scanButton = new javax.swing.JButton(); + messageLabel = new javax.swing.JLabel(); + selectDriveLabel = new javax.swing.JLabel(); + driveListScrollPane = new javax.swing.JScrollPane(); + driveList = new javax.swing.JList<>(); + selectAcquisitionFromDriveLabel = new javax.swing.JLabel(); + jLabel6 = new javax.swing.JLabel(); + browseButton = new javax.swing.JButton(); + imageScrollPane = new javax.swing.JScrollPane(); + imageTable = new javax.swing.JTable(); + jSeparator1 = new javax.swing.JSeparator(); + + setMinimumSize(new java.awt.Dimension(0, 65)); + setPreferredSize(new java.awt.Dimension(403, 65)); + + org.openide.awt.Mnemonics.setLocalizedText(topLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.topLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(scanButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.scanButton.text")); // NOI18N + scanButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + scanButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(messageLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.messageLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(selectDriveLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectDriveLabel.text")); // NOI18N + + driveList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + driveList.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + driveListMouseClicked(evt); + } + }); + driveList.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + driveListKeyReleased(evt); + } + }); + driveListScrollPane.setViewportView(driveList); + + org.openide.awt.Mnemonics.setLocalizedText(selectAcquisitionFromDriveLabel, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.selectAcquisitionFromDriveLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.jLabel6.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LogicalImagerPanel.class, "LogicalImagerPanel.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + imageScrollPane.setPreferredSize(new java.awt.Dimension(346, 402)); + + imageTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + + } + )); + imageTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF); + imageTable.setShowHorizontalLines(false); + imageTable.setShowVerticalLines(false); + imageTable.getTableHeader().setReorderingAllowed(false); + imageTable.setUpdateSelectionOnSort(false); + imageTable.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + imageTableMouseClicked(evt); + } + }); + imageTable.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + imageTableKeyReleased(evt); + } + }); + imageScrollPane.setViewportView(imageTable); + imageTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(238, 238, 238) + .addComponent(topLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 163, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGap(28, 28, 28) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(messageLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jSeparator1, javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(selectDriveLabel) + .addGap(289, 289, 289)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(scanButton) + .addGap(126, 126, 126))) + .addGap(36, 36, 36) + .addComponent(browseButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(driveListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 211, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(28, 28, 28) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(selectAcquisitionFromDriveLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 305, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 346, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(layout.createSequentialGroup() + .addGap(346, 346, 346) + .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 154, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addGap(144, 144, 144) + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addContainerGap(48, Short.MAX_VALUE)))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(topLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jLabel6)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(scanButton) + .addComponent(browseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(selectDriveLabel) + .addComponent(selectAcquisitionFromDriveLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(imageScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(driveListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE)) + .addGap(26, 26, 26) + .addComponent(messageLabel) + .addGap(154, 154, 154)) + ); + }// //GEN-END:initComponents + + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) { + return bytes + " B"; //NON-NLS + } + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); //NON-NLS + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS + } + + @Messages({ + "LogicalImagerPanel.messageLabel.scanningExternalDrives=Scanning external drives for sparse_image.vhd ...", + "LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found" + }) + private void scanButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_scanButtonActionPerformed + // Scan external drives for sparse_image.vhd + clearImageTable(); + setNormalMessage(Bundle.LogicalImagerPanel_messageLabel_scanningExternalDrives()); + List listData = new ArrayList<>(); + File[] roots = File.listRoots(); + int firstRemovableDrive = -1; + int i = 0; + for (File root : roots) { + String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); + long spaceInBytes = root.getTotalSpace(); + String sizeWithUnit = humanReadableByteCount(spaceInBytes, false); + listData.add(root + " (" + description + ") (" + sizeWithUnit + ")"); + if (firstRemovableDrive == -1) { + try { + FileStore fileStore = Files.getFileStore(root.toPath()); + if ((boolean) fileStore.getAttribute("volume:isRemovable")) { //NON-NLS + firstRemovableDrive = i; + } + } catch (IOException ex) { + ; // skip + } + } + i++; + } + driveList.setListData(listData.toArray(new String[0])); + if (!listData.isEmpty()) { + // auto-select the first external drive, if any + driveList.setSelectedIndex(firstRemovableDrive == -1 ? 0 : firstRemovableDrive); + driveListMouseClicked(null); + driveList.requestFocusInWindow(); + } else { + setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_noExternalDriveFound()); + } + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + }//GEN-LAST:event_scanButtonActionPerformed + + @Messages({ + "LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}", + "LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS" + }) + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + imageTable.clearSelection(); + choosenImageDirPath = null; + setErrorMessage(NO_IMAGE_SELECTED); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int retval = fileChooser.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fileChooser.getSelectedFile().getPath(); + Matcher m = regex.matcher(path); + if (m.find()) { + Path vhdPath = Paths.get(path, SPARSE_IMAGE_VHD); + if (!vhdPath.toFile().exists()) { + setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path,SPARSE_IMAGE_VHD)); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + return; + } + choosenImageDirPath = Paths.get(path); + setNormalMessage(SELECTED_IMAGE + " " + path); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } else { + setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryFormatInvalid(path)); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + } + } else { + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + } + }//GEN-LAST:event_browseButtonActionPerformed + + private void imageTableSelect() { + int index = imageTable.getSelectedRow(); + if (index != -1) { + choosenImageDirPath = Paths.get((String) imageTableModel.getValueAt(index, 2)); + setNormalMessage(SELECTED_IMAGE + " " + choosenImageDirPath.toString()); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } else { + choosenImageDirPath = null; + setErrorMessage(NO_IMAGE_SELECTED); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + } + } + + private void imageTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_imageTableMouseClicked + imageTableSelect(); + }//GEN-LAST:event_imageTableMouseClicked + + private void driveListSelect() { + String selectedStr = driveList.getSelectedValue(); + if (selectedStr == null) { + return; + } + String driveLetter = selectedStr.substring(0, 3); + File directory = new File(driveLetter); + File[] fList = directory.listFiles(); + + if (fList != null) { + imageTableModel = new ImageTableModel(); + int row = 0; + // Find all directories with name like Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS + // and has a sparse_image.vhd file in it + for (File file : fList) { + if (file.isDirectory() + && Paths.get(driveLetter, file.getName(), SPARSE_IMAGE_VHD).toFile().exists()) { + String dir = file.getName(); + Matcher m = regex.matcher(dir); + if (m.find()) { + String imageDirPath = driveLetter + dir; + String hostname = m.group(1); + String year = m.group(2); + String month = m.group(3); + String day = m.group(4); + String hour = m.group(5); + String minute = m.group(6); + String second = m.group(7); + String extractDate = year + "/" + month + "/" + day + + " " + hour + ":" + minute + ":" + second; + imageTableModel.setValueAt(hostname, row, 0); + imageTableModel.setValueAt(extractDate, row, 1); + imageTableModel.setValueAt(imageDirPath, row, 2); + row++; + } + } + } + selectAcquisitionFromDriveLabel.setText(Bundle.LogicalImagerPanel_selectAcquisitionFromDriveLabel_text() + + " " + driveLetter); + imageTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + imageTable.setModel(imageTableModel); + fixImageTableColumnWidth(); + // If there are any images, select the first one + if (imageTable.getRowCount() > 0) { + imageTable.setRowSelectionInterval(0, 0); + imageTableSelect(); + } else { + choosenImageDirPath = null; + setErrorMessage(DRIVE_HAS_NO_IMAGES); + } + } + } + + private void fixImageTableColumnWidth() { + int width = imageScrollPane.getPreferredSize().width - 2; + imageTable.getColumnModel().getColumn(0).setPreferredWidth((int) (.60 * width)); + imageTable.getColumnModel().getColumn(1).setPreferredWidth((int) (.40 * width)); + } + + private void setErrorMessage(String msg) { + messageLabel.setForeground(Color.red); + messageLabel.setText(msg); + } + + private void setNormalMessage(String msg) { + messageLabel.setForeground(Color.black); + messageLabel.setText(msg); + } + + private void clearImageTable() { + imageTableModel = new ImageTableModel(); + imageTable.setModel(imageTableModel); + fixImageTableColumnWidth(); + } + + private void driveListMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_driveListMouseClicked + driveListSelect(); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + }//GEN-LAST:event_driveListMouseClicked + + private void driveListKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_driveListKeyReleased + driveListSelect(); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + }//GEN-LAST:event_driveListKeyReleased + + private void imageTableKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_imageTableKeyReleased + imageTableSelect(); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + }//GEN-LAST:event_imageTableKeyReleased + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; + private javax.swing.JList driveList; + private javax.swing.JScrollPane driveListScrollPane; + private javax.swing.JScrollPane imageScrollPane; + private javax.swing.JTable imageTable; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel6; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JLabel messageLabel; + private javax.swing.JButton scanButton; + private javax.swing.JLabel selectAcquisitionFromDriveLabel; + private javax.swing.JLabel selectDriveLabel; + private javax.swing.JLabel topLabel; + // End of variables declaration//GEN-END:variables + + public void reset() { + //reset the UI elements to default + choosenImageDirPath = null; + driveList.setListData(EMPTY_LIST_DATA); + clearImageTable(); + setNormalMessage(Bundle.LogicalImagerPanel_messageLabel_clickScanOrBrowse()); + } + + /** + * Should we enable the next button of the wizard? + * + * @return true if a proper image has been selected, false otherwise + */ + public boolean validatePanel() { + return choosenImageDirPath != null && choosenImageDirPath.toFile().exists(); + } + + Path getImageDirPath() { + return choosenImageDirPath; + } + + @Override + public void insertUpdate(DocumentEvent e) { + } + + @Override + public void removeUpdate(DocumentEvent e) { + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + + void storeSettings() { + } + + private class ImageTableModel extends AbstractTableModel { + private final List hostnames = new ArrayList<>(); + private final List extractDates = new ArrayList<>(); + private final List imageDirPaths = new ArrayList<>(); + + @Override + public int getRowCount() { + return hostnames.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Messages({ + "LogicalImagerPanel.imageTable.columnModel.title0=Hostname", + "LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date" + }) + @Override + public String getColumnName(int column) { + String colName = null; + switch (column) { + case 0: + colName = Bundle.LogicalImagerPanel_imageTable_columnModel_title0(); + break; + case 1: + colName = Bundle.LogicalImagerPanel_imageTable_columnModel_title1(); + break; + default: + break; + } + return colName; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Object ret = null; + switch (columnIndex) { + case 0: + ret = hostnames.get(rowIndex); + break; + case 1: + ret = extractDates.get(rowIndex); + break; + case 2: + ret = imageDirPaths.get(rowIndex); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + return ret; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + hostnames.add((String) aValue); + break; + case 1: + extractDates.add((String) aValue); + break; + case 2: + imageDirPaths.add((String) aValue); + break; + default: + throw new UnsupportedOperationException("Invalid table column index: " + columnIndex); //NON-NLS + } + // Only show the hostname and extractDates column + if (columnIndex < 2) { + super.setValueAt(aValue, rowIndex, columnIndex); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java deleted file mode 100644 index 39eae2b9f3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDetailsNode.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017 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.communications; - -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.python.google.common.collect.Iterables; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AccountDeviceInstance; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.CommunicationsFilter; -import org.sleuthkit.datamodel.CommunicationsManager; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * 'Root' Node for the Account/Messages area. Has children which are all the - * relationships of all the accounts in this node. - * - */ -final class AccountDetailsNode extends AbstractNode { - - private final static Logger logger = Logger.getLogger(AccountDetailsNode.class.getName()); - - AccountDetailsNode(Set accountDeviceInstances, CommunicationsFilter filter, CommunicationsManager commsManager) { - super(Children.create(new AccountRelationshipChildren(accountDeviceInstances, commsManager, filter), true)); - String displayName = (accountDeviceInstances.size() == 1) - ? Iterables.getOnlyElement(accountDeviceInstances).getAccount().getTypeSpecificID() - : accountDeviceInstances.size() + " accounts"; - setDisplayName(displayName); - } - - /** - * Children object for the relationships that the accounts are part of. - */ - private static class AccountRelationshipChildren extends ChildFactory { - - private final Set accountDeviceInstances; - private final CommunicationsManager commsManager; - private final CommunicationsFilter filter; - - private AccountRelationshipChildren(Set accountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) { - this.accountDeviceInstances = accountDeviceInstances; - this.commsManager = commsManager; - this.filter = filter; - } - - @Override - protected boolean createKeys(List list) { - try { - list.addAll(commsManager.getRelationshipSources(accountDeviceInstances, filter)); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting communications", ex); - } - return true; - } - - @Override - protected Node createNodeForKey(Content t) { - if (t instanceof BlackboardArtifact) { - return new RelationshipNode((BlackboardArtifact) t); - } else { - throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content."); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index 6abd4a70d0..fcf64182a3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; import java.awt.Component; +import java.util.HashSet; +import java.util.Set; import java.util.logging.Level; import javax.swing.JPanel; import javax.swing.ListSelectionModel; @@ -31,11 +33,16 @@ import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.relationships.RelationshipBrowser; +import org.sleuthkit.autopsy.communications.relationships.SelectionInfo; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.TskCoreException; @@ -56,8 +63,9 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro private final Outline outline; - private final ExplorerManager messageBrowserEM = new ExplorerManager(); private final ExplorerManager accountsTableEM = new ExplorerManager(); + + final RelationshipBrowser relationshipBrowser; /* * This lookup proxies the selection lookup of both he accounts table and @@ -78,21 +86,30 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AccountNode_accountName()); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setColumnSorted(3, false, 1); //it would be nice if the column index wasn't hardcoded + + relationshipBrowser = new RelationshipBrowser(); + jSplitPane1.setRightComponent(relationshipBrowser); accountsTableEM.addPropertyChangeListener(evt -> { if (ExplorerManager.PROP_ROOT_CONTEXT.equals(evt.getPropertyName())) { SwingUtilities.invokeLater(this::setColumnWidths); } else if (ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) { SwingUtilities.invokeLater(this::setColumnWidths); + } else if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] selectedNodes = accountsTableEM.getSelectedNodes(); + final Set accountDeviceInstances = new HashSet<>(); + + CommunicationsFilter filter = null; + for (final Node node : selectedNodes) { + accountDeviceInstances.add(((AccountDeviceInstanceNode) node).getAccountDeviceInstance()); + filter = ((AccountDeviceInstanceNode)node).getFilter(); + } + relationshipBrowser.setSelectionInfo(new SelectionInfo(accountDeviceInstances, filter)); } }); - final MessageBrowser messageBrowser = new MessageBrowser(accountsTableEM, messageBrowserEM); - - jSplitPane1.setRightComponent(messageBrowser); - - proxyLookup = new ProxyLookup( - messageBrowser.getLookup(), - ExplorerUtils.createLookup(accountsTableEM, getActionMap())); + + proxyLookup = new ProxyLookup(relationshipBrowser.getLookup(), + ExplorerUtils.createLookup(accountsTableEM, getActionMap())); } private void setColumnWidths() { diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index f00a02cf9d..6603682cfb 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -32,21 +32,16 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomOutButton.toolTipText=Zoom out VisualizationPanel.zoomOutButton.text= -<<<<<<< HEAD -VisualizationPanel.fastOrganicLayoutButton.text=Redraw -VisualizationPanel.clearVizButton.text_1=Clear +VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.backButton.text_1= -VisualizationPanel.forwardButton.text= -======= VisualizationPanel.circleLayoutButton.text=Circle VisualizationPanel.organicLayoutButton.text=Organic -VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1= VisualizationPanel.snapshotButton.text_1=Snapshot Report ->>>>>>> develop VisualizationPanel.clearVizButton.actionCommand= VisualizationPanel.backButton.toolTipText=Click to go back VisualizationPanel.forwardButton.toolTipText=Click to go forward VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to redraw the chart VisualizationPanel.clearVizButton.toolTipText=Click to clear the chart +VisualizationPanel.forwardButton.text= diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index c9f38865a8..1ad8b5d9c5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -22,7 +22,6 @@ FiltersPanel.refreshButton.text=Refresh FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one. FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. -MessageBrowser.DataResultViewerTable.title=Messages OpenCVTAction.displayName=Communications PinAccountsAction.pluralText=Add Selected Accounts to Visualization PinAccountsAction.singularText=Add Selected Account to Visualization @@ -76,24 +75,19 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomOutButton.toolTipText=Zoom out VisualizationPanel.zoomOutButton.text= -<<<<<<< HEAD -VisualizationPanel.fastOrganicLayoutButton.text=Redraw -VisualizationPanel.clearVizButton.text_1=Clear +VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.backButton.text_1= -VisualizationPanel.forwardButton.text= -======= VisualizationPanel.circleLayoutButton.text=Circle VisualizationPanel.organicLayoutButton.text=Organic -VisualizationPanel.fastOrganicLayoutButton.text= VisualizationPanel.hierarchyLayoutButton.text=Hierarchical VisualizationPanel.clearVizButton.text_1= VisualizationPanel.snapshotButton.text_1=Snapshot Report ->>>>>>> develop VisualizationPanel.clearVizButton.actionCommand= VisualizationPanel.backButton.toolTipText=Click to go back VisualizationPanel.forwardButton.toolTipText=Click to go forward VisualizationPanel.fastOrganicLayoutButton.toolTipText=Click to redraw the chart VisualizationPanel.clearVizButton.toolTipText=Click to clear the chart +VisualizationPanel.forwardButton.text= VisualizationPanel_action_dialogs_title=Communications VisualizationPanel_action_name_text=Snapshot Report VisualizationPanel_module_name=Communications diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index bc8047d473..16c3208ff8 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.communications; import com.google.common.eventbus.Subscribe; +import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.util.List; @@ -61,8 +62,11 @@ public final class CVTTopComponent extends TopComponent { associateLookup(proxyLookup); // Make sure the Global Actions Context is proxying the selection of the active tab. browseVisualizeTabPane.addChangeListener(changeEvent -> { - Lookup.Provider selectedComponent = (Lookup.Provider) browseVisualizeTabPane.getSelectedComponent(); - proxyLookup.setNewLookups(selectedComponent.getLookup()); + Component selectedComponent = browseVisualizeTabPane.getSelectedComponent(); + if(selectedComponent instanceof Lookup.Provider) { + Lookup lookup = ((Lookup.Provider)selectedComponent).getLookup(); + proxyLookup.setNewLookups(lookup); + } filtersPane.setDeviceAccountTypeEnabled(browseVisualizeTabPane.getSelectedIndex() != 0); }); diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 56c8be50a7..aeff26a67f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -52,6 +52,7 @@ import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; import org.sleuthkit.datamodel.DataSource; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; +import static org.sleuthkit.datamodel.Relationship.Type.CONTACT; import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -598,7 +599,7 @@ final public class FiltersPanel extends JPanel { commsFilter.addAndFilter(getAccountTypeFilter()); commsFilter.addAndFilter(getDateRangeFilter()); commsFilter.addAndFilter(new CommunicationsFilter.RelationshipTypeFilter( - ImmutableSet.of(CALL_LOG, MESSAGE))); + ImmutableSet.of(CALL_LOG, MESSAGE, CONTACT))); return commsFilter; } diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java deleted file mode 100644 index 339e842280..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2017-2018 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 obt ain 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.communications; - -import java.awt.Component; -import java.awt.KeyboardFocusManager; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.HashSet; -import java.util.Set; -import javax.swing.JPanel; -import static javax.swing.SwingUtilities.isDescendingFrom; -import org.openide.explorer.ExplorerManager; -import static org.openide.explorer.ExplorerUtils.createLookup; -import org.openide.nodes.Node; -import org.openide.util.Lookup; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.corecomponents.DataResultPanel; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.TableFilterNode; -import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; - -/** - * The right hand side of the CVT. Has a DataResultPanel to show a listing of - * messages and other account details, and a ContentViewer to show individual - * messages. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class MessageBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { - - private static final long serialVersionUID = 1L; - private final ExplorerManager tableEM; - private final ExplorerManager gacExplorerManager; - private final DataResultPanel messagesResultPanel; - /* lookup that will be exposed through the (Global Actions Context) */ - private final ModifiableProxyLookup proxyLookup = new ModifiableProxyLookup(); - - private final PropertyChangeListener focusPropertyListener = new PropertyChangeListener() { - /** - * Listener that keeps the proxyLookup in sync with the focused area of - * the UI. - * - * Since the embedded MessageContentViewer (attachments panel) is not in - * its own TopComponenet, its selection does not get proxied into the - * Global Actions Context (GAC), and many of the available actions don't - * work on it. Further, we can't put the selection from both the - * Messages table and the Attachments table in the GAC because they - * could both include AbstractFiles, muddling the selection seen by the - * actions. Instead, depending on where the focus is in the window, we - * want to put different Content in the Global Actions Context to be - * picked up by, e.g., the tagging actions. The best way I could figure - * to do this was to listen to all focus events and swap out what is in - * the lookup appropriately. An alternative to this would be to - * investigate using the ContextAwareAction interface. - * - * @see org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a - * similar situation and a similar solution. - * - * @param focusEvent The focus change event. - */ - @Override - public void propertyChange(final PropertyChangeEvent focusEvent) { - if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { - final Component newFocusOwner = (Component) focusEvent.getNewValue(); - - if (newFocusOwner == null) { - return; - } - if (isDescendingFrom(newFocusOwner, messageDataContent)) { - //if the focus owner is within the MessageContentViewer ( the attachments table) - proxyLookup.setNewLookups(createLookup(messageDataContent.getExplorerManager(), getActionMap())); - } else if (isDescendingFrom(newFocusOwner, messagesResultPanel)) { - //... or if it is within the Messages table. - proxyLookup.setNewLookups(createLookup(gacExplorerManager, getActionMap())); - } - - } - } - }; - - /** - * Constructs the right hand side of the Communications Visualization Tool - * (CVT). - * - * @param tableEM An explorer manager to listen to as the driver - * of the Message Table. - * @param gacExplorerManager An explorer manager associated with the - * GlobalActionsContext (GAC) so that selections - * in the messages browser can be exposed to - * context-sensitive actions. - */ - @NbBundle.Messages({"MessageBrowser.DataResultViewerTable.title=Messages"}) - MessageBrowser(final ExplorerManager tableEM, final ExplorerManager gacExplorerManager) { - this.tableEM = tableEM; - this.gacExplorerManager = gacExplorerManager; - initComponents(); - //create an uninitialized DataResultPanel so we can control the ResultViewers that get added. - messagesResultPanel = DataResultPanel.createInstanceUninitialized("Account", "", Node.EMPTY, 0, messageDataContent); - splitPane.setTopComponent(messagesResultPanel); - splitPane.setBottomComponent(messageDataContent); - messagesResultPanel.addResultViewer(new DataResultViewerTable(gacExplorerManager, - Bundle.MessageBrowser_DataResultViewerTable_title())); - messagesResultPanel.open(); - - this.tableEM.addPropertyChangeListener(new PropertyChangeListener() { - /** - * Listener that pushes selections in the tableEM (the Accounts - * table) into the Messages table. - * - * @param pce The ExplorerManager event. - */ - @Override - public void propertyChange(PropertyChangeEvent pce) { - if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); - messagesResultPanel.setNumberOfChildNodes(0); - messagesResultPanel.setNode(null); - messagesResultPanel.setPath(""); - if (selectedNodes.length > 0) { - Node rootNode; - final Node selectedNode = selectedNodes[0]; - - if (selectedNode instanceof AccountDeviceInstanceNode) { - rootNode = makeRootNodeFromAccountDeviceInstanceNodes(selectedNodes); - } else { - rootNode = selectedNode; - } - messagesResultPanel.setPath(rootNode.getDisplayName()); - messagesResultPanel.setNode(new TableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true)); - } - } - } - - private Node makeRootNodeFromAccountDeviceInstanceNodes(final Node[] selectedNodes) { - //Use lookup here? - final AccountDeviceInstanceNode adiNode = (AccountDeviceInstanceNode) selectedNodes[0]; - - final Set accountDeviceInstances = new HashSet<>(); - for (final Node n : selectedNodes) { - //Use lookup here? - accountDeviceInstances.add(((AccountDeviceInstanceNode) n).getAccountDeviceInstanceKey()); - } - return SelectionNode.createFromAccounts(accountDeviceInstances, adiNode.getFilter(), adiNode.getCommsManager()); - } - } - ); - } - - @Override - public ExplorerManager getExplorerManager() { - return gacExplorerManager; - } - - @Override - public Lookup getLookup() { - return proxyLookup; - } - - @Override - public void addNotify() { - super.addNotify(); - //add listener that maintains correct selection in the Global Actions Context - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .addPropertyChangeListener("focusOwner", focusPropertyListener); - } - - @Override - public void removeNotify() { - super.removeNotify(); - KeyboardFocusManager.getCurrentKeyboardFocusManager() - .removePropertyChangeListener("focusOwner", focusPropertyListener); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - splitPane = new javax.swing.JSplitPane(); - messageDataContent = new org.sleuthkit.autopsy.communications.MessageDataContent(); - - splitPane.setDividerLocation(400); - splitPane.setDividerSize(10); - splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); - splitPane.setResizeWeight(0.5); - splitPane.setBottomComponent(messageDataContent); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(splitPane)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(splitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1083, Short.MAX_VALUE) - .addGap(0, 0, 0)) - ); - }// //GEN-END:initComponents - - - // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.communications.MessageDataContent messageDataContent; - private javax.swing.JSplitPane splitPane; - // End of variables declaration//GEN-END:variables - -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/ModifiableProxyLookup.java b/Core/src/org/sleuthkit/autopsy/communications/ModifiableProxyLookup.java index ad3babc504..0eccb8bf10 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/ModifiableProxyLookup.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ModifiableProxyLookup.java @@ -26,9 +26,9 @@ import org.openide.util.lookup.ProxyLookup; * delegated to. * */ -final class ModifiableProxyLookup extends ProxyLookup { +final public class ModifiableProxyLookup extends ProxyLookup { - ModifiableProxyLookup(final Lookup... lookups) { + public ModifiableProxyLookup(final Lookup... lookups) { super(lookups); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java deleted file mode 100644 index b147ea9537..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/RelaionshipSetNodeFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.communications; - -import java.util.Collection; -import java.util.List; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Node; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * - */ -public class RelaionshipSetNodeFactory extends ChildFactory { - - private final Collection artifacts; - - public RelaionshipSetNodeFactory(Collection artifacts) { - this.artifacts = artifacts; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(artifacts); - return true; - } - - @Override - protected Node createNodeForKey(BlackboardArtifact key) { - return new RelationshipNode(key); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java b/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java deleted file mode 100644 index 1618bb9ecc..0000000000 --- a/Core/src/org/sleuthkit/autopsy/communications/SelectionNode.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 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.communications; - -import com.google.common.collect.Iterables; -import com.google.common.collect.Sets; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.stream.Collectors; -import org.openide.nodes.AbstractNode; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.util.Lookup; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AccountDeviceInstance; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.CommunicationsFilter; -import org.sleuthkit.datamodel.CommunicationsManager; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * 'Root' Node for the Account/Messages area. Represents all the relationships - * that are selected in the AccountsBrowser or the VisualizationPanel. Can be - * populated with AccountDeviceInstance and/or directly with relationships - * (Content). - */ -final class SelectionNode extends AbstractNode { - - private SelectionNode(Children children, Lookup lookup) { - super(children, lookup); - } - - static SelectionNode createFromAccountsAndRelationships( - Set edgeRelationshipArtifacts, - Set accountDeviceInstanceKeys, - CommunicationsFilter filter, - CommunicationsManager commsManager) { - - Set accountDeviceInstances = accountDeviceInstanceKeys.stream() - .map(AccountDeviceInstanceKey::getAccountDeviceInstance) - .collect(Collectors.toSet()); - - SelectionNode node = new SelectionNode(Children.create( - new RelationshipChildren( - edgeRelationshipArtifacts, - accountDeviceInstances, - commsManager, - filter), - true), Lookups.fixed(accountDeviceInstanceKeys.toArray())); - - //This is not good for internationalization!!! - String name = ""; - final int accounts = accountDeviceInstances.size(); - if (accounts > 1) { - name = accounts + " accounts"; - } else if (accounts == 1) { - name = Iterables.getOnlyElement(accountDeviceInstances).getAccount().getTypeSpecificID(); - } - - final int edges = edgeRelationshipArtifacts.size(); - - if (edges > 0) { - name = name + (name.isEmpty() ? "" : " and ") + edges + " relationship" + (edges > 1 ? "s" : ""); - } - - node.setDisplayName(name); - return node; - } - - static SelectionNode createFromAccounts( - Set accountDeviceInstances, - CommunicationsFilter filter, - CommunicationsManager commsManager) { - - return createFromAccountsAndRelationships(Collections.emptySet(), accountDeviceInstances, filter, commsManager); - } - - /** - * Children object for the relationships that the accounts are part of. - */ - private static class RelationshipChildren extends ChildFactory { - - static final private Logger logger = Logger.getLogger(RelationshipChildren.class.getName()); - - private final Set edgeRelationshipArtifacts; - - private final Set accountDeviceInstances; - - private final CommunicationsManager commsManager; - private final CommunicationsFilter filter; - - private RelationshipChildren(Set selectedEdgeRelationshipSources, Set selecedAccountDeviceInstances, CommunicationsManager commsManager, CommunicationsFilter filter) { - this.edgeRelationshipArtifacts = selectedEdgeRelationshipSources; - this.accountDeviceInstances = selecedAccountDeviceInstances; - this.commsManager = commsManager; - this.filter = filter; - } - - @Override - protected boolean createKeys(List list) { - try { - final Set relationshipSources = commsManager.getRelationshipSources(accountDeviceInstances, filter); - list.addAll(Sets.union(relationshipSources, edgeRelationshipArtifacts)); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting communications", ex); - } - return true; - } - - @Override - protected Node createNodeForKey(Content content) { - if (content instanceof BlackboardArtifact) { - return new RelationshipNode((BlackboardArtifact) content); - } else { - throw new UnsupportedOperationException("Cannot create a RelationshipNode for non BlackboardArtifact content."); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/communications/Utils.java b/Core/src/org/sleuthkit/autopsy/communications/Utils.java index c4a62209c7..4a3e03e1f2 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Utils.java +++ b/Core/src/org/sleuthkit/autopsy/communications/Utils.java @@ -28,12 +28,12 @@ import org.sleuthkit.datamodel.Account; /** * Utility class with helpers for dealing with accounts. */ -class Utils { +public final class Utils { private Utils() { } - static ZoneId getUserPreferredZoneId() { + static public ZoneId getUserPreferredZoneId() { ZoneId zone = UserPreferences.displayTimesInLocalTime() ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()).toZoneId(); return zone; @@ -44,7 +44,7 @@ class Utils { * * @return The path of the icon for the given Account Type. */ - static final String getIconFilePath(Account.Type type) { + static public final String getIconFilePath(Account.Type type) { return Accounts.getIconFilePath(type); } diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index a0f63348ee..027f2f285a 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -54,7 +54,6 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; -import java.beans.PropertyVetoException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -62,11 +61,11 @@ import java.nio.file.Paths; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; @@ -98,23 +97,20 @@ import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.Notifications; import org.jdesktop.layout.GroupLayout; import org.jdesktop.layout.LayoutStyle; -import org.openide.explorer.ExplorerManager; -import org.openide.explorer.ExplorerUtils; -import org.openide.nodes.Node; -import org.openide.util.Lookup; import org.openide.util.NbBundle; -import org.openide.util.lookup.ProxyLookup; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.relationships.RelationshipBrowser; +import org.sleuthkit.autopsy.communications.relationships.SelectionInfo; import org.sleuthkit.autopsy.communications.snapshot.CommSnapShotReportWriter; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; +import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsManager; -import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** * A panel that goes in the Visualize tab of the Communications Visualization @@ -127,7 +123,7 @@ import org.sleuthkit.datamodel.TskCoreException; * actions to work correctly. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -final public class VisualizationPanel extends JPanel implements Lookup.Provider { +final public class VisualizationPanel extends JPanel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(VisualizationPanel.class.getName()); @@ -140,9 +136,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @NbBundle.Messages("VisualizationPanel.cancelButton.text=Cancel") private static final String CANCEL = Bundle.VisualizationPanel_cancelButton_text(); - private final ExplorerManager vizEM = new ExplorerManager(); - private final ExplorerManager gacEM = new ExplorerManager(); - private final ProxyLookup proxyLookup; private Frame windowAncestor; private CommunicationsManager commsManager; @@ -161,6 +154,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private final Map layoutButtons = new HashMap<>(); private NamedGraphLayout currentLayout; + + private final RelationshipBrowser relationshipBrowser; private final StateManager stateManager; @@ -225,13 +220,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider final GraphMouseListener graphMouseListener = new GraphMouseListener(); graphComponent.getGraphControl().addMouseWheelListener(graphMouseListener); graphComponent.getGraphControl().addMouseListener(graphMouseListener); - - final MessageBrowser messageBrowser = new MessageBrowser(vizEM, gacEM); - splitPane.setRightComponent(messageBrowser); - proxyLookup = new ProxyLookup( - ExplorerUtils.createLookup(vizEM, getActionMap()), - messageBrowser.getLookup() - ); + + relationshipBrowser = new RelationshipBrowser(); + splitPane.setRightComponent(relationshipBrowser); //feed selection to explorermanager graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionListener()); @@ -257,12 +248,7 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider setStateButtonsEnabled(); } - - @Override - public Lookup getLookup() { - return proxyLookup; - } - + @Subscribe void handle(LockedVertexModel.VertexLockEvent event) { final Set vertices = event.getVertices(); @@ -387,219 +373,223 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider // //GEN-BEGIN:initComponents private void initComponents() { - splitPane = new JSplitPane(); - borderLayoutPanel = new JPanel(); - placeHolderPanel = new JPanel(); - jTextArea1 = new JTextArea(); - toolbar = new JPanel(); - fastOrganicLayoutButton = new JButton(); - zoomOutButton = new JButton(); - zoomInButton = new JButton(); - zoomActualButton = new JButton(); - fitZoomButton = new JButton(); - jLabel2 = new JLabel(); - zoomLabel = new JLabel(); - clearVizButton = new JButton(); - jSeparator2 = new JToolBar.Separator(); - backButton = new JButton(); - forwardButton = new JButton(); - snapshotButton = new JButton(); - jSeparator3 = new JToolBar.Separator(); - jSeparator4 = new JToolBar.Separator(); - notificationsJFXPanel = new JFXPanel(); + splitPane = new javax.swing.JSplitPane(); + borderLayoutPanel = new javax.swing.JPanel(); + placeHolderPanel = new javax.swing.JPanel(); + jTextArea1 = new javax.swing.JTextArea(); + toolbar = new javax.swing.JPanel(); + fastOrganicLayoutButton = new javax.swing.JButton(); + zoomOutButton = new javax.swing.JButton(); + zoomInButton = new javax.swing.JButton(); + zoomActualButton = new javax.swing.JButton(); + fitZoomButton = new javax.swing.JButton(); + jLabel2 = new javax.swing.JLabel(); + zoomLabel = new javax.swing.JLabel(); + clearVizButton = new javax.swing.JButton(); + jSeparator2 = new javax.swing.JToolBar.Separator(); + backButton = new javax.swing.JButton(); + forwardButton = new javax.swing.JButton(); + snapshotButton = new javax.swing.JButton(); + jSeparator3 = new javax.swing.JToolBar.Separator(); + jSeparator4 = new javax.swing.JToolBar.Separator(); + notificationsJFXPanel = new javafx.embed.swing.JFXPanel(); - setLayout(new BorderLayout()); + setLayout(new java.awt.BorderLayout()); splitPane.setDividerLocation(800); splitPane.setResizeWeight(0.5); - borderLayoutPanel.setLayout(new BorderLayout()); + borderLayoutPanel.setLayout(new java.awt.BorderLayout()); - jTextArea1.setBackground(new Color(240, 240, 240)); + jTextArea1.setBackground(new java.awt.Color(240, 240, 240)); jTextArea1.setColumns(20); jTextArea1.setLineWrap(true); jTextArea1.setRows(5); - jTextArea1.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N + jTextArea1.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jTextArea1.text")); // NOI18N - GroupLayout placeHolderPanelLayout = new GroupLayout(placeHolderPanel); + org.jdesktop.layout.GroupLayout placeHolderPanelLayout = new org.jdesktop.layout.GroupLayout(placeHolderPanel); placeHolderPanel.setLayout(placeHolderPanelLayout); - placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) + placeHolderPanelLayout.setHorizontalGroup( + placeHolderPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() .addContainerGap(250, Short.MAX_VALUE) - .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE) + .add(jTextArea1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 424, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) .addContainerGap(423, Short.MAX_VALUE)) ); - placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) + placeHolderPanelLayout.setVerticalGroup( + placeHolderPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 47, GroupLayout.PREFERRED_SIZE) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jTextArea1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 47, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - borderLayoutPanel.add(placeHolderPanel, BorderLayout.CENTER); + borderLayoutPanel.add(placeHolderPanel, java.awt.BorderLayout.CENTER); - fastOrganicLayoutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N - fastOrganicLayoutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N - fastOrganicLayoutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N + fastOrganicLayoutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-circle-double-135.png"))); // NOI18N + fastOrganicLayoutButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.text")); // NOI18N + fastOrganicLayoutButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fastOrganicLayoutButton.toolTipText")); // NOI18N fastOrganicLayoutButton.setFocusable(false); - fastOrganicLayoutButton.setVerticalTextPosition(SwingConstants.BOTTOM); + fastOrganicLayoutButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - zoomOutButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N - zoomOutButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N - zoomOutButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N + zoomOutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-out-red.png"))); // NOI18N + zoomOutButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.text")); // NOI18N + zoomOutButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomOutButton.toolTipText")); // NOI18N zoomOutButton.setFocusable(false); - zoomOutButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomOutButton.setVerticalTextPosition(SwingConstants.BOTTOM); - zoomOutButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomOutButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + zoomOutButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { zoomOutButtonActionPerformed(evt); } }); - zoomInButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N - zoomInButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N - zoomInButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N + zoomInButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-in-green.png"))); // NOI18N + zoomInButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.text")); // NOI18N + zoomInButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomInButton.toolTipText")); // NOI18N zoomInButton.setFocusable(false); - zoomInButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomInButton.setVerticalTextPosition(SwingConstants.BOTTOM); - zoomInButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomInButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + zoomInButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { zoomInButtonActionPerformed(evt); } }); - zoomActualButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N - zoomActualButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N - zoomActualButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N + zoomActualButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-actual.png"))); // NOI18N + zoomActualButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.text")); // NOI18N + zoomActualButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomActualButton.toolTipText")); // NOI18N zoomActualButton.setFocusable(false); - zoomActualButton.setHorizontalTextPosition(SwingConstants.CENTER); - zoomActualButton.setVerticalTextPosition(SwingConstants.BOTTOM); - zoomActualButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + zoomActualButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomActualButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + zoomActualButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { zoomActualButtonActionPerformed(evt); } }); - fitZoomButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N - fitZoomButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N - fitZoomButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N + fitZoomButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/magnifier-zoom-fit.png"))); // NOI18N + fitZoomButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.text")); // NOI18N + fitZoomButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.fitZoomButton.toolTipText")); // NOI18N fitZoomButton.setFocusable(false); - fitZoomButton.setHorizontalTextPosition(SwingConstants.CENTER); - fitZoomButton.setVerticalTextPosition(SwingConstants.BOTTOM); - fitZoomButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + fitZoomButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + fitZoomButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + fitZoomButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { fitZoomButtonActionPerformed(evt); } }); - jLabel2.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jLabel2.text")); // NOI18N + jLabel2.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.jLabel2.text")); // NOI18N - zoomLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N + zoomLabel.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomLabel.text")); // NOI18N - clearVizButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N - clearVizButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N - clearVizButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N - clearVizButton.setActionCommand(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N - clearVizButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + clearVizButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/broom.png"))); // NOI18N + clearVizButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.text_1")); // NOI18N + clearVizButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.toolTipText")); // NOI18N + clearVizButton.setActionCommand(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.clearVizButton.actionCommand")); // NOI18N + clearVizButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { clearVizButtonActionPerformed(evt); } }); - jSeparator2.setOrientation(SwingConstants.VERTICAL); + jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); - backButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N - backButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N - backButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N - backButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_previous.png"))); // NOI18N + backButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.text_1")); // NOI18N + backButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.backButton.toolTipText")); // NOI18N + backButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); - forwardButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N - forwardButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N - forwardButton.setToolTipText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N - forwardButton.setHorizontalTextPosition(SwingConstants.LEADING); - forwardButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/resultset_next.png"))); // NOI18N + forwardButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.text")); // NOI18N + forwardButton.setToolTipText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.forwardButton.toolTipText")); // NOI18N + forwardButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + forwardButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { forwardButtonActionPerformed(evt); } }); - snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N - snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N - snapshotButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent evt) { + snapshotButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N + snapshotButton.setText(org.openide.util.NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N + snapshotButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { snapshotButtonActionPerformed(evt); } }); - jSeparator3.setOrientation(SwingConstants.VERTICAL); + jSeparator3.setOrientation(javax.swing.SwingConstants.VERTICAL); - jSeparator4.setOrientation(SwingConstants.VERTICAL); + jSeparator4.setOrientation(javax.swing.SwingConstants.VERTICAL); - GroupLayout toolbarLayout = new GroupLayout(toolbar); + org.jdesktop.layout.GroupLayout toolbarLayout = new org.jdesktop.layout.GroupLayout(toolbar); toolbar.setLayout(toolbarLayout); - toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) + toolbarLayout.setHorizontalGroup( + toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() .addContainerGap() .add(backButton) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(forwardButton) - .addPreferredGap(LayoutStyle.RELATED) - .add(jSeparator4, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(fastOrganicLayoutButton) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(clearVizButton) - .addPreferredGap(LayoutStyle.RELATED) - .add(jSeparator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(jLabel2) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(zoomLabel) - .addPreferredGap(LayoutStyle.RELATED) - .add(zoomOutButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) - .add(zoomInButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) - .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) - .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) - .add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) - .addPreferredGap(LayoutStyle.RELATED) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(zoomOutButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(zoomInButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(zoomActualButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 33, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(fitZoomButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 32, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jSeparator3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 10, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(snapshotButton) - .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) + toolbarLayout.setVerticalGroup( + toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() .add(3, 3, 3) - .add(toolbarLayout.createParallelGroup(GroupLayout.CENTER) + .add(toolbarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.CENTER) .add(fastOrganicLayoutButton) .add(zoomOutButton) - .add(zoomInButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(zoomActualButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(fitZoomButton, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(zoomInButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(zoomActualButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(fitZoomButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(jLabel2) .add(zoomLabel) .add(clearVizButton) - .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jSeparator2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(backButton) .add(forwardButton) .add(snapshotButton) - .add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .add(jSeparator4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .add(jSeparator3, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .add(jSeparator4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .add(3, 3, 3)) ); - borderLayoutPanel.add(toolbar, BorderLayout.PAGE_START); - borderLayoutPanel.add(notificationsJFXPanel, BorderLayout.PAGE_END); + borderLayoutPanel.add(toolbar, java.awt.BorderLayout.PAGE_START); + borderLayoutPanel.add(notificationsJFXPanel, java.awt.BorderLayout.PAGE_END); splitPane.setLeftComponent(borderLayoutPanel); - add(splitPane, BorderLayout.CENTER); + add(splitPane, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents private void fitZoomButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_fitZoomButtonActionPerformed @@ -890,26 +880,26 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } // Variables declaration - do not modify//GEN-BEGIN:variables - private JButton backButton; - private JPanel borderLayoutPanel; - private JButton clearVizButton; - private JButton fastOrganicLayoutButton; - private JButton fitZoomButton; - private JButton forwardButton; - private JLabel jLabel2; - private JToolBar.Separator jSeparator2; - private JToolBar.Separator jSeparator3; - private JToolBar.Separator jSeparator4; - private JTextArea jTextArea1; - private JFXPanel notificationsJFXPanel; - private JPanel placeHolderPanel; - private JButton snapshotButton; - private JSplitPane splitPane; - private JPanel toolbar; - private JButton zoomActualButton; - private JButton zoomInButton; - private JLabel zoomLabel; - private JButton zoomOutButton; + private javax.swing.JButton backButton; + private javax.swing.JPanel borderLayoutPanel; + private javax.swing.JButton clearVizButton; + private javax.swing.JButton fastOrganicLayoutButton; + private javax.swing.JButton fitZoomButton; + private javax.swing.JButton forwardButton; + private javax.swing.JLabel jLabel2; + private javax.swing.JToolBar.Separator jSeparator2; + private javax.swing.JToolBar.Separator jSeparator3; + private javax.swing.JToolBar.Separator jSeparator4; + private javax.swing.JTextArea jTextArea1; + private javafx.embed.swing.JFXPanel notificationsJFXPanel; + private javax.swing.JPanel placeHolderPanel; + private javax.swing.JButton snapshotButton; + private javax.swing.JSplitPane splitPane; + private javax.swing.JPanel toolbar; + private javax.swing.JButton zoomActualButton; + private javax.swing.JButton zoomInButton; + private javax.swing.JLabel zoomLabel; + private javax.swing.JButton zoomOutButton; // End of variables declaration//GEN-END:variables /** @@ -922,40 +912,25 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider @Override public void invoke(Object sender, mxEventObject evt) { Object[] selectionCells = graph.getSelectionCells(); - Node rootNode = Node.EMPTY; - Node[] selectedNodes = new Node[0]; if (selectionCells.length > 0) { mxICell[] selectedCells = Arrays.asList(selectionCells).toArray(new mxCell[selectionCells.length]); - HashSet relationshipSources = new HashSet<>(); - HashSet adis = new HashSet<>(); + HashSet deviceInstances = new HashSet<>(); for (mxICell cell : selectedCells) { if (cell.isEdge()) { mxICell source = (mxICell) graph.getModel().getTerminal(cell, true); - AccountDeviceInstanceKey account1 = (AccountDeviceInstanceKey) source.getValue(); mxICell target = (mxICell) graph.getModel().getTerminal(cell, false); - AccountDeviceInstanceKey account2 = (AccountDeviceInstanceKey) target.getValue(); - try { - final List relationshipSources1 = commsManager.getRelationshipSources( - account1.getAccountDeviceInstance(), - account2.getAccountDeviceInstance(), - currentFilter); - relationshipSources.addAll(relationshipSources1); - } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, " Error getting relationsips....", tskCoreException); - } + + deviceInstances.add(((AccountDeviceInstanceKey) source.getValue()).getAccountDeviceInstance()); + deviceInstances.add(((AccountDeviceInstanceKey) target.getValue()).getAccountDeviceInstance()); + } else if (cell.isVertex()) { - adis.add((AccountDeviceInstanceKey) cell.getValue()); + deviceInstances.add(((AccountDeviceInstanceKey) cell.getValue()).getAccountDeviceInstance()); } } - rootNode = SelectionNode.createFromAccountsAndRelationships(relationshipSources, adis, currentFilter, commsManager); - selectedNodes = new Node[]{rootNode}; - } - vizEM.setRootContext(rootNode); - try { - vizEM.setSelectedNodes(selectedNodes); - } catch (PropertyVetoException ex) { - logger.log(Level.SEVERE, "Selection vetoed.", ex); + relationshipBrowser.setSelectionInfo(new SelectionInfo(deviceInstances, currentFilter)); + } else { + relationshipBrowser.setSelectionInfo(new SelectionInfo(Collections.EMPTY_SET, currentFilter)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSourceContentChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSourceContentChildNodeFactory.java new file mode 100755 index 0000000000..328a0f0dac --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSourceContentChildNodeFactory.java @@ -0,0 +1,108 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountFileInstance; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory that creates ContentNode representing the files that reference + * the given list of accounts. + */ +final class AccountSourceContentChildNodeFactory extends ChildFactory { + + private static final Logger logger = Logger.getLogger(AccountSourceContentChildNodeFactory.class.getName()); + + private final Set accounts; + + AccountSourceContentChildNodeFactory(Set accounts) { + this.accounts = accounts; + } + + @Override + protected boolean createKeys(List list) { + if (accounts == null || accounts.isEmpty()) { + return true; + } + + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + accounts.forEach((account) -> { + try { + List accountFileInstanceList = communicationManager.getAccountFileInstances(account); + + for (AccountFileInstance fileInstance : accountFileInstanceList) { + list.add(fileInstance.getFile()); + } + + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Failed to getAccountFileInstances for account: %d", account.getAccountID()), ex); //NON-NLS + } + }); + + return true; + } + + @Override + protected Node createNodeForKey(Content content) { + return new ContentNode(content); + } + + /** + * Simple AbstractNode for a Content (file) object. + */ + final class ContentNode extends AbstractNode { + + private final Content content; + + ContentNode(Content content) { + super(Children.LEAF); + this.content = content; + + try { + setDisplayName(content.getUniquePath()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to getUniquePath for Content: %d", content.getId()), ex); //NON-NLS + setDisplayName(content.getName()); + } + + setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java new file mode 100755 index 0000000000..7dbe2133e0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/AttachmentsChildren.java @@ -0,0 +1,127 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Factory for creating thumbnail children nodes. + */ +final class AttachmentsChildren extends Children.Keys { + + private static final Logger logger = Logger.getLogger(AttachmentsChildren.class.getName()); + + private final Set artifacts; + + /* + * Creates the list of thumbnails from the given list of + * BlackboardArtifacts. + * + * The thumbnails will be initialls sorted by size, then name so that they + * appear sorted by size by default. + */ + AttachmentsChildren(Set artifacts) { + super(false); + + this.artifacts = artifacts; + + + } + + @Override + protected Node[] createNodes(AbstractFile t) { + return new Node[]{new AttachementNode(t)}; + } + + @Override + protected void addNotify() { + super.addNotify(); + + Set thumbnails = new TreeSet<>((AbstractFile file1, AbstractFile file2) -> { + int result = Long.compare(file1.getSize(), file2.getSize()); + if (result == 0) { + result = file1.getName().compareTo(file2.getName()); + } + + return result; + }); + + artifacts.forEach((bba) -> { + try { + for (Content childContent : bba.getChildren()) { + if (childContent instanceof AbstractFile) { + thumbnails.add((AbstractFile) childContent); + } + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get children from artifact.", ex); //NON-NLS + } + }); + + setKeys(thumbnails); + } + + /** + * A node for representing a thumbnail. + */ + static class AttachementNode extends FileNode { + + AttachementNode(AbstractFile file) { + super(file, false); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Set keepProps = new HashSet<>(Arrays.asList( + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.nameColLbl"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.score.name"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.comment.name"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.createSheet.count.name"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.sizeColLbl"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.mimeType"), + NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.knownColLbl"))); + + //Remove all other props except for the ones above + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + for (Node.Property p : sheetSet.getProperties()) { + if (!keepProps.contains(p.getName())) { + sheetSet.remove(p.getName()); + } + } + + return sheet; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties new file mode 100755 index 0000000000..6ef03951af --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties @@ -0,0 +1,15 @@ +ContactDetailsPane.nameLabel.text=Placeholder +SummaryViewer.countsPanel.border.title=Counts +SummaryViewer.emailLabel.text=Emails: +SummaryViewer.contactsLabel.text=Contacts: +SummaryViewer.attachmentsLabel.text=Attachments: +SummaryViewer.fileReferencesPanel.border.title=File References in Current Case +SummaryViewer.caseReferencesPanel.border.title=Other Occurrences +OutlineViewPanel.messageLabel.text= +SummaryViewer.messagesDataLabel.text=messages +SummaryViewer.callLogsDataLabel.text=callLogs +SummaryViewer.contactsDataLabel.text=contacts +SummaryViewer.emailDataLabel.text=emails +SummaryViewer.attachmentsDataLabel.text=attachments +SummaryViewer.messagesLabel.text=Messages: +SummaryViewer.callLogsLabel.text=Call Logs: diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED new file mode 100755 index 0000000000..6082d83375 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -0,0 +1,44 @@ +ContactDetailsPane.nameLabel.text=Placeholder +ContactNode_Email=Email Address +ContactNode_Home_Number=Home Number +ContactNode_Mobile_Number=Mobile Number +ContactNode_Name=Name +ContactNode_Office_Number=Office Number +ContactNode_Phone=Phone Number +ContactNode_URL=URL +ContactsViewer_columnHeader_Email=Email +ContactsViewer_columnHeader_Name=Name +ContactsViewer_columnHeader_Phone=Phone +ContactsViewer_tabTitle=Contacts +MediaViewer_Name=Media +MessageNode_Node_Property_Attms=Attachments +MessageNode_Node_Property_Date=Date +MessageNode_Node_Property_From=From +MessageNode_Node_Property_Subject=Subject +MessageNode_Node_Property_To=To +MessageNode_Node_Property_Type=Type +MessageViewer_columnHeader_Attms=Attachments +MessageViewer_columnHeader_Date=Date +MessageViewer_columnHeader_From=From +MessageViewer_columnHeader_Subject=Subject +MessageViewer_columnHeader_To=To +MessageViewer_tabTitle=Messages +SummaryViewer.countsPanel.border.title=Counts +SummaryViewer.emailLabel.text=Emails: +SummaryViewer.contactsLabel.text=Contacts: +SummaryViewer.attachmentsLabel.text=Attachments: +SummaryViewer.fileReferencesPanel.border.title=File References in Current Case +SummaryViewer.caseReferencesPanel.border.title=Other Occurrences +OutlineViewPanel.messageLabel.text= +SummaryViewer.messagesDataLabel.text=messages +SummaryViewer.callLogsDataLabel.text=callLogs +SummaryViewer.contactsDataLabel.text=contacts +SummaryViewer.emailDataLabel.text=emails +SummaryViewer.attachmentsDataLabel.text=attachments +SummaryViewer.messagesLabel.text=Messages: +SummaryViewer.callLogsLabel.text=Call Logs: +SummaryViewer_CaseRefNameColumn_Title=Case Name +SummaryViewer_CentralRepository_Message= +SummaryViewer_Creation_Date_Title=Creation Date +SummaryViewer_FileRefNameColumn_Title=Path +SummaryViewer_TabTitle=Summary diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form new file mode 100755 index 0000000000..2ae2165358 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.form @@ -0,0 +1,65 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java new file mode 100755 index 0000000000..82d4668e43 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactDetailsPane.java @@ -0,0 +1,116 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; + +/** + * Displays the propertied of a ContactNode in a PropertySheet. + */ +public final class ContactDetailsPane extends javax.swing.JPanel implements ExplorerManager.Provider { + + final private ExplorerManager explorerManager = new ExplorerManager(); + + /** + * Displays the propertied of a ContactNode in a PropertySheet. + */ + public ContactDetailsPane() { + initComponents(); + this.setEnabled(false); + + nameLabel.setText(""); + } + + /** + * Sets the list of nodes for the property sheet. + * + * @param nodes List of nodes to set + */ + public void setNode(Node[] nodes) { + if (nodes != null && nodes.length == 1) { + nameLabel.setText(nodes[0].getDisplayName()); + propertySheet.setNodes(nodes); + } else { + nameLabel.setText(""); + propertySheet.setNodes(null); + } + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + nameLabel.setEnabled(enabled); + propertySheet.setEnabled(enabled); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + messageContentViewer1 = new org.sleuthkit.autopsy.contentviewers.MessageContentViewer(); + nameLabel = new javax.swing.JLabel(); + propertySheet = new org.openide.explorer.propertysheet.PropertySheet(); + + nameLabel.setFont(new java.awt.Font("Tahoma", 0, 24)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(ContactDetailsPane.class, "ContactDetailsPane.nameLabel.text")); // NOI18N + + propertySheet.setDescriptionAreaVisible(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(nameLabel) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(nameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(propertySheet, javax.swing.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.contentviewers.MessageContentViewer messageContentViewer1; + private javax.swing.JLabel nameLabel; + private org.openide.explorer.propertysheet.PropertySheet propertySheet; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java new file mode 100755 index 0000000000..9f275ec228 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactNode.java @@ -0,0 +1,161 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; +import java.util.logging.Level; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; +import org.sleuthkit.datamodel.BlackboardAttribute; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME; +import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON; +import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME; +import org.sleuthkit.datamodel.TimeUtilities; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.communications.Utils; + +/** + * Extends BlackboardArtifactNode to override createSheet to create a contact + * artifact specific sheet. + */ +final class ContactNode extends BlackboardArtifactNode { + + private static final Logger logger = Logger.getLogger(ContactNode.class.getName()); + + @Messages({ + "ContactNode_Name=Name", + "ContactNode_Phone=Phone Number", + "ContactNode_Email=Email Address", + "ContactNode_Mobile_Number=Mobile Number", + "ContactNode_Office_Number=Office Number", + "ContactNode_URL=URL", + "ContactNode_Home_Number=Home Number",}) + + ContactNode(BlackboardArtifact artifact) { + super(artifact); + + String name = getAttributeDisplayString(artifact, TSK_NAME); + if (name == null || name.trim().isEmpty()) { + // VCards use TSK_NAME_PERSON instead of TSK_NAME + name = getAttributeDisplayString(artifact, TSK_NAME_PERSON); + } + setDisplayName(name); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + + final BlackboardArtifact artifact = getArtifact(); + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if (fromID != TSK_CONTACT) { + return sheet; + } + + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + // Sorting the attributes by type so that the duplicates can be removed + // and they can be grouped by type for display. The attribute prefixes + // are used so that all attributed of that type are found, including + // ones that are not predefined as part of BlackboardAttributes + try { + HashMap phoneNumMap = new HashMap<>(); + HashMap emailMap = new HashMap<>(); + HashMap nameMap = new HashMap<>(); + HashMap otherMap = new HashMap<>(); + for (BlackboardAttribute bba : artifact.getAttributes()) { + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + phoneNumMap.put(bba.getDisplayString(), bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) { + emailMap.put(bba.getDisplayString(), bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { + nameMap.put(bba.getDisplayString(), bba); + } else { + otherMap.put(bba.getDisplayString(), bba); + } + } + + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getLabel(), + sheetSet, nameMap); + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getLabel(), + sheetSet, phoneNumMap); + addPropertiesToSheet(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getLabel(), + sheetSet, emailMap); + + for (BlackboardAttribute bba : otherMap.values()) { + sheetSet.put(new NodeProperty<>(bba.getAttributeType().getTypeName(), bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } + + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting attribute values.", ex); //NON-NLS + } + + return sheet; + } + + private void addPropertiesToSheet(String propertyID, Sheet.Set sheetSet, Map attributeMap) { + int count = 0; + for (BlackboardAttribute bba : attributeMap.values()) { + if (count++ > 0) { + sheetSet.put(new NodeProperty<>(propertyID + "_" + count, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } else { + sheetSet.put(new NodeProperty<>(propertyID, bba.getAttributeType().getDisplayName(), "", bba.getDisplayString())); + } + } + } + + private static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + try { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); + if (attribute == null) { + return ""; + } else if (attributeType.getValueType() == DATETIME) { + return TimeUtilities.epochToTime(attribute.getValueLong(), + TimeZone.getTimeZone(Utils.getUserPreferredZoneId())); + } else { + return attribute.getDisplayString(); + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS + return ""; + } + } + + /** + * Circumvent DataResultFilterNode's slightly odd delegation to + * BlackboardArtifactNode.getSourceName(). + * + * @return the displayName of this Node, which is the type. + */ + @Override + public String getSourceName() { + return getDisplayName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java new file mode 100755 index 0000000000..681a572b96 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsChildNodeFactory.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory for ContactNodes. + */ +final class ContactsChildNodeFactory extends ChildFactory{ + private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + /** + * Construct a new ContactsChildNodeFactory from the currently selectionInfo + * + * @param selectionInfo SelectionInfo object for the currently selected + * accounts + */ + ContactsChildNodeFactory(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + } + + /** + * Updates the current instance of selectionInfo and calls the refresh method. + * + * @param selectionInfo New instance of the currently selected accounts + */ + public void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + /** + * Creates a list of Keys (BlackboardArtifact) for only contacts of the + * currently selected accounts + * @param list List of BlackboardArtifact to populate + * @return True on success + */ + @Override + protected boolean createKeys(List list) { + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if (fromID == TSK_CONTACT) { + list.add(bba); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new ContactNode(key); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form new file mode 100755 index 0000000000..82fb67dad9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.form @@ -0,0 +1,42 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java new file mode 100755 index 0000000000..68c48d8166 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/ContactsViewer.java @@ -0,0 +1,186 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.datamodel.BlackboardAttribute; + +/** + * Visualization for contact nodes. + * + */ +@ServiceProvider(service = RelationshipsViewer.class) +public final class ContactsViewer extends JPanel implements RelationshipsViewer, ExplorerManager.Provider, Lookup.Provider { + + private final ExplorerManager tableEM; + private final Outline outline; + private final ModifiableProxyLookup proxyLookup; + private final PropertyChangeListener focusPropertyListener; + private final ContactsChildNodeFactory nodeFactory; + + @NbBundle.Messages({ + "ContactsViewer_tabTitle=Contacts", + "ContactsViewer_columnHeader_Name=Name", + "ContactsViewer_columnHeader_Phone=Phone", + "ContactsViewer_columnHeader_Email=Email",}) + + /** + * Visualization for contact nodes. + */ + public ContactsViewer() { + tableEM = new ExplorerManager(); + proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap())); + nodeFactory = new ContactsChildNodeFactory(null); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, contactPane)) { + //if the focus owner is within the MessageContentViewer (the attachments table) + proxyLookup.setNewLookups(createLookup(contactPane.getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, ContactsViewer.this)) { + //... or if it is within the Results table. + proxyLookup.setNewLookups(createLookup(tableEM, getActionMap())); + + } + } + }; + + initComponents(); + + outline = outlineView.getOutline(); + outlineView.setPropertyColumns( + "TSK_EMAIL", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getDisplayName(), + "TSK_PHONE_NUMBER", BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getDisplayName() + ); + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.ContactsViewer_columnHeader_Name()); + + tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] nodes = tableEM.getSelectedNodes(); + contactPane.setNode(nodes); + } + }); + + tableEM.setRootContext(new TableFilterNode(new DataResultFilterNode(new AbstractNode(Children.create(nodeFactory, true)), getExplorerManager()), true)); + } + + @Override + public String getDisplayName() { + return Bundle.ContactsViewer_tabTitle(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + contactPane.setNode(new Node[]{new AbstractNode(Children.LEAF)}); + contactPane.setEnabled(false); + + nodeFactory.refresh(info); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEM; + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new org.openide.explorer.view.OutlineView(); + contactPane = new org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane(); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE) + .addGap(1, 1, 1) + .addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane contactPane; + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java new file mode 100755 index 0000000000..564c8e4f54 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java @@ -0,0 +1,169 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.Account; + +/** + * ChildFactory for CorrelationCases. Finds the cases that reference the given + * list of accounts. + */ +final class CorrelationCaseChildNodeFactory extends ChildFactory { + + private static final Logger logger = Logger.getLogger(CorrelationCaseChildNodeFactory.class.getName()); + + private Map correlationTypeMap; + private final Set accounts; + + /** + * ChildFactory for CorrelationCases. + * + * @param accounts List of Account objects + */ + CorrelationCaseChildNodeFactory(Set accounts) { + this.accounts = accounts; + } + + @Override + protected boolean createKeys(List list) { + if (!EamDb.isEnabled()) { + return true; + } + + EamDb dbInstance; + try { + dbInstance = EamDb.getInstance(); + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Unable to connect to the Central Repository database.", ex); //NON-NLS + return false; + } + + Map uniqueCaseMap = new HashMap<>(); + + accounts.forEach((account) -> { + try { + CorrelationAttributeInstance.Type correlationType = getCorrelationType(account.getAccountType()); + if (correlationType != null) { + List correlationInstances = dbInstance.getArtifactInstancesByTypeValue(correlationType, account.getTypeSpecificID()); + correlationInstances.forEach((correlationInstance) -> { + CorrelationCase correlationCase = correlationInstance.getCorrelationCase(); + uniqueCaseMap.put(correlationCase.getCaseUUID(), correlationCase); + }); + } + } catch (EamDbException | CorrelationAttributeNormalizationException ex) { + logger.log(Level.WARNING, String.format("Unable to getArtifactInstance for accountID: %d", account.getAccountID()), ex); //NON-NLS + } + }); + + list.addAll(uniqueCaseMap.values()); + + return true; + } + + @Override + protected Node createNodeForKey(CorrelationCase correlationCase) { + return new CaseNode(correlationCase); + } + + /** + * Find the CorrelationAttributeInstance.Type for the given Account.Type. + * + * @param accountType Account type + * + * @return CorrelationAttributeInstance.Type for given account or null if + * there is no match + * + * @throws EamDbException + */ + private CorrelationAttributeInstance.Type getCorrelationType(Account.Type accountType) throws EamDbException { + if (correlationTypeMap == null) { + correlationTypeMap = new HashMap<>(); + List correcationTypeList = CorrelationAttributeInstance.getDefaultCorrelationTypes(); + correcationTypeList.forEach((type) -> { + correlationTypeMap.put(type.getId(), type); + }); + } + + if (Account.Type.EMAIL.equals(accountType)) { + return correlationTypeMap.get(CorrelationAttributeInstance.EMAIL_TYPE_ID); + } else if (Account.Type.PHONE.equals(accountType)) { + return correlationTypeMap.get(CorrelationAttributeInstance.PHONE_TYPE_ID); + } else { + return null; + } + } + + /** + * Simple AbstractNode for a CorrelationCase. The property sheet only + * contains the creation date. + */ + final class CaseNode extends AbstractNode { + + private final CorrelationCase correlationCase; + + /** + * Construct the object, set the display name and icon. + * + * @param correlationCase + */ + CaseNode(CorrelationCase correlationCase) { + super(Children.LEAF); + this.correlationCase = correlationCase; + + setDisplayName(correlationCase.getDisplayName()); + setIconBaseWithExtension("org/sleuthkit/autopsy/images/briefcase.png"); //NON-NLS + } + + @Override + protected Sheet createSheet() { + super.createSheet(); + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>("creationDate", //NON-NLS + correlationCase.getTitleCreationDate(), + correlationCase.getTitleCreationDate(), + correlationCase.getCreationDate())); + + return sheet; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form new file mode 100755 index 0000000000..8f311d3dd4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.form @@ -0,0 +1,68 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java new file mode 100755 index 0000000000..95405b0887 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MediaViewer.java @@ -0,0 +1,233 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.JPanel; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.datamodel.AbstractContent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A Panel that shows the media (thumbnails) for the selected account. + */ +final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerManager.Provider, Lookup.Provider { + + private static final Logger logger = Logger.getLogger(MediaViewer.class.getName()); + + private final ExplorerManager tableEM = new ExplorerManager(); + private final PropertyChangeListener focusPropertyListener; + + private final ModifiableProxyLookup proxyLookup; + + @Messages({ + "MediaViewer_Name=Media" + }) + /** + * Creates new form ThumbnailViewer + */ + public MediaViewer() { + proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap())); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, contentViewer)) { + //if the focus owner is within the MessageContentViewer (the attachments table) + proxyLookup.setNewLookups(createLookup(((MessageDataContent) contentViewer).getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, MediaViewer.this)) { + //... or if it is within the Results table. + proxyLookup.setNewLookups(createLookup(tableEM, getActionMap())); + + } + } + }; + + initComponents(); + + tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + handleNodeSelectionChange(); + } + }); + + thumbnailViewer.resetComponent(); + } + + @Override + public String getDisplayName() { + return Bundle.MediaViewer_Name(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + final Set relationshipSources; + + CommunicationsManager communicationManager; + Set artifactList = new HashSet<>(); + + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + relationshipSources = communicationManager.getRelationshipSources(info.getAccountDevicesInstances(), info.getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + artifactList.add((BlackboardArtifact) content); + }); + + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Unable to update selection." , ex); + } + + if(artifactList.size() == 0) { + thumbnailViewer.resetComponent(); + } + + thumbnailViewer.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode(new AttachmentsChildren(artifactList)), tableEM), true, this.getClass().getName())); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEM; + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); + } + + /** + * Handle the change in thumbnail node selection. + */ + private void handleNodeSelectionChange() { + final Node[] nodes = tableEM.getSelectedNodes(); + + if (nodes != null && nodes.length == 1) { + AbstractContent thumbnail = nodes[0].getLookup().lookup(AbstractContent.class); + if (thumbnail != null) { + try { + Content parentContent = thumbnail.getParent(); + if (parentContent != null && parentContent instanceof BlackboardArtifact) { + contentViewer.setNode(new BlackboardArtifactNode((BlackboardArtifact) parentContent)); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get parent Content from AbstraceContent instance.", ex); //NON-NLS + } + } + } else { + contentViewer.setNode(null); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + thumbnailViewer = new org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail(tableEM); + contentViewer = new MessageDataContent(); + separator = new javax.swing.JSeparator(); + + thumbnailViewer.setMinimumSize(new java.awt.Dimension(350, 102)); + thumbnailViewer.setPreferredSize(new java.awt.Dimension(450, 400)); + + contentViewer.setPreferredSize(new java.awt.Dimension(450, 400)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(thumbnailViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contentViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(separator) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(thumbnailViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(contentViewer, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(3, 3, 3)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer; + private javax.swing.JSeparator separator; + private org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail thumbnailViewer; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageDataContent.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java rename to Core/src/org/sleuthkit/autopsy/communications/relationships/MessageDataContent.java index 33df9bd3d9..108ce7dc20 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageDataContent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageDataContent.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.communications; +package org.sleuthkit.autopsy.communications.relationships; import java.beans.PropertyChangeEvent; import org.openide.explorer.ExplorerManager; diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java old mode 100644 new mode 100755 similarity index 67% rename from Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java rename to Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index 3fc7e36e4e..e6ac3f507f --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-2018 Basis Technology Corp. + * Copyright 2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.communications; +package org.sleuthkit.autopsy.communications.relationships; import java.util.List; import java.util.TimeZone; @@ -25,12 +25,12 @@ import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.core.UserPreferences; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START; @@ -43,24 +43,35 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBU import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TimeUtilities; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.communications.Utils; /** - * Node for a relationship, as represented by a BlackboardArtifact. + * Wraps a BlackboardArtifact as an AbstractNode for use in an OutlookView */ -final class RelationshipNode extends BlackboardArtifactNode { +final class MessageNode extends BlackboardArtifactNode { - private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); + private static final Logger logger = Logger.getLogger(MessageNode.class.getName()); - RelationshipNode(BlackboardArtifact artifact) { + MessageNode(BlackboardArtifact artifact) { super(artifact); - final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); - String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); + + final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); // NON-NLS + String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); // NON-NLS setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase); } + @Messages({ + "MessageNode_Node_Property_Type=Type", + "MessageNode_Node_Property_From=From", + "MessageNode_Node_Property_To=To", + "MessageNode_Node_Property_Date=Date", + "MessageNode_Node_Property_Subject=Subject", + "MessageNode_Node_Property_Attms=Attachments" + }) + @Override protected Sheet createSheet() { - Sheet sheet = new Sheet(); + Sheet sheet = super.createSheet(); List tags = getAllTagsFromDatabase(); Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { @@ -68,62 +79,63 @@ final class RelationshipNode extends BlackboardArtifactNode { sheet.put(sheetSet); } - sheetSet.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName())); - + sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS + addScoreProperty(sheetSet, tags); - + CorrelationAttributeInstance correlationAttribute = null; - if (UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) { + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { correlationAttribute = getCorrelationAttributeInstance(); } addCommentProperty(sheetSet, tags, correlationAttribute); - - if (UserPreferences.hideCentralRepoCommentsAndOccurrences()== false) { + + if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) { addCountProperty(sheetSet, correlationAttribute); } final BlackboardArtifact artifact = getArtifact(); - BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID()); + + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (null != fromID) { //Consider refactoring this to reduce boilerplate switch (fromID) { case TSK_EMAIL_MSG: - sheetSet.put(new NodeProperty<>("From", "From", "From", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); - sheetSet.put(new NodeProperty<>("To", "To", "To", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); - sheetSet.put(new NodeProperty<>("Date", "Date", "Date", - getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); - sheetSet.put(new NodeProperty<>("Subject", "Subject", "Subject", - getAttributeDisplayString(artifact, TSK_SUBJECT))); + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS try { - sheetSet.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } break; case TSK_MESSAGE: - sheetSet.put(new NodeProperty<>("From", "From", "From", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); - sheetSet.put(new NodeProperty<>("To", "To", "To", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); - sheetSet.put(new NodeProperty<>("Date", "Date", "Date", - getAttributeDisplayString(artifact, TSK_DATETIME))); - sheetSet.put(new NodeProperty<>("Subject", "Subject", "Subject", - getAttributeDisplayString(artifact, TSK_SUBJECT))); + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS + sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS try { - sheetSet.put(new NodeProperty<>("Attms", "Attms", "Attms", artifact.getChildrenCount())); + sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", artifact.getChildrenCount())); //NON-NLS } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); + logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } break; case TSK_CALLLOG: - sheetSet.put(new NodeProperty<>("From", "From", "From", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); - sheetSet.put(new NodeProperty<>("To", "To", "To", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); - sheetSet.put(new NodeProperty<>("Date", "Date", "Date", - getAttributeDisplayString(artifact, TSK_DATETIME_START))); + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + getAttributeDisplayString(artifact, TSK_DATETIME_START))); //NON-NLS break; default: break; @@ -144,7 +156,7 @@ final class RelationshipNode extends BlackboardArtifactNode { * @return The display string, or an empty string if there is no such * attribute or an an error. */ - private static String getAttributeDisplayString(final BlackboardArtifact artifact, final ATTRIBUTE_TYPE attributeType) { + private static String getAttributeDisplayString(final BlackboardArtifact artifact, final BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { try { BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attributeType.getTypeID()))); if (attribute == null) { @@ -156,7 +168,7 @@ final class RelationshipNode extends BlackboardArtifactNode { return attribute.getDisplayString(); } } catch (TskCoreException tskCoreException) { - logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); + logger.log(Level.WARNING, "Error getting attribute value.", tskCoreException); //NON-NLS return ""; } } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java new file mode 100755 index 0000000000..bb986c3920 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesChildNodeFactory.java @@ -0,0 +1,115 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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.communications.relationships; + +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * ChildFactory that creates createKeys and nodes from a given selectionInfo for + * only emails, call logs and messages. + * + */ +final class MessagesChildNodeFactory extends ChildFactory { + + private static final Logger logger = Logger.getLogger(MessagesChildNodeFactory.class.getName()); + + private SelectionInfo selectionInfo; + + /** + * Construct a new MessageChildNodeFactory from the currently selectionInfo + * + * @param selectionInfo SelectionInfo object for the currently selected + * accounts + */ + MessagesChildNodeFactory(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + } + + /** + * Updates the current instance of selectionInfo and calls the refresh method. + * + * @param selectionInfo New instance of the currently selected accounts + */ + public void refresh(SelectionInfo selectionInfo) { + this.selectionInfo = selectionInfo; + refresh(true); + } + + /** + * Creates a list of Keys (BlackboardArtifact) for only messages for the + * currently selected accounts + * + * @param list List of BlackboardArtifact to populate + * + * @return True on success + */ + @Override + protected boolean createKeys(List list) { + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return false; + } + + if(selectionInfo == null) { + return true; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(selectionInfo.getAccountDevicesInstances(), selectionInfo.getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + + BlackboardArtifact bba = (BlackboardArtifact) content; + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(bba.getArtifactTypeID()); + + if (fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG + || fromID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE) { + list.add(bba); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + + return true; + } + + @Override + protected Node createNodeForKey(BlackboardArtifact key) { + return new MessageNode(key); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form new file mode 100755 index 0000000000..76cf240254 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.form @@ -0,0 +1,42 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java new file mode 100755 index 0000000000..fa7bbab6bb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessagesViewer.java @@ -0,0 +1,194 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.awt.Component; +import java.awt.KeyboardFocusManager; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import javax.swing.JPanel; +import javax.swing.ListSelectionModel; +import static javax.swing.SwingUtilities.isDescendingFrom; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; + +/** + * Visualation for the messages of the currently selected accounts. + */ +@ServiceProvider(service = RelationshipsViewer.class) +public final class MessagesViewer extends JPanel implements RelationshipsViewer, ExplorerManager.Provider, Lookup.Provider { + + private final ExplorerManager tableEM; + private final Outline outline; + private final ModifiableProxyLookup proxyLookup; + private final PropertyChangeListener focusPropertyListener; + private final MessagesChildNodeFactory nodeFactory; + + @Messages({ + "MessageViewer_tabTitle=Messages", + "MessageViewer_columnHeader_From=From", + "MessageViewer_columnHeader_To=To", + "MessageViewer_columnHeader_Date=Date", + "MessageViewer_columnHeader_Subject=Subject", + "MessageViewer_columnHeader_Attms=Attachments" + }) + + /** + * Visualation for the messages of the currently selected accounts. + */ + public MessagesViewer() { + tableEM = new ExplorerManager(); + proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap())); + nodeFactory = new MessagesChildNodeFactory(null); + + // See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed + // explaination of focusPropertyListener + focusPropertyListener = (final PropertyChangeEvent focusEvent) -> { + if (focusEvent.getPropertyName().equalsIgnoreCase("focusOwner")) { + final Component newFocusOwner = (Component) focusEvent.getNewValue(); + + if (newFocusOwner == null) { + return; + } + if (isDescendingFrom(newFocusOwner, contentViewer)) { + //if the focus owner is within the MessageContentViewer (the attachments table) + proxyLookup.setNewLookups(createLookup(((MessageDataContent) contentViewer).getExplorerManager(), getActionMap())); + } else if (isDescendingFrom(newFocusOwner, MessagesViewer.this)) { + //... or if it is within the Results table. + proxyLookup.setNewLookups(createLookup(tableEM, getActionMap())); + + } + } + }; + + initComponents(); + + outline = outlineView.getOutline(); + outlineView.setPropertyColumns( + "From", Bundle.MessageViewer_columnHeader_From(), + "To", Bundle.MessageViewer_columnHeader_To(), + "Date", Bundle.MessageViewer_columnHeader_Date(), + "Subject", Bundle.MessageViewer_columnHeader_Subject(), + "Attms", Bundle.MessageViewer_columnHeader_Attms(), + "Type", "Type" + ); + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Type"); + + tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { + final Node[] nodes = tableEM.getSelectedNodes(); + + if (nodes != null && nodes.length == 1) { + contentViewer.setNode(nodes[0]); + } + else { + contentViewer.setNode(null); + } + } + }); + + tableEM.setRootContext(new TableFilterNode(new DataResultFilterNode(new AbstractNode(Children.create(nodeFactory, true)), getExplorerManager()), true)); + } + + @Override + public String getDisplayName() { + return Bundle.MessageViewer_tabTitle(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + nodeFactory.refresh(info); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEM; + } + + @Override + public Lookup getLookup() { + return proxyLookup; + } + + @Override + public void addNotify() { + super.addNotify(); + //add listener that maintains correct selection in the Global Actions Context + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .addPropertyChangeListener("focusOwner", focusPropertyListener); + } + + @Override + public void removeNotify() { + super.removeNotify(); + KeyboardFocusManager.getCurrentKeyboardFocusManager() + .removePropertyChangeListener("focusOwner", focusPropertyListener); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new org.openide.explorer.view.OutlineView(); + contentViewer = new MessageDataContent(); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(contentViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer; + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form new file mode 100755 index 0000000000..5efb16c2b1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.form @@ -0,0 +1,60 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java new file mode 100755 index 0000000000..c84c7ae0a7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/OutlineViewPanel.java @@ -0,0 +1,132 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.awt.CardLayout; +import org.openide.explorer.ExplorerManager; +import static org.openide.explorer.ExplorerUtils.createLookup; +import org.openide.explorer.view.OutlineView; +import org.openide.nodes.Node;; +import org.openide.util.Lookup; + +/** + * This class is a simple wrapper around a OutlineView with its own ExplorerManager. + * + * This panel has the added feature of being able to hide the OutlineView and show + * a message. + * + */ +public class OutlineViewPanel extends javax.swing.JPanel implements ExplorerManager.Provider, Lookup.Provider{ + + private final ExplorerManager tableEm; + private final Lookup lookup; + /** + * Creates new form OutlineViewPanel + */ + public OutlineViewPanel() { + tableEm = new ExplorerManager(); + lookup = createLookup(tableEm, getActionMap()); + + initComponents(); + } + + @Override + public ExplorerManager getExplorerManager() { + return tableEm; + } + + @Override + public Lookup getLookup() { + return lookup; + } + + /** + * Hide the OutlineView and replace with a panel with the given message. + * + * @param message String message to show on the panel. + */ + public void hideOutlineView(String message) { + CardLayout layout = (CardLayout)this.getLayout(); + layout.show(this, "messageCard"); //NON-NLS + messageLabel.setText(message); + } + + /** + * Hides the message panel and shows the OutlineView. + */ + public void showOutlineView() { + CardLayout layout = (CardLayout)this.getLayout(); + layout.show(this, "outlineCard"); //NON-NLS + } + + /** + * Returns the OutlineView instance for ease of customization. + * + * @return Returns the OutlineView + */ + public OutlineView getOutlineView() { + return outlineView; + } + + public void setNode(Node node) { + tableEm.setRootContext(node); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + outlineView.setEnabled(enabled); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new org.openide.explorer.view.OutlineView(); + messagePanel = new javax.swing.JPanel(); + messageLabel = new javax.swing.JLabel(); + + setLayout(new java.awt.CardLayout(5, 5)); + + outlineView.setPreferredSize(new java.awt.Dimension(300, 400)); + add(outlineView, "outlineCard"); + + messagePanel.setLayout(new java.awt.BorderLayout()); + + messageLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + org.openide.awt.Mnemonics.setLocalizedText(messageLabel, org.openide.util.NbBundle.getMessage(OutlineViewPanel.class, "OutlineViewPanel.messageLabel.text")); // NOI18N + messageLabel.setEnabled(false); + messagePanel.add(messageLabel, java.awt.BorderLayout.CENTER); + + add(messagePanel, "messageCard"); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel messageLabel; + private javax.swing.JPanel messagePanel; + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form old mode 100644 new mode 100755 similarity index 52% rename from Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form rename to Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form index 58e19523f1..ac40867a25 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.form @@ -5,7 +5,7 @@ - + @@ -16,40 +16,36 @@ - - - + + + - - - - + + + - + - - - - + - + - - - - - - - + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java new file mode 100755 index 0000000000..03729540df --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipBrowser.java @@ -0,0 +1,132 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.awt.Component; +import javax.swing.JPanel; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; + +/** + * Displays the Relationship information for the currently selected accounts. + * + */ +public final class RelationshipBrowser extends JPanel implements Lookup.Provider { + + private SelectionInfo currentSelection; + + private final MessagesViewer messagesViewer; + private final ContactsViewer contactsViewer; + private final SummaryViewer summaryViewer; + private final MediaViewer mediaViewer; + + private final ModifiableProxyLookup proxyLookup; + + /** + * Creates new form RelationshipBrowser + */ + public RelationshipBrowser() { + messagesViewer = new MessagesViewer(); + contactsViewer = new ContactsViewer(); + summaryViewer = new SummaryViewer(); + mediaViewer = new MediaViewer(); + + proxyLookup = new ModifiableProxyLookup(messagesViewer.getLookup()); + + initComponents(); + + tabPane.add(summaryViewer.getDisplayName(), summaryViewer); + tabPane.add(messagesViewer.getDisplayName(), messagesViewer); + tabPane.add(contactsViewer.getDisplayName(), contactsViewer); + tabPane.add(mediaViewer.getDisplayName(), mediaViewer); + + + } + + /** + * Sets the value of currentSelection and passes the SelectionInfo onto the + * currently selected\visible tab. + * + * @param info Currently selected account nodes + */ + public void setSelectionInfo(SelectionInfo info) { + currentSelection = info; + ((RelationshipsViewer) tabPane.getSelectedComponent()).setSelectionInfo(info); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + scrollPane = new javax.swing.JScrollPane(); + tabPane = new javax.swing.JTabbedPane(); + + scrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + + tabPane.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + tabPaneStateChanged(evt); + } + }); + scrollPane.setViewportView(tabPane); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 400, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 300, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(scrollPane)) + ); + }// //GEN-END:initComponents + + private void tabPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabPaneStateChanged + if(currentSelection != null) { + ((RelationshipsViewer) tabPane.getSelectedComponent()).setSelectionInfo(currentSelection); + } + + Component selectedComponent = tabPane.getSelectedComponent(); + if(selectedComponent instanceof Lookup.Provider) { + Lookup lookup = ((Lookup.Provider)selectedComponent).getLookup(); + proxyLookup.setNewLookups(lookup); + } + }//GEN-LAST:event_tabPaneStateChanged + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane scrollPane; + private javax.swing.JTabbedPane tabPane; + // End of variables declaration//GEN-END:variables + + @Override + public Lookup getLookup() { + return proxyLookup; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsViewer.java new file mode 100755 index 0000000000..25f5c83701 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsViewer.java @@ -0,0 +1,50 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import javax.swing.JPanel; +import org.openide.util.Lookup; + +/** + * Interface for Controls wishing to appear in the RelationshipBrowser tabPane. + */ +public interface RelationshipsViewer extends Lookup.Provider { + + /** + * Returns the value to be displayed on the "tab" + * + * @return String display name + */ + public String getDisplayName(); + + /** + * Returns the JPanel to be displayed in the RelationshipBrowser. + * + * @return JPanel to be displayed + */ + public JPanel getPanel(); + + /** + * Sets current SelectionInfo allowing the panel to update accordingly. + * + * @param info SelectionInfo instance representing the currently selected + * accounts + */ + public void setSelectionInfo(SelectionInfo info); +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java new file mode 100755 index 0000000000..16c92b3b94 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java @@ -0,0 +1,185 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.AccountDeviceInstance; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsManager; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Class to wrap the details of the current selection from the AccountBrowser or + * VisualizationPane + */ +public final class SelectionInfo { + + private static final Logger logger = Logger.getLogger(SelectionInfo.class.getName()); + + private final Set accountDeviceInstances; + private final CommunicationsFilter communicationFilter; + private final Set accounts; + + private Set accountArtifacts = null; + private SelectionSummary summary = null; + + /** + * Wraps the details of the currently selected accounts. + * + * @param accountDeviceInstances Selected accountDecivedInstances + * @param communicationFilter Currently selected communications filters + */ + public SelectionInfo(Set accountDeviceInstances, CommunicationsFilter communicationFilter) { + this.accountDeviceInstances = accountDeviceInstances; + this.communicationFilter = communicationFilter; + + accounts = new HashSet<>(); + accountDeviceInstances.forEach((instance) -> { + accounts.add(instance.getAccount()); + }); + } + + /** + * Returns the currently selected accountDeviceInstances + * + * @return Set of AccountDeviceInstance + */ + public Set getAccountDevicesInstances() { + return accountDeviceInstances; + } + + /** + * Returns the currently selected communications filters. + * + * @return Instance of CommunicationsFilter + */ + public CommunicationsFilter getCommunicationsFilter() { + return communicationFilter; + } + + public Set getAccounts() { + return accounts; + } + + public Set getArtifacts() { + if(accountArtifacts == null) { + accountArtifacts = new HashSet<>(); + CommunicationsManager communicationManager; + try { + communicationManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get communications manager from case.", ex); //NON-NLS + return null; + } + + final Set relationshipSources; + + try { + relationshipSources = communicationManager.getRelationshipSources(getAccountDevicesInstances(), getCommunicationsFilter()); + + relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { + accountArtifacts.add((BlackboardArtifact) content); + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get relationship sources.", ex); //NON-NLS + } + } + + return accountArtifacts; + } + + public SelectionSummary getSummary() { + if(summary == null) { + summary = new SelectionSummary(); + } + + return summary; + } + + final class SelectionSummary{ + int attachmentCnt; + int messagesCnt; + int emailCnt; + int callLogCnt; + int contactsCnt; + + SelectionSummary() { + getCounts(); + } + + private void getCounts(){ + for(BlackboardArtifact artifact: getArtifacts()) { + BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + if(null != fromID) switch (fromID) { + case TSK_EMAIL_MSG: + emailCnt++; + break; + case TSK_CALLLOG: + callLogCnt++; + break; + case TSK_MESSAGE: + messagesCnt++; + break; + case TSK_CONTACT: + contactsCnt++; + break; + default: + break; + } + try{ + attachmentCnt+= artifact.getChildrenCount(); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Exception thrown " + + "from getChildrenCount artifactID: %d", + artifact.getArtifactID()), ex); //NON-NLS + } + } + } + + public int getAttachmentCnt() { + return attachmentCnt; + } + + public int getMessagesCnt() { + return messagesCnt; + } + + public int getEmailCnt() { + return emailCnt; + } + + public int getCallLogCnt() { + return callLogCnt; + } + + public int getContactsCnt() { + return contactsCnt; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form new file mode 100755 index 0000000000..af02a7dee7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form @@ -0,0 +1,215 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java new file mode 100755 index 0000000000..5641fad521 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java @@ -0,0 +1,313 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 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 obt ain 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.communications.relationships; + +import java.util.Set; +import javax.swing.JPanel; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.view.OutlineView; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.Lookup; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.communications.relationships.SelectionInfo.SelectionSummary; +import org.sleuthkit.datamodel.Account; + +/** + * Account Summary View Panel. This panel shows a list of various counts related + * to the currently selected account. As well has a panel showing a list of + * cases and files that reference the account. + * + */ +public class SummaryViewer extends javax.swing.JPanel implements RelationshipsViewer { + + private final Lookup lookup; + + @Messages({ + "SummaryViewer_TabTitle=Summary", + "SummaryViewer_FileRefNameColumn_Title=Path", + "SummaryViewer_CaseRefNameColumn_Title=Case Name", + "SummaryViewer_CentralRepository_Message=", + "SummaryViewer_Creation_Date_Title=Creation Date" + }) + + /** + * Creates new form SummaryViewer + */ + public SummaryViewer() { + lookup = Lookup.getDefault(); + initComponents(); + + OutlineView outlineView = fileReferencesPanel.getOutlineView(); + Outline outline = outlineView.getOutline(); + + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.SummaryViewer_FileRefNameColumn_Title()); + + outlineView = caseReferencesPanel.getOutlineView(); + outline = outlineView.getOutline(); + outlineView.setPropertyColumns("creationDate", Bundle.SummaryViewer_Creation_Date_Title()); //NON-NLS + + outline.setRootVisible(false); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.SummaryViewer_CaseRefNameColumn_Title()); + + clearControls(); + } + + @Override + public String getDisplayName() { + return Bundle.SummaryViewer_TabTitle(); + } + + @Override + public JPanel getPanel() { + return this; + } + + @Override + public void setSelectionInfo(SelectionInfo info) { + + if (!EamDb.isEnabled()) { + caseReferencesPanel.hideOutlineView(Bundle.SummaryViewer_CentralRepository_Message()); + } else { + caseReferencesPanel.showOutlineView(); + } + + // Request is that the SummaryViewer only show information if one + // account is selected + if (info.getAccounts().size() != 1) { + setEnabled(false); + clearControls(); + + } else { + SelectionSummary summaryDetails = info.getSummary(); + + attachmentsDataLabel.setText(Integer.toString(summaryDetails.getAttachmentCnt())); + callLogsDataLabel.setText(Integer.toString(summaryDetails.getCallLogCnt())); + contactsDataLabel.setText(Integer.toString(summaryDetails.getContactsCnt())); + emailDataLabel.setText(Integer.toString(summaryDetails.getEmailCnt())); + messagesDataLabel.setText(Integer.toString(summaryDetails.getMessagesCnt())); + + fileReferencesPanel.setNode(new AbstractNode(Children.create(new AccountSourceContentChildNodeFactory(info.getAccounts()), true))); + caseReferencesPanel.setNode(new AbstractNode(Children.create(new CorrelationCaseChildNodeFactory(info.getAccounts()), true))); + + setEnabled(true); + } + } + + @Override + public Lookup getLookup() { + return lookup; + } + + /** + * Sets whether or not the text fields are enabled. + * + * @param enabled true if this component should be enabled, false otherwise + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + attachmentsLabel.setEnabled(enabled); + callLogsLabel.setEnabled(enabled); + contactsLabel.setEnabled(enabled); + emailLabel.setEnabled(enabled); + messagesLabel.setEnabled(enabled); + caseReferencesPanel.setEnabled(enabled); + fileReferencesPanel.setEnabled(enabled); + countsPanel.setEnabled(enabled); + } + + /** + * Clears the text fields and OutlookViews. + */ + private void clearControls() { + attachmentsDataLabel.setText(""); + callLogsDataLabel.setText(""); + contactsDataLabel.setText(""); + emailDataLabel.setText(""); + messagesDataLabel.setText(""); + + fileReferencesPanel.setNode(new AbstractNode(Children.LEAF)); + caseReferencesPanel.setNode(new AbstractNode(Children.LEAF)); + } + + /** + * For the given accounts create a comma separated string of all of the + * names (TypeSpecificID). + * + * @param accounts Set of selected accounts + * + * @return String listing the account names + */ + private String createAccountLabel(Set accounts) { + StringBuilder buffer = new StringBuilder(); + accounts.stream().map((account) -> { + buffer.append(account.getTypeSpecificID()); + return account; + }).forEachOrdered((_item) -> { + buffer.append(", "); + }); + + return buffer.toString().substring(0, buffer.length() - 2); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + countsPanel = new javax.swing.JPanel(); + emailLabel = new javax.swing.JLabel(); + contactsLabel = new javax.swing.JLabel(); + messagesLabel = new javax.swing.JLabel(); + callLogsLabel = new javax.swing.JLabel(); + attachmentsLabel = new javax.swing.JLabel(); + attachmentsDataLabel = new javax.swing.JLabel(); + messagesDataLabel = new javax.swing.JLabel(); + callLogsDataLabel = new javax.swing.JLabel(); + contactsDataLabel = new javax.swing.JLabel(); + emailDataLabel = new javax.swing.JLabel(); + fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + + countsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.countsPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(emailLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.emailLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(contactsLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.contactsLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(messagesLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.messagesLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(callLogsLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.callLogsLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(attachmentsLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentsLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(attachmentsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentsDataLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(messagesDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.messagesDataLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(callLogsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.callLogsDataLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(contactsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.contactsDataLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(emailDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.emailDataLabel.text")); // NOI18N + + javax.swing.GroupLayout countsPanelLayout = new javax.swing.GroupLayout(countsPanel); + countsPanel.setLayout(countsPanelLayout); + countsPanelLayout.setHorizontalGroup( + countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(countsPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(attachmentsLabel) + .addComponent(messagesLabel) + .addComponent(callLogsLabel) + .addComponent(contactsLabel) + .addComponent(emailLabel)) + .addGap(18, 18, 18) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(emailDataLabel) + .addComponent(contactsDataLabel) + .addComponent(callLogsDataLabel) + .addComponent(messagesDataLabel) + .addComponent(attachmentsDataLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + countsPanelLayout.setVerticalGroup( + countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(countsPanelLayout.createSequentialGroup() + .addGap(7, 7, 7) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(attachmentsLabel) + .addComponent(attachmentsDataLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(messagesLabel) + .addComponent(messagesDataLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(callLogsLabel) + .addComponent(callLogsDataLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(contactsLabel) + .addComponent(contactsDataLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(emailLabel) + .addComponent(emailDataLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N + fileReferencesPanel.setPreferredSize(new java.awt.Dimension(472, 300)); + + caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N + caseReferencesPanel.setPreferredSize(new java.awt.Dimension(472, 300)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(countsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(fileReferencesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 485, Short.MAX_VALUE) + .addComponent(caseReferencesPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(countsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileReferencesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(caseReferencesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel attachmentsDataLabel; + private javax.swing.JLabel attachmentsLabel; + private javax.swing.JLabel callLogsDataLabel; + private javax.swing.JLabel callLogsLabel; + private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel caseReferencesPanel; + private javax.swing.JLabel contactsDataLabel; + private javax.swing.JLabel contactsLabel; + private javax.swing.JPanel countsPanel; + private javax.swing.JLabel emailDataLabel; + private javax.swing.JLabel emailLabel; + private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel fileReferencesPanel; + private javax.swing.JLabel messagesDataLabel; + private javax.swing.JLabel messagesLabel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 201f3f1f6f..b2d4baef99 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org *s * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import org.sleuthkit.datamodel.AbstractFile; class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName()); + private static final long serialVersionUID = 1L; private AbstractFile lastFile; //UI private MediaPlayerPanel mediaPlayerPanel; @@ -48,7 +49,7 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { /** * Creates a new MediaFileViewer. */ - public MediaFileViewer() { + MediaFileViewer() { initComponents(); @@ -69,8 +70,8 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { private void customizeComponents() { add(imagePanel, IMAGE_VIEWER_LAYER); - - if(mediaPlayerPanel != null) { + + if (mediaPlayerPanel != null) { add(mediaPlayerPanel, MEDIA_PLAYER_LAYER); } @@ -103,10 +104,10 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { List mimeTypes = new ArrayList<>(); mimeTypes.addAll(this.imagePanel.getSupportedMimeTypes()); - if(mediaPlayerPanel != null) { + if (mediaPlayerPanel != null) { mimeTypes.addAll(this.mediaPlayerPanel.getSupportedMimeTypes()); } - + return mimeTypes; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index 936716fc78..5c16dbed93 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -49,6 +49,10 @@ import javax.swing.JPanel; import org.controlsfx.control.MaskerPane; import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; +import javafx.scene.Group; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.datamodel.FileNode; @@ -70,18 +74,20 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private final boolean fxInited; private JFXPanel fxPanel; + private Group imageGroup; + private ImageTaggingTool tagger; private ImageView fxImageView; private ScrollPane scrollPane; private final ProgressBar progressBar = new ProgressBar(); private final MaskerPane maskerPane = new MaskerPane(); - + private double zoomRatio; private double rotation; // Can be 0, 90, 180, and 270. - + private static final double[] ZOOM_STEPS = { 0.0625, 0.125, 0.25, 0.375, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10}; - + private static final double MIN_ZOOM_RATIO = 0.0625; // 6.25% private static final double MAX_ZOOM_RATIO = 10.0; // 1000% @@ -115,11 +121,13 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // build jfx ui (we could do this in FXML?) fxImageView = new ImageView(); // will hold image - scrollPane = new ScrollPane(fxImageView); // scrolls and sizes imageview + imageGroup = new Group(); + imageGroup.getChildren().add(fxImageView); + scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview scrollPane.getStyleClass().add("bg"); //NOI18N scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED); scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED); - + fxPanel = new JFXPanel(); // bridge jfx-swing Scene scene = new Scene(scrollPane); //root of jfx tree scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N @@ -146,9 +154,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan Platform.runLater(() -> { fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0)); fxImageView.setImage(null); - + tagger.defaultSettings(); + scrollPane.setContent(null); - scrollPane.setContent(fxImageView); + scrollPane.setContent(imageGroup); }); } @@ -160,7 +169,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan * TODO: why is the name passed into the action constructor? it * means we duplicate this string all over the place -jm */ new ExternalViewerAction(Bundle.MediaViewImagePanel_externalViewerButton_text(), new FileNode(file)) - .actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent + .actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")) //Swing ActionEvent ); final VBox errorNode = new VBox(10, new Label(errorMessage), externalViewerButton); @@ -199,8 +208,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan if (nonNull(fxImage)) { // We have a non-null image, so let's show it. fxImageView.setImage(fxImage); + imageGroup.getChildren().remove(tagger); + tagger = new ImageTaggingTool(fxImageView, Color.RED); + imageGroup.getChildren().add(tagger); resetView(); - scrollPane.setContent(fxImageView); + scrollPane.setContent(imageGroup); } else { showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file); } @@ -410,7 +422,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed // Find the next zoom step. - for (int i=0; i < ZOOM_STEPS.length; i++) { + for (int i = 0; i < ZOOM_STEPS.length; i++) { if (zoomRatio < ZOOM_STEPS[i]) { zoomRatio = ZOOM_STEPS[i]; break; @@ -421,7 +433,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed // Find the next zoom step. - for (int i=ZOOM_STEPS.length-1; i >= 0; i--) { + for (int i = ZOOM_STEPS.length - 1; i >= 0; i--) { if (zoomRatio > ZOOM_STEPS[i]) { zoomRatio = ZOOM_STEPS[i]; break; @@ -450,12 +462,12 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan private javax.swing.JButton zoomResetButton; private javax.swing.JTextField zoomTextField; // End of variables declaration//GEN-END:variables - + /** * Reset the zoom and rotation values to their defaults. The zoom level gets * defaulted to the current size of the panel. The rotation will be set to * zero. - * + * * Note: This method will make a call to 'updateView()' after the values * have been reset. */ @@ -464,28 +476,28 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan if (image == null) { return; } - + double imageWidth = image.getWidth(); double imageHeight = image.getHeight(); double scrollPaneWidth = fxPanel.getWidth(); double scrollPaneHeight = fxPanel.getHeight(); double zoomRatioWidth = scrollPaneWidth / imageWidth; double zoomRatioHeight = scrollPaneHeight / imageHeight; - + // Use the smallest ratio size to fit the entire image in the view area. zoomRatio = zoomRatioWidth < zoomRatioHeight ? zoomRatioWidth : zoomRatioHeight; - + rotation = 0; - + scrollPane.setHvalue(0); scrollPane.setVvalue(0); - + updateView(); } - + /** * Update the image to use the current zoom and rotation values. - * + * * Note: For zoom levels less than 100%, special accomodations are made in * order to keep the image fully visible. This is because the viewport size * change occurs before any transforms execute, thus chopping off part of @@ -498,39 +510,39 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan if (image == null) { return; } - + // Image dimensions double imageWidth = image.getWidth(); double imageHeight = image.getHeight(); - + // Image dimensions with zooming applied double adjustedImageWidth = imageWidth * zoomRatio; double adjustedImageHeight = imageHeight * zoomRatio; - + // ImageView viewport dimensions double viewportWidth; double viewportHeight; - + // Panel dimensions double panelWidth = fxPanel.getWidth(); double panelHeight = fxPanel.getHeight(); - + // Coordinates to center the image on the panel double centerOffsetX = (panelWidth / 2) - (imageWidth / 2); double centerOffsetY = (panelHeight / 2) - (imageHeight / 2); - + // Coordinates to keep the image inside the left/top boundaries double leftOffsetX; double topOffsetY; - + // Scroll bar positions double scrollX = scrollPane.getHvalue(); double scrollY = scrollPane.getVvalue(); - + // Scroll bar position boundaries (work-around for viewport size bug) double maxScrollX; double maxScrollY; - + // Set viewport size and translation offsets. if ((rotation % 180) == 0) { // Rotation is 0 or 180. @@ -549,7 +561,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan maxScrollX = (adjustedImageHeight - panelWidth) / (imageWidth - panelWidth); maxScrollY = (adjustedImageWidth - panelHeight) / (imageHeight - panelHeight); } - + // Work around bug that truncates image if dimensions are too small. if (viewportWidth < imageWidth) { viewportWidth = imageWidth; @@ -563,7 +575,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan scrollY = maxScrollY; } } - + // Update the viewport size. fxImageView.setViewport(new Rectangle2D( 0, 0, viewportWidth, viewportHeight)); @@ -589,9 +601,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan // Add the transforms in reverse order of intended execution. // Note: They MUST be added in this order to ensure translate is // executed last. - fxImageView.getTransforms().clear(); - fxImageView.getTransforms().addAll(translate, rotate, scale); - + imageGroup.getTransforms().clear(); + imageGroup.getTransforms().addAll(translate, rotate, scale); + // Adjust scroll bar positions for view changes. if (viewportWidth > fxPanel.getWidth()) { scrollPane.setHvalue(scrollX); @@ -599,11 +611,114 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan if (viewportHeight > fxPanel.getHeight()) { scrollPane.setVvalue(scrollY); } - + // Update all image controls to reflect the current values. zoomOutButton.setEnabled(zoomRatio > MIN_ZOOM_RATIO); zoomInButton.setEnabled(zoomRatio < MAX_ZOOM_RATIO); rotationTextField.setText((int) rotation + "°"); zoomTextField.setText((Math.round(zoomRatio * 100.0)) + "%"); } + + /** + * Enables users to 'tag' a region of an image by clicking and dragging a + * rectangle overtop. + */ + class ImageTaggingTool extends Rectangle { + + private final double imageWidth; + private final double imageHeight; + private final double imageOriginX; + private final double imageOriginY; + + //Origin of the drag event. + private double rectangleOriginX; + private double rectangleOriginY; + + //Rectangle lines should be 1.5% of the image. This level of thickness has + //a good balance between visual acuity and loss of selection at the borders + //of the image. + private double lineThicknessAsPercent = 1.5; + + /** + * Adds tagging support to an image, where the 'tag' rectangle will be + * the specified color. + * + * @param image Image to tag + * @param color Color of the 'tag' rectangle + */ + private ImageTaggingTool(ImageView image, Color color) { + defaultSettings(); + + imageWidth = image.getImage().getWidth(); + imageHeight = image.getImage().getHeight(); + imageOriginX = image.getX(); + imageOriginY = image.getY(); + + setStroke(color); + setFill(color.deriveColor(0, 0, 0, 0)); + + //Calculate how many pixels the stroke width should be to guarentee + //a consistent % of image consumed by the rectangle border. + double min = Math.min(imageWidth, imageHeight); + double lineThicknessPixels = min * lineThicknessAsPercent / 100.0; + setStrokeWidth(lineThicknessPixels); + setVisible(false); + + //Create a rectangle by left clicking on the image + image.setOnMousePressed((MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + return; + } + + //Reset box on new click. + defaultSettings(); + + rectangleOriginX = event.getX(); + rectangleOriginY = event.getY(); + + setX(rectangleOriginX); + setY(rectangleOriginY); + }); + + //Adjust the rectangle by dragging the left mouse button + image.setOnMouseDragged((MouseEvent event) -> { + if (event.isSecondaryButtonDown()) { + return; + } + + /** + * Ensure the rectangle is contained within image boundaries and + * that the line thickness is kept within bounds. + */ + double newX = Math.min(Math.max(event.getX(), imageOriginX) + + lineThicknessPixels / 2, imageWidth - lineThicknessPixels / 2); + double newY = Math.min(Math.max(event.getY(), imageOriginY) + + lineThicknessPixels / 2, imageHeight - lineThicknessPixels / 2); + + setVisible(true); + double offsetX = newX - rectangleOriginX; + if (offsetX < 0) { + setX(newX); + } + setWidth(Math.abs(offsetX)); + + double offsetY = newY - rectangleOriginY; + if (offsetY < 0) { + setY(newY); + } + setHeight(Math.abs(offsetY)); + }); + } + + /** + * Reset the rectangle to default dimensions. + */ + public final void defaultSettings() { + setX(0); + setY(0); + setWidth(0); + setHeight(0); + setVisible(false); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 00c87b3417..48f7e4e82e 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -467,6 +467,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont htmlPanel.reset(); textbodyTextArea.setText(""); msgbodyTabbedPane.setEnabled(false); + drp.setNode(null); } @Override @@ -561,7 +562,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, numberOfAttachments > 0); msgbodyTabbedPane.setTitleAt(ATTM_TAB_INDEX, "Attachments (" + numberOfAttachments + ")"); drp.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode( - new AttachmentsChildren(attachments)), null), true)); + new AttachmentsChildren(attachments))), true)); } private static String wrapInHtmlBody(String htmlText) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java index dd73b6f6ca..c1fdda1efc 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,7 +79,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer /** * Creates new form PListViewer */ - public PListViewer() { + PListViewer() { // Create an Outlineview and add to the panel outlineView = new org.openide.explorer.view.OutlineView(); @@ -193,16 +193,16 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer Case openCase; try { openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog(this, - "Failed to export plist file.", - Bundle.PListViewer_ExportFailed_message(), - JOptionPane.ERROR_MESSAGE); + } catch (NoCurrentCaseException ex) { + JOptionPane.showMessageDialog(this, + "Failed to export plist file.", + Bundle.PListViewer_ExportFailed_message(), + JOptionPane.ERROR_MESSAGE); - logger.log(Level.SEVERE, "Exception while getting open case.", ex); - return; + logger.log(Level.SEVERE, "Exception while getting open case.", ex); + return; } - + final JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(openCase.getExportDirectory())); fileChooser.setFileFilter(new FileNameExtensionFilter("XML file", "xml")); @@ -289,11 +289,11 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer // Read in and parse the file final byte[] plistFileBuf = new byte[(int) plistFile.getSize()]; plistFile.read(plistFileBuf, 0, plistFile.getSize()); - final List plist = parsePList(plistFileBuf); - + final List plist = parsePList(plistFileBuf); + return plist; } - + @Override protected void done() { super.done(); @@ -301,28 +301,28 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer try { plist = get(); setupTable(plist); - + SwingUtilities.invokeLater(() -> { - setColumnWidths(); + setColumnWidths(); }); } catch (InterruptedException ex) { logger.log(Level.SEVERE, "Interruption while parsing/dislaying plist file " + plistFile.getName(), ex); - - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getMessage(), - Bundle.PListViewer_processPlist_interruptedMessage(), - JOptionPane.ERROR_MESSAGE); - + + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + ex.getMessage(), + Bundle.PListViewer_processPlist_interruptedMessage(), + JOptionPane.ERROR_MESSAGE); + } catch (ExecutionException ex) { logger.log(Level.SEVERE, "Exception while parsing/dislaying plist file " + plistFile.getName(), ex); - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - ex.getCause().getMessage(), - Bundle.PListViewer_processPlist_errorMessage(), - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + ex.getCause().getMessage(), + Bundle.PListViewer_processPlist_errorMessage(), + JOptionPane.ERROR_MESSAGE); } - + } - }.execute(); + }.execute(); } /** @@ -431,16 +431,16 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer * else is unexpected and will be ignored. */ if (rootDict instanceof NSArray) { - for (int i=0; i < ((NSArray)rootDict).count(); i++) { - final PropKeyValue pkv = parseProperty("", ((NSArray)rootDict).objectAtIndex(i)); + for (int i = 0; i < ((NSArray) rootDict).count(); i++) { + final PropKeyValue pkv = parseProperty("", ((NSArray) rootDict).objectAtIndex(i)); if (null != pkv) { plist.add(pkv); } } } else if (rootDict instanceof NSDictionary) { - final String[] keys = ((NSDictionary)rootDict).allKeys(); + final String[] keys = ((NSDictionary) rootDict).allKeys(); for (final String key : keys) { - final PropKeyValue pkv = parseProperty(key, ((NSDictionary)rootDict).objectForKey(key)); + final PropKeyValue pkv = parseProperty(key, ((NSDictionary) rootDict).objectForKey(key)); if (null != pkv) { plist.add(pkv); } @@ -533,7 +533,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer .map(child -> new PropKeyValue(child)) .toArray(PropKeyValue[]::new); } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 104c70ef6b..078cc78c58 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -74,7 +74,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { /** * Constructs a file content viewer for SQLite database files. */ - public SQLiteViewer() { + SQLiteViewer() { initComponents(); jTableDataPanel.add(selectedTableView, BorderLayout.CENTER); } @@ -544,19 +544,19 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void accept(String columnName) { columnIndex++; - + String csvString = columnName; //Format the value to adhere to the format of a CSV file if (columnIndex == 1) { - columnName = "\"" + columnName + "\""; + csvString = "\"" + csvString + "\""; } else { - columnName = ",\"" + columnName + "\""; + csvString = ",\"" + csvString + "\""; } if (columnIndex == totalColumnCount) { - columnName += "\n"; + csvString += "\n"; } try { - out.write(columnName.getBytes()); + out.write(csvString.getBytes()); } catch (IOException ex) { /* * If we can no longer write to the output stream, toss a @@ -613,7 +613,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { */ throw new RuntimeException(ex); } - rowIndex = rowIndex % totalColumnCount; + rowIndex %= totalColumnCount; } }; } diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index b58e310fe0..d6f68086c4 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -60,6 +60,7 @@ public final class UserPreferences { private static final String MESSAGE_SERVICE_USER = "MessageServiceUser"; //NON-NLS private static final String MESSAGE_SERVICE_HOST = "MessageServiceHost"; //NON-NLS private static final String MESSAGE_SERVICE_PORT = "MessageServicePort"; //NON-NLS + private static final String TEXT_TRANSLATOR_NAME = "TextTranslatorName"; public static final String PROCESS_TIME_OUT_ENABLED = "ProcessTimeOutEnabled"; //NON-NLS public static final String PROCESS_TIME_OUT_HOURS = "ProcessTimeOutHours"; //NON-NLS private static final int DEFAULT_PROCESS_TIMEOUT_HR = 60; @@ -335,6 +336,14 @@ public final class UserPreferences { public static void setIndexingServerPort(int port) { preferences.putInt(INDEXING_SERVER_PORT, port); } + + public static void setTextTranslatorName(String textTranslatorName){ + preferences.put(TEXT_TRANSLATOR_NAME, textTranslatorName); + } + + public static String getTextTranslatorName(){ + return preferences.get(TEXT_TRANSLATOR_NAME, null); + } /** * Persists message service connection info. diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index f03c7b01cf..4f4e41d04e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -386,7 +386,13 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { rootNodeChildren.cancelLoadingThumbnails(); } try { - if (givenNode != null) { + // There is an issue with ThumbnailViewChildren + // addNotify, that it's call to getChildren.getNodes() does not cause the + // children nodes to be created. Adding a call to getChildren.getNodesCount() + // here will assure that the children nodes are created particularly in the + // case where the DataResultViewerThumbnail stands along from the + // DataResultViewer. See DataResultViewer setNode for more information. + if (givenNode != null && givenNode.getChildren().getNodesCount() > 0) { rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 5a3b75ca69..97922760c5 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -29,6 +29,7 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; @@ -798,7 +799,12 @@ public class ImageUtils { imageSaver.execute(() -> { try { synchronized (cacheFile) { - Files.createParentDirs(cacheFile); + Path path = Paths.get(cacheFile.getParent()); + File thumbsDir = Paths.get(cacheFile.getParent()).toFile(); + if (!thumbsDir.exists()) { + thumbsDir.mkdirs(); + } + if (cacheFile.exists()) { cacheFile.delete(); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index daa184def3..0b3a6f698f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -95,7 +95,20 @@ public class DataResultFilterNode extends FilterNode { static private final DisplayableItemNodeVisitor> getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor(); private final DisplayableItemNodeVisitor getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor(); + // Assumptions are made in GetPreferredActionsDisplayableItemNodeVisitor that + // sourceEm is the directory tree explorer manager. private final ExplorerManager sourceEm; + + /** + * Constructs a node used to wrap another node before passing it to the + * result viewers. The wrapper node defines the actions associated with the + * wrapped node and may filter out some of its children. + * + * @param node The node to wrap. + */ + public DataResultFilterNode(Node node) { + this(node, null); + } /** * Constructs a node used to wrap another node before passing it to the @@ -549,6 +562,9 @@ public class DataResultFilterNode extends FilterNode { // is a DirectoryTreeFilterNode that wraps the dataModelNode. We need // to set that wrapped node as the selection and root context of the // directory tree explorer manager (sourceEm) + if(sourceEm == null) { + return null; + } final Node currentSelectionInDirectoryTree = sourceEm.getSelectedNodes()[0]; return new AbstractAction() { @@ -589,6 +605,9 @@ public class DataResultFilterNode extends FilterNode { * @return */ private AbstractAction openParent(AbstractNode node) { + if(sourceEm == null) { + return null; + } // @@@ Why do we ignore node? Node[] selectedFilterNodes = sourceEm.getSelectedNodes(); Node selectedFilterNode = selectedFilterNodes[0]; diff --git a/Core/src/org/sleuthkit/autopsy/images/translate32.png b/Core/src/org/sleuthkit/autopsy/images/translate32.png new file mode 100644 index 0000000000..c3579aed78 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/translate32.png differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index 85fa762300..ce3f971bee 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -163,6 +163,8 @@ final class ContactAnalyzer { data1 = resultSet.getString("data1"); //NON-NLS mimetype = resultSet.getString("mimetype"); //NON-NLS if (name.equals(oldName) == false) { + bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + attributes = new ArrayList<>(); attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, moduleName, name)); } if (mimetype.equals("vnd.android.cursor.item/phone_v2")) { //NON-NLS @@ -170,6 +172,12 @@ final class ContactAnalyzer { } else { attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, moduleName, data1)); } + + // TODO: If this code comes back to life, add code to create the account + // and relationship between the phone numbers & emails. Also + // investigate if the mimetype "vnd.android.cursor.item/phone_v2" + // makes sense in an ios word + oldName = name; bba.addAttributes(attributes); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java index b5a4662ec6..8c41b87042 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/EvalRegistryObj.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2013-2018 Basis Technology Corp. + * + * Copyright 2013-2019 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. @@ -18,12 +18,15 @@ */ package org.sleuthkit.autopsy.modules.stix; +import com.williballenthin.rejistry.RegistryHiveFile; +import com.williballenthin.rejistry.RegistryKey; +import com.williballenthin.rejistry.RegistryParseException; +import com.williballenthin.rejistry.RegistryValue; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.Content; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; - import java.util.List; import java.util.ArrayList; import java.io.IOException; @@ -31,10 +34,8 @@ import java.io.UnsupportedEncodingException; import java.io.File; import java.util.regex.Pattern; import java.util.regex.Matcher; - import org.mitre.cybox.objects.WindowsRegistryKey; import org.mitre.cybox.common_2.ConditionTypeEnum; -import com.williballenthin.rejistry.*; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /** @@ -43,9 +44,9 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; class EvalRegistryObj extends EvaluatableObject { private final WindowsRegistryKey obj; - private final List regFiles = new ArrayList(); + private final List regFiles = new ArrayList<>(); - public EvalRegistryObj(WindowsRegistryKey a_obj, String a_id, String a_spacing, List a_regFiles) { + EvalRegistryObj(WindowsRegistryKey a_obj, String a_id, String a_spacing, List a_regFiles) { obj = a_obj; id = a_id; spacing = a_spacing; @@ -80,7 +81,7 @@ class EvalRegistryObj extends EvaluatableObject { setUnsupportedFieldWarnings(); // Make a list of hives to test - List hiveList = new ArrayList(); + List hiveList = new ArrayList<>(); if (obj.getHive() == null) { // If the hive field is missing, add everything hiveList.addAll(regFiles); @@ -88,9 +89,9 @@ class EvalRegistryObj extends EvaluatableObject { // If the hive name is HKEY_LOCAL_MACHINE, add the ones from the config directory. // Otherwise, add the others for (RegistryFileInfo regFile : regFiles) { - if (regFile.abstractFile.getParentPath() != null) { + if (regFile.getAbstractFile().getParentPath() != null) { Pattern pattern = Pattern.compile("system32", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(regFile.abstractFile.getParentPath()); + Matcher matcher = pattern.matcher(regFile.getAbstractFile().getParentPath()); if (matcher.find()) { // Looking for system files and found one, so add it to the list if (obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) { //NON-NLS @@ -112,7 +113,7 @@ class EvalRegistryObj extends EvaluatableObject { Pattern pattern = Pattern.compile("Temp.STIX." + stixHiveName, Pattern.CASE_INSENSITIVE); for (RegistryFileInfo hive : regFiles) { - Matcher matcher = pattern.matcher(hive.tempFileName); + Matcher matcher = pattern.matcher(hive.getTempFileName()); if (matcher.find()) { hiveList.add(hive); } @@ -163,7 +164,7 @@ class EvalRegistryObj extends EvaluatableObject { */ private ObservableResult testRegistryFile(RegistryFileInfo a_regInfo) { try { - RegistryKey root = openRegistry(a_regInfo.tempFileName); + RegistryKey root = openRegistry(a_regInfo.getTempFileName()); RegistryKey result = findKey(root, obj.getKey().getValue().toString()); if (result == null) { @@ -192,8 +193,8 @@ class EvalRegistryObj extends EvaluatableObject { if ((obj.getValues() == null) || (obj.getValues().getValues().isEmpty())) { // No values to test - List artData = new ArrayList(); - artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry")); //NON-NLS + List artData = new ArrayList<>(); + artData.add(new StixArtifactData(a_regInfo.getAbstractFile().getId(), id, "Registry")); //NON-NLS return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue(), //NON-NLS spacing, ObservableResult.ObservableState.TRUE, artData); } @@ -262,8 +263,8 @@ class EvalRegistryObj extends EvaluatableObject { if (nameSuccess && valueSuccess) { // Found a match for all values - List artData = new ArrayList(); - artData.add(new StixArtifactData(a_regInfo.abstractFile.getId(), id, "Registry")); //NON-NLS + List artData = new ArrayList<>(); + artData.add(new StixArtifactData(a_regInfo.getAbstractFile().getId(), id, "Registry")); //NON-NLS return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue() //NON-NLS + " and value " + stixRegValue.getName().getValue().toString() //NON-NLS + " = " + stixRegValue.getData().getValue().toString(), @@ -343,13 +344,13 @@ class EvalRegistryObj extends EvaluatableObject { List regFilesAbstract = findRegistryFiles(); // List to hold all the extracted file names plus their abstract file - List regFilesLocal = new ArrayList(); + List regFilesLocal = new ArrayList<>(); // Make the temp directory String tmpDir; try { - tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + "STIX"; //NON-NLS - } catch (NoCurrentCaseException ex) { + tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + "STIX"; //NON-NLS + } catch (NoCurrentCaseException ex) { throw new TskCoreException(ex.getLocalizedMessage()); } File dir = new File(tmpDir); @@ -382,11 +383,11 @@ class EvalRegistryObj extends EvaluatableObject { * RecentActivity */ private static List findRegistryFiles() throws TskCoreException { - List registryFiles = new ArrayList(); + List registryFiles = new ArrayList<>(); Case openCase; try { openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { + } catch (NoCurrentCaseException ex) { throw new TskCoreException(ex.getLocalizedMessage()); } org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = openCase.getServices().getFileManager(); @@ -413,7 +414,7 @@ class EvalRegistryObj extends EvaluatableObject { } private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList(); + List fieldNames = new ArrayList<>(); if (obj.getNumberValues() != null) { fieldNames.add("Number_Values"); //NON-NLS @@ -462,5 +463,23 @@ class EvalRegistryObj extends EvaluatableObject { tempFileName = a_tempFileName; } + /** + * Get the AbstractFile for this RegistryFileInfo + * + * @return the abstractFile + */ + AbstractFile getAbstractFile() { + return abstractFile; + } + + /** + * Get the Temporary file name for this RegistryFileInfo + * + * @return the tempFileName + */ + String getTempFileName() { + return tempFileName; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/HtmlTextExtractor.java b/Core/src/org/sleuthkit/autopsy/textextractors/HtmlTextExtractor.java index defcdc23a5..d117e80537 100644 --- a/Core/src/org/sleuthkit/autopsy/textextractors/HtmlTextExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/HtmlTextExtractor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,9 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import net.htmlparser.jericho.Attributes; import net.htmlparser.jericho.Config; @@ -71,7 +73,7 @@ final class HtmlTextExtractor implements TextExtractor { /** * Determines if this content type is supported by this extractor. * - * @param content Content instance to be analyzed + * @param content Content instance to be analyzed * @param detectedFormat Mimetype of content instance * * @return flag indicating support @@ -84,27 +86,21 @@ final class HtmlTextExtractor implements TextExtractor { } /** - * Returns a reader that will iterate over the text of an HTML document. - * - * @param content Html document source - * - * @return A reader instance containing the document source text - * - * @throws TextExtractorException + * Get the metadata as a key -> value map. HTML metadata will include + * scripts, links, images, comments, and misc attributes. + * + * @return Map containing metadata key -> value pairs. */ @Override - public Reader getReader() throws InitReaderException { - //TODO JIRA-4467, there is only harm in excluding HTML documents greater - //than 50MB due to our troubled approach of extraction. - ReadContentInputStream stream = new ReadContentInputStream(file); - - //Parse the stream with Jericho and put the results in a Reader + public Map getMetadata() { + Map metadataMap = new HashMap<>(); try { - StringBuilder scripts = new StringBuilder(); - StringBuilder links = new StringBuilder(); - StringBuilder images = new StringBuilder(); - StringBuilder comments = new StringBuilder(); - StringBuilder others = new StringBuilder(); + ReadContentInputStream stream = new ReadContentInputStream(file); + StringBuilder scripts = new StringBuilder("\n"); + StringBuilder links = new StringBuilder("\n"); + StringBuilder images = new StringBuilder("\n"); + StringBuilder comments = new StringBuilder("\n"); + StringBuilder others = new StringBuilder("\n"); int numScripts = 0; int numLinks = 0; int numImages = 0; @@ -113,17 +109,8 @@ final class HtmlTextExtractor implements TextExtractor { Source source = new Source(stream); source.fullSequentialParse(); - Renderer renderer = source.getRenderer(); - renderer.setNewLine("\n"); - renderer.setIncludeHyperlinkURLs(false); - renderer.setDecorateFontStyles(false); - renderer.setIncludeAlternateText(false); - String text = renderer.toString(); - // Get all the tags in the source List tags = source.getAllStartTags(); - - StringBuilder stringBuilder = new StringBuilder(); for (StartTag tag : tags) { if (tag.getName().equals("script")) { //NON-NLS // If the