From f003254ad547bbc7acb4679b2d018dcc8dfa85f6 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 4 Jan 2018 15:31:54 -0500 Subject: [PATCH 01/71] 3414: Create generic Application Content Viewer --- .../ApplicationContentViewer.form | 41 +++ .../ApplicationContentViewer.java | 251 ++++++++++++++++++ .../autopsy/contentviewers/Bundle.properties | 2 + .../autopsy/contentviewers/ContentViewer.java | 50 ++++ .../contentviewers/JPEGViewerDummy.form | 58 ++++ .../contentviewers/JPEGViewerDummy.java | 89 +++++++ .../modules/filetypeid/FileTypeDetector.java | 34 ++- 7 files changed, 522 insertions(+), 3 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form new file mode 100644 index 0000000000..d07831cafe --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form @@ -0,0 +1,41 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java new file mode 100644 index 0000000000..30141c3f4c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java @@ -0,0 +1,251 @@ +/* + * 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.contentviewers; + +import com.google.common.base.Strings; +import java.awt.Component; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Generic Application content viewer + */ +@ServiceProvider(service = DataContentViewer.class, position = 5) +public class ApplicationContentViewer extends javax.swing.JPanel implements DataContentViewer { + + private static final int CONFIDENCE_LEVEL = 7; + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(ApplicationContentViewer.class.getName()); + + private final Map mimeTypeToViewerMap = new HashMap<>(); + + // TBD: This hardcoded list of viewers should be replaced with a dynamic lookup + private static final ContentViewer[] KNOWN_VIEWERS = new ContentViewer[]{ + new JPEGViewerDummy() // this if for testing only + }; + + private ContentViewer lastViewer; + + /** + * Creates new form ApplicationContentViewer + */ + public ApplicationContentViewer() { + + // init the mimetype to viewer map + for (ContentViewer cv : KNOWN_VIEWERS) { + cv.getSupportedMIMETypes().forEach((mimeType) -> { + if (mimeTypeToViewerMap.containsKey(mimeType) == false) { + mimeTypeToViewerMap.put(mimeType, cv); + } else { + LOGGER.log(Level.WARNING, "Duplicate viewer for mimtype: {0}", mimeType); //NON-NLS + } + }); + } + + initComponents(); + + LOGGER.log(Level.INFO, "Created ApplicationContentViewer instance: {0}", this); //NON-NLS + } + + /** + * Get the ContentViewer for a given mimetype + * + * @param mimeType + * + * @return ContentViewer, null if no known content viewer supports the + * mimetype + */ + private ContentViewer getSupportingViewer(String mimeType) { + return mimeTypeToViewerMap.containsKey(mimeType) ? mimeTypeToViewerMap.get(mimeType) : 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() { + + setLayout(new javax.swing.OverlayLayout(this)); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + @Override + public void setNode(Node selectedNode) { + if (selectedNode == null) { + return; + } + + AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); + if (file == null) { + return; + } + + String mimeType = file.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", file.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectFileType(file); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Could not detect format using fileTypeDetector for file: %s", file), ex); //NON-NLS + return; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return; + } + else { + ContentViewer viewer = getSupportingViewer(mimeType); + if (viewer != null) { + lastViewer = viewer; + + viewer.setFile(file); + this.removeAll(); + this.add(viewer.getComponent()); + this.repaint(); + } + } + + } + + @Override + @NbBundle.Messages("ApplicationContentViewer.title=Application Content Viewer") + public String getTitle() { + return Bundle.ApplicationContentViewer_title(); + } + + @Override + @NbBundle.Messages("ApplicationContentViewer.toolTip=Displays file contents.") + public String getToolTip() { + return Bundle.ApplicationContentViewer_toolTip(); + } + + @Override + public DataContentViewer createInstance() { + return new ApplicationContentViewer(); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + + if (lastViewer != null) { + lastViewer.resetComponent(); + } + this.removeAll(); + lastViewer = null; + } + + @Override + public boolean isSupported(Node node) { + + if (node == null) { + return false; + } + + AbstractFile aFile = node.getLookup().lookup(AbstractFile.class); + if (aFile == null) { + return false; + } + + String mimeType = aFile.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", aFile.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectFileType(aFile); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return false; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Could not detect format using fileTypeDetector for file: %s", aFile), ex); //NON-NLS + return false; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return false; + } else { + return (getSupportingViewer(mimeType) != null); + } + + } + + @Override + public int isPreferred(Node node) { + if (node == null) { + return 0; + } + + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return 0; + } + + String mimeType = file.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", file.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectFileType(file); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return 0; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Could not detect format using fileTypeDetector for file: %s", file), ex); //NON-NLS + return 0; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return 0; + } else { + if (null != getSupportingViewer(mimeType)) { + return CONFIDENCE_LEVEL; + } + } + + return 0; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 45916b87b3..cf082b2bab 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -33,3 +33,5 @@ MessageContentViewer.directionText.text=direction MessageContentViewer.ccLabel.text=CC: MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageContentViewer.viewInNewWindowButton.text=View in New Window +JPEGViewerDummy.jLabel1.text=You are looking at a JPEG file: +JPEGViewerDummy.jTextField1.text=jTextField1 diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java new file mode 100644 index 0000000000..98dbbd0b08 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java @@ -0,0 +1,50 @@ +/* + * 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.contentviewers; + +import java.awt.Component; +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Defines an interface for application specific content viewer + * + */ +interface ContentViewer { + + /** + * Returns list of MIME types supported by this viewer + */ + List getSupportedMIMETypes(); + + /** + * Display the given file's content in the view panel + */ + void setFile(AbstractFile file); + + /** + * Returns panel + */ + Component getComponent(); + + /** + * Clears the data in the panel + */ + void resetComponent(); +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form new file mode 100644 index 0000000000..587dd3c9a0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form @@ -0,0 +1,58 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java new file mode 100644 index 0000000000..cae2db12a0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java @@ -0,0 +1,89 @@ +/* + * 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.contentviewers; + +import java.awt.Component; +import java.util.Arrays; +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; + +public class JPEGViewerDummy extends javax.swing.JPanel implements ContentViewer { + + public static final String[] SUPPORTED_MIMETYPES = new String[]{"image/jpeg"}; + + /** + * Creates new form JPEGViewer + */ + public JPEGViewerDummy() { + initComponents(); + } + + /** + * 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() { + + jLabel1 = new javax.swing.JLabel(); + jTextField1 = new javax.swing.JTextField(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(JPEGViewerDummy.class, "JPEGViewerDummy.jLabel1.text")); // NOI18N + + jTextField1.setEditable(false); + jTextField1.setText(org.openide.util.NbBundle.getMessage(JPEGViewerDummy.class, "JPEGViewerDummy.jTextField1.text")); // NOI18N + + 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(43, 43, 43) + .addComponent(jLabel1) + .addGap(35, 35, 35) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(120, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(269, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + @Override + public List getSupportedMIMETypes() { + return Arrays.asList(SUPPORTED_MIMETYPES); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + this.jTextField1.setText(""); + } + + @Override + public void setFile(AbstractFile file) { + this.jTextField1.setText(file.getName()); + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + private javax.swing.JTextField jTextField1; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 5851b9887c..baa6a1135b 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; +import com.google.common.base.Strings; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -205,9 +206,18 @@ public class FileTypeDetector { * * @throws TskCoreException If there is a problem writing the result to the * case database. + * + */ - public String detect(AbstractFile file) throws TskCoreException { - return detect(file, false); + public String detectFileType(AbstractFile file) throws TskCoreException { + String mimeType = file.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType) == false) { + return mimeType; + } + else { + return detect(file, false); + } + } /** @@ -477,4 +487,22 @@ public class FileTypeDetector { return getFileType(file); } + /** + * Detects the MIME type of a file. The result is not added to the case + * database. + * + * @param file The file to test. + * + * @return A MIME type name. If file type could not be detected or results + * were uncertain, octet-stream is returned. + * + * @throws TskCoreException If there is a problem writing the result to the + * case database. + * + * @deprecated - use detectFileType to detect + */ + @Deprecated + public String detect(AbstractFile file) throws TskCoreException { + return detect(file, true); + } } From 7ff02fa62a1af599cd11b1a1435abb986d9f7f74 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 8 Jan 2018 13:47:24 -0500 Subject: [PATCH 02/71] Added menu item to create live triage drive --- .../autopsy/livetriage/Bundle.properties | 11 + .../CreateLiveTriageDriveAction.java | 265 +++++++++++ .../autopsy/livetriage/SelectDriveDialog.form | 180 ++++++++ .../autopsy/livetriage/SelectDriveDialog.java | 423 ++++++++++++++++++ 4 files changed, 879 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form create mode 100644 Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties new file mode 100644 index 0000000000..49fdf032cb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties @@ -0,0 +1,11 @@ +SelectDriveDialog.bnOk.text=Ok +SelectDriveDialog.bnRefresh.text=Refresh +SelectDriveDialog.lbSelectDrive.text=Select the drive to copy the application and script to: +SelectDriveDialog.jLabel1.text=Select drive to use for live triage (may take time to load): +SelectDriveDialog.errorLabel.text=jLabel2 +SelectDriveDialog.jLabel2.text=This feature copies the application and a batch file to a removable drive, +SelectDriveDialog.jLabel3.text=allowing systems to be analyzed without installing the software or imaging +SelectDriveDialog.jLabel4.text=the drives. +SelectDriveDialog.jLabel5.text=To analyze a system, insert the drive and run "RunFromUSB.bat" as +SelectDriveDialog.jLabel6.text=administrator. +SelectDriveDialog.bnCancel.text=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java new file mode 100644 index 0000000000..531e978618 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -0,0 +1,265 @@ +/* + * 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.livetriage; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.InvalidPathException; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import java.awt.Frame; +import javax.swing.SwingWorker; +import org.apache.commons.io.FileUtils; +import org.netbeans.api.progress.ProgressHandle; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.livetriage.CreateLiveTriageDriveAction") +@ActionReference(path = "Menu/Tools", position = 1401) +@ActionRegistration(displayName = "#CTL_CreateLiveTriageDriveAction", lazy = false) +@NbBundle.Messages({"CTL_CreateLiveTriageDriveAction=Make Live Triage Drive"}) +public final class CreateLiveTriageDriveAction extends CallableSystemAction { + + private static final String DISPLAY_NAME = Bundle.CTL_CreateLiveTriageDriveAction(); + + @Override + public boolean isEnabled() { + return true; + } + + @NbBundle.Messages({"CreateLiveTriageDriveAction.error.title=Error creating live triage disk", + "CreateLiveTriageDriveAction.exenotfound.message=Executable could not be found", + "CreateLiveTriageDriveAction.batchFileError.message=Error creating batch file", + "CreateLiveTriageDriveAction.appPathError.message=Could not location application directory", + "CreateLiveTriageDriveAction.copyError.message=Could not copy application", + "CreateLiveTriageDriveAction.success.title=Success", + "CreateLiveTriageDriveAction.success.message=Live triage drive created. Use RunFromUSB.bat to run the application" + }) + @Override + @SuppressWarnings("fallthrough") + public void performAction() { + + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + + // If this is an installed version, there should be an 64.exe file in the bin folder + String appName = UserPreferences.getAppName(); + String exeName = appName + "64.exe"; + String installPath = PlatformUtil.getInstallPath(); + + Path exePath = Paths.get(installPath, "bin", exeName); + System.out.println("Exe expected at " + exePath); + + // TEMP for testing - allows this to run from Netbeans + exePath = Paths.get("C:\\Program Files\\Autopsy-4.5.0", "bin", exeName); + + if(! exePath.toFile().exists()) { + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_exenotfound_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + Path applicationBasePath; + try { + applicationBasePath = exePath.getParent().getParent(); + } catch (InvalidPathException ex){ + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_appPathError_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + SelectDriveDialog driveDialog = new SelectDriveDialog(mainWindow, true); + driveDialog.display(); + + if(! driveDialog.getSelectedDrive().isEmpty()) { + String drivePath = driveDialog.getSelectedDrive(); + if(drivePath.startsWith("\\\\.\\")){ + drivePath = drivePath.substring(4); + } + System.out.println("Destination path: " + drivePath); + + CopyFilesWorker worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); + worker.execute(); + } + + } + + private class CopyFilesWorker extends SwingWorker { + + private final Path sourceFolder; + private final String drivePath; + private final String appName; + private ProgressHandle progressHandle = null; + + CopyFilesWorker(Path sourceFolder, String drivePath, String appName){ + this.sourceFolder = sourceFolder; + this.drivePath = drivePath; + this.appName = appName; + } + + @NbBundle.Messages({"# {0} - drivePath", + "CopyFilesWorker.progressBar.text=Live Triage: Copying files to {0}"}) + @Override + protected Void doInBackground() throws Exception { + // Setup progress bar. + String displayName = NbBundle.getMessage(this.getClass(), + "CopyFilesWorker.progressBar.text", + drivePath); + + // There's no way to stop FileUtils.copyDirectory, so don't include cancellation + progressHandle = ProgressHandle.createHandle(displayName); + progressHandle.start(); + progressHandle.switchToIndeterminate(); + + copyBatchFile(drivePath, appName); + copyApplication(sourceFolder, drivePath, appName); + + return null; + } + + @NbBundle.Messages({"CopyFilesWorker.title=Create Live Triage Drive", + "CopyFilesWorker.error.text=Error copying live triage files", + "CopyFilesWorker.done.text=Finished creating live triage disk"}) + @Override + protected void done() { + try { + super.get(); + + MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), + NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + + } catch (Exception ex) { + Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS + MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), + NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); + } finally { + if(progressHandle != null){ + progressHandle.finish(); + } + } + } + } + + private void copyApplication(Path sourceFolder, String destBaseFolder, String appName) throws IOException { + + // Create an appName folder in the destination + Path destAppFolder = Paths.get(destBaseFolder, appName); + if(! destAppFolder.toFile().exists()) { + if(! destAppFolder.toFile().mkdirs()){ + throw new IOException("Failed to create directory " + destAppFolder.toString()); + } + } + + // Now copy the files + FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile()); + } + + private void copyBatchFile(String destPath, String appName) throws IOException, InvalidPathException { + Path batchFilePath = Paths.get(destPath, "RunFromUSB.bat"); + FileUtils.writeStringToFile(batchFilePath.toFile(), getBatchFileContents(appName), "UTF-8"); + + } + + private String getBatchFileContents(String appName){ + + String batchFile = + "@echo off\n" + + "SET appName=\"" + appName + "\"\n" + + "\n" + + "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData mkdir configData\n" + + "if not exist configData (\n" + + " echo Error creating directory configData\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the userdir sub directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData\\userdir mkdir configData\\userdir\n" + + "if not exist configData\\userdir (\n" + + " echo Error creating directory configData\\userdir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cachedir sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\cachedir rd /s /q configData\\cachedir\n" + + "mkdir configData\\cachedir\n" + + "if not exist configData\\cachedir (\n" + + " echo Error creating directory configData\\cachedir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the temp sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\temp rd /s /q configData\\temp\n" + + "mkdir configData\\temp\n" + + "if not exist configData\\temp (\n" + + " echo Error creating directory configData\\temp\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cases directory. It's ok if this fails.\n" + + "if not exist cases mkdir cases\n" + + "\n" + + "if exist %appName% (\n" + + " if not exist %appName%\\bin\\%appName%64.exe (\n" + + " echo %appName%\\bin\\%appName%64.exe does not exist\n" + + " goto end\n" + + " )\n" + + " %appName%\\bin\\%appName%64.exe --userdir ..\\configData\\userdir --cachedir ..\\configData\\cachedir -J-Djava.io.tmpdir=..\\configData\\temp\n" + + ") else (\n" + + " echo Could not find %appName% directory\n" + + " goto end\n" + + ")\n" + + "\n" + + ":end\n" + + "\n" + + "REM Keep the cmd window open in case there was an error\n" + + "@pause\n"; + return batchFile; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form new file mode 100644 index 0000000000..87592836f0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form @@ -0,0 +1,180 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java new file mode 100644 index 0000000000..d50d7aee2c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -0,0 +1,423 @@ +/* + * 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.livetriage; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.awt.Dimension; +import java.awt.Point; +import javax.swing.SwingWorker; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.LocalDisk; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * + */ +class SelectDriveDialog extends javax.swing.JDialog { + + private List disks = new ArrayList<>(); + private final LocalDiskModel model = new LocalDiskModel(); + private final java.awt.Frame parent; + private String drivePath = ""; + + /** + * Creates new form SelectDriveDialog + */ + @NbBundle.Messages({"SelectDriveDialog.title=Create Live Triage Drive"}) + SelectDriveDialog(java.awt.Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + this.parent = parent; + + model.loadDisks(); + bnOk.setEnabled(false); + diskTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + bnOk.setEnabled(true); + } else { //The selection changed to nothing valid being selected, such as with ctrl+click + bnOk.setEnabled(false); + } + } + }); + } + + void display(){ + this.setTitle(Bundle.SelectDriveDialog_title()); + + final Dimension parentSize = parent.getSize(); + final Point parentLocationOnScreen = parent.getLocationOnScreen(); + final Dimension childSize = this.getSize(); + int x; + int y; + x = (parentSize.width - childSize.width) / 2; + y = (parentSize.height - childSize.height) / 2; + x += parentLocationOnScreen.x; + y += parentLocationOnScreen.y; + + setLocation(x, y); + setVisible(true); + } + + String getSelectedDrive(){ + return this.drivePath; + } + + + /** + * 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() { + + jScrollPane1 = new javax.swing.JScrollPane(); + diskTable = new javax.swing.JTable(); + jLabel1 = new javax.swing.JLabel(); + bnRefresh = new javax.swing.JButton(); + bnOk = new javax.swing.JButton(); + errorLabel = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + jSeparator1 = new javax.swing.JSeparator(); + jLabel5 = new javax.swing.JLabel(); + jLabel6 = new javax.swing.JLabel(); + bnCancel = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + diskTable.setModel(model); + jScrollPane1.setViewportView(diskTable); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnRefresh.text")); // NOI18N + bnRefresh.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnRefreshActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnOk, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnOk.text")); // NOI18N + bnOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnOkActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.errorLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel2.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel3.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel4.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel5.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel6.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnCancel.text")); // NOI18N + bnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnCancelActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel6) + .addGroup(layout.createSequentialGroup() + .addComponent(bnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jLabel4) + .addComponent(jSeparator1) + .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, 368, Short.MAX_VALUE) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel2) + .addGap(1, 1, 1) + .addComponent(jLabel3) + .addGap(1, 1, 1) + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabel5) + .addGap(1, 1, 1) + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 112, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnRefresh) + .addComponent(bnCancel) + .addComponent(bnOk)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void bnRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnRefreshActionPerformed + model.loadDisks(); + }//GEN-LAST:event_bnRefreshActionPerformed + + private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + LocalDisk selectedDisk = disks.get(diskTable.getSelectedRow()); + drivePath = selectedDisk.getPath(); + } else { + drivePath = ""; + } + dispose(); + }//GEN-LAST:event_bnOkActionPerformed + + private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed + dispose(); + }//GEN-LAST:event_bnCancelActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton bnCancel; + private javax.swing.JButton bnOk; + private javax.swing.JButton bnRefresh; + private javax.swing.JTable diskTable; + private javax.swing.JLabel errorLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JSeparator jSeparator1; + // End of variables declaration//GEN-END:variables + + + /** + * Table model for displaying information from LocalDisk Objects in a table. + */ + @NbBundle.Messages({"SelectDriveDialog.localDiskModel.loading.msg=", + "SelectDriveDialog.localDiskModel.nodrives.msg=Executable could not be found", + "SelectDriveDialog.diskTable.column1.title=Disk Name", + "SelectDriveDialog.diskTable.column2.title=Disk Size", + "SelectDriveDialog.errLabel.disksNotDetected.text=Disks were not detected. On some systems it requires admin privileges", + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText=Disks were not detected. On some systems it requires admin privileges", + "SelectDriveDialog.errLabel.drivesNotDetected.text=Local drives were not detected. Auto-detection not supported on this OS or admin privileges required", + "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText=Local drives were not detected. Auto-detection not supported on this OS or admin privileges required", + "SelectDriveDialog.errLabel.someDisksNotDetected.text=Some disks were not detected. On some systems it requires admin privileges", + "SelectDriveDialog.errLabel.someDisksNotDetected.toolTipText=Some disks were not detected. On some systems it requires admin privileges" + + }) + private class LocalDiskModel implements TableModel { + + private LocalDiskThread worker = null; + private boolean ready = false; + private volatile boolean loadingDisks = false; + + //private String SELECT = "Select a local disk:"; + private final String LOADING = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.loading.msg"); + private final String NO_DRIVES = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.nodrives.msg"); + + private void loadDisks() { + + // if there is a worker already building the lists, then cancel it first. + if (loadingDisks && worker != null) { + worker.cancel(false); + } + + // Clear the lists + errorLabel.setText(""); + diskTable.setEnabled(false); + ready = false; + loadingDisks = true; + worker = new LocalDiskThread(); + worker.execute(); + } + + @Override + public int getRowCount() { + if (disks.isEmpty()) { + return 0; + } + return disks.size(); + } + + @Override + public int getColumnCount() { + return 2; + + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column1.title"); + case 1: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column2.title"); + default: + return "Unnamed"; //NON-NLS + } + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (ready) { + if (disks.isEmpty()) { + return NO_DRIVES; + } + switch (columnIndex) { + case 0: + return disks.get(rowIndex).getName(); + case 1: + return disks.get(rowIndex).getReadableSize(); + default: + return disks.get(rowIndex).getPath(); + } + } else { + return LOADING; + } + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + //setter does nothing they should not be able to modify table + } + + @Override + public void addTableModelListener(TableModelListener l) { + + } + + @Override + public void removeTableModelListener(TableModelListener l) { + + } + + /** + * Gets the lists of physical drives and partitions and combines them + * into a list of disks. + */ + class LocalDiskThread extends SwingWorker { + + private final Logger logger = Logger.getLogger(LocalDiskThread.class.getName()); + private List physicalDrives = new ArrayList<>(); + private List partitions = new ArrayList<>(); + + @Override + protected Object doInBackground() throws Exception { + // Populate the lists + //physicalDrives = new ArrayList<>(); + partitions = new ArrayList<>(); + //physicalDrives = PlatformUtil.getPhysicalDrives(); + partitions = PlatformUtil.getPartitions(); + return null; + } + + /** + * Display any error messages that might of occurred when getting + * the lists of physical drives or partitions. + */ + private void displayErrors() { + if (physicalDrives.isEmpty() && partitions.isEmpty()) { + if (PlatformUtil.isWindowsOS()) { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.disksNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText")); + } else { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.drivesNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText")); + } + errorLabel.setVisible(true); + diskTable.setEnabled(false); + }/* else if (physicalDrives.isEmpty()) { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.someDisksNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.someDisksNotDetected.toolTipText")); + errorLabel.setVisible(true); + }*/ + } + + @Override + protected void done() { + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Loading local disks was canceled."); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Loading local disks was interrupted."); //NON-NLS + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error when loading local disks", ex); //NON-NLS + } finally { + if (!this.isCancelled()) { + displayErrors(); + worker = null; + loadingDisks = false; + disks = new ArrayList<>(); + //disks.addAll(physicalDrives); + disks.addAll(partitions); + if (disks.size() > 0) { + diskTable.setEnabled(true); + diskTable.clearSelection(); + } + ready = true; + } + } + diskTable.revalidate(); + } + } + } +} From a055757c64dd0d2eb59474b068fdd658daf806d2 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 8 Jan 2018 13:48:35 -0500 Subject: [PATCH 03/71] Formatting --- .../CreateLiveTriageDriveAction.java | 218 +++++++++--------- .../autopsy/livetriage/SelectDriveDialog.java | 18 +- 2 files changed, 117 insertions(+), 119 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java index 531e978618..a90993bb73 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -45,14 +45,14 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; @ActionRegistration(displayName = "#CTL_CreateLiveTriageDriveAction", lazy = false) @NbBundle.Messages({"CTL_CreateLiveTriageDriveAction=Make Live Triage Drive"}) public final class CreateLiveTriageDriveAction extends CallableSystemAction { - + private static final String DISPLAY_NAME = Bundle.CTL_CreateLiveTriageDriveAction(); - + @Override public boolean isEnabled() { return true; } - + @NbBundle.Messages({"CreateLiveTriageDriveAction.error.title=Error creating live triage disk", "CreateLiveTriageDriveAction.exenotfound.message=Executable could not be found", "CreateLiveTriageDriveAction.batchFileError.message=Error creating batch file", @@ -64,45 +64,45 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { @Override @SuppressWarnings("fallthrough") public void performAction() { - + Frame mainWindow = WindowManager.getDefault().getMainWindow(); - + // If this is an installed version, there should be an 64.exe file in the bin folder String appName = UserPreferences.getAppName(); String exeName = appName + "64.exe"; String installPath = PlatformUtil.getInstallPath(); - + Path exePath = Paths.get(installPath, "bin", exeName); System.out.println("Exe expected at " + exePath); - + // TEMP for testing - allows this to run from Netbeans exePath = Paths.get("C:\\Program Files\\Autopsy-4.5.0", "bin", exeName); - - if(! exePath.toFile().exists()) { + + if (!exePath.toFile().exists()) { JOptionPane.showMessageDialog(mainWindow, - Bundle.CreateLiveTriageDriveAction_exenotfound_message(), - Bundle.CreateLiveTriageDriveAction_error_title(), - JOptionPane.ERROR_MESSAGE); + Bundle.CreateLiveTriageDriveAction_exenotfound_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); return; - } - + } + Path applicationBasePath; try { applicationBasePath = exePath.getParent().getParent(); - } catch (InvalidPathException ex){ + } catch (InvalidPathException ex) { JOptionPane.showMessageDialog(mainWindow, - Bundle.CreateLiveTriageDriveAction_appPathError_message(), - Bundle.CreateLiveTriageDriveAction_error_title(), - JOptionPane.ERROR_MESSAGE); + Bundle.CreateLiveTriageDriveAction_appPathError_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); return; } - + SelectDriveDialog driveDialog = new SelectDriveDialog(mainWindow, true); driveDialog.display(); - - if(! driveDialog.getSelectedDrive().isEmpty()) { + + if (!driveDialog.getSelectedDrive().isEmpty()) { String drivePath = driveDialog.getSelectedDrive(); - if(drivePath.startsWith("\\\\.\\")){ + if (drivePath.startsWith("\\\\.\\")) { drivePath = drivePath.substring(4); } System.out.println("Destination path: " + drivePath); @@ -110,154 +110,154 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { CopyFilesWorker worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); worker.execute(); } - + } - + private class CopyFilesWorker extends SwingWorker { private final Path sourceFolder; private final String drivePath; private final String appName; private ProgressHandle progressHandle = null; - - CopyFilesWorker(Path sourceFolder, String drivePath, String appName){ + + CopyFilesWorker(Path sourceFolder, String drivePath, String appName) { this.sourceFolder = sourceFolder; this.drivePath = drivePath; this.appName = appName; } - + @NbBundle.Messages({"# {0} - drivePath", "CopyFilesWorker.progressBar.text=Live Triage: Copying files to {0}"}) @Override protected Void doInBackground() throws Exception { // Setup progress bar. String displayName = NbBundle.getMessage(this.getClass(), - "CopyFilesWorker.progressBar.text", - drivePath); - + "CopyFilesWorker.progressBar.text", + drivePath); + // There's no way to stop FileUtils.copyDirectory, so don't include cancellation progressHandle = ProgressHandle.createHandle(displayName); progressHandle.start(); progressHandle.switchToIndeterminate(); - + copyBatchFile(drivePath, appName); copyApplication(sourceFolder, drivePath, appName); - + return null; } - + @NbBundle.Messages({"CopyFilesWorker.title=Create Live Triage Drive", - "CopyFilesWorker.error.text=Error copying live triage files", - "CopyFilesWorker.done.text=Finished creating live triage disk"}) + "CopyFilesWorker.error.text=Error copying live triage files", + "CopyFilesWorker.done.text=Finished creating live triage disk"}) @Override protected void done() { try { super.get(); - + MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), - NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); - + NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + } catch (Exception ex) { Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), - NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); + NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); } finally { - if(progressHandle != null){ + if (progressHandle != null) { progressHandle.finish(); } - } + } } } - + private void copyApplication(Path sourceFolder, String destBaseFolder, String appName) throws IOException { - + // Create an appName folder in the destination Path destAppFolder = Paths.get(destBaseFolder, appName); - if(! destAppFolder.toFile().exists()) { - if(! destAppFolder.toFile().mkdirs()){ + if (!destAppFolder.toFile().exists()) { + if (!destAppFolder.toFile().mkdirs()) { throw new IOException("Failed to create directory " + destAppFolder.toString()); } } - + // Now copy the files FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile()); } - + private void copyBatchFile(String destPath, String appName) throws IOException, InvalidPathException { Path batchFilePath = Paths.get(destPath, "RunFromUSB.bat"); FileUtils.writeStringToFile(batchFilePath.toFile(), getBatchFileContents(appName), "UTF-8"); - + } - - private String getBatchFileContents(String appName){ - - String batchFile = - "@echo off\n" + - "SET appName=\"" + appName + "\"\n" + - "\n" + - "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" + - "if not exist configData mkdir configData\n" + - "if not exist configData (\n" + - " echo Error creating directory configData\n" + - " goto end\n" + - ")\n" + - "\n" + - "REM Create the userdir sub directory. Exit if it does not exist after attempting to create it\n" + - "if not exist configData\\userdir mkdir configData\\userdir\n" + - "if not exist configData\\userdir (\n" + - " echo Error creating directory configData\\userdir\n" + - " goto end\n" + - ")\n" + - "\n" + - "REM Create the cachedir sub directory. Exit if it does not exist after attempting to create it\n" + - "REM If it exists to start with, delete it to clear out old data\n" + - "if exist configData\\cachedir rd /s /q configData\\cachedir\n" + - "mkdir configData\\cachedir\n" + - "if not exist configData\\cachedir (\n" + - " echo Error creating directory configData\\cachedir\n" + - " goto end\n" + - ")\n" + - "\n" + - "REM Create the temp sub directory. Exit if it does not exist after attempting to create it\n" + - "REM If it exists to start with, delete it to clear out old data\n" + - "if exist configData\\temp rd /s /q configData\\temp\n" + - "mkdir configData\\temp\n" + - "if not exist configData\\temp (\n" + - " echo Error creating directory configData\\temp\n" + - " goto end\n" + - ")\n" + - "\n" + - "REM Create the cases directory. It's ok if this fails.\n" + - "if not exist cases mkdir cases\n" + - "\n" + - "if exist %appName% (\n" + - " if not exist %appName%\\bin\\%appName%64.exe (\n" + - " echo %appName%\\bin\\%appName%64.exe does not exist\n" + - " goto end\n" + - " )\n" + - " %appName%\\bin\\%appName%64.exe --userdir ..\\configData\\userdir --cachedir ..\\configData\\cachedir -J-Djava.io.tmpdir=..\\configData\\temp\n" + - ") else (\n" + - " echo Could not find %appName% directory\n" + - " goto end\n" + - ")\n" + - "\n" + - ":end\n" + - "\n" + - "REM Keep the cmd window open in case there was an error\n" + - "@pause\n"; - return batchFile; + + private String getBatchFileContents(String appName) { + + String batchFile + = "@echo off\n" + + "SET appName=\"" + appName + "\"\n" + + "\n" + + "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData mkdir configData\n" + + "if not exist configData (\n" + + " echo Error creating directory configData\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the userdir sub directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData\\userdir mkdir configData\\userdir\n" + + "if not exist configData\\userdir (\n" + + " echo Error creating directory configData\\userdir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cachedir sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\cachedir rd /s /q configData\\cachedir\n" + + "mkdir configData\\cachedir\n" + + "if not exist configData\\cachedir (\n" + + " echo Error creating directory configData\\cachedir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the temp sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\temp rd /s /q configData\\temp\n" + + "mkdir configData\\temp\n" + + "if not exist configData\\temp (\n" + + " echo Error creating directory configData\\temp\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cases directory. It's ok if this fails.\n" + + "if not exist cases mkdir cases\n" + + "\n" + + "if exist %appName% (\n" + + " if not exist %appName%\\bin\\%appName%64.exe (\n" + + " echo %appName%\\bin\\%appName%64.exe does not exist\n" + + " goto end\n" + + " )\n" + + " %appName%\\bin\\%appName%64.exe --userdir ..\\configData\\userdir --cachedir ..\\configData\\cachedir -J-Djava.io.tmpdir=..\\configData\\temp\n" + + ") else (\n" + + " echo Could not find %appName% directory\n" + + " goto end\n" + + ")\n" + + "\n" + + ":end\n" + + "\n" + + "REM Keep the cmd window open in case there was an error\n" + + "@pause\n"; + return batchFile; } - + @Override public String getName() { return DISPLAY_NAME; } - + @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } - + @Override public boolean asynchronous() { return false; // run on edt diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java index d50d7aee2c..1ca506d323 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -30,7 +30,7 @@ class SelectDriveDialog extends javax.swing.JDialog { private final LocalDiskModel model = new LocalDiskModel(); private final java.awt.Frame parent; private String drivePath = ""; - + /** * Creates new form SelectDriveDialog */ @@ -39,7 +39,7 @@ class SelectDriveDialog extends javax.swing.JDialog { super(parent, modal); initComponents(); this.parent = parent; - + model.loadDisks(); bnOk.setEnabled(false); diskTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @@ -53,10 +53,10 @@ class SelectDriveDialog extends javax.swing.JDialog { } }); } - - void display(){ + + void display() { this.setTitle(Bundle.SelectDriveDialog_title()); - + final Dimension parentSize = parent.getSize(); final Point parentLocationOnScreen = parent.getLocationOnScreen(); final Dimension childSize = this.getSize(); @@ -70,12 +70,11 @@ class SelectDriveDialog extends javax.swing.JDialog { setLocation(x, y); setVisible(true); } - - String getSelectedDrive(){ + + String getSelectedDrive() { return this.drivePath; } - /** * 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 @@ -231,7 +230,6 @@ class SelectDriveDialog extends javax.swing.JDialog { private javax.swing.JSeparator jSeparator1; // End of variables declaration//GEN-END:variables - /** * Table model for displaying information from LocalDisk Objects in a table. */ @@ -245,7 +243,7 @@ class SelectDriveDialog extends javax.swing.JDialog { "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText=Local drives were not detected. Auto-detection not supported on this OS or admin privileges required", "SelectDriveDialog.errLabel.someDisksNotDetected.text=Some disks were not detected. On some systems it requires admin privileges", "SelectDriveDialog.errLabel.someDisksNotDetected.toolTipText=Some disks were not detected. On some systems it requires admin privileges" - + }) private class LocalDiskModel implements TableModel { From 15e296145df3a8d6f30c51a721ff034fc03551c2 Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 9 Jan 2018 09:39:26 -0500 Subject: [PATCH 04/71] Address review comments --- .../contentviewers/ApplicationContentViewer.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java index 4c079baca3..bf7c13279a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java @@ -83,7 +83,7 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data * mimetype */ private ContentViewer getSupportingViewer(String mimeType) { - return mimeTypeToViewerMap.containsKey(mimeType) ? mimeTypeToViewerMap.get(mimeType) : null; + return mimeTypeToViewerMap.get(mimeType); } /** @@ -104,6 +104,9 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data @Override public void setNode(Node selectedNode) { + + resetComponent(); + if (selectedNode == null) { return; } @@ -208,16 +211,9 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data @Override public int isPreferred(Node node) { - if (node == null) { - return 0; - } - AbstractFile file = node.getLookup().lookup(AbstractFile.class); - if (file == null) { - return 0; - } - String mimeType = file.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", file.getName()); //NON-NLS try { From 00e68c44aa6e14bb80d237068a98c0e7cd35246f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 9 Jan 2018 09:44:27 -0500 Subject: [PATCH 05/71] Add fix to restore directory when running as admin --- .../autopsy/livetriage/CreateLiveTriageDriveAction.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java index a90993bb73..5f10b4f9df 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -73,10 +73,6 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { String installPath = PlatformUtil.getInstallPath(); Path exePath = Paths.get(installPath, "bin", exeName); - System.out.println("Exe expected at " + exePath); - - // TEMP for testing - allows this to run from Netbeans - exePath = Paths.get("C:\\Program Files\\Autopsy-4.5.0", "bin", exeName); if (!exePath.toFile().exists()) { JOptionPane.showMessageDialog(mainWindow, @@ -193,6 +189,11 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { String batchFile = "@echo off\n" + + "\n" + + "REM This restores the working directory when using 'Run as administrator'" + + "@setlocal enableextensions\n" + + "@cd /d \"%~dp0\"" + + "\n" + "SET appName=\"" + appName + "\"\n" + "\n" + "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" From 62e96ff4e06d61e6536434bc7d851db568de7e65 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Tue, 9 Jan 2018 17:11:45 -0500 Subject: [PATCH 06/71] 3449: Failed to generate report if couldn't create a report directory. --- .../autopsy/report/ReportGenerator.java | 44 +++++++------------ .../autopsy/report/ReportWizardAction.java | 21 ++++++--- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 0c8aa263c3..1bb05652d4 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -136,16 +136,9 @@ class ReportGenerator { /** * Run the GeneralReportModules using a SwingWorker. */ - void generateGeneralReport(GeneralReportModule generalReportModule) { + void generateGeneralReport(GeneralReportModule generalReportModule) throws IOException { if (generalReportModule != null) { - reportPathFormatString = String.format(reportPathFormatString, generalReportModule.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } + reportPathFormatString = createReportDirectory(generalReportModule, reportPathFormatString); setupProgressPanel(generalReportModule); ReportWorker worker = new ReportWorker(() -> { generalReportModule.generateReport(reportPathFormatString, progressPanel); @@ -163,16 +156,9 @@ class ReportGenerator { * @param tagSelections the enabled/disabled state of the tag names * to be included in the report */ - void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) { + void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) throws IOException { if (tableReport != null && null != artifactTypeSelections) { - reportPathFormatString = String.format(reportPathFormatString, tableReport.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } + reportPathFormatString = createReportDirectory(tableReport, reportPathFormatString); setupProgressPanel(tableReport); ReportWorker worker = new ReportWorker(() -> { tableReport.startReport(reportPathFormatString); @@ -194,16 +180,9 @@ class ReportGenerator { * @param enabledInfo the Information that should be included about each * file in the report. */ - void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) { + void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) throws IOException { if (fileReportModule != null && null != enabledInfo) { - reportPathFormatString = String.format(reportPathFormatString, fileReportModule.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } + reportPathFormatString = createReportDirectory(fileReportModule, reportPathFormatString); List enabled = new ArrayList<>(); for (Entry e : enabledInfo.entrySet()) { if (e.getValue()) { @@ -285,6 +264,17 @@ class ReportGenerator { } } + private static String createReportDirectory(ReportModule module, String pathFormat) throws IOException { + String reportPath = String.format(pathFormat, module.getName()); + // Create the root reports directory. + try { + FileUtil.createFolder(new File(reportPath)); + } catch (IOException ex) { + throw new IOException("Failed to make report folder, unable to generate reports.", ex); + } + return reportPath; + } + private class ReportWorker extends SwingWorker { private final Runnable doInBackground; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 49f37008be..618cba2a38 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2013-2015 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -26,12 +26,14 @@ import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; +import java.io.IOException; import java.text.MessageFormat; import java.util.EnumSet; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JButton; import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -70,12 +72,17 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr TableReportModule tableReport = (TableReportModule) wiz.getProperty("tableModule"); GeneralReportModule generalReport = (GeneralReportModule) wiz.getProperty("generalModule"); FileReportModule fileReport = (FileReportModule) wiz.getProperty("fileModule"); - if (tableReport != null) { - generator.generateTableReport(tableReport, (Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS - } else if (generalReport != null) { - generator.generateGeneralReport(generalReport); - } else if (fileReport != null) { - generator.generateFileListReport(fileReport, (Map) wiz.getProperty("fileReportOptions")); //NON-NLS + try { + if (tableReport != null) { + generator.generateTableReport(tableReport, (Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS + } else if (generalReport != null) { + generator.generateGeneralReport(generalReport); + } else if (fileReport != null) { + generator.generateFileListReport(fileReport, (Map) wiz.getProperty("fileReportOptions")); //NON-NLS + } + } catch (IOException e) { + NotifyDescriptor descriptor = new NotifyDescriptor.Message(e.getMessage(), NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(descriptor); } } } From 43d32eb1c809377eb1aa05f966d5ad47ebc5b432 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 10 Jan 2018 09:31:24 -0500 Subject: [PATCH 07/71] Changed progress bar --- .../autopsy/livetriage/Bundle.properties | 6 +- .../CreateLiveTriageDriveAction.java | 86 ++++++++++------- .../autopsy/livetriage/SelectDriveDialog.form | 93 ++++++++----------- .../autopsy/livetriage/SelectDriveDialog.java | 79 +++++----------- 4 files changed, 114 insertions(+), 150 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties index 49fdf032cb..61f522a6b4 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties @@ -3,9 +3,5 @@ SelectDriveDialog.bnRefresh.text=Refresh SelectDriveDialog.lbSelectDrive.text=Select the drive to copy the application and script to: SelectDriveDialog.jLabel1.text=Select drive to use for live triage (may take time to load): SelectDriveDialog.errorLabel.text=jLabel2 -SelectDriveDialog.jLabel2.text=This feature copies the application and a batch file to a removable drive, -SelectDriveDialog.jLabel3.text=allowing systems to be analyzed without installing the software or imaging -SelectDriveDialog.jLabel4.text=the drives. -SelectDriveDialog.jLabel5.text=To analyze a system, insert the drive and run "RunFromUSB.bat" as -SelectDriveDialog.jLabel6.text=administrator. SelectDriveDialog.bnCancel.text=Cancel +SelectDriveDialog.jTextArea1.text=This feature copies the application and a batch file to a removable drive,\nallowing systems to be analyzed without installing the software or\nimaging the drives.\n\nTo analyze a system, insert the drive and run "RunFromUSB.bat" as\nadministrator, then select the "Local Disk" option on the Add Data Source\npanel. diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java index 5f10b4f9df..1c54ee633b 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -23,11 +23,12 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.InvalidPathException; import java.util.logging.Level; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; import javax.swing.JOptionPane; import java.awt.Frame; import javax.swing.SwingWorker; import org.apache.commons.io.FileUtils; -import org.netbeans.api.progress.ProgressHandle; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; @@ -39,14 +40,18 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.livetriage.CreateLiveTriageDriveAction") @ActionReference(path = "Menu/Tools", position = 1401) @ActionRegistration(displayName = "#CTL_CreateLiveTriageDriveAction", lazy = false) @NbBundle.Messages({"CTL_CreateLiveTriageDriveAction=Make Live Triage Drive"}) -public final class CreateLiveTriageDriveAction extends CallableSystemAction { +public final class CreateLiveTriageDriveAction extends CallableSystemAction implements PropertyChangeListener { private static final String DISPLAY_NAME = Bundle.CTL_CreateLiveTriageDriveAction(); + private ModalDialogProgressIndicator progressIndicator = null; + private String drivePath = ""; + private CopyFilesWorker worker; @Override public boolean isEnabled() { @@ -57,7 +62,7 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { "CreateLiveTriageDriveAction.exenotfound.message=Executable could not be found", "CreateLiveTriageDriveAction.batchFileError.message=Error creating batch file", "CreateLiveTriageDriveAction.appPathError.message=Could not location application directory", - "CreateLiveTriageDriveAction.copyError.message=Could not copy application", + "CreateLiveTriageDriveAction.copyError.message=Could not copy application. Only works on installed version.", "CreateLiveTriageDriveAction.success.title=Success", "CreateLiveTriageDriveAction.success.message=Live triage drive created. Use RunFromUSB.bat to run the application" }) @@ -97,16 +102,46 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { driveDialog.display(); if (!driveDialog.getSelectedDrive().isEmpty()) { - String drivePath = driveDialog.getSelectedDrive(); + drivePath = driveDialog.getSelectedDrive(); if (drivePath.startsWith("\\\\.\\")) { drivePath = drivePath.substring(4); } - System.out.println("Destination path: " + drivePath); - - CopyFilesWorker worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); + + worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); + worker.addPropertyChangeListener(this); worker.execute(); } - + } + + @NbBundle.Messages({"# {0} - drivePath", + "CreateLiveTriageDriveAction.progressBar.text=Copying live triage files to {0}", + "CreateLiveTriageDriveAction.progressBar.title=Please wait"}) + @Override + public void propertyChange(PropertyChangeEvent evt) { + + if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.STARTED.equals(evt.getNewValue()))) { + + // Setup progress bar. + String displayStr = NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.text", + drivePath); + + progressIndicator = new ModalDialogProgressIndicator(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.title")); + progressIndicator.start(displayStr); + + } else if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) { + if(progressIndicator != null){ + progressIndicator.finish(); + } + + if(worker.hadError()){ + MessageNotifyUtil.Message.error(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); + } else { + MessageNotifyUtil.Message.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + } + } } private class CopyFilesWorker extends SwingWorker { @@ -114,27 +149,20 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { private final Path sourceFolder; private final String drivePath; private final String appName; - private ProgressHandle progressHandle = null; + private boolean error = false; CopyFilesWorker(Path sourceFolder, String drivePath, String appName) { this.sourceFolder = sourceFolder; this.drivePath = drivePath; this.appName = appName; } - - @NbBundle.Messages({"# {0} - drivePath", - "CopyFilesWorker.progressBar.text=Live Triage: Copying files to {0}"}) + + boolean hadError(){ + return error; + } + @Override protected Void doInBackground() throws Exception { - // Setup progress bar. - String displayName = NbBundle.getMessage(this.getClass(), - "CopyFilesWorker.progressBar.text", - drivePath); - - // There's no way to stop FileUtils.copyDirectory, so don't include cancellation - progressHandle = ProgressHandle.createHandle(displayName); - progressHandle.start(); - progressHandle.switchToIndeterminate(); copyBatchFile(drivePath, appName); copyApplication(sourceFolder, drivePath, appName); @@ -142,25 +170,15 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction { return null; } - @NbBundle.Messages({"CopyFilesWorker.title=Create Live Triage Drive", - "CopyFilesWorker.error.text=Error copying live triage files", + @NbBundle.Messages({"CopyFilesWorker.error.text=Error copying live triage files", "CopyFilesWorker.done.text=Finished creating live triage disk"}) @Override protected void done() { try { super.get(); - - MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), - NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); - } catch (Exception ex) { - Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS - MessageNotifyUtil.Notify.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.title"), - NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); - } finally { - if (progressHandle != null) { - progressHandle.finish(); - } + error = true; + Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS } } } diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form index 87592836f0..ee776348dc 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form @@ -23,27 +23,21 @@ - + - - - + + - - - - - - + @@ -53,16 +47,8 @@ - - - - - - - - - - + + @@ -129,43 +115,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -176,5 +127,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java index 1ca506d323..c13d82d324 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -90,13 +90,10 @@ class SelectDriveDialog extends javax.swing.JDialog { bnRefresh = new javax.swing.JButton(); bnOk = new javax.swing.JButton(); errorLabel = new javax.swing.JLabel(); - jLabel2 = new javax.swing.JLabel(); - jLabel3 = new javax.swing.JLabel(); - jLabel4 = new javax.swing.JLabel(); jSeparator1 = new javax.swing.JSeparator(); - jLabel5 = new javax.swing.JLabel(); - jLabel6 = new javax.swing.JLabel(); bnCancel = new javax.swing.JButton(); + jScrollPane2 = new javax.swing.JScrollPane(); + jTextArea1 = new javax.swing.JTextArea(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -121,16 +118,6 @@ class SelectDriveDialog extends javax.swing.JDialog { org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.errorLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel2.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel3.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel4.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel5, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel5.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(jLabel6, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel6.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnCancel.text")); // NOI18N bnCancel.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -138,6 +125,16 @@ class SelectDriveDialog extends javax.swing.JDialog { } }); + jScrollPane2.setBorder(null); + + jTextArea1.setBackground(new java.awt.Color(240, 240, 240)); + jTextArea1.setColumns(20); + jTextArea1.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + jTextArea1.setRows(5); + jTextArea1.setText(org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jTextArea1.text")); // NOI18N + jTextArea1.setBorder(null); + jScrollPane2.setViewportView(jTextArea1); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -145,38 +142,25 @@ class SelectDriveDialog extends javax.swing.JDialog { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jLabel6) .addGroup(layout.createSequentialGroup() - .addComponent(bnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, 112, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) .addComponent(bnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addComponent(jLabel4) .addComponent(jSeparator1) - .addComponent(jLabel5, javax.swing.GroupLayout.DEFAULT_SIZE, 368, Short.MAX_VALUE) - .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane2)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addComponent(jLabel2) - .addGap(1, 1, 1) - .addComponent(jLabel3) - .addGap(1, 1, 1) - .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jLabel5) - .addGap(1, 1, 1) - .addComponent(jLabel6) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel1) @@ -221,13 +205,10 @@ class SelectDriveDialog extends javax.swing.JDialog { private javax.swing.JTable diskTable; private javax.swing.JLabel errorLabel; private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; - private javax.swing.JLabel jLabel4; - private javax.swing.JLabel jLabel5; - private javax.swing.JLabel jLabel6; private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; private javax.swing.JSeparator jSeparator1; + private javax.swing.JTextArea jTextArea1; // End of variables declaration//GEN-END:variables /** @@ -238,11 +219,7 @@ class SelectDriveDialog extends javax.swing.JDialog { "SelectDriveDialog.diskTable.column1.title=Disk Name", "SelectDriveDialog.diskTable.column2.title=Disk Size", "SelectDriveDialog.errLabel.disksNotDetected.text=Disks were not detected. On some systems it requires admin privileges", - "SelectDriveDialog.errLabel.disksNotDetected.toolTipText=Disks were not detected. On some systems it requires admin privileges", - "SelectDriveDialog.errLabel.drivesNotDetected.text=Local drives were not detected. Auto-detection not supported on this OS or admin privileges required", - "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText=Local drives were not detected. Auto-detection not supported on this OS or admin privileges required", - "SelectDriveDialog.errLabel.someDisksNotDetected.text=Some disks were not detected. On some systems it requires admin privileges", - "SelectDriveDialog.errLabel.someDisksNotDetected.toolTipText=Some disks were not detected. On some systems it requires admin privileges" + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText=Disks were not detected." }) private class LocalDiskModel implements TableModel { @@ -348,15 +325,12 @@ class SelectDriveDialog extends javax.swing.JDialog { class LocalDiskThread extends SwingWorker { private final Logger logger = Logger.getLogger(LocalDiskThread.class.getName()); - private List physicalDrives = new ArrayList<>(); private List partitions = new ArrayList<>(); @Override protected Object doInBackground() throws Exception { // Populate the lists - //physicalDrives = new ArrayList<>(); partitions = new ArrayList<>(); - //physicalDrives = PlatformUtil.getPhysicalDrives(); partitions = PlatformUtil.getPartitions(); return null; } @@ -366,7 +340,7 @@ class SelectDriveDialog extends javax.swing.JDialog { * the lists of physical drives or partitions. */ private void displayErrors() { - if (physicalDrives.isEmpty() && partitions.isEmpty()) { + if (partitions.isEmpty()) { if (PlatformUtil.isWindowsOS()) { errorLabel.setText( NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.disksNotDetected.text")); @@ -380,13 +354,7 @@ class SelectDriveDialog extends javax.swing.JDialog { } errorLabel.setVisible(true); diskTable.setEnabled(false); - }/* else if (physicalDrives.isEmpty()) { - errorLabel.setText( - NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.someDisksNotDetected.text")); - errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), - "SelectDriveDialog.errLabel.someDisksNotDetected.toolTipText")); - errorLabel.setVisible(true); - }*/ + } } @Override @@ -405,7 +373,6 @@ class SelectDriveDialog extends javax.swing.JDialog { worker = null; loadingDisks = false; disks = new ArrayList<>(); - //disks.addAll(physicalDrives); disks.addAll(partitions); if (disks.size() > 0) { diskTable.setEnabled(true); From 6c973af0b6e1e163b03b8d59275fee063763d6c6 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 10 Jan 2018 09:31:58 -0500 Subject: [PATCH 08/71] Formatting --- .../CreateLiveTriageDriveAction.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java index 1c54ee633b..846632a092 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -106,40 +106,40 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction impl if (drivePath.startsWith("\\\\.\\")) { drivePath = drivePath.substring(4); } - + worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); worker.addPropertyChangeListener(this); worker.execute(); } } - + @NbBundle.Messages({"# {0} - drivePath", - "CreateLiveTriageDriveAction.progressBar.text=Copying live triage files to {0}", - "CreateLiveTriageDriveAction.progressBar.title=Please wait"}) + "CreateLiveTriageDriveAction.progressBar.text=Copying live triage files to {0}", + "CreateLiveTriageDriveAction.progressBar.title=Please wait"}) @Override public void propertyChange(PropertyChangeEvent evt) { - + if ("state".equals(evt.getPropertyName()) - && (SwingWorker.StateValue.STARTED.equals(evt.getNewValue()))) { - + && (SwingWorker.StateValue.STARTED.equals(evt.getNewValue()))) { + // Setup progress bar. String displayStr = NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.text", drivePath); - + progressIndicator = new ModalDialogProgressIndicator(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.title")); progressIndicator.start(displayStr); - + } else if ("state".equals(evt.getPropertyName()) - && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) { - if(progressIndicator != null){ + && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) { + if (progressIndicator != null) { progressIndicator.finish(); } - - if(worker.hadError()){ + + if (worker.hadError()) { MessageNotifyUtil.Message.error(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); } else { - MessageNotifyUtil.Message.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + MessageNotifyUtil.Message.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); } } } @@ -156,11 +156,11 @@ public final class CreateLiveTriageDriveAction extends CallableSystemAction impl this.drivePath = drivePath; this.appName = appName; } - - boolean hadError(){ + + boolean hadError() { return error; } - + @Override protected Void doInBackground() throws Exception { From 30793eb51f962cd5a48e2d2535c8611e848b636f Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 11 Jan 2018 09:53:07 -0500 Subject: [PATCH 09/71] 3238: Add a SQLite content viewer: List tables - Also renamed the ContentViewer & ApplicationContentViewer based on review comments. --- .../autopsy/contentviewers/Bundle.properties | 2 + ...ContentViewer.java => FileTypeViewer.java} | 2 +- ...tionContentViewer.form => FileViewer.form} | 0 ...tionContentViewer.java => FileViewer.java} | 33 +- .../contentviewers/JPEGViewerDummy.java | 2 +- .../autopsy/contentviewers/SQLiteViewer.form | 153 +++++++ .../autopsy/contentviewers/SQLiteViewer.java | 389 ++++++++++++++++++ 7 files changed, 561 insertions(+), 20 deletions(-) rename Core/src/org/sleuthkit/autopsy/contentviewers/{ContentViewer.java => FileTypeViewer.java} (97%) rename Core/src/org/sleuthkit/autopsy/contentviewers/{ApplicationContentViewer.form => FileViewer.form} (100%) rename Core/src/org/sleuthkit/autopsy/contentviewers/{ApplicationContentViewer.java => FileViewer.java} (87%) create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index cf082b2bab..8267587395 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -35,3 +35,5 @@ MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageContentViewer.viewInNewWindowButton.text=View in New Window JPEGViewerDummy.jLabel1.text=You are looking at a JPEG file: JPEGViewerDummy.jTextField1.text=jTextField1 +SQLiteViewer.jLabel1.text=Table +SQLiteViewer.numEntriesField.text=num Entries diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java similarity index 97% rename from Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java index 98dbbd0b08..f4a677c4f4 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/ContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.AbstractFile; * Defines an interface for application specific content viewer * */ -interface ContentViewer { +interface FileTypeViewer { /** * Returns list of MIME types supported by this viewer diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.form similarity index 100% rename from Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.form rename to Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.form diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java similarity index 87% rename from Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java rename to Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index bf7c13279a..a58dea2d25 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/ApplicationContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -24,42 +24,40 @@ import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskCoreException; /** * Generic Application content viewer */ @ServiceProvider(service = DataContentViewer.class, position = 5) -public class ApplicationContentViewer extends javax.swing.JPanel implements DataContentViewer { +public class FileViewer extends javax.swing.JPanel implements DataContentViewer { private static final int CONFIDENCE_LEVEL = 7; private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(ApplicationContentViewer.class.getName()); + private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); - private final Map mimeTypeToViewerMap = new HashMap<>(); + private final Map mimeTypeToViewerMap = new HashMap<>(); // TBD: This hardcoded list of viewers should be replaced with a dynamic lookup - private static final ContentViewer[] KNOWN_VIEWERS = new ContentViewer[]{ - new JPEGViewerDummy() // this if for testing only + private static final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{ + new JPEGViewerDummy(), // this if for testing only + new SQLiteViewer() }; - private ContentViewer lastViewer; + private FileTypeViewer lastViewer; /** * Creates new form ApplicationContentViewer */ - public ApplicationContentViewer() { + public FileViewer() { // init the mimetype to viewer map - for (ContentViewer cv : KNOWN_VIEWERS) { + for (FileTypeViewer cv : KNOWN_VIEWERS) { cv.getSupportedMIMETypes().forEach((mimeType) -> { if (mimeTypeToViewerMap.containsKey(mimeType) == false) { mimeTypeToViewerMap.put(mimeType, cv); @@ -75,14 +73,13 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data } /** - * Get the ContentViewer for a given mimetype + * Get the FileTypeViewer for a given mimetype * * @param mimeType * - * @return ContentViewer, null if no known content viewer supports the - * mimetype + * @return FileTypeViewer, null if no known content viewer supports the mimetype */ - private ContentViewer getSupportingViewer(String mimeType) { + private FileTypeViewer getSupportingViewer(String mimeType) { return mimeTypeToViewerMap.get(mimeType); } @@ -132,7 +129,7 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data return; } else { - ContentViewer viewer = getSupportingViewer(mimeType); + FileTypeViewer viewer = getSupportingViewer(mimeType); if (viewer != null) { lastViewer = viewer; @@ -146,7 +143,7 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data } @Override - @NbBundle.Messages("ApplicationContentViewer.title=Application Content Viewer") + @NbBundle.Messages("ApplicationContentViewer.title=Application") public String getTitle() { return Bundle.ApplicationContentViewer_title(); } @@ -159,7 +156,7 @@ public class ApplicationContentViewer extends javax.swing.JPanel implements Data @Override public DataContentViewer createInstance() { - return new ApplicationContentViewer(); + return new FileViewer(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java index cae2db12a0..8aea7540e1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java @@ -10,7 +10,7 @@ import java.util.Arrays; import java.util.List; import org.sleuthkit.datamodel.AbstractFile; -public class JPEGViewerDummy extends javax.swing.JPanel implements ContentViewer { +public class JPEGViewerDummy extends javax.swing.JPanel implements FileTypeViewer { public static final String[] SUPPORTED_MIMETYPES = new String[]{"image/jpeg"}; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form new file mode 100644 index 0000000000..3cdee8658c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form @@ -0,0 +1,153 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java new file mode 100644 index 0000000000..358277fd97 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -0,0 +1,389 @@ +/* + * 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.contentviewers; + +import java.awt.Component; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.SQLException; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import javax.swing.JComboBox; +import javax.swing.SwingWorker; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamOrganization; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; + + +public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { + + public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; + private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); + private Connection connection = null; + + private String tmpDBPathName = null; + private File tmpDBFile = null; + + // TBD: Change the value to be a Array of ColDefs + Map dbTablesMap = new TreeMap<>(); + + /** + * Creates new form SQLiteViewer + */ + public SQLiteViewer() { + initComponents(); + + customizeComponents(); + } + + /** + * 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() { + + jHdrPanel = new javax.swing.JPanel(); + tablesDropdownList = new javax.swing.JComboBox<>(); + jLabel1 = new javax.swing.JLabel(); + numEntriesField = new javax.swing.JTextField(); + jTableDataPanel = new javax.swing.JPanel(); + jScrollPane1 = new javax.swing.JScrollPane(); + jTable1 = new javax.swing.JTable(); + + tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + tablesDropdownList.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tablesDropdownListActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel1.text")); // NOI18N + + numEntriesField.setEditable(false); + numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N + numEntriesField.setBorder(null); + numEntriesField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + numEntriesFieldActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel); + jHdrPanel.setLayout(jHdrPanelLayout); + jHdrPanelLayout.setHorizontalGroup( + jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jHdrPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(23, 23, 23) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(130, Short.MAX_VALUE)) + ); + jHdrPanelLayout.setVerticalGroup( + jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jHdrPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(16, Short.MAX_VALUE)) + ); + + jTable1.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + {null, null, null, null}, + {null, null, null, null}, + {null, null, null, null}, + {null, null, null, null} + }, + new String [] { + "Title 1", "Title 2", "Title 3", "Title 4" + } + )); + jScrollPane1.setViewportView(jTable1); + + javax.swing.GroupLayout jTableDataPanelLayout = new javax.swing.GroupLayout(jTableDataPanel); + jTableDataPanel.setLayout(jTableDataPanelLayout); + jTableDataPanelLayout.setHorizontalGroup( + jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jTableDataPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGap(15, 15, 15)) + ); + jTableDataPanelLayout.setVerticalGroup( + jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jTableDataPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 275, Short.MAX_VALUE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jTableDataPanel, 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(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void numEntriesFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numEntriesFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_numEntriesFieldActionPerformed + + private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed + JComboBox cb = (JComboBox) evt.getSource(); + String tableName = (String) cb.getSelectedItem(); + if (null == tableName) { + return; + } + + readTable(tableName); + }//GEN-LAST:event_tablesDropdownListActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel jHdrPanel; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JTable jTable1; + private javax.swing.JPanel jTableDataPanel; + private javax.swing.JTextField numEntriesField; + private javax.swing.JComboBox tablesDropdownList; + // End of variables declaration//GEN-END:variables + + @Override + public List getSupportedMIMETypes() { + return Arrays.asList(SUPPORTED_MIMETYPES); + } + + @Override + public void setFile(AbstractFile file) { + processSQLiteFile( file); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + + dbTablesMap.clear(); + + tablesDropdownList.setEnabled(true); + tablesDropdownList.removeAllItems(); + numEntriesField.setText(""); + + // close DB connection to file + if (null != connection) { + try { + connection.close(); + connection = null; + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS + } + } + + // delete last temp file + if (null != tmpDBFile) { + tmpDBFile.delete(); + tmpDBFile = null; + } + } + + private void customizeComponents() { + + // add a actionListener to jTablesComboBox + } + + /** + * Process the given SQLite DB file + * + * @param sqliteFile - + * + * @return none + */ + private void processSQLiteFile(AbstractFile sqliteFile) { + + tablesDropdownList.removeAllItems(); + + new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + + try { + tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId(); + tmpDBFile = new File(tmpDBPathName); + + // Copy the file to temp folder + ContentUtils.writeToFile(sqliteFile, tmpDBFile); + System.out.println("RAMAN: SQLite file copied to: " + tmpDBPathName); + + // Open copy using JDBC + Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver + connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS + + // Read all table names and schema + return getTables(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Failed to copy DB file.", ex); //NON-NLS + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to Open DB.", ex); //NON-NLS + } catch (ClassNotFoundException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize JDBC Sqlite.", ex); //NON-NLS + } + return false; + } + + @Override + protected void done() { + super.done(); + try { + boolean status = get(); + if ( (status == true) && (dbTablesMap.size() > 0) ) { + dbTablesMap.keySet().forEach((tableName) -> { + tablesDropdownList.addItem(tableName); + }); + } + else { + // Populate error message + tablesDropdownList.addItem("No tables found"); + tablesDropdownList.setEnabled(false); + } + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while opening DB file", ex); //NON-NLS + } + } + }.execute(); + + } + + /** + * Gets the table names and their schema from loaded SQLite db file + * + * @return true if success, false otherwise + */ + private boolean getTables() { + + try { + Statement statement = connection.createStatement(); + + ResultSet resultSet = statement.executeQuery( + "SELECT name, sql FROM sqlite_master " + + " WHERE type= 'table' " + + " ORDER BY name;"); //NON-NLS + + while (resultSet.next()) { + String tableName = resultSet.getString("name"); //NON-NLS + String tableSQL = resultSet.getString("sql"); //NON-NLS + + dbTablesMap.put(tableName, tableSQL); + String query = "PRAGMA table_info(" + tableName + ")"; //NON-NLS + ResultSet rs2; + try { + Statement statement2 = connection.createStatement(); + rs2 = statement2.executeQuery(query); + while (rs2.next()) { + + System.out.println("RAMAN: Col Name = " + rs2.getString("name") ); + System.out.println("RAMAN: Col Type = " + rs2.getString("type") ); + + // RAMAN TBD: parse and save the table schema + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS + } + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS + } + return true; + } + + private void readTable(String tableName) { + + System.out.println("RAMAN: selected table = " + tableName); + + // TBD: need to handle cancelling if one is already in progress + new SwingWorker() { + @Override + protected Integer doInBackground() throws Exception { + + try { + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT COUNT(*) as count FROM " + tableName ); //NON-NLS + + System.out.println("Row count = " + resultSet.getInt("count") ); + // Read all table names and schema + return resultSet.getInt("count"); + }catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to Open DB.", ex); //NON-NLS + } + //NON-NLS + return 0; + } + + @Override + protected void done() { + super.done(); + try { + int numRows = get(); + numEntriesField.setText( numRows + " entries"); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS + } + } + }.execute(); + + } + + enum SQLStorageClass { + NULL, + INTEGER, + REAL, + TEXT, + BLOB + }; + + private class SQLColDef { + + private final String colName; + private final SQLStorageClass storageClass; + + SQLColDef(String colName, SQLStorageClass sc ) { + this.colName = colName; + this.storageClass = sc; + } + + } +} From 1f44f08783cc7891ea5c692563371ccfc7efde03 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 11 Jan 2018 10:17:33 -0500 Subject: [PATCH 10/71] Formatted file, & minor cleanup --- .../autopsy/contentviewers/FileViewer.java | 2 +- .../autopsy/contentviewers/SQLiteViewer.java | 164 +++++++++--------- 2 files changed, 86 insertions(+), 80 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index a58dea2d25..0d17ce2cd6 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -45,7 +45,7 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer // TBD: This hardcoded list of viewers should be replaced with a dynamic lookup private static final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{ - new JPEGViewerDummy(), // this if for testing only + // new JPEGViewerDummy(), // this if for testing only new SQLiteViewer() }; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 358277fd97..fff2f5d781 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -1,7 +1,20 @@ /* - * 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. + * 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.contentviewers; @@ -22,30 +35,28 @@ import java.util.concurrent.ExecutionException; import javax.swing.JComboBox; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamOrganization; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.AbstractFile; - public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); private Connection connection = null; - + private String tmpDBPathName = null; private File tmpDBFile = null; - + // TBD: Change the value to be a Array of ColDefs Map dbTablesMap = new TreeMap<>(); - + /** * Creates new form SQLiteViewer */ public SQLiteViewer() { initComponents(); - + customizeComponents(); } @@ -164,7 +175,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { if (null == tableName) { return; } - + readTable(tableName); }//GEN-LAST:event_tablesDropdownListActionPerformed @@ -181,12 +192,12 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public List getSupportedMIMETypes() { - return Arrays.asList(SUPPORTED_MIMETYPES); + return Arrays.asList(SUPPORTED_MIMETYPES); } @Override public void setFile(AbstractFile file) { - processSQLiteFile( file); + processSQLiteFile(file); } @Override @@ -196,13 +207,13 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public void resetComponent() { - + dbTablesMap.clear(); - + tablesDropdownList.setEnabled(true); tablesDropdownList.removeAllItems(); numEntriesField.setText(""); - + // close DB connection to file if (null != connection) { try { @@ -212,23 +223,23 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { LOGGER.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS } } - + // delete last temp file if (null != tmpDBFile) { tmpDBFile.delete(); tmpDBFile = null; } } - + private void customizeComponents() { - + // add a actionListener to jTablesComboBox } - + /** * Process the given SQLite DB file * - * @param sqliteFile - + * @param sqliteFile - * * @return none */ @@ -239,15 +250,13 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { new SwingWorker() { @Override protected Boolean doInBackground() throws Exception { - + try { + // Copy the file to temp folder tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId(); tmpDBFile = new File(tmpDBPathName); - - // Copy the file to temp folder ContentUtils.writeToFile(sqliteFile, tmpDBFile); - System.out.println("RAMAN: SQLite file copied to: " + tmpDBPathName); - + // Open copy using JDBC Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS @@ -266,18 +275,17 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override protected void done() { - super.done(); + super.done(); try { boolean status = get(); - if ( (status == true) && (dbTablesMap.size() > 0) ) { + if ((status == true) && (dbTablesMap.size() > 0)) { dbTablesMap.keySet().forEach((tableName) -> { tablesDropdownList.addItem(tableName); }); - } - else { + } else { // Populate error message - tablesDropdownList.addItem("No tables found"); - tablesDropdownList.setEnabled(false); + tablesDropdownList.addItem("No tables found"); + tablesDropdownList.setEnabled(false); } } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while opening DB file", ex); //NON-NLS @@ -286,68 +294,66 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }.execute(); } - + /** * Gets the table names and their schema from loaded SQLite db file * * @return true if success, false otherwise */ private boolean getTables() { - + try { Statement statement = connection.createStatement(); - - ResultSet resultSet = statement.executeQuery( - "SELECT name, sql FROM sqlite_master " - + " WHERE type= 'table' " - + " ORDER BY name;"); //NON-NLS - while (resultSet.next()) { - String tableName = resultSet.getString("name"); //NON-NLS - String tableSQL = resultSet.getString("sql"); //NON-NLS - - dbTablesMap.put(tableName, tableSQL); - String query = "PRAGMA table_info(" + tableName + ")"; //NON-NLS - ResultSet rs2; - try { - Statement statement2 = connection.createStatement(); - rs2 = statement2.executeQuery(query); - while (rs2.next()) { - - System.out.println("RAMAN: Col Name = " + rs2.getString("name") ); - System.out.println("RAMAN: Col Type = " + rs2.getString("type") ); - - // RAMAN TBD: parse and save the table schema - } - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS + ResultSet resultSet = statement.executeQuery( + "SELECT name, sql FROM sqlite_master " + + " WHERE type= 'table' " + + " ORDER BY name;"); //NON-NLS + + while (resultSet.next()) { + String tableName = resultSet.getString("name"); //NON-NLS + String tableSQL = resultSet.getString("sql"); //NON-NLS + + dbTablesMap.put(tableName, tableSQL); + String query = "PRAGMA table_info(" + tableName + ")"; //NON-NLS + ResultSet rs2; + try { + Statement statement2 = connection.createStatement(); + rs2 = statement2.executeQuery(query); + while (rs2.next()) { + + // System.out.println("RAMAN: Col Name = " + rs2.getString("name")); + // System.out.println("RAMAN: Col Type = " + rs2.getString("type")); + + // RAMAN TBD: parse and save the table schema } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS } - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS + } return true; } - + private void readTable(String tableName) { - - System.out.println("RAMAN: selected table = " + tableName); - // TBD: need to handle cancelling if one is already in progress + new SwingWorker() { @Override protected Integer doInBackground() throws Exception { - + try { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery( - "SELECT COUNT(*) as count FROM " + tableName ); //NON-NLS + "SELECT COUNT(*) as count FROM " + tableName); //NON-NLS - System.out.println("Row count = " + resultSet.getInt("count") ); - // Read all table names and schema + // TBD: read the rows here and popluate the ExplorerManager. + return resultSet.getInt("count"); - }catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Failed to Open DB.", ex); //NON-NLS + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get data for table.", ex); //NON-NLS } //NON-NLS return 0; @@ -355,35 +361,35 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override protected void done() { - super.done(); + super.done(); try { int numRows = get(); - numEntriesField.setText( numRows + " entries"); + numEntriesField.setText(numRows + " entries"); } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS } } }.execute(); - + } - + enum SQLStorageClass { NULL, - INTEGER, + INTEGER, REAL, TEXT, BLOB }; - + private class SQLColDef { - + private final String colName; private final SQLStorageClass storageClass; - - SQLColDef(String colName, SQLStorageClass sc ) { + + SQLColDef(String colName, SQLStorageClass sc) { this.colName = colName; this.storageClass = sc; } - + } } From 8fe3d587dd18195bbbaa96076f02ada8f8e0b31f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 11 Jan 2018 15:40:09 -0500 Subject: [PATCH 11/71] 3408 make search tip more helpful and fix comments --- .../org/sleuthkit/autopsy/casemodule/Bundle.properties | 2 +- .../org/sleuthkit/autopsy/casemodule/CaseBrowser.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index ab2b5c12dc..2d87cd9a06 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -229,5 +229,5 @@ CueBannerPanel.openCaseButton.text= CueBannerPanel.openCaseLabel.text=Open Case MultiUserCasesPanel.bnOpenSingleUserCase.text=Open Single-User Case... CueBannerPanel.newCaseButton.text= -MultiUserCasesPanel.searchLabel.text=Start typing to search by case name +MultiUserCasesPanel.searchLabel.text=Select any case and start typing to search by case name MultiUserCasesPanel.cancelButton.text=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 9440ca1356..b25a231e83 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -18,11 +18,9 @@ */ package org.sleuthkit.autopsy.casemodule; -import java.awt.Component; import java.lang.reflect.InvocationTargetException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -100,7 +98,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider dateColumnIndex = index; } } - //Hide path column by default will need to + //Hide path column by default (user can unhide it) ETableColumn column = (ETableColumn) columnModel.getColumn(originalPathColumnIndex); ((ETableColumnModel) columnModel).setColumnHidden(column, true); outline.setRootVisible(false); @@ -124,6 +122,11 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider outline.getSelectionModel().addListSelectionListener(listener); } + /** + * Get the path to the .aut file for the selected case. + * + * @return the full path to the selected case's .aut file + */ String getCasePath() { int[] selectedRows = outline.getSelectedRows(); if (selectedRows.length == 1) { From d865b8ac7f23d446d91dae5ab4e91e027723bd62 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Tue, 16 Jan 2018 16:10:28 -0500 Subject: [PATCH 12/71] 3449: Use local variable instead of override a base format directory string. --- .../autopsy/report/ReportGenerator.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 1bb05652d4..ef20238c48 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -63,7 +63,7 @@ class ReportGenerator { */ private ReportProgressPanel progressPanel; - private String reportPathFormatString; + private static final String REPORT_PATH_FMT_STR = "%s" + File.separator + "%s %s %s" + File.separator; private final ReportGenerationPanel reportGenerationPanel = new ReportGenerationPanel(); static final String REPORTS_DIR = "Reports"; //NON-NLS @@ -89,12 +89,6 @@ class ReportGenerator { * Creates a report generator. */ ReportGenerator() { - // Create the root reports directory path of the form: /Reports/ / - DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); - Date date = new Date(); - String dateNoTime = dateFormat.format(date); - this.reportPathFormatString = currentCase.getReportDirectory() + File.separator + currentCase.getDisplayName() + " %s " + dateNoTime + File.separator; - this.errorList = new ArrayList<>(); } @@ -138,10 +132,10 @@ class ReportGenerator { */ void generateGeneralReport(GeneralReportModule generalReportModule) throws IOException { if (generalReportModule != null) { - reportPathFormatString = createReportDirectory(generalReportModule, reportPathFormatString); - setupProgressPanel(generalReportModule); + String reportDir = createReportDirectory(generalReportModule); + setupProgressPanel(generalReportModule, reportDir); ReportWorker worker = new ReportWorker(() -> { - generalReportModule.generateReport(reportPathFormatString, progressPanel); + generalReportModule.generateReport(reportDir, progressPanel); }); worker.execute(); displayProgressPanel(); @@ -158,10 +152,10 @@ class ReportGenerator { */ void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) throws IOException { if (tableReport != null && null != artifactTypeSelections) { - reportPathFormatString = createReportDirectory(tableReport, reportPathFormatString); - setupProgressPanel(tableReport); + String reportDir = createReportDirectory(tableReport); + setupProgressPanel(tableReport, reportDir); ReportWorker worker = new ReportWorker(() -> { - tableReport.startReport(reportPathFormatString); + tableReport.startReport(reportDir); TableReportGenerator generator = new TableReportGenerator(artifactTypeSelections, tagNameSelections, progressPanel, tableReport); generator.execute(); tableReport.endReport(); @@ -182,14 +176,14 @@ class ReportGenerator { */ void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) throws IOException { if (fileReportModule != null && null != enabledInfo) { - reportPathFormatString = createReportDirectory(fileReportModule, reportPathFormatString); + String reportDir = createReportDirectory(fileReportModule); List enabled = new ArrayList<>(); for (Entry e : enabledInfo.entrySet()) { if (e.getValue()) { enabled.add(e.getKey()); } } - setupProgressPanel(fileReportModule); + setupProgressPanel(fileReportModule, reportDir); ReportWorker worker = new ReportWorker(() -> { if (progressPanel.getStatus() != ReportStatus.CANCELED) { progressPanel.start(); @@ -200,7 +194,7 @@ class ReportGenerator { List files = getFiles(); int numFiles = files.size(); if (progressPanel.getStatus() != ReportStatus.CANCELED) { - fileReportModule.startReport(reportPathFormatString); + fileReportModule.startReport(reportDir); fileReportModule.startTable(enabled); } progressPanel.setIndeterminate(false); @@ -255,17 +249,22 @@ class ReportGenerator { } } - private void setupProgressPanel(ReportModule module) { + private void setupProgressPanel(ReportModule module, String reportDir) { String reportFilePath = module.getRelativeFilePath(); if (!reportFilePath.isEmpty()) { - this.progressPanel = reportGenerationPanel.addReport(module.getName(), String.format(reportPathFormatString, module.getName()) + reportFilePath); + this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportDir + reportFilePath); } else { this.progressPanel = reportGenerationPanel.addReport(module.getName(), null); } } - private static String createReportDirectory(ReportModule module, String pathFormat) throws IOException { - String reportPath = String.format(pathFormat, module.getName()); + private static String createReportDirectory(ReportModule module) throws IOException { + Case currentCase = Case.getCurrentCase(); + // Create the root reports directory path of the form: /Reports/ / + DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); + Date date = new Date(); + String dateNoTime = dateFormat.format(date); + String reportPath = String.format(REPORT_PATH_FMT_STR, currentCase.getReportDirectory(), currentCase.getDisplayName(), module.getName(), dateNoTime); // Create the root reports directory. try { FileUtil.createFolder(new File(reportPath)); From c4bb6dba614753e83cc87c20ca3f67729a0d0ec3 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 19 Jan 2018 13:01:08 -0500 Subject: [PATCH 13/71] 3415: Show SQLite table contents - First cut. --- .../autopsy/contentviewers/Bundle.properties | 8 +- .../contentviewers/SQLiteTableRowFactory.java | 89 ++++++ .../contentviewers/SQLiteTableView.form | 34 ++ .../contentviewers/SQLiteTableView.java | 143 +++++++++ .../autopsy/contentviewers/SQLiteViewer.form | 189 +++++++---- .../autopsy/contentviewers/SQLiteViewer.java | 299 +++++++++++++----- 6 files changed, 619 insertions(+), 143 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form create mode 100644 Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 8267587395..68e938a107 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -35,5 +35,11 @@ MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageContentViewer.viewInNewWindowButton.text=View in New Window JPEGViewerDummy.jLabel1.text=You are looking at a JPEG file: JPEGViewerDummy.jTextField1.text=jTextField1 -SQLiteViewer.jLabel1.text=Table +SQLiteViewer.nextPageButton.text= +SQLiteViewer.prevPageButton.text= +SQLiteViewer.numPagesLabel.text=N +SQLiteViewer.jLabel3.text=of +SQLiteViewer.currPageLabel.text=x +SQLiteViewer.jLabel2.text=Page SQLiteViewer.numEntriesField.text=num Entries +SQLiteViewer.jLabel1.text=Table diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java new file mode 100644 index 0000000000..98b21149ce --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -0,0 +1,89 @@ +/* + * 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.contentviewers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +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.datamodel.NodeProperty; + +public class SQLiteTableRowFactory extends ChildFactory { + + private final ArrayList> rows; + + public SQLiteTableRowFactory(ArrayList> rows) { + this.rows = rows; + } + + @Override + protected boolean createKeys(List keys) { + if (rows != null) { + for (int i = 0; i < rows.size(); i++) { + keys.add(i); + } + } + return true; + } + + @Override + protected Node createNodeForKey(Integer key) { + if (Objects.isNull(rows) || rows.isEmpty() || key >= rows.size()) { + return null; + } + + return new SQLiteTableRowNode(rows.get(key)); + } + +} + +class SQLiteTableRowNode extends AbstractNode { + + private final Map row; + + public SQLiteTableRowNode(Map row) { + super(Children.LEAF); + this.row = row; + } + + @Override + protected Sheet createSheet() { + + Sheet s = super.createSheet(); + Sheet.Set properties = s.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + s.put(properties); + } + + for (Map.Entry col : row.entrySet()) { + String colName = col.getKey(); + String colVal = col.getValue().toString(); + + properties.put(new NodeProperty<>(colName, colName, colName, colVal)); // NON-NLS + } + + return s; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form new file mode 100644 index 0000000000..a34fd71d16 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form @@ -0,0 +1,34 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java new file mode 100644 index 0000000000..b2fdc912d5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -0,0 +1,143 @@ +/* + * 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.contentviewers; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import javax.swing.ListSelectionModel; +import javax.swing.table.TableColumnModel; +import org.netbeans.swing.etable.ETableColumn; +import org.netbeans.swing.etable.ETableColumnModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; + + +public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManager.Provider { + + private final Outline outline; + private final org.openide.explorer.view.OutlineView outlineView; + private ExplorerManager explorerManager; + + private final ArrayList> tableRows; + + /** + * Creates new form SQLiteTableView + * @param rows + */ + public SQLiteTableView(ArrayList> rows) { + + this.tableRows = rows; + + outlineView = new org.openide.explorer.view.OutlineView(); + + initComponents(); + + outline = outlineView.getOutline(); + outlineView.setPropertyColumns(); // column headers will be set later + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + customize(); + } + + private void customize() { + + tableScrollPane.setViewportView(outlineView); + this.setVisible(true); + outline.setRowSelectionAllowed(false); + outline.setRootVisible(false); + + explorerManager = new ExplorerManager(); + explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); + + setupColumns(); + } + + /** + * Sets up the columns in the display table + */ + private void setupColumns() { + if (Objects.isNull(tableRows) || tableRows.isEmpty()) + return; + + Map row = tableRows.get(0); + + // Get the columns setup with respect to names and sortability + String[] propStrings = new String[row.size() * 2]; + + int i = 0; + for (Map.Entry col: row.entrySet()) { + String colName = col.getKey(); + propStrings[2 * i] = colName; + propStrings[2 * i + 1] = colName; + i++; + } + + + outlineView.setPropertyColumns(propStrings); + + // RAMAN TBD: Set width based on actual data in the top N rows?? + // TBD: Cant seem to geta horizontal scroll bar + for (int col = 0; col< outline.getModel().getColumnCount(); col++) { + outline.getColumnModel().getColumn(col).setMinWidth(50); + } + + // Hide the 'Nodes' column + TableColumnModel columnModel = outline.getColumnModel(); + ETableColumn column = (ETableColumn) columnModel.getColumn(0); + ((ETableColumnModel) columnModel).setColumnHidden(column, true); + + } + + /** + * 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() { + + tableScrollPane = new javax.swing.JScrollPane(); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane tableScrollPane; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form index 3cdee8658c..121a96c823 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form @@ -1,6 +1,6 @@ -
+ @@ -16,22 +16,27 @@ - - + + - - - + + + + + + + + @@ -39,11 +44,23 @@ - + - + - + + + + + + + + + + + + + @@ -55,8 +72,14 @@ + + + + + + - + @@ -97,57 +120,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index fff2f5d781..a60037fad1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -1,21 +1,4 @@ -/* - * 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.contentviewers; import java.awt.Component; @@ -27,12 +10,17 @@ import java.util.logging.Level; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.Statement; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import javax.swing.JComboBox; +import javax.swing.JPanel; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; @@ -44,20 +32,21 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); private Connection connection = null; - + private String tmpDBPathName = null; private File tmpDBFile = null; - // TBD: Change the value to be a Array of ColDefs Map dbTablesMap = new TreeMap<>(); + private static final int ROWS_PER_PAGE = 100; + private int numRows; // num of rows in the selected table + private int currPage = 0; // curr page of rows being displayed + /** * Creates new form SQLiteViewer */ public SQLiteViewer() { initComponents(); - - customizeComponents(); } /** @@ -73,9 +62,15 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { tablesDropdownList = new javax.swing.JComboBox<>(); jLabel1 = new javax.swing.JLabel(); numEntriesField = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + currPageLabel = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + numPagesLabel = new javax.swing.JLabel(); + prevPageButton = new javax.swing.JButton(); + nextPageButton = new javax.swing.JButton(); jTableDataPanel = new javax.swing.JPanel(); - jScrollPane1 = new javax.swing.JScrollPane(); - jTable1 = new javax.swing.JTable(); + + jHdrPanel.setPreferredSize(new java.awt.Dimension(536, 40)); tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); tablesDropdownList.addActionListener(new java.awt.event.ActionListener() { @@ -89,9 +84,38 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { numEntriesField.setEditable(false); numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N numEntriesField.setBorder(null); - numEntriesField.addActionListener(new java.awt.event.ActionListener() { + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel2.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(currPageLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.currPageLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel3.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numPagesLabel.text")); // NOI18N + + prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(prevPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.prevPageButton.text")); // NOI18N + prevPageButton.setBorderPainted(false); + prevPageButton.setContentAreaFilled(false); + prevPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N + prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23)); + prevPageButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - numEntriesFieldActionPerformed(evt); + prevPageButtonActionPerformed(evt); + } + }); + + nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(nextPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.nextPageButton.text")); // NOI18N + nextPageButton.setBorderPainted(false); + nextPageButton.setContentAreaFilled(false); + nextPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N + nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); + nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23)); + nextPageButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + nextPageButtonActionPerformed(evt); } }); @@ -102,11 +126,23 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { .addGroup(jHdrPanelLayout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(23, 23, 23) + .addGap(18, 18, 18) .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(130, Short.MAX_VALUE)) + .addGap(15, 15, 15) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(currPageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(numPagesLabel) + .addGap(18, 18, 18) + .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(133, Short.MAX_VALUE)) ); jHdrPanelLayout.setVerticalGroup( jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -115,40 +151,18 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel1) - .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(16, Short.MAX_VALUE)) - ); - - jTable1.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - {null, null, null, null}, - {null, null, null, null}, - {null, null, null, null}, - {null, null, null, null} - }, - new String [] { - "Title 1", "Title 2", "Title 3", "Title 4" - } - )); - jScrollPane1.setViewportView(jTable1); - - javax.swing.GroupLayout jTableDataPanelLayout = new javax.swing.GroupLayout(jTableDataPanel); - jTableDataPanel.setLayout(jTableDataPanelLayout); - jTableDataPanelLayout.setHorizontalGroup( - jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jTableDataPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGap(15, 15, 15)) - ); - jTableDataPanelLayout.setVerticalGroup( - jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jTableDataPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 275, Short.MAX_VALUE) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel2) + .addComponent(currPageLabel) + .addComponent(jLabel3) + .addComponent(numPagesLabel) + .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); + jTableDataPanel.setLayout(new javax.swing.OverlayLayout(jTableDataPanel)); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -159,15 +173,39 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE)) ); }// //GEN-END:initComponents - private void numEntriesFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numEntriesFieldActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_numEntriesFieldActionPerformed + private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed + + currPage++; + if (currPage * ROWS_PER_PAGE > numRows) { + nextPageButton.setEnabled(false); + } + currPageLabel.setText(Integer.toString(currPage)); + prevPageButton.setEnabled(true); + + // read and display a page of rows + String tableName = (String) this.tablesDropdownList.getSelectedItem(); + readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + }//GEN-LAST:event_nextPageButtonActionPerformed + + private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed + + currPage--; + if (currPage == 1) { + prevPageButton.setEnabled(false); + } + currPageLabel.setText(Integer.toString(currPage)); + nextPageButton.setEnabled(true); + + // read and display a page of rows + String tableName = (String) this.tablesDropdownList.getSelectedItem(); + readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + }//GEN-LAST:event_prevPageButtonActionPerformed private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed JComboBox cb = (JComboBox) evt.getSource(); @@ -176,17 +214,21 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { return; } - readTable(tableName); + selectTable(tableName); }//GEN-LAST:event_tablesDropdownListActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel currPageLabel; private javax.swing.JPanel jHdrPanel; private javax.swing.JLabel jLabel1; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JTable jTable1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; private javax.swing.JPanel jTableDataPanel; + private javax.swing.JButton nextPageButton; private javax.swing.JTextField numEntriesField; + private javax.swing.JLabel numPagesLabel; + private javax.swing.JButton prevPageButton; private javax.swing.JComboBox tablesDropdownList; // End of variables declaration//GEN-END:variables @@ -209,11 +251,11 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public void resetComponent() { dbTablesMap.clear(); - + tablesDropdownList.setEnabled(true); tablesDropdownList.removeAllItems(); numEntriesField.setText(""); - + // close DB connection to file if (null != connection) { try { @@ -231,11 +273,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } } - private void customizeComponents() { - - // add a actionListener to jTablesComboBox - } - /** * Process the given SQLite DB file * @@ -327,30 +364,26 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { // RAMAN TBD: parse and save the table schema } - } catch (Exception ex) { + } catch (SQLException ex) { LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS } } - } catch (Exception e) { + } catch (SQLException e) { LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS } return true; } - private void readTable(String tableName) { - // TBD: need to handle cancelling if one is already in progress - - new SwingWorker() { + private void selectTable(String tableName) { + new SwingWorker() { @Override protected Integer doInBackground() throws Exception { try { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery( - "SELECT COUNT(*) as count FROM " + tableName); //NON-NLS + "SELECT count (*) as count FROM " + tableName); //NON-NLS - // TBD: read the rows here and popluate the ExplorerManager. - return resultSet.getInt("count"); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Failed to get data for table.", ex); //NON-NLS @@ -363,8 +396,70 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { protected void done() { super.done(); try { - int numRows = get(); + + numRows = get(); numEntriesField.setText(numRows + " entries"); + + currPage = 1; + currPageLabel.setText(Integer.toString(currPage)); + numPagesLabel.setText(Integer.toString((numRows/ROWS_PER_PAGE)+1)); + + prevPageButton.setEnabled(false); + + jTableDataPanel.removeAll(); + jTableDataPanel.repaint(); + + if (numRows > 0) { + nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); + readTable(tableName, (currPage-1)*ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + } + else { + nextPageButton.setEnabled(false); + } + + + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS + } + } + }.execute(); + } + private void readTable(String tableName, int startRow, int numRowsToRead) { + + // TBD: need to handle cancelling if one is already in progress + + new SwingWorker>, Void>() { + @Override + protected ArrayList> doInBackground() throws Exception { + try { + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT * FROM " + tableName + + " LIMIT " + Integer.toString(numRowsToRead) + + " OFFSET " + Integer.toString(startRow - 1) + ); //NON-NLS + + return resultSetToArrayList(resultSet); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get data for table.", ex); //NON-NLS + } + //NON-NLS + return null; + } + + @Override + protected void done() { + super.done(); + try { + ArrayList> rows = get(); + if (Objects.nonNull(rows)) { + JPanel selectedTableView = new SQLiteTableView(rows); + + jTableDataPanel.removeAll(); + jTableDataPanel.setLayout(new javax.swing.OverlayLayout(jTableDataPanel)); + jTableDataPanel.add(selectedTableView); + jTableDataPanel.repaint(); + } } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS } @@ -373,6 +468,29 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } + private ArrayList> resultSetToArrayList(ResultSet rs) throws SQLException { + ResultSetMetaData md = rs.getMetaData(); + int columns = md.getColumnCount(); + ArrayList> arraylist = new ArrayList<>(); + while (rs.next()) { + Map row = new LinkedHashMap<>(columns); + for (int i = 1; i <= columns; ++i) { + if (rs.getObject(i) == null) { + row.put(md.getColumnName(i), ""); + } else { + if (md.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) { + row.put(md.getColumnName(i), "BLOB Data not shown..."); + } else { + row.put(md.getColumnName(i), rs.getObject(i)); + } + } + } + arraylist.add(row); + } + + return arraylist; + } + enum SQLStorageClass { NULL, INTEGER, @@ -390,6 +508,13 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { this.colName = colName; this.storageClass = sc; } - + + public String getColName() { + return colName; + } + + public SQLStorageClass getColStorageClass() { + return storageClass; + } } } From b8d9efb181ffb99b59eed5e0007afd6b88ae5ef2 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 22 Jan 2018 10:19:06 -0500 Subject: [PATCH 14/71] Fixing scrollbar on the SQLiteTableView. - Removes the doubles vertical scroll bar. - A horizontal scroll bar appears but it is disabled and not functional --- .../contentviewers/SQLiteTableView.form | 25 +++++------------ .../contentviewers/SQLiteTableView.java | 28 ++++++++----------- 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form index a34fd71d16..75cb61a443 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form @@ -1,6 +1,11 @@
+ + + + + @@ -11,24 +16,8 @@ + - - - - - - - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index b2fdc912d5..dc5d6c97af 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.contentviewers; +import java.awt.BorderLayout; import java.util.ArrayList; import java.util.Map; import java.util.Objects; @@ -48,9 +49,12 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag this.tableRows = rows; outlineView = new org.openide.explorer.view.OutlineView(); - + outlineView.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + initComponents(); + add(outlineView,BorderLayout.CENTER); + outline = outlineView.getOutline(); outlineView.setPropertyColumns(); // column headers will be set later outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -60,11 +64,12 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag private void customize() { - tableScrollPane.setViewportView(outlineView); + //tableScrollPane.setViewportView(outlineView); + this.setVisible(true); outline.setRowSelectionAllowed(false); outline.setRootVisible(false); - + explorerManager = new ExplorerManager(); explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); @@ -95,7 +100,7 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag outlineView.setPropertyColumns(propStrings); // RAMAN TBD: Set width based on actual data in the top N rows?? - // TBD: Cant seem to geta horizontal scroll bar + // TBD: Can't seem to geta horizontal scroll bar for (int col = 0; col< outline.getModel().getColumnCount(); col++) { outline.getColumnModel().getColumn(col).setMinWidth(50); } @@ -116,18 +121,8 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag // //GEN-BEGIN:initComponents private void initComponents() { - tableScrollPane = new javax.swing.JScrollPane(); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) - ); + setPreferredSize(new java.awt.Dimension(600, 400)); + setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents @@ -138,6 +133,5 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane tableScrollPane; // End of variables declaration//GEN-END:variables } From 06132c8e825f303c33120cdaa99a59a938addbd7 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 22 Jan 2018 12:22:21 -0500 Subject: [PATCH 15/71] 3448 vs Relase 4.6.0: retain more log files --- .../autopsy/core/UserPreferences.java | 28 +- .../corecomponents/AutopsyOptionsPanel.form | 145 ++++++--- .../corecomponents/AutopsyOptionsPanel.java | 278 +++++++++++------- .../autopsy/corecomponents/Bundle.properties | 1 + .../sleuthkit/autopsy/coreutils/Logger.java | 66 +---- 5 files changed, 325 insertions(+), 193 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 725c050563..81d207abae 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2017 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,7 +67,9 @@ public final class UserPreferences { private static final String APP_NAME = "AppName"; public static final String SETTINGS_PROPERTIES = "AutoIngest"; private static final String MODE = "AutopsyMode"; // NON-NLS - + private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; + private static final int LOG_FILE_NUM_INT = 10; + // Prevent instantiation. private UserPreferences() { } @@ -369,4 +371,26 @@ public final class UserPreferences { preferences.put(APP_NAME, name); } + /** + * get the maximum number of log files to save + * @return Number of log files + */ + public static int getLogFileCount() { + return preferences.getInt(MAX_NUM_OF_LOG_FILE, LOG_FILE_NUM_INT); + } + + /** + * get the default number of log files to save + * @return LOG_FILE_COUNT + */ + public static int getDefaultLogFileCount() { + return LOG_FILE_NUM_INT; + } + /** + * Set the maximum number of log files to save + * @param count number of log files + */ + public static void setLogFileCount(int count) { + preferences.putInt(MAX_NUM_OF_LOG_FILE, count); + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index c1fc384c5e..36b88c10ca 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -24,7 +24,7 @@ - + @@ -44,18 +44,26 @@ + + + + + - + - + - - + + + + + - + @@ -63,10 +71,11 @@ - - - - + + + + + @@ -245,7 +254,7 @@ - +
@@ -436,28 +445,43 @@ - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + @@ -466,22 +490,29 @@ + - - - - - - - - - - + + + + + + + + + + + + + + + +
@@ -545,6 +576,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 17aad44582..f4e2a38671 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -73,7 +73,9 @@ import org.sleuthkit.autopsy.report.ReportBranding; "AutopsyOptionsPanel.browseLogosButton.text=Browse", "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidPath.text=Path is not valid.", "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.invalidImageSpecified.text=Invalid image file specified.", - "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.pathNotSet.text=Agency logo path must be set." + "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.pathNotSet.text=Agency logo path must be set.", + "AutopsyOptionsPanel.maxLogFileCount.text=Maximum Log Files:", + "AutopsyOptionsPanel.logNumAlert.invalidInput.text=A positive integer is required here." }) final class AutopsyOptionsPanel extends javax.swing.JPanel { @@ -110,6 +112,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { textFieldListener = new TextFieldListener(); agencyLogoPathField.getDocument().addDocumentListener(textFieldListener); + logFileCount.setText(String.valueOf(UserPreferences.getLogFileCount())); } /** @@ -309,6 +312,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { specifyLogoRB.setSelected(!useDefault); agencyLogoPathField.setEnabled(!useDefault); browseLogosButton.setEnabled(!useDefault); + logFileCount.setText(String.valueOf(UserPreferences.getLogFileCount())); try { updateAgencyLogo(path); } catch (IOException ex) { @@ -363,6 +367,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { UserPreferences.setHideSlackFilesInDataSourcesTree(dataSourcesHideSlackCB.isSelected()); UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCB.isSelected()); UserPreferences.setDisplayTimesInLocalTime(useLocalTimeRB.isSelected()); + UserPreferences.setLogFileCount(Integer.parseInt(logFileCount.getText())); if (!agencyLogoPathField.getText().isEmpty()) { File file = new File(agencyLogoPathField.getText()); if (file.exists()) { @@ -394,6 +399,9 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { if (!isMemFieldValid()) { valid = false; } + if (!isLogNumFieldValid()) { + valid = false; + } return valid; } @@ -476,6 +484,26 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { return true; } + /** + * Check if the logFileCount field is valid. + * + * @return true if the logFileCount is valid false if it is not + */ + private boolean isLogNumFieldValid() { + String count = logFileCount.getText(); + logNumAlert.setText(""); + try { + int count_num = Integer.parseInt(count); + if (count_num < 1) { + logNumAlert.setText(Bundle.AutopsyOptionsPanel_logNumAlert_invalidInput_text()); + return false; + } + } catch (NumberFormatException e) { + logNumAlert.setText(Bundle.AutopsyOptionsPanel_logNumAlert_invalidInput_text()); + return false; + } + return true; + } /** * Listens for registered text fields that have changed and fires a * PropertyChangeEvent accordingly. @@ -540,9 +568,14 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { memField = new javax.swing.JTextField(); memFieldValidationLabel = new javax.swing.JLabel(); maxMemoryUnitsLabel1 = new javax.swing.JLabel(); + maxLogFileCount = new javax.swing.JLabel(); + logFileCount = new javax.swing.JTextField(); + logNumAlert = new javax.swing.JTextField(); jScrollPane1.setBorder(null); + jPanel1.setPreferredSize(new java.awt.Dimension(671, 488)); + logoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logoPanel.border.title"))); // NOI18N agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N @@ -712,7 +745,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { .addComponent(dataSourcesHideSlackCB) .addComponent(viewsHideSlackCB) .addComponent(useLocalTimeRB)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap(158, Short.MAX_VALUE)))) .addGroup(viewPanelLayout.createSequentialGroup() .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabelHideSlackFiles) @@ -774,68 +807,103 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(maxMemoryUnitsLabel1, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxMemoryUnitsLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(maxLogFileCount, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxLogFileCount.text")); // NOI18N + + logFileCount.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + logFileCount.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + logFileCountKeyReleased(evt); + } + }); + + logNumAlert.setEditable(false); + logNumAlert.setFont(logNumAlert.getFont().deriveFont(logNumAlert.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + logNumAlert.setForeground(new java.awt.Color(255, 0, 0)); + logNumAlert.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logNumAlert.text")); // NOI18N + logNumAlert.setBorder(null); + javax.swing.GroupLayout runtimePanelLayout = new javax.swing.GroupLayout(runtimePanel); runtimePanel.setLayout(runtimePanelLayout); runtimePanelLayout.setHorizontalGroup( runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(runtimePanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(totalMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(memField, javax.swing.GroupLayout.DEFAULT_SIZE, 37, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE) - .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(totalMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(systemMemoryTotal, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(2, 2, 2) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 333, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 263, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)))) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(maxLogFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(logFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(logNumAlert))) + .addContainerGap()) ); runtimePanelLayout.setVerticalGroup( runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(runtimePanelLayout.createSequentialGroup() .addContainerGap() .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(maxMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(totalMemoryLabel) .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(totalMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(maxLogFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, 19, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(logFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(logNumAlert, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(runtimePanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(runtimePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addContainerGap()) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGap(0, 0, 0) - .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(runtimePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(runtimePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(logoPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); @@ -846,7 +914,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 1010, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -854,60 +922,14 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { ); }// //GEN-END:initComponents - private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useBestViewerRBActionPerformed - - private void keepCurrentViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_keepCurrentViewerRBActionPerformed - - private void dataSourcesHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_dataSourcesHideKnownCBActionPerformed - - private void viewsHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_viewsHideKnownCBActionPerformed - - private void useLocalTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useLocalTimeRBActionPerformed - - private void useGMTTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useGMTTimeRBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_useGMTTimeRBActionPerformed - - private void dataSourcesHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_dataSourcesHideSlackCBActionPerformed - - private void viewsHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCBActionPerformed - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_viewsHideSlackCBActionPerformed - - private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed - String oldLogoPath = agencyLogoPathField.getText(); - int returnState = fileChooser.showOpenDialog(this); - if (returnState == JFileChooser.APPROVE_OPTION) { - String path = fileChooser.getSelectedFile().getPath(); - try { - updateAgencyLogo(path); - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - } catch (IOException | IndexOutOfBoundsException ex) { - JOptionPane.showMessageDialog(null, - NbBundle.getMessage(this.getClass(), - "AutopsyOptionsPanel.invalidImageFile.msg"), - NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), - JOptionPane.ERROR_MESSAGE); - try { - updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid - } catch (IOException ex1) { - LOGGER.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); - } - } + private void logFileCountKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_logFileCountKeyReleased + String count = logFileCount.getText(); + if (count.equals(String.valueOf(UserPreferences.getDefaultLogFileCount()))) { + //if it is still the default value don't fire change + return; } - }//GEN-LAST:event_browseLogosButtonActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_logFileCountKeyReleased private void memFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_memFieldKeyReleased String memText = memField.getText(); @@ -918,17 +940,37 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_memFieldKeyReleased - private void defaultLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultLogoRBActionPerformed - agencyLogoPathField.setEnabled(false); - browseLogosButton.setEnabled(false); - try { - updateAgencyLogo(""); - } catch (IOException ex) { - // This should never happen since we're not reading from a file. - LOGGER.log(Level.SEVERE, "Unexpected error occurred while updating the agency logo.", ex); - } + private void useGMTTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useGMTTimeRBActionPerformed firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - }//GEN-LAST:event_defaultLogoRBActionPerformed + }//GEN-LAST:event_useGMTTimeRBActionPerformed + + private void useLocalTimeRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useLocalTimeRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useLocalTimeRBActionPerformed + + private void viewsHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideSlackCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_viewsHideSlackCBActionPerformed + + private void dataSourcesHideSlackCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideSlackCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_dataSourcesHideSlackCBActionPerformed + + private void viewsHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewsHideKnownCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_viewsHideKnownCBActionPerformed + + private void dataSourcesHideKnownCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourcesHideKnownCBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_dataSourcesHideKnownCBActionPerformed + + private void keepCurrentViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keepCurrentViewerRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_keepCurrentViewerRBActionPerformed + + private void useBestViewerRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useBestViewerRBActionPerformed + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_useBestViewerRBActionPerformed private void specifyLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specifyLogoRBActionPerformed agencyLogoPathField.setEnabled(true); @@ -946,6 +988,41 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_specifyLogoRBActionPerformed + private void defaultLogoRBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultLogoRBActionPerformed + agencyLogoPathField.setEnabled(false); + browseLogosButton.setEnabled(false); + try { + updateAgencyLogo(""); + } catch (IOException ex) { + // This should never happen since we're not reading from a file. + LOGGER.log(Level.SEVERE, "Unexpected error occurred while updating the agency logo.", ex); + } + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_defaultLogoRBActionPerformed + + private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed + String oldLogoPath = agencyLogoPathField.getText(); + int returnState = fileChooser.showOpenDialog(this); + if (returnState == JFileChooser.APPROVE_OPTION) { + String path = fileChooser.getSelectedFile().getPath(); + try { + updateAgencyLogo(path); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } catch (IOException | IndexOutOfBoundsException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(this.getClass(), + "AutopsyOptionsPanel.invalidImageFile.msg"), + NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), + JOptionPane.ERROR_MESSAGE); + try { + updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid + } catch (IOException ex1) { + LOGGER.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); + } + } + } + }//GEN-LAST:event_browseLogosButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField agencyLogoPathField; private javax.swing.JLabel agencyLogoPathFieldValidationLabel; @@ -963,8 +1040,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JRadioButton keepCurrentViewerRB; + private javax.swing.JTextField logFileCount; + private javax.swing.JTextField logNumAlert; private javax.swing.JPanel logoPanel; private javax.swing.ButtonGroup logoSourceButtonGroup; + private javax.swing.JLabel maxLogFileCount; private javax.swing.JLabel maxMemoryLabel; private javax.swing.JLabel maxMemoryUnitsLabel; private javax.swing.JLabel maxMemoryUnitsLabel1; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 6e9b722d71..1aecff7348 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -200,3 +200,4 @@ CriterionChooser.ascendingRadio.text=\u25b2 Ascending\n CriterionChooser.removeButton.text=Remove CriterionChooser.descendingRadio.text=\u25bc Descending AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.text= +AutopsyOptionsPanel.logNumAlert.text= diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java index 16fde58e29..710e7ff9a1 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2015 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,10 @@ import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Handler; import java.sql.Timestamp; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.logging.LogRecord; +import org.sleuthkit.autopsy.core.UserPreferences; /** * Autopsy specialization of the Java Logger class with custom file handlers. @@ -37,55 +37,23 @@ public final class Logger extends java.util.logging.Logger { private static final String LOG_ENCODING = PlatformUtil.getLogFileEncoding(); private static final int LOG_SIZE = 0; // In bytes, zero is unlimited - private static final int LOG_FILE_COUNT = 10; - private static final String LOG_WITHOUT_STACK_TRACES = "autopsy.log"; //NON-NLS - private static final String LOG_WITH_STACK_TRACES = "autopsy_traces.log"; //NON-NLS + private static final String LOG_FILE_NAME = "autopsy.log"; //NON-NLS private static final Map namesToLoggers = new HashMap<>(); private static final Handler consoleHandler = new java.util.logging.ConsoleHandler(); - private static FileHandler userFriendlyHandler = createFileHandlerWithoutTraces(PlatformUtil.getLogDirectory()); - private static FileHandler developerFriendlyHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); - - /** - * Creates a custom file handler with a custom message formatter that does - * not include stack traces. - * - * @param logDirectory The directory where the log files should reside. - * - * @return A custom file handler. - */ - private static FileHandler createFileHandlerWithoutTraces(String logDirectory) { - String logFilePath = Paths.get(logDirectory, LOG_WITHOUT_STACK_TRACES).toString(); - try { - FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); - fileHandler.setEncoding(LOG_ENCODING); - fileHandler.setFormatter(new Formatter() { - @Override - public String format(LogRecord record) { - return (new Date(record.getMillis())).toString() + " " - + record.getSourceClassName() + " " - + record.getSourceMethodName() + "\n" - + record.getLevel() + ": " - + this.formatMessage(record) + "\n"; - } - }); - return fileHandler; - } catch (IOException ex) { - throw new RuntimeException(String.format("Error initializing file handler for %s", logFilePath), ex); //NON-NLS - } - } + private static FileHandler fileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); /** * Creates a custom file handler with a custom message formatter that - * incldues stack traces. + * includes stack traces. * * @param logDirectory The directory where the log files should reside. * * @return A custom file handler. */ private static FileHandler createFileHandlerWithTraces(String logDirectory) { - String logFilePath = Paths.get(logDirectory, LOG_WITH_STACK_TRACES).toString(); + String logFilePath = Paths.get(logDirectory, LOG_FILE_NAME).toString(); try { - FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, LOG_FILE_COUNT); + FileHandler fileHandler = new FileHandler(logFilePath, LOG_SIZE, UserPreferences.getLogFileCount()); fileHandler.setEncoding(LOG_ENCODING); fileHandler.setFormatter(new Formatter() { @Override @@ -128,25 +96,20 @@ public final class Logger extends java.util.logging.Logger { * add/remove handler calls (currently, the base class handlers * collection is a CopyOnWriteArrayList). */ - FileHandler newUserFriendlyHandler = createFileHandlerWithoutTraces(directoryPath); - FileHandler newDeveloperFriendlyHandler = createFileHandlerWithTraces(directoryPath); + FileHandler newFileHandler = createFileHandlerWithTraces(directoryPath); for (Logger logger : namesToLoggers.values()) { - logger.addHandler(newUserFriendlyHandler); - logger.addHandler(newDeveloperFriendlyHandler); - logger.removeHandler(userFriendlyHandler); - logger.removeHandler(userFriendlyHandler); + logger.addHandler(newFileHandler); + logger.removeHandler(fileHandler); } /* * Close the old file handlers and save references to the new handlers * so they can be added to any new loggers. This swap is why this method * and the two overloads of getLogger() are synchronized, serializing - * access to userFriendlyHandler and developerFriendlyHandler. + * access to userFileHandler. */ - userFriendlyHandler.close(); - userFriendlyHandler = newUserFriendlyHandler; - developerFriendlyHandler.close(); - developerFriendlyHandler = newDeveloperFriendlyHandler; + fileHandler.close(); + fileHandler = newFileHandler; } /** @@ -178,8 +141,7 @@ public final class Logger extends java.util.logging.Logger { synchronized public static Logger getLogger(String name, String resourceBundleName) { if (!namesToLoggers.containsKey(name)) { Logger logger = new Logger(name, resourceBundleName); - logger.addHandler(userFriendlyHandler); - logger.addHandler(developerFriendlyHandler); + logger.addHandler(fileHandler); namesToLoggers.put(name, logger); } return namesToLoggers.get(name); From f472a7b2ae1787d3e1e884cdc8ceceb8759acbb9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 22 Jan 2018 13:34:40 -0500 Subject: [PATCH 16/71] 3448: Update comments for the fileHandler and variable names --- .../sleuthkit/autopsy/coreutils/Logger.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java index 710e7ff9a1..5153e613b2 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Logger.java @@ -30,7 +30,7 @@ import java.util.logging.LogRecord; import org.sleuthkit.autopsy.core.UserPreferences; /** - * Autopsy specialization of the Java Logger class with custom file handlers. + * Autopsy specialization of the Java Logger class with a custom file handler. * Note that the custom loggers are not obtained from the global log manager. */ public final class Logger extends java.util.logging.Logger { @@ -40,7 +40,7 @@ public final class Logger extends java.util.logging.Logger { private static final String LOG_FILE_NAME = "autopsy.log"; //NON-NLS private static final Map namesToLoggers = new HashMap<>(); private static final Handler consoleHandler = new java.util.logging.ConsoleHandler(); - private static FileHandler fileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); + private static FileHandler logFileHandler = createFileHandlerWithTraces(PlatformUtil.getLogDirectory()); /** * Creates a custom file handler with a custom message formatter that @@ -88,7 +88,7 @@ public final class Logger extends java.util.logging.Logger { */ synchronized public static void setLogDirectory(String directoryPath) { /* - * Create file handlers for the new directory and swap them into all of + * Create a file handler for the new directory and swap it into all of * the existing loggers using thread-safe Logger methods. The new * handlers are added before the old handlers so that no messages will * be lost, but this makes it possible for log messages to be written @@ -99,17 +99,17 @@ public final class Logger extends java.util.logging.Logger { FileHandler newFileHandler = createFileHandlerWithTraces(directoryPath); for (Logger logger : namesToLoggers.values()) { logger.addHandler(newFileHandler); - logger.removeHandler(fileHandler); + logger.removeHandler(logFileHandler); } /* - * Close the old file handlers and save references to the new handlers + * Close the old file handler and save reference to the new handler * so they can be added to any new loggers. This swap is why this method * and the two overloads of getLogger() are synchronized, serializing - * access to userFileHandler. + * access to logFileHandler. */ - fileHandler.close(); - fileHandler = newFileHandler; + logFileHandler.close(); + logFileHandler = newFileHandler; } /** @@ -141,7 +141,7 @@ public final class Logger extends java.util.logging.Logger { synchronized public static Logger getLogger(String name, String resourceBundleName) { if (!namesToLoggers.containsKey(name)) { Logger logger = new Logger(name, resourceBundleName); - logger.addHandler(fileHandler); + logger.addHandler(logFileHandler); namesToLoggers.put(name, logger); } return namesToLoggers.get(name); From 71c510c4916d0267a6515bacb2c7c0e410a2afb1 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 22 Jan 2018 13:39:02 -0500 Subject: [PATCH 17/71] 3415: Show SQLite table contents. - Cleanup --- .../contentviewers/SQLiteTableView.java | 4 +- .../autopsy/contentviewers/SQLiteViewer.java | 80 ++++++++----------- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index dc5d6c97af..c795133f73 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -99,8 +99,8 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag outlineView.setPropertyColumns(propStrings); - // RAMAN TBD: Set width based on actual data in the top N rows?? - // TBD: Can't seem to geta horizontal scroll bar + // TBD: Set width based on actual data in the top N rows?? + // TBD: Can't seem to get the horizontal scrollbar working for (int col = 0; col< outline.getModel().getColumnCount(); col++) { outline.getColumnModel().getColumn(col).setMinWidth(50); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index a60037fad1..5d90562834 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -1,3 +1,21 @@ +/* + * 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.contentviewers; @@ -42,6 +60,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private int numRows; // num of rows in the selected table private int currPage = 0; // curr page of rows being displayed + private SwingWorker>, Void> worker; /** * Creates new form SQLiteViewer */ @@ -352,21 +371,6 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { String tableSQL = resultSet.getString("sql"); //NON-NLS dbTablesMap.put(tableName, tableSQL); - String query = "PRAGMA table_info(" + tableName + ")"; //NON-NLS - ResultSet rs2; - try { - Statement statement2 = connection.createStatement(); - rs2 = statement2.executeQuery(query); - while (rs2.next()) { - - // System.out.println("RAMAN: Col Name = " + rs2.getString("name")); - // System.out.println("RAMAN: Col Type = " + rs2.getString("type")); - - // RAMAN TBD: parse and save the table schema - } - } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS - } } } catch (SQLException e) { LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS @@ -426,9 +430,12 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } private void readTable(String tableName, int startRow, int numRowsToRead) { - // TBD: need to handle cancelling if one is already in progress - - new SwingWorker>, Void>() { + if (worker != null && !worker.isDone()) { + worker.cancel(false); + worker = null; + } + + worker = new SwingWorker>, Void>() { @Override protected ArrayList> doInBackground() throws Exception { try { @@ -449,6 +456,11 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override protected void done() { + + if (isCancelled()) { + return; + } + super.done(); try { ArrayList> rows = get(); @@ -464,8 +476,9 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS } } - }.execute(); - + }; + + worker.execute(); } private ArrayList> resultSetToArrayList(ResultSet rs) throws SQLException { @@ -490,31 +503,4 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { return arraylist; } - - enum SQLStorageClass { - NULL, - INTEGER, - REAL, - TEXT, - BLOB - }; - - private class SQLColDef { - - private final String colName; - private final SQLStorageClass storageClass; - - SQLColDef(String colName, SQLStorageClass sc) { - this.colName = colName; - this.storageClass = sc; - } - - public String getColName() { - return colName; - } - - public SQLStorageClass getColStorageClass() { - return storageClass; - } - } } From eefc085bc1846719713cc5bd418ca785cd26e17e Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 22 Jan 2018 15:55:12 -0500 Subject: [PATCH 18/71] 3501-Remove status column from open multi-user case browser --- .../autopsy/casemodule/CaseBrowser.java | 74 ++++--------------- .../autopsy/casemodule/MultiUserNode.java | 41 ++++------ 2 files changed, 32 insertions(+), 83 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index b25a231e83..24aaf70d2e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -31,14 +31,12 @@ import java.awt.EventQueue; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.logging.Level; import javax.swing.SwingWorker; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coordinationservice.CaseNodeData; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.EmptyNode; @@ -59,7 +57,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider private final org.openide.explorer.view.OutlineView outlineView; private int originalPathColumnIndex = 0; private static final Logger LOGGER = Logger.getLogger(CaseBrowser.class.getName()); - private LoadCaseMapWorker tableWorker; + private LoadCaseListWorker tableWorker; @Override public ExplorerManager getExplorerManager() { @@ -76,7 +74,6 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider outline = outlineView.getOutline(); outlineView.setPropertyColumns( Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), - Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath()); ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.CaseNode_column_name()); customize(); @@ -123,8 +120,8 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider } /** - * Get the path to the .aut file for the selected case. - * + * Get the path to the .aut file for the selected case. + * * @return the full path to the selected case's .aut file */ String getCasePath() { @@ -160,7 +157,7 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider //set the table to display text informing the user that the list is being retreived and disable case selection EmptyNode emptyNode = new EmptyNode(Bundle.CaseBrowser_caseListLoading_message()); em.setRootContext(emptyNode); - tableWorker = new LoadCaseMapWorker(); + tableWorker = new LoadCaseListWorker(); tableWorker.execute(); } @@ -192,13 +189,11 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider // End of variables declaration//GEN-END:variables /** - * Swingworker to fetch the updated map of cases and their status in a - * background thread + * Swingworker to fetch the updated List of cases in a background thread */ - private class LoadCaseMapWorker extends SwingWorker { + private class LoadCaseListWorker extends SwingWorker { - private static final String ALERT_FILE_NAME = "autoingest.alert"; - private Map cases; + private List cases; /** * Gets a list of the cases in the top level case folder @@ -207,8 +202,8 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider * * @throws CoordinationServiceException */ - private Map getCases() throws CoordinationService.CoordinationServiceException { - Map casesMap = new HashMap<>(); + private List getCases() throws CoordinationService.CoordinationServiceException { + List caseList = new ArrayList<>(); List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); for (String node : nodeList) { @@ -216,64 +211,27 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider File caseFolder = casePath.toFile(); if (caseFolder.exists()) { /* - * Search for '*.aut' and 'autoingest.alert' files. + * Search for '*.aut' files. */ File[] fileArray = caseFolder.listFiles(); if (fileArray == null) { continue; } String autFilePath = null; - boolean alertFileFound = false; for (File file : fileArray) { String name = file.getName().toLowerCase(); if (autFilePath == null && name.endsWith(".aut")) { - autFilePath = file.getAbsolutePath(); - if (!alertFileFound) { - continue; + try { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); } - } - if (!alertFileFound && name.endsWith(ALERT_FILE_NAME)) { - alertFileFound = true; - } - if (autFilePath != null && alertFileFound) { break; } } - - if (autFilePath != null) { - try { - boolean hasAlertStatus = false; - if (alertFileFound) { - /* - * When an alert file exists, ignore the node - * data and use the ALERT status. - */ - hasAlertStatus = true; - } else { - byte[] rawData = CoordinationService.getInstance().getNodeData(CoordinationService.CategoryNode.CASES, node); - if (rawData != null && rawData.length > 0) { - /* - * When node data exists, use the status - * stored in the node data. - */ - CaseNodeData caseNodeData = new CaseNodeData(rawData); - if (caseNodeData.getErrorsOccurred()) { - hasAlertStatus = true; - } - } - } - - CaseMetadata caseMetadata = new CaseMetadata(Paths.get(autFilePath)); - casesMap.put(caseMetadata, hasAlertStatus); - } catch (CaseMetadata.CaseMetadataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } catch (InterruptedException | CaseNodeData.InvalidDataException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case node data for '%s'.", node), ex); - } - } } } - return casesMap; + return caseList; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java index 9443ae35e9..fa125e6a32 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserNode.java @@ -25,8 +25,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; @@ -49,7 +47,6 @@ final class MultiUserNode extends AbstractNode { @Messages({"CaseNode.column.name=Name", "CaseNode.column.createdTime=Created Time", - "CaseNode.column.status=Status", "CaseNode.column.metadataFilePath=Path"}) private static final Logger LOGGER = Logger.getLogger(MultiUserNode.class.getName()); private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; @@ -57,31 +54,30 @@ final class MultiUserNode extends AbstractNode { /** * Provides a root node with children which each represent a case. * - * @param caseMap the map of cases and a boolean indicating if they have an - * alert + * @param caseList the list of CaseMetadata objects representing the cases */ - MultiUserNode(Map caseMap) { - super(Children.create(new MultiUserNodeChildren(caseMap), true)); + MultiUserNode(List caseList) { + super(Children.create(new MultiUserNodeChildren(caseList), true)); } - static class MultiUserNodeChildren extends ChildFactory> { + static class MultiUserNodeChildren extends ChildFactory { - private final Map caseMap; + private final List caseList; - MultiUserNodeChildren(Map caseMap) { - this.caseMap = caseMap; + MultiUserNodeChildren(List caseList) { + this.caseList = caseList; } @Override - protected boolean createKeys(List> list) { - if (caseMap != null && caseMap.size() > 0) { - list.addAll(caseMap.entrySet()); + protected boolean createKeys(List list) { + if (caseList != null && caseList.size() > 0) { + list.addAll(caseList); } return true; } @Override - protected Node createNodeForKey(Entry key) { + protected Node createNodeForKey(CaseMetadata key) { return new MultiUserCaseNode(key); } @@ -95,19 +91,17 @@ final class MultiUserNode extends AbstractNode { private final String caseName; private final String caseCreatedDate; private final String caseMetadataFilePath; - private final boolean caseHasAlert; private final Path caseLogFilePath; - MultiUserCaseNode(Entry multiUserCase) { + MultiUserCaseNode(CaseMetadata multiUserCase) { super(Children.LEAF); - caseName = multiUserCase.getKey().getCaseDisplayName(); - caseCreatedDate = multiUserCase.getKey().getCreatedDate(); - caseHasAlert = multiUserCase.getValue(); + caseName = multiUserCase.getCaseDisplayName(); + caseCreatedDate = multiUserCase.getCreatedDate(); super.setName(caseName); setName(caseName); setDisplayName(caseName); - caseMetadataFilePath = multiUserCase.getKey().getFilePath().toString(); - caseLogFilePath = Paths.get(multiUserCase.getKey().getCaseDirectory(), LOG_FILE_NAME); + caseMetadataFilePath = multiUserCase.getFilePath().toString(); + caseLogFilePath = Paths.get(multiUserCase.getCaseDirectory(), LOG_FILE_NAME); } /** @@ -119,7 +113,6 @@ final class MultiUserNode extends AbstractNode { return new OpenMultiUserCaseAction(caseMetadataFilePath); } - @Messages({"MultiUserNode.AlertColumn.text=Alert"}) //text to display when there is an alert present @Override protected Sheet createSheet() { Sheet s = super.createSheet(); @@ -132,8 +125,6 @@ final class MultiUserNode extends AbstractNode { caseName)); ss.put(new NodeProperty<>(Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), Bundle.CaseNode_column_createdTime(), caseCreatedDate)); - ss.put(new NodeProperty<>(Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), Bundle.CaseNode_column_status(), - (caseHasAlert == true ? Bundle.MultiUserNode_AlertColumn_text() : ""))); ss.put(new NodeProperty<>(Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), Bundle.CaseNode_column_metadataFilePath(), caseMetadataFilePath)); return s; From 2416f7e31d02832f3f37a548752244c4560b6a17 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 22 Jan 2018 16:09:27 -0500 Subject: [PATCH 19/71] 3501 Open Single User Case button moved to center of the open multi-user case panel --- .../casemodule/MultiUserCasesPanel.form | 36 +++++++++++++++---- .../casemodule/MultiUserCasesPanel.java | 31 ++++++++++------ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form index 3f897d70c0..beb38e312e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form @@ -27,13 +27,13 @@ - - - - + + + + - + @@ -52,7 +52,7 @@ - + @@ -64,6 +64,15 @@ + + + + + + + + + @@ -74,6 +83,12 @@ + + + + + + @@ -84,6 +99,15 @@ + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index a2a2ec13ad..a5a1f32879 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -165,6 +165,9 @@ final class MultiUserCasesPanel extends JPanel{ org.openide.awt.Mnemonics.setLocalizedText(bnOpen, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpen.text")); // NOI18N bnOpen.setEnabled(false); + bnOpen.setMaximumSize(new java.awt.Dimension(80, 23)); + bnOpen.setMinimumSize(new java.awt.Dimension(80, 23)); + bnOpen.setPreferredSize(new java.awt.Dimension(80, 23)); bnOpen.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnOpenActionPerformed(evt); @@ -172,6 +175,8 @@ final class MultiUserCasesPanel extends JPanel{ }); org.openide.awt.Mnemonics.setLocalizedText(bnOpenSingleUserCase, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.bnOpenSingleUserCase.text")); // NOI18N + bnOpenSingleUserCase.setMinimumSize(new java.awt.Dimension(156, 23)); + bnOpenSingleUserCase.setPreferredSize(new java.awt.Dimension(156, 23)); bnOpenSingleUserCase.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { bnOpenSingleUserCaseActionPerformed(evt); @@ -179,6 +184,9 @@ final class MultiUserCasesPanel extends JPanel{ }); org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUserCasesPanel.cancelButton.text")); // NOI18N + cancelButton.setMaximumSize(new java.awt.Dimension(80, 23)); + cancelButton.setMinimumSize(new java.awt.Dimension(80, 23)); + cancelButton.setPreferredSize(new java.awt.Dimension(80, 23)); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); @@ -196,15 +204,18 @@ final class MultiUserCasesPanel extends JPanel{ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(caseExplorerScrollPane) .addGroup(layout.createSequentialGroup() - .addComponent(searchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 555, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 175, Short.MAX_VALUE) - .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(searchLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE) + .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(226, 226, 226) + .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(bnOpenSingleUserCase) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton))) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {bnOpen, cancelButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -212,11 +223,11 @@ final class MultiUserCasesPanel extends JPanel{ .addComponent(caseExplorerScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cancelButton) - .addComponent(bnOpen) - .addComponent(bnOpenSingleUserCase) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(bnOpen, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(bnOpenSingleUserCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(searchLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); }// //GEN-END:initComponents From 73241a637bc869905828634148bd8611c13cfd65 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 23 Jan 2018 11:14:12 -0500 Subject: [PATCH 20/71] Add side effect to FileTypeDetector.getMIMEType --- .../corecomponents/MediaViewVideoPanel.java | 2 +- .../autopsy/coreutils/ImageUtils.java | 2 +- .../MSOfficeEmbeddedContentExtractor.java | 2 +- .../SevenZipExtractor.java | 2 +- .../EncryptionDetectionFileIngestModule.java | 2 +- .../exif/ExifParserFileIngestModule.java | 2 +- .../FileExtMismatchIngestModule.java | 2 +- .../modules/filetypeid/FileTypeDetector.java | 18 ++++++++++++------ .../filetypeid/FileTypeIdIngestModule.java | 2 +- .../autopsy/imagegallery/FileTypeUtils.java | 4 ++-- .../KeywordSearchIngestModule.java | 2 +- .../netbeans/core/startup/Bundle.properties | 2 +- .../core/windows/view/ui/Bundle.properties | 2 +- 13 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java index 07426a3a73..28efdac6ff 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MediaViewVideoPanel.java @@ -151,7 +151,7 @@ public abstract class MediaViewVideoPanel extends JPanel implements FrameCapture if (AUDIO_EXTENSIONS.contains("." + extension) || getExtensionsList().contains("." + extension)) { SortedSet mimeTypes = new TreeSet<>(getMimeTypes()); try { - String mimeType = new FileTypeDetector().detectMIMEType(file); + String mimeType = new FileTypeDetector().getMIMEType(file); return mimeTypes.contains(mimeType); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { logger.log(Level.WARNING, "Failed to look up mimetype for " + file.getName() + " using FileTypeDetector. Fallingback on AbstractFile.isMimeType", ex); diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index c7a5ce997f..206ebb3dea 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -263,7 +263,7 @@ public class ImageUtils { return true; } else { try { - String mimeType = getFileTypeDetector().detectMIMEType(file); + String mimeType = getFileTypeDetector().getMIMEType(file); if (StringUtils.isNotBlank(mimeTypePrefix) && mimeType.startsWith(mimeTypePrefix)) { return true; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java index 7fca6a896c..506fc5a6f3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/MSOfficeEmbeddedContentExtractor.java @@ -135,7 +135,7 @@ class MSOfficeEmbeddedContentExtractor { * supported. Else it returns false. */ boolean isContentExtractionSupported(AbstractFile abstractFile) { - String abstractFileMimeType = fileTypeDetector.detectMIMEType(abstractFile); + String abstractFileMimeType = fileTypeDetector.getMIMEType(abstractFile); for (SupportedExtractionFormats s : SupportedExtractionFormats.values()) { if (s.toString().equals(abstractFileMimeType)) { abstractFileExtractionFormat = s; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index deeb2e9ecf..ee63ac7754 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -139,7 +139,7 @@ class SevenZipExtractor { * supported. Else it returns false. */ boolean isSevenZipExtractionSupported(AbstractFile abstractFile) { - String abstractFileMimeType = fileTypeDetector.detectMIMEType(abstractFile); + String abstractFileMimeType = fileTypeDetector.getMIMEType(abstractFile); for (SupportedArchiveExtractionFormats s : SupportedArchiveExtractionFormats.values()) { if (s.toString().equals(abstractFileMimeType)) { return true; diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 0430ec7ce3..7dd8258263 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -187,7 +187,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Qualify the MIME type. */ - String mimeType = fileTypeDetector.detectMIMEType(file); + String mimeType = fileTypeDetector.getMIMEType(file); if (mimeType.equals("application/octet-stream")) { possiblyEncrypted = true; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index 86aae82126..7aa1224e18 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -250,7 +250,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule { * @return true if to be processed */ private boolean parsableFormat(AbstractFile f) { - String mimeType = fileTypeDetector.detectMIMEType(f); + String mimeType = fileTypeDetector.getMIMEType(f); return supportedMimeTypes.contains(mimeType); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java index 7d50f34f6a..672bf83d97 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java @@ -169,7 +169,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule { if (settings.skipFilesWithNoExtension() && currActualExt.isEmpty()) { return false; } - String currActualSigType = detector.detectMIMEType(abstractFile); + String currActualSigType = detector.getMIMEType(abstractFile); if (settings.getCheckType() != CHECK_TYPE.ALL) { if (settings.getCheckType() == CHECK_TYPE.NO_TEXT_FILES) { if (!currActualExt.isEmpty() && currActualSigType.equals("text/plain")) { //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index c742110f6b..76ce936202 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -171,14 +171,15 @@ public class FileTypeDetector { } /** - * Detects the MIME type of a file. + * Detects the MIME type of a file, then writes it the AbstractFile object + * representing the file and also returns it. * * @param file The file to test. * * @return A MIME type name. If file type could not be detected, or results * were uncertain, octet-stream is returned. */ - public String detectMIMEType(AbstractFile file) { + public String getMIMEType(AbstractFile file) { /* * Check to see if the file has already been typed. */ @@ -250,6 +251,11 @@ public class FileTypeDetector { } } + /* + * Documented side effect: write the result to the AbstractFile object. + */ + file.setMIMEType(mimeType); + return mimeType; } @@ -293,7 +299,7 @@ public class FileTypeDetector { attributes.add(setNameAttribute); BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, FileTypeIdModuleFactory.getModuleName(), fileType.getMimeType()); attributes.add(ruleNameAttribute); - artifact.addAttributes(attributes); + artifact.addAttributes(attributes); try { Case.getCurrentCase().getServices().getBlackboard().indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { @@ -393,7 +399,7 @@ public class FileTypeDetector { */ @Deprecated public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); file.setMIMEType(fileType); file.save(); return fileType; @@ -417,7 +423,7 @@ public class FileTypeDetector { */ @Deprecated public String getFileType(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); file.setMIMEType(fileType); file.save(); return fileType; @@ -437,7 +443,7 @@ public class FileTypeDetector { */ @Deprecated public String detect(AbstractFile file) throws TskCoreException { - String fileType = detectMIMEType(file); + String fileType = getMIMEType(file); return fileType; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java index 64650ed0c4..8e6b3a58b2 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java @@ -91,7 +91,7 @@ public class FileTypeIdIngestModule implements FileIngestModule { */ try { long startTime = System.currentTimeMillis(); - file.setMIMEType(fileTypeDetector.detectMIMEType(file)); + fileTypeDetector.getMIMEType(file); addToTotals(jobId, (System.currentTimeMillis() - startTime)); return ProcessResult.OK; } catch (Exception e) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java index 9e47579a46..91ce6d28f5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileTypeUtils.java @@ -220,7 +220,7 @@ public enum FileTypeUtils { * mimetype could not be detected. */ static boolean hasDrawableMIMEType(AbstractFile file) throws FileTypeDetector.FileTypeDetectorInitException { - String mimeType = getFileTypeDetector().detectMIMEType(file).toLowerCase(); + String mimeType = getFileTypeDetector().getMIMEType(file).toLowerCase(); return isDrawableMimeType(mimeType) || (mimeType.equals("audio/x-aiff") && "tiff".equalsIgnoreCase(file.getNameExtension())); } @@ -235,7 +235,7 @@ public enum FileTypeUtils { */ public static boolean hasVideoMIMEType(AbstractFile file) { try { - String mimeType = getFileTypeDetector().detectMIMEType(file).toLowerCase(); + String mimeType = getFileTypeDetector().getMIMEType(file).toLowerCase(); return mimeType.startsWith("video/") || videoMimeTypes.contains(mimeType); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index ea45987a4d..2e0cd18c0e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -511,7 +511,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (context.fileIngestIsCancelled()) { return; } - String fileType = fileTypeDetector.detectMIMEType(aFile); + String fileType = fileTypeDetector.getMIMEType(aFile); // we skip archive formats that are opened by the archive module. // @@@ We could have a check here to see if the archive module was enabled though... diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index e0a4c85328..c890aff42c 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Tue, 23 Jan 2018 11:13:26 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 8f4ccbd194..5178eab0a3 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Tue, 23 Jan 2018 11:13:26 -0500 CTL_MainWindow_Title=Autopsy 4.5.0 CTL_MainWindow_Title_No_Project=Autopsy 4.5.0 From 23724fa4fc7809dd176eb10c6d652a7fea72a9c2 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 23 Jan 2018 11:16:54 -0500 Subject: [PATCH 21/71] Update docs comment for FileTypeDetector.getMIMEType --- .../sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 76ce936202..b5ebcae990 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -172,7 +172,7 @@ public class FileTypeDetector { /** * Detects the MIME type of a file, then writes it the AbstractFile object - * representing the file and also returns it. + * representing the file and returns the detected type. * * @param file The file to test. * From 3af2bb74294fa0d42bdde773a2ce5adee15079e4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 23 Jan 2018 11:29:06 -0500 Subject: [PATCH 22/71] 3485 Open Case Folder moved to case menu, tools menu reordered --- Core/src/org/sleuthkit/autopsy/actions/Bundle.properties | 8 ++++---- .../sleuthkit/autopsy/actions/OpenOutputFolderAction.java | 8 ++++---- Core/src/org/sleuthkit/autopsy/core/layer.xml | 6 +++--- .../autoingest/AutoIngestDashboardOpenAction.java | 5 +++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 1cbc4b70b6..84f64f3190 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -36,10 +36,10 @@ GetTagNameDialog.tagNameExistsTskCore.msg=The {0} tag name already exists in the OpenLogFolder.error1=Log File Not Found: {0} OpenLogFolder.CouldNotOpenLogFolder=Could not open log folder CTL_OpenLogFolder=Open Log Folder -CTL_OpenOutputFolder=Open Output Folder -OpenOutputFolder.error1=Output Folder Not Found\: {0} -OpenOutputFolder.noCaseOpen=No open case, therefore no current output folder available. -OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder +CTL_OpenOutputFolder=Open Case Folder +OpenOutputFolder.error1=Case Output Folder Not Found\: {0} +OpenOutputFolder.noCaseOpen=No open case, therefore no current case output folder available. +OpenOutputFolder.CouldNotOpenOutputFolder=Could not open case output folder ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} diff --git a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java index c3477859e6..24370b5a73 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/OpenOutputFolderAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; /** - * The action associated with the Tools/Open Output Folder menu item. It opens a + * The action associated with the Tools/Open Case Folder menu item. It opens a * file explorer window for the root output directory for the currently open * case. If the case is a single-user case, this is the case directory. If the * case is a multi-user case, this is a subdirectory of the case directory @@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * This action should only be invoked in the event dispatch thread (EDT). */ @ActionRegistration(displayName = "#CTL_OpenOutputFolder", iconInMenu = true, lazy = false) -@ActionReference(path = "Menu/Tools", position = 1850, separatorBefore = 1849) +@ActionReference(path = "Menu/Case", position = 302) @ActionID(id = "org.sleuthkit.autopsy.actions.OpenOutputFolderAction", category = "Help") public final class OpenOutputFolderAction extends CallableSystemAction { @@ -61,7 +61,7 @@ public final class OpenOutputFolderAction extends CallableSystemAction { try { Desktop.getDesktop().open(outputDir); } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Failed to open output folder %s", outputDir), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to open case output folder %s", outputDir), ex); //NON-NLS NotifyDescriptor descriptor = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "OpenOutputFolder.CouldNotOpenOutputFolder", outputDir.getAbsolutePath()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(descriptor); diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 465529fa9f..fa78fe5485 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -198,10 +198,10 @@ - + - + @@ -213,7 +213,7 @@ --> - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java index 45563a4f4b..f299fbc5a3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardOpenAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,13 +28,14 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.experimental.autoingest.AutoIngestDashboardOpenAction") -@ActionReference(path = "Menu/Tools", position = 104) +@ActionReference(path = "Menu/Tools", position = 201) @ActionRegistration(displayName = "#CTL_AutoIngestDashboardOpenAction", lazy = false) @Messages({"CTL_AutoIngestDashboardOpenAction=Auto Ingest Dashboard"}) public final class AutoIngestDashboardOpenAction extends CallableSystemAction { private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboardOpenAction.class.getName()); private static final String DISPLAY_NAME = Bundle.CTL_AutoIngestDashboardOpenAction(); + private static final long serialVersionUID = 1L; @Override public boolean isEnabled() { From 75f6198190266102e433e589dcc9c2e2a42b6e5c Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 23 Jan 2018 13:33:03 -0500 Subject: [PATCH 23/71] Fixed a warning. --- Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 5d90562834..6442b645d1 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -227,7 +227,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { }//GEN-LAST:event_prevPageButtonActionPerformed private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed - JComboBox cb = (JComboBox) evt.getSource(); + JComboBox cb = (JComboBox) evt.getSource(); String tableName = (String) cb.getSelectedItem(); if (null == tableName) { return; From fb580e2bffb8ffa1471ac245ab1417df96c20ff7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 23 Jan 2018 14:53:54 -0500 Subject: [PATCH 24/71] Added page documenting the Live Triage function --- docs/doxygen-user/images/live_triage_case.png | Bin 0 -> 26594 bytes .../images/live_triage_dialog.png | Bin 0 -> 17794 bytes docs/doxygen-user/images/live_triage_ds.png | Bin 0 -> 39635 bytes .../images/live_triage_script.png | Bin 0 -> 12839 bytes docs/doxygen-user/live_triage.dox | 33 ++++++++++++++++++ docs/doxygen-user/main.dox | 1 + 6 files changed, 34 insertions(+) create mode 100644 docs/doxygen-user/images/live_triage_case.png create mode 100644 docs/doxygen-user/images/live_triage_dialog.png create mode 100644 docs/doxygen-user/images/live_triage_ds.png create mode 100644 docs/doxygen-user/images/live_triage_script.png create mode 100644 docs/doxygen-user/live_triage.dox diff --git a/docs/doxygen-user/images/live_triage_case.png b/docs/doxygen-user/images/live_triage_case.png new file mode 100644 index 0000000000000000000000000000000000000000..fee49485f5cb23e3a5d06c5304fa7dff4c91f06c GIT binary patch literal 26594 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 S3b;TDxB%GI&t;ucLK6TvSzk^7 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/live_triage_ds.png b/docs/doxygen-user/images/live_triage_ds.png new file mode 100644 index 0000000000000000000000000000000000000000..f21a50aa82317547361a10f34ee02e5b34512221 GIT binary patch literal 39635 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 Live Triage Drive to bring up the main dialog. + +\image html live_triage_dialog.png + +Select the drive you want to use - any type of USB storage device will work. For best results use the fastest drive available. Once the process is complete the root folder will contain an Autopsy folder and a RunFromUSB.bat file. + +\section live_triage_usage Running Autopsy from the live triage drive + +Insert the drive into the target machine and browse to it in Windows Explorer. Right click on RunFromUSB.bat and select "Run as administrator". This is necessary to analyze the local drives. + +\image html live_triage_script.png + +Running the script will generate a few more directories on the USB drive. configData stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. + +Once Autopsy is running, proceed to create a case as normal, making sure to save it on the USB drive. + +\image html live_triage_case.png + +Then choose the Local Disk data source and select the desired drive. + +\image html live_triage_ds.png + +See the \ref ds_local page for more information on local disk data sources. + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index e4a1e2b3a9..a72256c7f6 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -60,6 +60,7 @@ The following topics are available here: - \subpage windows_authentication - \subpage multiuser_sec_page - \subpage multiuser_page +- \subpage live_triage_page - \subpage advanced_page If the topic you need is not listed, refer to the Autopsy Wiki or join the SleuthKit User List at SourceForge. From 6b02ab135864fff8ca223cab11743a95a849aafc Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 24 Jan 2018 11:44:57 +0100 Subject: [PATCH 25/71] refactor SQLiteViewer to fix scroll bar and painting issues. --- .../contentviewers/SQLiteTableRowFactory.java | 7 +- .../contentviewers/SQLiteTableView.form | 5 - .../contentviewers/SQLiteTableView.java | 99 +++++++--------- .../autopsy/contentviewers/SQLiteViewer.form | 47 ++------ .../autopsy/contentviewers/SQLiteViewer.java | 110 +++++++++--------- 5 files changed, 118 insertions(+), 150 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java index 98b21149ce..633f40260c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableRowFactory.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.contentviewers; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -31,9 +30,9 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; public class SQLiteTableRowFactory extends ChildFactory { - private final ArrayList> rows; + private final List> rows; - public SQLiteTableRowFactory(ArrayList> rows) { + public SQLiteTableRowFactory(List> rows) { this.rows = rows; } @@ -62,7 +61,7 @@ class SQLiteTableRowNode extends AbstractNode { private final Map row; - public SQLiteTableRowNode(Map row) { + SQLiteTableRowNode(Map row) { super(Children.LEAF); this.row = row; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form index 75cb61a443..2c7924e2a4 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.form @@ -1,11 +1,6 @@
- - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index c795133f73..ad1117f0a0 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -19,10 +19,13 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.BorderLayout; -import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; +import javax.swing.JPanel; +import javax.swing.JTable; import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -31,73 +34,64 @@ import org.openide.explorer.ExplorerManager; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +class SQLiteTableView extends JPanel implements ExplorerManager.Provider { -public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManager.Provider { - - private final Outline outline; private final org.openide.explorer.view.OutlineView outlineView; - private ExplorerManager explorerManager; + private final Outline outline; + private final ExplorerManager explorerManager; - private final ArrayList> tableRows; - /** * Creates new form SQLiteTableView - * @param rows + * */ - public SQLiteTableView(ArrayList> rows) { - - this.tableRows = rows; - - outlineView = new org.openide.explorer.view.OutlineView(); - outlineView.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - + SQLiteTableView() { + initComponents(); - - add(outlineView,BorderLayout.CENTER); - - outline = outlineView.getOutline(); + outlineView = new org.openide.explorer.view.OutlineView(); + add(outlineView, BorderLayout.CENTER); outlineView.setPropertyColumns(); // column headers will be set later + outlineView.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + outlineView.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + outline = outlineView.getOutline(); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - customize(); + outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + outline.setRowSelectionAllowed(false); + outline.setRootVisible(false); + + explorerManager = new ExplorerManager(); } - private void customize() { - - //tableScrollPane.setViewportView(outlineView); - - this.setVisible(true); - outline.setRowSelectionAllowed(false); - outline.setRootVisible(false); - - explorerManager = new ExplorerManager(); - explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); - - setupColumns(); - } - /** * Sets up the columns in the display table + * + * @param tableRows */ - private void setupColumns() { - if (Objects.isNull(tableRows) || tableRows.isEmpty()) - return; - - Map row = tableRows.get(0); - - // Get the columns setup with respect to names and sortability - String[] propStrings = new String[row.size() * 2]; - - int i = 0; - for (Map.Entry col: row.entrySet()) { + void setupTable(List> tableRows) { + + explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); + + if (Objects.isNull(tableRows) || tableRows.isEmpty()) { + outlineView.setPropertyColumns(); +// return; + } else { + + Map row = tableRows.get(0); + + // Get the columns setup with respect to names and sortability + String[] propStrings = new String[row.size() * 2]; + + int i = 0; + for (Map.Entry col : row.entrySet()) { String colName = col.getKey(); propStrings[2 * i] = colName; propStrings[2 * i + 1] = colName; i++; + } + + outlineView.setPropertyColumns(propStrings); } - - - outlineView.setPropertyColumns(propStrings); // TBD: Set width based on actual data in the top N rows?? // TBD: Can't seem to get the horizontal scrollbar working @@ -109,9 +103,9 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag TableColumnModel columnModel = outline.getColumnModel(); ETableColumn column = (ETableColumn) columnModel.getColumn(0); ((ETableColumnModel) columnModel).setColumnHidden(column, true); - + } - + /** * 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 @@ -121,12 +115,9 @@ public class SQLiteTableView extends javax.swing.JPanel implements ExplorerManag // //GEN-BEGIN:initComponents private void initComponents() { - setPreferredSize(new java.awt.Dimension(600, 400)); setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents - - @Override public ExplorerManager getExplorerManager() { return explorerManager; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form index 121a96c823..0469da7b73 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form @@ -68,16 +68,18 @@ - - - - - - - - + + + + + + + + + + @@ -200,33 +202,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 6442b645d1..d5b5afdb68 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -16,29 +16,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.contentviewers; +import java.awt.BorderLayout; import java.awt.Component; import java.io.File; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.logging.Level; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; -import java.sql.Statement; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import javax.swing.JComboBox; -import javax.swing.JPanel; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; @@ -50,22 +50,26 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); private Connection connection = null; - + private String tmpDBPathName = null; private File tmpDBFile = null; - Map dbTablesMap = new TreeMap<>(); + private final Map dbTablesMap = new TreeMap<>(); private static final int ROWS_PER_PAGE = 100; private int numRows; // num of rows in the selected table private int currPage = 0; // curr page of rows being displayed - - private SwingWorker>, Void> worker; + + SQLiteTableView selectedTableView = new SQLiteTableView(); + + private SwingWorker worker; + /** * Creates new form SQLiteViewer */ public SQLiteViewer() { initComponents(); + jTableDataPanel.add(selectedTableView, BorderLayout.CENTER); } /** @@ -167,20 +171,21 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jHdrPanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel1) - .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel2) - .addComponent(currPageLabel) - .addComponent(jLabel3) - .addComponent(numPagesLabel) + .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel2) + .addComponent(currPageLabel) + .addComponent(jLabel3) + .addComponent(numPagesLabel))) .addContainerGap()) ); - jTableDataPanel.setLayout(new javax.swing.OverlayLayout(jTableDataPanel)); + jTableDataPanel.setLayout(new java.awt.BorderLayout()); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -270,11 +275,11 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { public void resetComponent() { dbTablesMap.clear(); - + tablesDropdownList.setEnabled(true); tablesDropdownList.removeAllItems(); numEntriesField.setText(""); - + // close DB connection to file if (null != connection) { try { @@ -312,7 +317,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId(); tmpDBFile = new File(tmpDBPathName); ContentUtils.writeToFile(sqliteFile, tmpDBFile); - + // Open copy using JDBC Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS @@ -379,7 +384,12 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } private void selectTable(String tableName) { - new SwingWorker() { + if (worker != null && !worker.isDone()) { + worker.cancel(false); + worker = null; + } + + worker = new SwingWorker() { @Override protected Integer doInBackground() throws Exception { @@ -400,48 +410,47 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { protected void done() { super.done(); try { - + numRows = get(); numEntriesField.setText(numRows + " entries"); - + currPage = 1; currPageLabel.setText(Integer.toString(currPage)); - numPagesLabel.setText(Integer.toString((numRows/ROWS_PER_PAGE)+1)); - + numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1)); + prevPageButton.setEnabled(false); - - jTableDataPanel.removeAll(); - jTableDataPanel.repaint(); - + + if (numRows > 0) { nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); - readTable(tableName, (currPage-1)*ROWS_PER_PAGE + 1, ROWS_PER_PAGE); - } - else { + readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); + } else { nextPageButton.setEnabled(false); + selectedTableView.setupTable(Collections.emptyList()); } - - + } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS } } - }.execute(); + }; + worker.execute(); } + private void readTable(String tableName, int startRow, int numRowsToRead) { - + if (worker != null && !worker.isDone()) { worker.cancel(false); worker = null; } - + worker = new SwingWorker>, Void>() { @Override protected ArrayList> doInBackground() throws Exception { try { Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery( - "SELECT * FROM " + tableName + "SELECT * FROM " + tableName + " LIMIT " + Integer.toString(numRowsToRead) + " OFFSET " + Integer.toString(startRow - 1) ); //NON-NLS @@ -456,32 +465,29 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { @Override protected void done() { - + if (isCancelled()) { return; } - + super.done(); try { ArrayList> rows = get(); if (Objects.nonNull(rows)) { - JPanel selectedTableView = new SQLiteTableView(rows); - - jTableDataPanel.removeAll(); - jTableDataPanel.setLayout(new javax.swing.OverlayLayout(jTableDataPanel)); - jTableDataPanel.add(selectedTableView); - jTableDataPanel.repaint(); + selectedTableView.setupTable(rows); + }else{ + selectedTableView.setupTable(Collections.emptyList()); } } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS } } }; - + worker.execute(); } - private ArrayList> resultSetToArrayList(ResultSet rs) throws SQLException { + private ArrayList> resultSetToArrayList(ResultSet rs) throws SQLException { ResultSetMetaData md = rs.getMetaData(); int columns = md.getColumnCount(); ArrayList> arraylist = new ArrayList<>(); @@ -502,5 +508,5 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } return arraylist; - } + } } From 0130993b31011a657e77455c9a320e9a8b1815a9 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 24 Jan 2018 08:30:19 -0500 Subject: [PATCH 26/71] Minor text fix --- docs/doxygen-user/live_triage.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/live_triage.dox b/docs/doxygen-user/live_triage.dox index f8272a1b62..d85b54a20a 100644 --- a/docs/doxygen-user/live_triage.dox +++ b/docs/doxygen-user/live_triage.dox @@ -18,7 +18,7 @@ Insert the drive into the target machine and browse to it in Windows Explorer. R \image html live_triage_script.png -Running the script will generate a few more directories on the USB drive. configData stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. +Running the script will generate a few more directories on the USB drive. The configData directory stores all the data used by Autopsy - primarily configuration files and temporary files. You can make changes to the Autopsy settings and they will persist between runs. The cases directory is created as a recommended place to save your case data. You will need to browse to it when creating a case in Autopsy. Once Autopsy is running, proceed to create a case as normal, making sure to save it on the USB drive. From 9190b8a811b1f56d8d214294aad9476d2cbeff8c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 24 Jan 2018 11:35:12 -0500 Subject: [PATCH 27/71] Update multi user case open screenshot --- .../images/multi_user_case_select.png | Bin 44244 -> 15919 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doxygen-user/images/multi_user_case_select.png b/docs/doxygen-user/images/multi_user_case_select.png index f87baa555a20ec519a2ea90c7521900812f4a25d..c4ca377fc6572917d4e321491a2db8a560f068f0 100644 GIT binary patch literal 15919 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 literal 44244 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 Date: Thu, 25 Jan 2018 11:23:13 -0500 Subject: [PATCH 28/71] Fix correlation data source argument order --- .../autopsy/centralrepository/datamodel/AbstractSqlEamDb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 2cac9b4927..87478529f7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -2172,7 +2172,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } CorrelationAttributeInstance eamArtifactInstance = new CorrelationAttributeInstance( new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")), - new CorrelationDataSource(-1, resultSet.getInt("case_id"), resultSet.getString("device_id"), resultSet.getString("name")), + new CorrelationDataSource(resultSet.getInt("case_id"), -1, resultSet.getString("device_id"), resultSet.getString("name")), resultSet.getString("file_path"), resultSet.getString("comment"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")) From 299c80669ca4d34009f8f91513a011480c3a790c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 25 Jan 2018 13:10:06 -0500 Subject: [PATCH 29/71] Made initial test method for central repo. Made a few method public/made new public methods needed for tests. --- .../datamodel/CorrelationCase.java | 6 +- .../datamodel/CorrelationDataSource.java | 10 + .../datamodel/CentralRepoDatamodelTest.java | 174 ++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationCase.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationCase.java index 79d94837ee..2d441881de 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationCase.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationCase.java @@ -50,6 +50,10 @@ public class CorrelationCase implements Serializable { * @param caseUUID Globally unique identifier * @param displayName */ + public CorrelationCase(String caseUUID, String displayName) { + this(-1, caseUUID, displayName); + } + CorrelationCase(int ID, String caseUUID, String displayName) { this(ID, caseUUID, null, displayName, DATE_FORMAT.format(new Date()), null, null, null, null, null); } @@ -156,7 +160,7 @@ public class CorrelationCase implements Serializable { /** * @return the database ID for the case or -1 if it is unknown (or not in the DB) */ - int getID() { + public int getID() { // @@@ Should probably have some lazy logic here to lead the ID from the DB if it is -1 return databaseId; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 9bad6dbac7..9aa9fada32 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -38,6 +38,16 @@ public class CorrelationDataSource implements Serializable { private final String deviceID; //< Unique to its associated case (not necessarily globally unique) private final String name; + /** + * + * @param caseId + * @param deviceId + * @param name + */ + public CorrelationDataSource(int caseId, String deviceId, String name) { + this(caseId, -1, deviceId, name); + } + CorrelationDataSource(int caseId, int dataSourceId, String deviceId, diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java new file mode 100644 index 0000000000..345dff4cd3 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -0,0 +1,174 @@ +/* + * 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.centralrepository.datamodel; + +import java.io.IOException; +import java.util.Map; +import java.nio.file.Path; +import java.nio.file.Paths; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.commons.io.FileUtils; +import org.netbeans.junit.NbModuleSuite; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; + +/** + * + */ +public class CentralRepoDatamodelTest extends TestCase{ + + private static final String PROPERTIES_FILE = "CentralRepository"; + private static final String CR_DB_NAME = "testcentralrepo.db"; + private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); + + private CorrelationCase case1; + private CorrelationCase case2; + private CorrelationDataSource dataSource1fromCase1; + private CorrelationDataSource dataSource2fromCase1; + private EamOrganization org1; + private EamOrganization org2; + + private Map propertiesMap = null; + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CentralRepoDatamodelTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + @Override + public void setUp() { + + boolean errorsOccurred; + SqliteEamDbSettings dbSettingsSqlite = new SqliteEamDbSettings(); + + // Delete the test directory, if it exists + if(testDirectory.toFile().exists()){ + try{ + FileUtils.deleteDirectory(testDirectory.toFile()); + } catch (IOException ex){ + Exceptions.printStackTrace(ex); + } + } + assertFalse("Unable to delete existing test directory", testDirectory.toFile().exists()); + + // Create the test directory + testDirectory.toFile().mkdirs(); + assertTrue("Unable to create test directory", testDirectory.toFile().exists()); + + // Save the current central repo settings + propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); + + // Set up an Autopsy case for testing + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, testDirectory.toString(), new CaseDetails("CentralRepoDatamodelTestCase")); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + } + assertTrue("Failed to create test case", testDirectory.toFile().exists()); + + errorsOccurred = false; + try{ + dbSettingsSqlite.setDbName(CR_DB_NAME); + dbSettingsSqlite.setDbDirectory(testDirectory.toString()); + if (!dbSettingsSqlite.dbDirectoryExists()) { + dbSettingsSqlite.createDbDirectory(); + } + + assertTrue("Failed to created central repo directory " + dbSettingsSqlite.getDbDirectory(), dbSettingsSqlite.dbDirectoryExists()); + + boolean result = dbSettingsSqlite.initializeDatabaseSchema() + && dbSettingsSqlite.insertDefaultDatabaseContent(); + + assertTrue("Failed to initialize central repo database", result); + + dbSettingsSqlite.saveSettings(); + EamDbUtil.setUseCentralRepo(true); + EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); + EamDbPlatformEnum.saveSelectedPlatform(); + + EamDb.getInstance().getCases(); + } catch (Exception ex){ + errorsOccurred = true; + Exceptions.printStackTrace(ex); + } + + assertFalse("Failed to create/initialize central repo database", errorsOccurred); + Path crDbFilePath = Paths.get(testDirectory.toString(), CR_DB_NAME); + assertTrue("Failed to create central repo database at " + crDbFilePath, crDbFilePath.toFile().exists()); + + // Set up some default objects to be used by the tests + errorsOccurred = false; + try{ + case1 = new CorrelationCase("case1_uuid", "case1"); + case1 = EamDb.getInstance().newCase(case1); + assertTrue("Failed to create test object case1", case1 != null); + + case2 = new CorrelationCase("case2_uuid", "case2"); + case2 = EamDb.getInstance().newCase(case2); + assertTrue("Failed to create test object case2", case2 != null); + + dataSource1fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource1_deviceID", "dataSource1"); + EamDb.getInstance().newDataSource(dataSource1fromCase1); + dataSource1fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource1fromCase1.getDeviceID()); + assertTrue("Failed to create test object dataSource1fromCase1", dataSource1fromCase1 != null); + + dataSource2fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource2_deviceID", "dataSource2"); + EamDb.getInstance().newDataSource(dataSource2fromCase1); + dataSource2fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource2fromCase1.getDeviceID()); + assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); + + org1 = new EamOrganization("org1"); + org1.setOrgID((int)EamDb.getInstance().newOrganization(org1)); + + org2 = new EamOrganization("org2"); + org2.setOrgID((int)EamDb.getInstance().newOrganization(org2)); + + + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + errorsOccurred = true; + } + + assertFalse("Failed to create default database objects", errorsOccurred); + + + } + + @Override + public void tearDown() { + + // Restore the original properties + ModuleSettings.setConfigSettings(PROPERTIES_FILE, propertiesMap); + + // Close and delete the test case and central repo db + try { + EamDb.getInstance().shutdownConnections(); + Case.closeCurrentCase(); + FileUtils.deleteDirectory(testDirectory.toFile()); + + } catch (EamDbException | CaseActionException | IOException ex) { + Exceptions.printStackTrace(ex); + } + assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); + } + + public void test1(){ + System.out.println("It's test 1"); + } + + // public void test2(){ + + // } + +} From f80b3d94c1478e008922033b4d5da3d299e24776 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 25 Jan 2018 13:37:59 -0500 Subject: [PATCH 30/71] 3469_3470: Catch Runtime exception for too large data for Excel cell and fix the HTML formatting issue. --- .../sleuthkit/autopsy/report/ReportExcel.java | 16 +++++++++++++++- .../org/sleuthkit/autopsy/report/ReportHTML.java | 4 +++- .../autopsy/report/TableReportGenerator.java | 3 +-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index 19526a0a19..f1b8c421b5 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -34,6 +34,7 @@ class ReportExcel implements TableReportModule { private static final Logger logger = Logger.getLogger(ReportExcel.class.getName()); private static ReportExcel instance; + private static final int EXCEL_CELL_MAXIMUM_SIZE = 36767; private Workbook wb; private Sheet sheet; @@ -236,10 +237,23 @@ class ReportExcel implements TableReportModule { * @param row cells to add */ @Override + @NbBundle.Messages({ + "ReportExcel.exceptionMessage.dataTooLarge=Value is too long to fit into an Excel cell. ", + "ReportExcel.exceptionMessage.errorText=Error showing data into an Excel cell." + }) + public void addRow(List rowData) { Row row = sheet.createRow(rowIndex); for (int i = 0; i < rowData.size(); ++i) { - row.createCell(i).setCellValue(rowData.get(i)); + try { + row.createCell(i).setCellValue(rowData.get(i)); + } catch (Exception e) { + if (e instanceof java.lang.IllegalArgumentException && rowData.get(i).length() > EXCEL_CELL_MAXIMUM_SIZE) { + row.getCell(i).setCellValue(Bundle.ReportExcel_exceptionMessage_dataTooLarge() + e.getMessage()); + } else { + row.getCell(i).setCellValue(Bundle.ReportExcel_exceptionMessage_errorText()); + } + } } ++rowIndex; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 88d9fe44b1..01a179a370 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -51,6 +51,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; @@ -554,7 +555,8 @@ class ReportHTML implements TableReportModule { StringBuilder builder = new StringBuilder(); builder.append("\t\n"); //NON-NLS for (String cell : row) { - builder.append("\t\t").append(cell).append("\n"); //NON-NLS + String escapeHTMLCell = EscapeUtil.escapeHtml(cell); + builder.append("\t\t").append(escapeHTMLCell).append("\n"); //NON-NLS } builder.append("\t\n"); //NON-NLS rowCount++; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 93894022b3..58e38a7d23 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -666,8 +666,7 @@ class TableReportGenerator { tableModule.startTable(columnHeaderNames); } - String previewreplace = EscapeUtil.escapeHtml(preview); - tableModule.addRow(Arrays.asList(new String[]{previewreplace.replaceAll(" Date: Fri, 26 Jan 2018 11:58:29 -0500 Subject: [PATCH 31/71] 3469_3470: cleanup --- .../src/org/sleuthkit/autopsy/report/TableReportGenerator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 58e38a7d23..499f848d54 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,6 @@ import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; From c7a7898b957516f0a08b8d2945a5aaa41c8b848c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Fri, 26 Jan 2018 15:14:27 -0500 Subject: [PATCH 32/71] 3469_3470: Use variable to make code more readable. --- Core/src/org/sleuthkit/autopsy/report/ReportExcel.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index f1b8c421b5..81045dc5b1 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -245,13 +245,14 @@ class ReportExcel implements TableReportModule { public void addRow(List rowData) { Row row = sheet.createRow(rowIndex); for (int i = 0; i < rowData.size(); ++i) { + Cell excelCell = row.createCell(i); try { - row.createCell(i).setCellValue(rowData.get(i)); + excelCell.setCellValue(rowData.get(i)); } catch (Exception e) { if (e instanceof java.lang.IllegalArgumentException && rowData.get(i).length() > EXCEL_CELL_MAXIMUM_SIZE) { - row.getCell(i).setCellValue(Bundle.ReportExcel_exceptionMessage_dataTooLarge() + e.getMessage()); + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_dataTooLarge() + e.getMessage()); } else { - row.getCell(i).setCellValue(Bundle.ReportExcel_exceptionMessage_errorText()); + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_errorText()); } } } From 487202790f109713aa81d7514b0d11f88c30595b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 29 Jan 2018 08:05:48 -0500 Subject: [PATCH 33/71] Case test method is done --- .../datamodel/AbstractSqlEamDb.java | 16 +- .../datamodel/CentralRepoDatamodelTest.java | 434 +++++++++++++++--- 2 files changed, 388 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 2cac9b4927..c5b9af62a6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -271,6 +271,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void updateCase(CorrelationCase eamCase) throws EamDbException { + if(eamCase == null) { + throw new EamDbException("CorrelationCase argument is null"); + } + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -929,12 +933,16 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertCases(List cases) throws EamDbException { - Connection conn = connect(); - + if(cases == null) { + throw new EamDbException("cases argument is null"); + } + if (cases.isEmpty()) { return; } + Connection conn = connect(); + int counter = 0; PreparedStatement bulkPs = null; try { @@ -1012,11 +1020,11 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException { - Connection conn = connect(); - if (1 != eamArtifact.getInstances().size()) { throw new EamDbException("Error: Artifact must have exactly one (1) Artifact Instance to set as notable."); // NON-NLS } + + Connection conn = connect(); List eamInstances = eamArtifact.getInstances(); CorrelationAttributeInstance eamInstance = eamInstances.get(0); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 345dff4cd3..489f651e23 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -7,8 +7,11 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.io.IOException; import java.util.Map; +import java.util.List; +import java.util.ArrayList; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.stream.Collectors; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import junit.framework.Test; @@ -16,6 +19,7 @@ import junit.framework.TestCase; import org.apache.commons.io.FileUtils; import org.netbeans.junit.NbModuleSuite; import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; @@ -24,100 +28,95 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** * */ -public class CentralRepoDatamodelTest extends TestCase{ - +public class CentralRepoDatamodelTest extends TestCase { + private static final String PROPERTIES_FILE = "CentralRepository"; private static final String CR_DB_NAME = "testcentralrepo.db"; private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); - + SqliteEamDbSettings dbSettingsSqlite; + private CorrelationCase case1; private CorrelationCase case2; private CorrelationDataSource dataSource1fromCase1; private CorrelationDataSource dataSource2fromCase1; private EamOrganization org1; private EamOrganization org2; - + private Map propertiesMap = null; - + public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CentralRepoDatamodelTest.class). clusters(".*"). enableModules(".*"); return conf.suite(); } - + @Override public void setUp() { - - boolean errorsOccurred; - SqliteEamDbSettings dbSettingsSqlite = new SqliteEamDbSettings(); - + dbSettingsSqlite = new SqliteEamDbSettings(); + // Delete the test directory, if it exists - if(testDirectory.toFile().exists()){ - try{ + if (testDirectory.toFile().exists()) { + try { FileUtils.deleteDirectory(testDirectory.toFile()); - } catch (IOException ex){ - Exceptions.printStackTrace(ex); + } catch (IOException ex) { + Assert.fail(ex); } } assertFalse("Unable to delete existing test directory", testDirectory.toFile().exists()); - + // Create the test directory testDirectory.toFile().mkdirs(); assertTrue("Unable to create test directory", testDirectory.toFile().exists()); - + // Save the current central repo settings propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); - + // Set up an Autopsy case for testing try { Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, testDirectory.toString(), new CaseDetails("CentralRepoDatamodelTestCase")); } catch (CaseActionException ex) { Exceptions.printStackTrace(ex); - } + Assert.fail(ex); + } assertTrue("Failed to create test case", testDirectory.toFile().exists()); - - errorsOccurred = false; - try{ + + try { dbSettingsSqlite.setDbName(CR_DB_NAME); dbSettingsSqlite.setDbDirectory(testDirectory.toString()); if (!dbSettingsSqlite.dbDirectoryExists()) { dbSettingsSqlite.createDbDirectory(); } - + assertTrue("Failed to created central repo directory " + dbSettingsSqlite.getDbDirectory(), dbSettingsSqlite.dbDirectoryExists()); boolean result = dbSettingsSqlite.initializeDatabaseSchema() - && dbSettingsSqlite.insertDefaultDatabaseContent(); + && dbSettingsSqlite.insertDefaultDatabaseContent(); assertTrue("Failed to initialize central repo database", result); - + dbSettingsSqlite.saveSettings(); EamDbUtil.setUseCentralRepo(true); EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); EamDbPlatformEnum.saveSelectedPlatform(); - - EamDb.getInstance().getCases(); - } catch (Exception ex){ - errorsOccurred = true; + } catch (Exception ex) { Exceptions.printStackTrace(ex); + Assert.fail(ex); } - - assertFalse("Failed to create/initialize central repo database", errorsOccurred); + Path crDbFilePath = Paths.get(testDirectory.toString(), CR_DB_NAME); assertTrue("Failed to create central repo database at " + crDbFilePath, crDbFilePath.toFile().exists()); - + // Set up some default objects to be used by the tests - errorsOccurred = false; - try{ + try { case1 = new CorrelationCase("case1_uuid", "case1"); case1 = EamDb.getInstance().newCase(case1); assertTrue("Failed to create test object case1", case1 != null); - + case2 = new CorrelationCase("case2_uuid", "case2"); case2 = EamDb.getInstance().newCase(case2); assertTrue("Failed to create test object case2", case2 != null); - + dataSource1fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource1_deviceID", "dataSource1"); EamDb.getInstance().newDataSource(dataSource1fromCase1); dataSource1fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource1fromCase1.getDeviceID()); @@ -126,31 +125,27 @@ public class CentralRepoDatamodelTest extends TestCase{ dataSource2fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource2_deviceID", "dataSource2"); EamDb.getInstance().newDataSource(dataSource2fromCase1); dataSource2fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource2fromCase1.getDeviceID()); - assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); - + assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); + org1 = new EamOrganization("org1"); - org1.setOrgID((int)EamDb.getInstance().newOrganization(org1)); - + org1.setOrgID((int) EamDb.getInstance().newOrganization(org1)); + org2 = new EamOrganization("org2"); - org2.setOrgID((int)EamDb.getInstance().newOrganization(org2)); - - - } catch (EamDbException ex){ + org2.setOrgID((int) EamDb.getInstance().newOrganization(org2)); + + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); - errorsOccurred = true; + Assert.fail(ex); } - - assertFalse("Failed to create default database objects", errorsOccurred); - } - + @Override public void tearDown() { - + // Restore the original properties ModuleSettings.setConfigSettings(PROPERTIES_FILE, propertiesMap); - + // Close and delete the test case and central repo db try { EamDb.getInstance().shutdownConnections(); @@ -158,17 +153,340 @@ public class CentralRepoDatamodelTest extends TestCase{ FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | CaseActionException | IOException ex) { + //} catch (EamDbException | CaseActionException ex) { Exceptions.printStackTrace(ex); + Assert.fail(ex); } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } - - public void test1(){ - System.out.println("It's test 1"); - } - - // public void test2(){ + + /** + * Test method for the methods related to the cases table + * newCase(CorrelationCase eamCase) tests: + * - Test valid data + * - Test null UUID + * - Test null case name + * - Test repeated UUID + * newCase(Case autopsyCase) tests: + * - Test valid data + * - Test null autopsyCase + * updateCase(CorrelationCase eamCase) tests: + * - Test with valid data, checking all fields + * - Test null eamCase + * getCase(Case autopsyCase) tests: + * - Test ?? + * getCaseByUUID(String caseUUID) getCases() + * - Test with UUID that is in the database + * - Test with UUID that is not in the database + * - Test with null UUID + * bulkInsertCases(List cases) + * - Test on a list of cases larger than the bulk insert threshold. + * - Test on a null list + */ + public void testCases() { + final String caseAname = "caseA"; + final String caseAuuid = "caseA_uuid"; + CorrelationCase caseA; + CorrelationCase caseB; + + // Test creating a case with valid name and uuid + try { + caseA = new CorrelationCase(caseAuuid, caseAname); + caseA = EamDb.getInstance().newCase(caseA); + assertTrue("Failed to create case", caseA != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test null uuid + try { + CorrelationCase tempCase = new CorrelationCase(null, "nullUuidCase"); + tempCase = EamDb.getInstance().newCase(tempCase); + Assert.fail("newCase did not throw expected exception from null uuid"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test null name + try { + CorrelationCase tempCase = new CorrelationCase("nullCaseUuid", null); + tempCase = EamDb.getInstance().newCase(tempCase); + Assert.fail("newCase did not throw expected exception from null name"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test creating a case with an already used UUID + // This should just return the existing case object. Check that the total + // number of cases does not increase. + try { + int nCases = EamDb.getInstance().getCases().size(); + CorrelationCase tempCase = new CorrelationCase(caseAuuid, "newCaseWithSameUUID"); + tempCase = EamDb.getInstance().newCase(tempCase); + assertTrue("newCase returned null for existing UUID", tempCase != null); + assertTrue("newCase created a new case for an already existing UUID", nCases == EamDb.getInstance().getCases().size()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test creating a case from an Autopsy case + // The case may already be in the database - the result is the same either way + try { + caseB = EamDb.getInstance().newCase(Case.getCurrentCase()); + assertTrue("Failed to create correlation case from Autopsy case", caseB != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test null Autopsy case + try { + Case nullCase = null; + CorrelationCase tempCase = EamDb.getInstance().newCase(nullCase); + Assert.fail("newCase did not throw expected exception from null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test update case + // Will update the fields of an existing case object, save it, and then + // pull a new copy out of the database + try { + assertTrue(caseA != null); + String caseNumber = "12-34-56"; + String creationDate = "01/12/2018"; + String displayName = "Test Case"; + String examinerEmail = "john@sample.com"; + String examinerName = "John Doe"; + String examinerPhone = "123-555-4567"; + String notes = "Notes"; + + caseA.setCaseNumber(caseNumber); + caseA.setCreationDate(creationDate); + caseA.setDisplayName(displayName); + caseA.setExaminerEmail(examinerEmail); + caseA.setExaminerName(examinerName); + caseA.setExaminerPhone(examinerPhone); + caseA.setNotes(notes); + caseA.setOrg(org1); + + EamDb.getInstance().updateCase(caseA); + + // Retrievex a new copy of the case from the database to check that the + // fields were properly updated + CorrelationCase updatedCase = EamDb.getInstance().getCaseByUUID(caseA.getCaseUUID()); + + assertTrue("updateCase failed to update case number", caseNumber.equals(updatedCase.getCaseNumber())); + assertTrue("updateCase failed to update creation date", creationDate.equals(updatedCase.getCreationDate())); + assertTrue("updateCase failed to update display name", displayName.equals(updatedCase.getDisplayName())); + assertTrue("updateCase failed to update examiner email", examinerEmail.equals(updatedCase.getExaminerEmail())); + assertTrue("updateCase failed to update examiner name", examinerName.equals(updatedCase.getExaminerName())); + assertTrue("updateCase failed to update examiner phone number", examinerPhone.equals(updatedCase.getExaminerPhone())); + assertTrue("updateCase failed to update notes", notes.equals(updatedCase.getNotes())); + assertTrue("updateCase failed to update org (org is null)", updatedCase.getOrg() != null); + assertTrue("updateCase failed to update org (org ID is wrong)", org1.getOrgID() == updatedCase.getOrg().getOrgID()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test update case with null case + try { + EamDb.getInstance().updateCase(null); + Assert.fail("updateCase did not throw expected exception from null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } - // } - + // Test getting a case from an Autopsy case + try { + CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getCurrentCase()); + assertTrue("getCase returned null for current Autopsy case", tempCase != null); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case by UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(caseAuuid); + assertTrue("Failed to get case by UUID", tempCase != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case with a non-existent UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID("badUUID"); + assertTrue("getCaseByUUID returned non-null case for non-existent UUID", tempCase == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case with null UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(null); + assertTrue("getCaseByUUID returned non-null case for null UUID", tempCase == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting the list of cases + // The test is to make sure the three cases we know are in the database are in the list + try { + List caseList = EamDb.getInstance().getCases(); + List uuidList + = caseList.stream().map(c -> c.getCaseUUID()).collect(Collectors.toList()); + assertTrue("getCases is missing data for existing cases", uuidList.contains(case1.getCaseUUID()) + && uuidList.contains(case2.getCaseUUID()) && (uuidList.contains(caseA.getCaseUUID())) + && uuidList.contains(caseB.getCaseUUID())); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test bulk case insert + try { + // Create a list of correlation cases. Make enough that the bulk threshold should be hit once. + List cases = new ArrayList<>(); + String bulkTestUuid = "bulkTestUUID_"; + String bulkTestName = "bulkTestName_"; + for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { + String name = bulkTestUuid + String.valueOf(i); + String uuid = bulkTestName + String.valueOf(i); + cases.add(new CorrelationCase(uuid, name)); + } + + // Get the current case count + int nCases = EamDb.getInstance().getCases().size(); + + // Insert the big list of cases + EamDb.getInstance().bulkInsertCases(cases); + + // Check that the case count is what is expected + assertTrue("bulkInsertCases did not insert the expected number of cases", nCases + cases.size() == EamDb.getInstance().getCases().size()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test bulk case insert with null list + try { + EamDb.getInstance().bulkInsertCases(null); + Assert.fail("bulkInsertCases did not throw expected exception from null list"); + } catch (EamDbException ex) { + // This is the expected behavior + } + } + + /** + * Test method for the three methods related to the db_info table + * newDbInfo(String name, String value) tests: - Test valid data - Test null + * name - Test null value getDbInfo(String name) - Test getting value for + * existing name - Test getting value for non-existing name - Test getting + * value for null name updateDbInfo(String name, String value) - Test + * updating existing name to valid new value - Test updating existing name + * to null value - Test updating null name - Test updating non-existing name + * to new value + */ + public void testDbInfo() { + final String name1 = "testName1"; + final String name2 = "testName2"; + final String name3 = "testName3"; + final String value1 = "testValue1"; + final String value2 = "testValue2"; + + // Test setting a valid value in DbInfo + try { + EamDb.getInstance().newDbInfo(name1, value1); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test null name + try { + EamDb.getInstance().newDbInfo(null, value1); + Assert.fail("newDbInfo did not throw expected exception from null name"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test null value + try { + EamDb.getInstance().newDbInfo(name2, null); + Assert.fail("newDbInfo did not throw expected exception from null value"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try getting the dbInfo entry that should exist + try { + String tempVal = EamDb.getInstance().getDbInfo(name1); + assertTrue("dbInfo value for name1 does not match", value1.equals(tempVal)); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Try getting the dbInfo entry that should not exist + try { + String tempVal = EamDb.getInstance().getDbInfo(name3); + assertTrue("dbInfo value is unexpectedly non-null given non-existent name", tempVal == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Try getting dbInfo for a null value + try { + String tempVal = EamDb.getInstance().getDbInfo(null); + assertTrue("dbInfo value is unexpectedly non-null given null name", tempVal == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Try updating an existing value to a valid new value + try { + EamDb.getInstance().updateDbInfo(name1, value2); + assertTrue("dbInfo value failed to update to expected value", value2.equals(EamDb.getInstance().getDbInfo(name1))); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Try updating an existing value to null + try { + EamDb.getInstance().updateDbInfo(name1, null); + Assert.fail("updateDbInfo did not throw expected exception from null value"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try updating a null name + // This seems like SQLite would throw an exception, but it does not + try { + EamDb.getInstance().updateDbInfo(null, value1); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Try updating the value for a non-existant name + try { + EamDb.getInstance().updateDbInfo(name1, null); + Assert.fail("updateDbInfo did not throw expected exception from non-existent name"); + } catch (EamDbException ex) { + // This is the expected behavior + } + } + } From 2dfa6a25f03f237db606c4647fe8136c764a7d75 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 29 Jan 2018 11:50:40 -0500 Subject: [PATCH 34/71] Wrote data sources tests --- .../datamodel/AbstractSqlEamDb.java | 4 + .../datamodel/SqliteEamDb.java | 2 + .../datamodel/CentralRepoDatamodelTest.java | 163 +++++++++++++++--- 3 files changed, 144 insertions(+), 25 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index c5b9af62a6..07937128c1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -448,6 +448,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException { + if(correlationCase == null) { + throw new EamDbException("CorrelationCase argument is null"); + } + Connection conn = connect(); CorrelationDataSource eamDataSourceResult = null; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index 13c4fb00a7..d9d1ea2fa3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -22,6 +22,7 @@ import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -165,6 +166,7 @@ public class SqliteEamDb extends AbstractSqlEamDb { connectionPool.setMaxIdle(-1); connectionPool.setMaxWaitMillis(1000); connectionPool.setValidationQuery(dbSettings.getValidationQuery()); + connectionPool.setConnectionInitSqls(Arrays.asList("PRAGMA foreign_keys = ON")); } /** diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 489f651e23..5d0237376f 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -153,35 +153,148 @@ public class CentralRepoDatamodelTest extends TestCase { FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | CaseActionException | IOException ex) { - //} catch (EamDbException | CaseActionException ex) { + //} catch (EamDbException | CaseActionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } + public void testDataSources() { + final String dataSourceAname = "dataSourceA"; + final String dataSourceAid = "dataSourceA_deviceID"; + CorrelationDataSource dataSourceA; + CorrelationDataSource dataSourceB; + + // Test creating a data source with valid case, name, and ID + try { + dataSourceA = new CorrelationDataSource(case2.getID(), dataSourceAid, dataSourceAname); + EamDb.getInstance().newDataSource(dataSourceA); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a data source with the same case, name, and ID + try { + CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), dataSourceAid, dataSourceAname); + EamDb.getInstance().newDataSource(temp); + Assert.fail("newDataSource did not throw exception from duplicate data source"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test creating a data source with the same name and ID but different case + try { + dataSourceB = new CorrelationDataSource(case1.getID(), dataSourceAid, dataSourceAname); + EamDb.getInstance().newDataSource(dataSourceB); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a data source with an invalid case ID + try { + CorrelationDataSource temp = new CorrelationDataSource(5000, "tempID", "tempName"); + EamDb.getInstance().newDataSource(temp); + Assert.fail("newDataSource did not throw exception from invalid case ID"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test creating a data source with null device ID + try { + CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), null, "tempName"); + EamDb.getInstance().newDataSource(temp); + Assert.fail("newDataSource did not throw exception from null device ID"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test creating a data source with null device ID + try { + CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), "tempID", null); + EamDb.getInstance().newDataSource(temp); + Assert.fail("newDataSource did not throw exception from null name"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting a data source with valid case and ID + try { + CorrelationDataSource temp = EamDb.getInstance().getDataSource(case2, dataSourceAid); + assertTrue("Failed to get data source", temp != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a data source with non-existent ID + try { + CorrelationDataSource temp = EamDb.getInstance().getDataSource(case2, "badID"); + assertTrue("getDataSource returned non-null value for non-existent data source", temp == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a data source with a null case + try { + CorrelationDataSource temp = EamDb.getInstance().getDataSource(null, dataSourceAid); + Assert.fail("getDataSource did not throw exception from null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting a data source with null ID + try { + CorrelationDataSource temp = EamDb.getInstance().getDataSource(case2, null); + assertTrue("getDataSource returned non-null value for null data source", temp == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting the list of data sources + // There should be three data sources, and we'll check for the expected device IDs + try { + List dataSources = EamDb.getInstance().getDataSources(); + List devIdList + = dataSources.stream().map(c -> c.getDeviceID()).collect(Collectors.toList()); + assertTrue("getDataSources returned unexpected number of data sources", dataSources.size() == 4); + assertTrue("getDataSources is missing expected data sources", + devIdList.contains(dataSourceAid) + && devIdList.contains(dataSource1fromCase1.getDeviceID()) + && devIdList.contains(dataSource2fromCase1.getDeviceID())); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test the data source count + try { + assertTrue("getCountUniqueDataSources returned unexpected number of data sources", + EamDb.getInstance().getCountUniqueDataSources() == 4); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + /** * Test method for the methods related to the cases table - * newCase(CorrelationCase eamCase) tests: - * - Test valid data - * - Test null UUID - * - Test null case name - * - Test repeated UUID - * newCase(Case autopsyCase) tests: - * - Test valid data - * - Test null autopsyCase - * updateCase(CorrelationCase eamCase) tests: - * - Test with valid data, checking all fields - * - Test null eamCase - * getCase(Case autopsyCase) tests: - * - Test ?? - * getCaseByUUID(String caseUUID) getCases() - * - Test with UUID that is in the database - * - Test with UUID that is not in the database - * - Test with null UUID - * bulkInsertCases(List cases) - * - Test on a list of cases larger than the bulk insert threshold. - * - Test on a null list + * newCase(CorrelationCase eamCase) tests: - Test valid data - Test null + * UUID - Test null case name - Test repeated UUID newCase(Case autopsyCase) + * tests: - Test valid data - Test null autopsyCase + * updateCase(CorrelationCase eamCase) tests: - Test with valid data, + * checking all fields - Test null eamCase getCase(Case autopsyCase) tests: + * - Test with current Autopsy case getCaseByUUID(String caseUUID) + * getCases() - Test with UUID that is in the database - Test with UUID that + * is not in the database - Test with null UUID + * bulkInsertCases(List cases) - Test on a list of cases + * larger than the bulk insert threshold. - Test on a null list */ public void testCases() { final String caseAname = "caseA"; @@ -301,15 +414,15 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting a case from an Autopsy case try { CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getCurrentCase()); assertTrue("getCase returned null for current Autopsy case", tempCase != null); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } + } // Test getting a case by UUID try { @@ -376,7 +489,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test bulk case insert with null list try { EamDb.getInstance().bulkInsertCases(null); From 36766e2d51de10128aee2e27e7d9682eda51f0dc Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 29 Jan 2018 14:16:08 -0500 Subject: [PATCH 35/71] 3366: Edit Details button should only be visible when Details tab selected. --- .../sleuthkit/autopsy/casemodule/CaseInformationPanel.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index b03a17920e..f0f7326222 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -61,6 +61,11 @@ class CaseInformationPanel extends javax.swing.JPanel { @Override public void stateChanged(ChangeEvent e) { tabbedPane.getSelectedComponent().setSize(tabbedPane.getSelectedComponent().getPreferredSize()); + if (tabbedPane.getSelectedComponent() instanceof CasePropertiesPanel) { + editDetailsButton.setVisible(true); + } else { + editDetailsButton.setVisible(false); + } } }); } From 78cb915b386896d4798dece39b12d3cee26ab0a5 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Mon, 29 Jan 2018 14:17:48 -0500 Subject: [PATCH 36/71] 3366: Update the copyright to current year. --- .../org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index f0f7326222..2a460d5c46 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); From e502fedd9bc2dffc9b42bc523bf702dfb27c01a4 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 29 Jan 2018 14:40:48 -0500 Subject: [PATCH 37/71] Partway through reference set tests --- .../datamodel/AbstractSqlEamDb.java | 24 +- .../datamodel/CentralRepoDatamodelTest.java | 268 +++++++++++++++++- 2 files changed, 288 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 07937128c1..3003b28a4a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1617,6 +1617,18 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException { + if(eamGlobalSet == null){ + throw new EamDbException("EamGlobalSet argument is null"); + } + + if(eamGlobalSet.getFileKnownStatus() == null){ + throw new EamDbException("File known status on the EamGlobalSet is null"); + } + + if(eamGlobalSet.getType() == null){ + throw new EamDbException("Type on the EamGlobalSet is null"); + } + Connection conn = connect(); PreparedStatement preparedStatement1 = null; @@ -1678,8 +1690,11 @@ public abstract class AbstractSqlEamDb implements EamDb { preparedStatement1 = conn.prepareStatement(sql1); preparedStatement1.setInt(1, referenceSetID); resultSet = preparedStatement1.executeQuery(); - resultSet.next(); - return getEamGlobalSetFromResultSet(resultSet); + if(resultSet.next()) { + return getEamGlobalSetFromResultSet(resultSet); + } else { + return null; + } } catch (SQLException ex) { throw new EamDbException("Error getting reference set by id.", ex); // NON-NLS @@ -1701,6 +1716,11 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getAllReferenceSets(CorrelationAttribute.Type correlationType) throws EamDbException { + + if(correlationType == null){ + throw new EamDbException("Correlation type is null"); + } + List results = new ArrayList<>(); Connection conn = connect(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 5d0237376f..f23421fdb9 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -24,6 +24,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.datamodel.TskData; /** * @@ -150,6 +151,13 @@ public class CentralRepoDatamodelTest extends TestCase { try { EamDb.getInstance().shutdownConnections(); Case.closeCurrentCase(); + + // This seems to help in allowing the Autopsy case to be deleted + try{ + Thread.sleep(2000); + } catch (Exception ex){ + + } FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | CaseActionException | IOException ex) { @@ -159,7 +167,263 @@ public class CentralRepoDatamodelTest extends TestCase { } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } - + + /** + * Test method for the methods related to reference sets + */ + public void testReferenceSets() { + String set1name = "referenceSet1"; + String set1version = "1.0"; + EamGlobalSet set1; + int set1id; + String set2name = "referenceSet2"; + EamGlobalSet set2; + int set2id; + EamGlobalSet set3; + int set3id; + + // Store the file type object to save time + CorrelationAttribute.Type fileType; + try{ + fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + assertTrue("In testReferenceSets, getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a notable reference set + try { + set1 = new EamGlobalSet(org1.getOrgID(), set1name, set1version, TskData.FileKnown.BAD, false, fileType); + set1id = EamDb.getInstance().newReferenceSet(set1); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a known reference set + try { + set2 = new EamGlobalSet(org2.getOrgID(), set2name, "", TskData.FileKnown.KNOWN, false, fileType); + set2id = EamDb.getInstance().newReferenceSet(set2); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a reference set with the same name and version + try { + EamGlobalSet temp = new EamGlobalSet(org1.getOrgID(), set1name, "1.0", TskData.FileKnown.BAD, false, fileType); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from duplicate name/version pair"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating a reference set with the same name but different version + try { + set3 = new EamGlobalSet(org1.getOrgID(), set1name, "2.0", TskData.FileKnown.BAD, false, fileType); + set3id = EamDb.getInstance().newReferenceSet(set3); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test creating a reference set with invalid org ID + try { + EamGlobalSet temp = new EamGlobalSet(5000, "tempName", "", TskData.FileKnown.BAD, false, fileType); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from invalid org ID"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating a reference set with null name + try { + EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), null, "", TskData.FileKnown.BAD, false, fileType); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from null name"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating a reference set with null version + try { + EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", null, TskData.FileKnown.BAD, false, fileType); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from null version"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating a reference set with null file known status + try { + EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", "", null, false, fileType); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from null file known status"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating a reference set with null file type + try { + EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", "", TskData.FileKnown.BAD, false, null); + EamDb.getInstance().newReferenceSet(temp); + Assert.fail("newReferenceSet failed to throw exception from null file type"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test validation with a valid reference set + try { + assertTrue("referenceSetIsValid returned false for valid reference set", EamDb.getInstance().referenceSetIsValid(set1id, set1name, set1version)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test validation with an invalid reference set + try { + assertFalse("referenceSetIsValid returned true for invalid reference set", EamDb.getInstance().referenceSetIsValid(5000, set1name, set1version)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test validation with a null name + try { + assertFalse("referenceSetIsValid returned true with null name", EamDb.getInstance().referenceSetIsValid(set1id, null, set1version)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test validation with a null version + try { + assertFalse("referenceSetIsValid returned true with null version", EamDb.getInstance().referenceSetIsValid(set1id, set1name, null)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test existence with a valid reference set + try { + assertTrue("referenceSetExists returned false for valid reference set", EamDb.getInstance().referenceSetExists(set1name, set1version)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test existence with an invalid reference set + try { + assertFalse("referenceSetExists returned true for invalid reference set", EamDb.getInstance().referenceSetExists(set1name, "5.5")); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test existence with null name + try { + assertFalse("referenceSetExists returned true for null name", EamDb.getInstance().referenceSetExists(null, "1.0")); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test existence with null version + try { + assertFalse("referenceSetExists returned true for null version", EamDb.getInstance().referenceSetExists(set1name, null)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting global set with valid ID + try { + EamGlobalSet temp = EamDb.getInstance().getReferenceSetByID(set1id); + assertTrue("getReferenceSetByID returned null for valid ID", temp != null); + assertTrue("getReferenceSetByID returned set with incorrect name and/or version", + set1name.equals(temp.getSetName()) && set1version.equals(temp.getVersion())); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting global set with invalid ID + try { + EamGlobalSet temp = EamDb.getInstance().getReferenceSetByID(1234); + assertTrue("getReferenceSetByID returned non-null result for invalid ID", temp == null); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting all file reference sets + try { + List referenceSets = EamDb.getInstance().getAllReferenceSets(fileType); + assertTrue("getAllReferenceSets(FILES) returned unexpected number", referenceSets.size() == 3); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting all email reference sets + try { + List referenceSets = EamDb.getInstance().getAllReferenceSets(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID)); + assertTrue("getAllReferenceSets(EMAIL) returned unexpected number", referenceSets.isEmpty()); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test null argument to getAllReferenceSets + try { + EamDb.getInstance().getAllReferenceSets(null); + Assert.fail("getAllReferenceSets failed to throw exception from null type argument"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test deleting an existing reference set + // First: create a new reference set, check that it's in the database, and get the number of reference sets + // Second: delete the reference set, check that it is no longer in the database, and the total number of sets decreased by one + try { + EamGlobalSet setToDelete = new EamGlobalSet(org1.getOrgID(), "deleteThis", "deleteThisVersion", TskData.FileKnown.BAD, false, fileType); + int setToDeleteID = EamDb.getInstance().newReferenceSet(setToDelete); + assertTrue("setToDelete wasn't found in database", EamDb.getInstance().referenceSetIsValid(setToDeleteID, setToDelete.getSetName(), setToDelete.getVersion())); + int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); + + + + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + /** + * Test method for the methods related to the data sources table + * newDataSource(CorrelationDataSource eamDataSource) tests: + * - Test with valid data + * - Test with duplicate data + * - Test with duplicate device ID and name but different case + * - Test with invalid case ID + * - Test with null device ID + * - Test with null name + * getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) tests: + * - Test with valid data + * - Test with non-existent data + * - Test with null correlationCase + * - Test with null device ID + * List getDataSources()tests: + * - Test that the count and device IDs are as expected + * Long getCountUniqueDataSources() tests: + * - Test that the result is as expected + */ public void testDataSources() { final String dataSourceAname = "dataSourceA"; final String dataSourceAid = "dataSourceA_deviceID"; @@ -213,7 +477,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This is the expected behavior } - // Test creating a data source with null device ID + // Test creating a data source with null name try { CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), "tempID", null); EamDb.getInstance().newDataSource(temp); From 3f208e1a3b7d27a46a88814e3c8b3adb42222709 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Mon, 29 Jan 2018 16:10:33 -0500 Subject: [PATCH 38/71] Handling multiple files for kws list imports. --- .../GlobalListsManagementPanel.java | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java index aebf606380..6aae71be33 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java @@ -365,81 +365,84 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa chooser.addChoosableFileFilter(autopsyFilter); chooser.addChoosableFileFilter(encaseFilter); chooser.setAcceptAllFileFilterUsed(false); + chooser.setMultiSelectionEnabled(true); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); String listName = null; int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { - File selFile = chooser.getSelectedFile(); - if (selFile == null) { - return; - } + File[] selFiles = chooser.getSelectedFiles(); - //force append extension if not given - String fileAbs = selFile.getAbsolutePath(); - - final KeywordSearchList reader; - - if (KeywordSearchUtil.isXMLList(fileAbs)) { - reader = new XmlKeywordSearchList(fileAbs); - } else { - reader = new EnCaseKeywordSearchList(fileAbs); - } - - if (!reader.load()) { - KeywordSearchUtil.displayDialog( - NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.importListFileDialogMsg", fileAbs), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); - return; - } - - List toImport = reader.getListsL(); - List toImportConfirmed = new ArrayList<>(); - - final XmlKeywordSearchList writer = XmlKeywordSearchList.getCurrent(); - - for (KeywordList list : toImport) { - //check name collisions - listName = list.getName(); - if (writer.listExists(listName)) { - String[] options; - if (toImport.size() == 1) { //only give them cancel and yes buttons for single list imports - options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; - } else { - options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.noSkipMsg"), - NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; - } - int choice = JOptionPane.showOptionDialog(this, - NbBundle.getMessage(this.getClass(), "KeywordSearch.overwriteListPrompt", listName), - NbBundle.getMessage(this.getClass(), "KeywordSearch.importOwConflict"), - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); - if (choice == JOptionPane.OK_OPTION) { - toImportConfirmed.add(list); - } else if (choice == JOptionPane.CANCEL_OPTION) { - break; - } + for (File file : selFiles) { + if (file == null) { + continue; + } + + //force append extension if not given + String fileAbs = file.getAbsolutePath(); + final KeywordSearchList reader; + if (KeywordSearchUtil.isXMLList(fileAbs)) { + reader = new XmlKeywordSearchList(fileAbs); } else { - //no conflict - toImportConfirmed.add(list); + reader = new EnCaseKeywordSearchList(fileAbs); } - } + if (!reader.load()) { + KeywordSearchUtil.displayDialog( + NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.importListFileDialogMsg", fileAbs), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); + return; + } - if (toImportConfirmed.isEmpty()) { - return; - } + List toImport = reader.getListsL(); + List toImportConfirmed = new ArrayList<>(); - if (!writer.writeLists(toImportConfirmed)) { - KeywordSearchUtil.displayDialog( - NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.kwListFailImportMsg"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + final XmlKeywordSearchList writer = XmlKeywordSearchList.getCurrent(); + + for (KeywordList list : toImport) { + //check name collisions + listName = list.getName(); + if (writer.listExists(listName)) { + String[] options; + if (toImport.size() == 1) { //only give them cancel and yes buttons for single list imports + options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; + } else { + options = new String[]{NbBundle.getMessage(this.getClass(), "KeywordSearch.yesOwMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.noSkipMsg"), + NbBundle.getMessage(this.getClass(), "KeywordSearch.cancelImportMsg")}; + } + int choice = JOptionPane.showOptionDialog(this, + NbBundle.getMessage(this.getClass(), "KeywordSearch.overwriteListPrompt", listName), + NbBundle.getMessage(this.getClass(), "KeywordSearch.importOwConflict"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (choice == JOptionPane.OK_OPTION) { + toImportConfirmed.add(list); + } else if (choice == JOptionPane.CANCEL_OPTION) { + break; + } + + } else { + //no conflict + toImportConfirmed.add(list); + } + + } + + if (toImportConfirmed.isEmpty()) { + return; + } + + if (!writer.writeLists(toImportConfirmed)) { + KeywordSearchUtil.displayDialog( + NbBundle.getMessage(this.getClass(), "KeywordSearch.listImportFeatureTitle"), NbBundle.getMessage(this.getClass(), "KeywordSearch.kwListFailImportMsg"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + } + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_KEYWORD_LIST_PATH_KEY, file.getParent()); } - ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_KEYWORD_LIST_PATH_KEY, selFile.getParent()); } tableModel.resync(); From 338334f62936c2d3ea30979d7076f5b39388d2cc Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 30 Jan 2018 09:50:25 -0500 Subject: [PATCH 39/71] 3415: Show SQLite table contents Resize columns based on data from top 20 rows --- .../contentviewers/SQLiteTableView.java | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index ad1117f0a0..7ca873e13c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.BorderLayout; +import java.awt.Component; import java.util.List; import java.util.Map; import java.util.Objects; @@ -26,6 +27,8 @@ import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; +import javax.swing.SwingWorker; +import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import org.netbeans.swing.etable.ETableColumn; import org.netbeans.swing.etable.ETableColumnModel; @@ -70,18 +73,14 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { */ void setupTable(List> tableRows) { - explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); - + if (Objects.isNull(tableRows) || tableRows.isEmpty()) { outlineView.setPropertyColumns(); -// return; } else { + // Set up the column names Map row = tableRows.get(0); - - // Get the columns setup with respect to names and sortability String[] propStrings = new String[row.size() * 2]; - int i = 0; for (Map.Entry col : row.entrySet()) { String colName = col.getKey(); @@ -93,19 +92,55 @@ class SQLiteTableView extends JPanel implements ExplorerManager.Provider { outlineView.setPropertyColumns(propStrings); } - // TBD: Set width based on actual data in the top N rows?? - // TBD: Can't seem to get the horizontal scrollbar working - for (int col = 0; col< outline.getModel().getColumnCount(); col++) { - outline.getColumnModel().getColumn(col).setMinWidth(50); - } - // Hide the 'Nodes' column TableColumnModel columnModel = outline.getColumnModel(); ETableColumn column = (ETableColumn) columnModel.getColumn(0); ((ETableColumnModel) columnModel).setColumnHidden(column, true); + // Set the Nodes for the ExplorerManager. + // The Swingworker ensures that setColumnWidths() is called after all nodes have been created. + new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + + explorerManager.setRootContext(new AbstractNode(Children.create(new SQLiteTableRowFactory(tableRows), true))); + return false; + } + + @Override + protected void done() { + super.done(); + + setColumnWidths(); + } + }.execute(); + } + private void setColumnWidths() { + int margin = 4; + int padding = 8; + + // find the maximum width needed to fit the values for the first N rows, at most + final int rows = Math.min(20, outline.getRowCount()); + for (int col = 1; col < outline.getColumnCount(); col++) { + int columnWidthLimit = 500; + int columnWidth = 50; + + for (int row = 0; row < rows; row++) { + TableCellRenderer renderer = outline.getCellRenderer(row, col); + Component comp = outline.prepareRenderer(renderer, row, col); + + columnWidth = Math.max(comp.getPreferredSize().width, columnWidth); + } + + columnWidth += 2 * margin + padding; // add margin and regular padding + columnWidth = Math.min(columnWidth, columnWidthLimit); + outline.getColumnModel().getColumn(col).setPreferredWidth(columnWidth); + } + } + + /** * 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 From 196a13ddb37ce453bc9a325143f9681914267366 Mon Sep 17 00:00:00 2001 From: rishwanth Date: Tue, 30 Jan 2018 12:10:55 -0500 Subject: [PATCH 40/71] removed xdock jvm.option for linux and window build --- build.xml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index 4eaa7df1c8..d044f68f06 100644 --- a/build.xml +++ b/build.xml @@ -32,7 +32,10 @@ - + + + + @@ -82,7 +85,13 @@ - + + + + + + + @@ -91,8 +100,17 @@ + - + + + + + + + + + From 1089c1b2052b7da76fcdf79878a82cde3c777641 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Tue, 30 Jan 2018 16:41:40 -0500 Subject: [PATCH 41/71] 3499: List of rulesets to test. --- ruleset.xml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index d4f2f075fc..41e2756faf 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -1,13 +1,38 @@ - + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> - Ruleset used by Autopsy + Ruleset used by Autopsy + + + + + + + + + + + + + + + + + + + + + + + + + + From d80a0a468d9da83c9f62b2274ceed10441586326 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Tue, 30 Jan 2018 16:54:28 -0500 Subject: [PATCH 42/71] 3499: Fix spelling. --- ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruleset.xml b/ruleset.xml index 41e2756faf..2b4e66b821 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -24,7 +24,7 @@ - + From 8bd4317f94b35a7bb9ec83d2fb8461cd5ed19a38 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 30 Jan 2018 16:57:01 -0500 Subject: [PATCH 43/71] Add NamedThreadFactory to ThreadUtils --- .../autopsy/coreutils/ThreadUtils.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java index b9873469e7..8a354a531a 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.coreutils; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /* @@ -50,6 +51,35 @@ final public class ThreadUtils { } } + /** + * A thread factory that allows for the creation of distinctly named task + * threads by an ExecutorService constructed using an Executors factory + * method. Each thread created using the factory will be named using the + * thread name and a numerical suffix. + */ + public static class NamedThreadFactory implements ThreadFactory { + + private final String threadName; + + /** + * Contructs a thread factory that allows for the creation of distinctly + * named task threads by an ExecutorService constructed using an + * Executors factory method. Each thread created using the factory will + * be named using the thread name and a numerical suffix. + * + * @param threadName The name of the threads. + */ + public NamedThreadFactory(String threadName) { + this.threadName = threadName; + } + + @Override + public Thread newThread(Runnable task) { + return new Thread(task, threadName); + } + + } + private ThreadUtils() { } } From 52cd06a1147d5aed1dec1b6a8b06d429917e0d5e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 30 Jan 2018 17:23:48 -0500 Subject: [PATCH 44/71] Revert "Add NamedThreadFactory to ThreadUtils" This reverts commit 8bd4317f94b35a7bb9ec83d2fb8461cd5ed19a38. --- .../autopsy/coreutils/ThreadUtils.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java index 8a354a531a..b9873469e7 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ThreadUtils.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.coreutils; import java.util.concurrent.ExecutorService; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /* @@ -51,35 +50,6 @@ final public class ThreadUtils { } } - /** - * A thread factory that allows for the creation of distinctly named task - * threads by an ExecutorService constructed using an Executors factory - * method. Each thread created using the factory will be named using the - * thread name and a numerical suffix. - */ - public static class NamedThreadFactory implements ThreadFactory { - - private final String threadName; - - /** - * Contructs a thread factory that allows for the creation of distinctly - * named task threads by an ExecutorService constructed using an - * Executors factory method. Each thread created using the factory will - * be named using the thread name and a numerical suffix. - * - * @param threadName The name of the threads. - */ - public NamedThreadFactory(String threadName) { - this.threadName = threadName; - } - - @Override - public Thread newThread(Runnable task) { - return new Thread(task, threadName); - } - - } - private ThreadUtils() { } } From 23c5e5be62195f448294cb7e25a4b7f2c283ec2c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 31 Jan 2018 08:48:04 -0500 Subject: [PATCH 45/71] Finished reference set tests --- .../datamodel/AbstractSqlEamDb.java | 3 + .../datamodel/CentralRepoDatamodelTest.java | 489 ++++++++++-------- 2 files changed, 284 insertions(+), 208 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 3003b28a4a..48a7f34a6e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1541,6 +1541,9 @@ public abstract class AbstractSqlEamDb implements EamDb { public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException { EamGlobalSet globalSet = getReferenceSetByID(referenceSetID); + if(globalSet == null) { + throw new EamDbException("Reference set with ID " + referenceSetID + " not found"); + } return (getOrganizationByID(globalSet.getOrgID())); } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index f23421fdb9..2aafcf0679 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -71,16 +71,7 @@ public class CentralRepoDatamodelTest extends TestCase { assertTrue("Unable to create test directory", testDirectory.toFile().exists()); // Save the current central repo settings - propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); - - // Set up an Autopsy case for testing - try { - Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, testDirectory.toString(), new CaseDetails("CentralRepoDatamodelTestCase")); - } catch (CaseActionException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - assertTrue("Failed to create test case", testDirectory.toFile().exists()); + propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); try { dbSettingsSqlite.setDbName(CR_DB_NAME); @@ -150,14 +141,8 @@ public class CentralRepoDatamodelTest extends TestCase { // Close and delete the test case and central repo db try { EamDb.getInstance().shutdownConnections(); - Case.closeCurrentCase(); - - // This seems to help in allowing the Autopsy case to be deleted - try{ - Thread.sleep(2000); - } catch (Exception ex){ - - } + Case.closeCurrentCase(); // There shouldn't be a case open, but it's fine to call this anyway + FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | CaseActionException | IOException ex) { @@ -169,7 +154,40 @@ public class CentralRepoDatamodelTest extends TestCase { } /** - * Test method for the methods related to reference sets + * Test method for the methods related to reference sets (does not include instance testing) + * newReferenceSet(EamGlobalSet eamGlobalSet) tests: + * - Test creating notable reference set + * - Test creating known reference set + * - Test creating duplicate reference set + * - Test creating almost duplicate reference set + * - Test with invalid org ID + * - Test with null name + * - Test with null version + * - Test with null known status + * - Test with null file type + * referenceSetIsValid(int referenceSetID, String referenceSetName, String version) tests: + * - Test on existing reference set + * - Test on invalid reference set + * - Test with null name + * - Test with null version + * referenceSetExists(String referenceSetName, String version) tests: + * - Test on existing reference set + * - Test on invalid reference set + * - Test with null name + * - Test with null version + * getReferenceSetByID(int globalSetID) tests: + * - Test with valid ID + * - Test with invalid ID + * getAllReferenceSets(CorrelationAttribute.Type correlationType) tests: + * - Test getting all file sets + * - Test getting all email sets + * - Test with null type parameter + * deleteReferenceSet(int referenceSetID) tests: + * - Test on valid reference set ID + * - Test on invalid reference set ID + * getReferenceSetOrganization(int referenceSetID) tests: + * - Test on valid reference set ID + * - Test on invalid reference set ID */ public void testReferenceSets() { String set1name = "referenceSet1"; @@ -396,13 +414,44 @@ public class CentralRepoDatamodelTest extends TestCase { assertTrue("setToDelete wasn't found in database", EamDb.getInstance().referenceSetIsValid(setToDeleteID, setToDelete.getSetName(), setToDelete.getVersion())); int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); - + EamDb.getInstance().deleteReferenceSet(setToDeleteID); + assertFalse("Deleted reference set was found in database", EamDb.getInstance().referenceSetIsValid(setToDeleteID, setToDelete.getSetName(), setToDelete.getVersion())); + assertTrue("Unexpected number of reference sets in database after deletion", currentCount - 1 == EamDb.getInstance().getAllReferenceSets(fileType).size()); } catch (EamDbException ex){ Exceptions.printStackTrace(ex); Assert.fail(ex); } + // Test deleting a non-existent reference set + // The expectation is that nothing will happen + try { + int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); + EamDb.getInstance().deleteReferenceSet(1234); + assertTrue("Number of reference sets changed after deleting non-existent set", currentCount == EamDb.getInstance().getAllReferenceSets(fileType).size()); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting reference set organization for valid ID with org set + try { + EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(set1id); + assertTrue("getReferenceSetOrganization returned null for valid set", org != null); + assertTrue("getReferenceSetOrganization returned the incorrect organization", org.getOrgID() == org1.getOrgID()); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting reference set organization for non-existent reference set + try { + EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(4567); + Assert.fail("getReferenceSetOrganization failed to throw exception for invalid reference set ID"); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } } /** @@ -565,201 +614,225 @@ public class CentralRepoDatamodelTest extends TestCase { final String caseAuuid = "caseA_uuid"; CorrelationCase caseA; CorrelationCase caseB; - - // Test creating a case with valid name and uuid + try { - caseA = new CorrelationCase(caseAuuid, caseAname); - caseA = EamDb.getInstance().newCase(caseA); - assertTrue("Failed to create case", caseA != null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - return; - } + // Set up an Autopsy case for testing + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, testDirectory.toString(), new CaseDetails("CentralRepoDatamodelTestCase")); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertTrue("Failed to create test case", testDirectory.toFile().exists()); - // Test null uuid - try { - CorrelationCase tempCase = new CorrelationCase(null, "nullUuidCase"); - tempCase = EamDb.getInstance().newCase(tempCase); - Assert.fail("newCase did not throw expected exception from null uuid"); - } catch (EamDbException ex) { - // This is the expected behavior - } - - // Test null name - try { - CorrelationCase tempCase = new CorrelationCase("nullCaseUuid", null); - tempCase = EamDb.getInstance().newCase(tempCase); - Assert.fail("newCase did not throw expected exception from null name"); - } catch (EamDbException ex) { - // This is the expected behavior - } - - // Test creating a case with an already used UUID - // This should just return the existing case object. Check that the total - // number of cases does not increase. - try { - int nCases = EamDb.getInstance().getCases().size(); - CorrelationCase tempCase = new CorrelationCase(caseAuuid, "newCaseWithSameUUID"); - tempCase = EamDb.getInstance().newCase(tempCase); - assertTrue("newCase returned null for existing UUID", tempCase != null); - assertTrue("newCase created a new case for an already existing UUID", nCases == EamDb.getInstance().getCases().size()); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test creating a case from an Autopsy case - // The case may already be in the database - the result is the same either way - try { - caseB = EamDb.getInstance().newCase(Case.getCurrentCase()); - assertTrue("Failed to create correlation case from Autopsy case", caseB != null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - return; - } - - // Test null Autopsy case - try { - Case nullCase = null; - CorrelationCase tempCase = EamDb.getInstance().newCase(nullCase); - Assert.fail("newCase did not throw expected exception from null case"); - } catch (EamDbException ex) { - // This is the expected behavior - } - - // Test update case - // Will update the fields of an existing case object, save it, and then - // pull a new copy out of the database - try { - assertTrue(caseA != null); - String caseNumber = "12-34-56"; - String creationDate = "01/12/2018"; - String displayName = "Test Case"; - String examinerEmail = "john@sample.com"; - String examinerName = "John Doe"; - String examinerPhone = "123-555-4567"; - String notes = "Notes"; - - caseA.setCaseNumber(caseNumber); - caseA.setCreationDate(creationDate); - caseA.setDisplayName(displayName); - caseA.setExaminerEmail(examinerEmail); - caseA.setExaminerName(examinerName); - caseA.setExaminerPhone(examinerPhone); - caseA.setNotes(notes); - caseA.setOrg(org1); - - EamDb.getInstance().updateCase(caseA); - - // Retrievex a new copy of the case from the database to check that the - // fields were properly updated - CorrelationCase updatedCase = EamDb.getInstance().getCaseByUUID(caseA.getCaseUUID()); - - assertTrue("updateCase failed to update case number", caseNumber.equals(updatedCase.getCaseNumber())); - assertTrue("updateCase failed to update creation date", creationDate.equals(updatedCase.getCreationDate())); - assertTrue("updateCase failed to update display name", displayName.equals(updatedCase.getDisplayName())); - assertTrue("updateCase failed to update examiner email", examinerEmail.equals(updatedCase.getExaminerEmail())); - assertTrue("updateCase failed to update examiner name", examinerName.equals(updatedCase.getExaminerName())); - assertTrue("updateCase failed to update examiner phone number", examinerPhone.equals(updatedCase.getExaminerPhone())); - assertTrue("updateCase failed to update notes", notes.equals(updatedCase.getNotes())); - assertTrue("updateCase failed to update org (org is null)", updatedCase.getOrg() != null); - assertTrue("updateCase failed to update org (org ID is wrong)", org1.getOrgID() == updatedCase.getOrg().getOrgID()); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test update case with null case - try { - EamDb.getInstance().updateCase(null); - Assert.fail("updateCase did not throw expected exception from null case"); - } catch (EamDbException ex) { - // This is the expected behavior - } - - // Test getting a case from an Autopsy case - try { - CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getCurrentCase()); - assertTrue("getCase returned null for current Autopsy case", tempCase != null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test getting a case by UUID - try { - CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(caseAuuid); - assertTrue("Failed to get case by UUID", tempCase != null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test getting a case with a non-existent UUID - try { - CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID("badUUID"); - assertTrue("getCaseByUUID returned non-null case for non-existent UUID", tempCase == null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test getting a case with null UUID - try { - CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(null); - assertTrue("getCaseByUUID returned non-null case for null UUID", tempCase == null); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test getting the list of cases - // The test is to make sure the three cases we know are in the database are in the list - try { - List caseList = EamDb.getInstance().getCases(); - List uuidList - = caseList.stream().map(c -> c.getCaseUUID()).collect(Collectors.toList()); - assertTrue("getCases is missing data for existing cases", uuidList.contains(case1.getCaseUUID()) - && uuidList.contains(case2.getCaseUUID()) && (uuidList.contains(caseA.getCaseUUID())) - && uuidList.contains(caseB.getCaseUUID())); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - - // Test bulk case insert - try { - // Create a list of correlation cases. Make enough that the bulk threshold should be hit once. - List cases = new ArrayList<>(); - String bulkTestUuid = "bulkTestUUID_"; - String bulkTestName = "bulkTestName_"; - for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { - String name = bulkTestUuid + String.valueOf(i); - String uuid = bulkTestName + String.valueOf(i); - cases.add(new CorrelationCase(uuid, name)); + // Test creating a case with valid name and uuid + try { + caseA = new CorrelationCase(caseAuuid, caseAname); + caseA = EamDb.getInstance().newCase(caseA); + assertTrue("Failed to create case", caseA != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; } - // Get the current case count - int nCases = EamDb.getInstance().getCases().size(); + // Test null uuid + try { + CorrelationCase tempCase = new CorrelationCase(null, "nullUuidCase"); + tempCase = EamDb.getInstance().newCase(tempCase); + Assert.fail("newCase did not throw expected exception from null uuid"); + } catch (EamDbException ex) { + // This is the expected behavior + } - // Insert the big list of cases - EamDb.getInstance().bulkInsertCases(cases); + // Test null name + try { + CorrelationCase tempCase = new CorrelationCase("nullCaseUuid", null); + tempCase = EamDb.getInstance().newCase(tempCase); + Assert.fail("newCase did not throw expected exception from null name"); + } catch (EamDbException ex) { + // This is the expected behavior + } - // Check that the case count is what is expected - assertTrue("bulkInsertCases did not insert the expected number of cases", nCases + cases.size() == EamDb.getInstance().getCases().size()); - } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } + // Test creating a case with an already used UUID + // This should just return the existing case object. Check that the total + // number of cases does not increase. + try { + int nCases = EamDb.getInstance().getCases().size(); + CorrelationCase tempCase = new CorrelationCase(caseAuuid, "newCaseWithSameUUID"); + tempCase = EamDb.getInstance().newCase(tempCase); + assertTrue("newCase returned null for existing UUID", tempCase != null); + assertTrue("newCase created a new case for an already existing UUID", nCases == EamDb.getInstance().getCases().size()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } - // Test bulk case insert with null list - try { - EamDb.getInstance().bulkInsertCases(null); - Assert.fail("bulkInsertCases did not throw expected exception from null list"); - } catch (EamDbException ex) { - // This is the expected behavior + // Test creating a case from an Autopsy case + // The case may already be in the database - the result is the same either way + try { + caseB = EamDb.getInstance().newCase(Case.getCurrentCase()); + assertTrue("Failed to create correlation case from Autopsy case", caseB != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test null Autopsy case + try { + Case nullCase = null; + CorrelationCase tempCase = EamDb.getInstance().newCase(nullCase); + Assert.fail("newCase did not throw expected exception from null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test update case + // Will update the fields of an existing case object, save it, and then + // pull a new copy out of the database + try { + assertTrue(caseA != null); + String caseNumber = "12-34-56"; + String creationDate = "01/12/2018"; + String displayName = "Test Case"; + String examinerEmail = "john@sample.com"; + String examinerName = "John Doe"; + String examinerPhone = "123-555-4567"; + String notes = "Notes"; + + caseA.setCaseNumber(caseNumber); + caseA.setCreationDate(creationDate); + caseA.setDisplayName(displayName); + caseA.setExaminerEmail(examinerEmail); + caseA.setExaminerName(examinerName); + caseA.setExaminerPhone(examinerPhone); + caseA.setNotes(notes); + caseA.setOrg(org1); + + EamDb.getInstance().updateCase(caseA); + + // Retrievex a new copy of the case from the database to check that the + // fields were properly updated + CorrelationCase updatedCase = EamDb.getInstance().getCaseByUUID(caseA.getCaseUUID()); + + assertTrue("updateCase failed to update case number", caseNumber.equals(updatedCase.getCaseNumber())); + assertTrue("updateCase failed to update creation date", creationDate.equals(updatedCase.getCreationDate())); + assertTrue("updateCase failed to update display name", displayName.equals(updatedCase.getDisplayName())); + assertTrue("updateCase failed to update examiner email", examinerEmail.equals(updatedCase.getExaminerEmail())); + assertTrue("updateCase failed to update examiner name", examinerName.equals(updatedCase.getExaminerName())); + assertTrue("updateCase failed to update examiner phone number", examinerPhone.equals(updatedCase.getExaminerPhone())); + assertTrue("updateCase failed to update notes", notes.equals(updatedCase.getNotes())); + assertTrue("updateCase failed to update org (org is null)", updatedCase.getOrg() != null); + assertTrue("updateCase failed to update org (org ID is wrong)", org1.getOrgID() == updatedCase.getOrg().getOrgID()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test update case with null case + try { + EamDb.getInstance().updateCase(null); + Assert.fail("updateCase did not throw expected exception from null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting a case from an Autopsy case + try { + CorrelationCase tempCase = EamDb.getInstance().getCase(Case.getCurrentCase()); + assertTrue("getCase returned null for current Autopsy case", tempCase != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case by UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(caseAuuid); + assertTrue("Failed to get case by UUID", tempCase != null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case with a non-existent UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID("badUUID"); + assertTrue("getCaseByUUID returned non-null case for non-existent UUID", tempCase == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting a case with null UUID + try { + CorrelationCase tempCase = EamDb.getInstance().getCaseByUUID(null); + assertTrue("getCaseByUUID returned non-null case for null UUID", tempCase == null); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting the list of cases + // The test is to make sure the three cases we know are in the database are in the list + try { + List caseList = EamDb.getInstance().getCases(); + List uuidList + = caseList.stream().map(c -> c.getCaseUUID()).collect(Collectors.toList()); + assertTrue("getCases is missing data for existing cases", uuidList.contains(case1.getCaseUUID()) + && uuidList.contains(case2.getCaseUUID()) && (uuidList.contains(caseA.getCaseUUID())) + && uuidList.contains(caseB.getCaseUUID())); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test bulk case insert + try { + // Create a list of correlation cases. Make enough that the bulk threshold should be hit once. + List cases = new ArrayList<>(); + String bulkTestUuid = "bulkTestUUID_"; + String bulkTestName = "bulkTestName_"; + for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { + String name = bulkTestUuid + String.valueOf(i); + String uuid = bulkTestName + String.valueOf(i); + cases.add(new CorrelationCase(uuid, name)); + } + + // Get the current case count + int nCases = EamDb.getInstance().getCases().size(); + + // Insert the big list of cases + EamDb.getInstance().bulkInsertCases(cases); + + // Check that the case count is what is expected + assertTrue("bulkInsertCases did not insert the expected number of cases", nCases + cases.size() == EamDb.getInstance().getCases().size()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test bulk case insert with null list + try { + EamDb.getInstance().bulkInsertCases(null); + Assert.fail("bulkInsertCases did not throw expected exception from null list"); + } catch (EamDbException ex) { + // This is the expected behavior + } + } finally { + try { + Case.closeCurrentCase(); + // This seems to help in allowing the Autopsy case to be deleted + try{ + Thread.sleep(2000); + } catch (Exception ex){ + + } + } catch (CaseActionException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } } } From 6e35b3d2142cff098615630e2e725c8b8ca2c69f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 31 Jan 2018 13:42:35 -0500 Subject: [PATCH 46/71] Finished reference set testing --- .../datamodel/AbstractSqlEamDb.java | 29 +- .../datamodel/EamGlobalFileInstance.java | 15 +- .../modules/hashdatabase/HashDbManager.java | 13 +- .../datamodel/CentralRepoDatamodelTest.java | 396 +++++++++++++++++- 4 files changed, 426 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 48a7f34a6e..e3b61fbece 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1394,6 +1394,9 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("null correlation type"); + } // TEMP: Only support file correlation type if (aType.getId() != CorrelationAttribute.FILES_TYPE_ID) { @@ -1758,6 +1761,13 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) throws EamDbException { + if(eamGlobalFileInstance.getKnownStatus() == null){ + throw new EamDbException("known status of EamGlobalFileInstance is null"); + } + if(correlationType == null){ + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -1821,6 +1831,13 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) throws EamDbException { + if(contentType == null) { + throw new EamDbException("Null correlation type"); + } + if(globalInstances == null) { + throw new EamDbException("Null set of EamGlobalFileInstance"); + } + Connection conn = connect(); PreparedStatement bulkPs = null; @@ -1834,6 +1851,10 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkPs = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(contentType))); for (EamGlobalFileInstance globalInstance : globalInstances) { + if(globalInstance.getKnownStatus() == null){ + throw new EamDbException("EamGlobalFileInstance with value " + globalInstance.getMD5Hash() + " has null known status"); + } + bulkPs.setInt(1, globalInstance.getGlobalSetID()); bulkPs.setString(2, globalInstance.getMD5Hash()); bulkPs.setByte(3, globalInstance.getKnownStatus().getFileKnownValue()); @@ -1843,7 +1864,7 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkPs.executeBatch(); conn.commit(); - } catch (SQLException ex) { + } catch (SQLException | EamDbException ex) { try { conn.rollback(); } catch (SQLException ex2) { @@ -1868,6 +1889,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException { + if(aType == null) { + throw new EamDbException("correlation type is null"); + } + Connection conn = connect(); List globalFileInstances = new ArrayList<>(); @@ -2251,7 +2276,7 @@ public abstract class AbstractSqlEamDb implements EamDb { return eamGlobalSet; } - private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException { + private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { if (null == resultSet) { return null; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java index 87d974f353..3c538e67c8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamGlobalFileInstance.java @@ -36,7 +36,7 @@ public class EamGlobalFileInstance { int globalSetID, String MD5Hash, TskData.FileKnown knownStatus, - String comment) { + String comment) throws EamDbException { this(-1, globalSetID, MD5Hash, knownStatus, comment); } @@ -45,7 +45,13 @@ public class EamGlobalFileInstance { int globalSetID, String MD5Hash, TskData.FileKnown knownStatus, - String comment) { + String comment) throws EamDbException { + if(MD5Hash == null){ + throw new EamDbException("null MD5 hash"); + } + if(knownStatus == null){ + throw new EamDbException("null known status"); + } this.instanceID = instanceID; this.globalSetID = globalSetID; // Normalize hashes by lower casing @@ -111,7 +117,10 @@ public class EamGlobalFileInstance { /** * @param MD5Hash the MD5Hash to set */ - public void setMD5Hash(String MD5Hash) { + public void setMD5Hash(String MD5Hash) throws EamDbException { + if(MD5Hash == null){ + throw new EamDbException("null MD5 hash"); + } // Normalize hashes by lower casing this.MD5Hash = MD5Hash.toLowerCase(); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index c032bdc83e..09aec0256f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -1232,9 +1232,10 @@ public class HashDbManager implements PropertyChangeListener { } else { type = TskData.FileKnown.KNOWN; } - EamGlobalFileInstance fileInstance = new EamGlobalFileInstance(referenceSetID, file.getMd5Hash(), - type, comment); + try{ + EamGlobalFileInstance fileInstance = new EamGlobalFileInstance(referenceSetID, file.getMd5Hash(), + type, comment); EamDb.getInstance().addReferenceInstance(fileInstance,EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID)); } catch (EamDbException ex){ throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); @@ -1259,8 +1260,12 @@ public class HashDbManager implements PropertyChangeListener { type = TskData.FileKnown.BAD; } else { type = TskData.FileKnown.KNOWN; - } - globalFileInstances.add(new EamGlobalFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment())); + } + try { + globalFileInstances.add(new EamGlobalFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment())); + } catch (EamDbException ex){ + throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); + } } try{ diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 2aafcf0679..cd0a0322a4 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -9,11 +9,12 @@ import java.io.IOException; import java.util.Map; import java.util.List; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.HashSet; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Collectors; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; import junit.framework.Test; import junit.framework.TestCase; import org.apache.commons.io.FileUtils; @@ -25,6 +26,8 @@ import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.TskData; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; /** * @@ -33,7 +36,8 @@ public class CentralRepoDatamodelTest extends TestCase { private static final String PROPERTIES_FILE = "CentralRepository"; private static final String CR_DB_NAME = "testcentralrepo.db"; - private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); + //private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); + private static final Path testDirectory = Paths.get("C:", "Work", "CRDatamodelTest"); SqliteEamDbSettings dbSettingsSqlite; private CorrelationCase case1; @@ -42,6 +46,7 @@ public class CentralRepoDatamodelTest extends TestCase { private CorrelationDataSource dataSource2fromCase1; private EamOrganization org1; private EamOrganization org2; + CorrelationAttribute.Type fileType; private Map propertiesMap = null; @@ -124,6 +129,10 @@ public class CentralRepoDatamodelTest extends TestCase { org2 = new EamOrganization("org2"); org2.setOrgID((int) EamDb.getInstance().newOrganization(org2)); + + // Store the file type object for later use + fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + assertTrue("getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -141,20 +150,382 @@ public class CentralRepoDatamodelTest extends TestCase { // Close and delete the test case and central repo db try { EamDb.getInstance().shutdownConnections(); - Case.closeCurrentCase(); // There shouldn't be a case open, but it's fine to call this anyway FileUtils.deleteDirectory(testDirectory.toFile()); - } catch (EamDbException | CaseActionException | IOException ex) { - //} catch (EamDbException | CaseActionException ex) { + } catch (EamDbException | IOException ex) { + // } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } + /** + * Tests for adding / retrieving reference instances + * Only the files type is currently implemented + * addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) tests: + * - Test adding multiple valid entries + * - Test invalid reference set ID + * - Test null hash (EamGlobalFileInstance constructor) + * - Test null known status (EamGlobalFileInstance constructor) + * - Test null correlation type + * bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) tests: + * - Test with large valid list + * - Test with null list + * - Test with invalid reference set ID + * - Test with null correlation type + * getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) tests: + * - Test with valid entries + * - Test with non-existent value + * - Test with invalid type + * - Test with null type + * - Test with null value + * isFileHashInReferenceSet(String hash, int referenceSetID)tests: + * - Test existing hash/ID + * - Test non-existent (but valid) hash/ID + * - Test invalid ID + * - Test null hash + * isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) tests: + * - Test existing value/ID + * - Test non-existent (but valid) value/ID + * - Test invalid ID + * - Test null value + * - Test invalid type ID + * isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) tests: + * - Test notable value + * - Test known value + * - Test non-existent value + * - Test null value + * - Test null type + * - Test invalid type + */ + public void testReferenceSetInstances(){ + + // After the two initial testing blocks, the reference sets should contain: + // notableSet1 - notableHash1, inAllSetsHash + // notableSet2 - inAllSetsHash + // knownSet1 - knownHash1, inAllSetsHash + EamGlobalSet notableSet1; + int notableSet1id; + EamGlobalSet notableSet2; + int notableSet2id; + EamGlobalSet knownSet1; + int knownSet1id; + + String notableHash1 = "d46feecd663c41648dbf690d9343cf4b"; + String knownHash1 = "39c844daee70485143da4ff926601b5b"; + String inAllSetsHash = "6449b39bb23c42879fa0c243726e27f7"; + + CorrelationAttribute.Type emailType; + + // Store the email type object for later use + try{ + emailType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID); + assertTrue("getCorrelationTypeById(EMAIL_TYPE_ID) returned null", emailType != null); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Set up a few reference sets + try { + notableSet1 = new EamGlobalSet(org1.getOrgID(), "notable set 1", "1.0", TskData.FileKnown.BAD, false, fileType); + notableSet1id = EamDb.getInstance().newReferenceSet(notableSet1); + notableSet2 = new EamGlobalSet(org1.getOrgID(), "notable set 2", "2.4", TskData.FileKnown.BAD, false, fileType); + notableSet2id = EamDb.getInstance().newReferenceSet(notableSet2); + knownSet1 = new EamGlobalSet(org1.getOrgID(), "known set 1", "5.5.4", TskData.FileKnown.KNOWN, false, fileType); + knownSet1id = EamDb.getInstance().newReferenceSet(knownSet1); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test adding file instances with valid data + try { + EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment1"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + + temp = new EamGlobalFileInstance(notableSet2id, inAllSetsHash, TskData.FileKnown.BAD, "comment2"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + + temp = new EamGlobalFileInstance(knownSet1id, inAllSetsHash, TskData.FileKnown.KNOWN, "comment3"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + + temp = new EamGlobalFileInstance(notableSet1id, notableHash1, TskData.FileKnown.BAD, "comment4"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + + temp = new EamGlobalFileInstance(knownSet1id, knownHash1, TskData.FileKnown.KNOWN, "comment5"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding file instance with invalid reference set ID + try { + EamGlobalFileInstance temp = new EamGlobalFileInstance(2345, inAllSetsHash, TskData.FileKnown.BAD, "comment"); + EamDb.getInstance().addReferenceInstance(temp, fileType); + Assert.fail("addReferenceInstance failed to throw exception for invalid ID"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test creating file instance with null hash + // Since it isn't possible to get a null hash into the EamGlobalFileInstance, skip trying to + // call addReferenceInstance and just test the EamGlobalFileInstance constructor + try { + EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, null, TskData.FileKnown.BAD, "comment"); + Assert.fail("EamGlobalFileInstance failed to throw exception for null hash"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding file instance with null known status + // Since it isn't possible to get a null known status into the EamGlobalFileInstance, skip trying to + // call addReferenceInstance and just test the EamGlobalFileInstance constructor + try { + EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, null, "comment"); + Assert.fail("EamGlobalFileInstance failed to throw exception for null type"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding file instance with null correlation type + try { + EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment"); + EamDb.getInstance().addReferenceInstance(temp, null); + Assert.fail("addReferenceInstance failed to throw exception for null type"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test bulk insert with large valid set + try { + // Create a list of global file instances. Make enough that the bulk threshold should be hit once. + Set instances = new HashSet<>(); + String bulkTestHash = "bulktesthash_"; + for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { + String hash = bulkTestHash + String.valueOf(i); + instances.add(new EamGlobalFileInstance(notableSet2id, hash, TskData.FileKnown.BAD, null)); + } + + // Insert the list + EamDb.getInstance().bulkInsertReferenceTypeEntries(instances, fileType); + + // There's no way to get a count of the number of entries in the database, so just do a spot check + if(dbSettingsSqlite.getBulkThreshold() > 10){ + String hash = bulkTestHash + "10"; + assertTrue("Sample bulk insert instance not found", EamDb.getInstance().isFileHashInReferenceSet(hash, notableSet2id)); + } + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test bulk add file instance with null list + try { + EamDb.getInstance().bulkInsertReferenceTypeEntries(null, fileType); + Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for null list"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test bulk add file instance with invalid reference set ID + try { + Set tempSet = new HashSet<>(Arrays.asList(new EamGlobalFileInstance(2345, inAllSetsHash, TskData.FileKnown.BAD, "comment"))); + EamDb.getInstance().bulkInsertReferenceTypeEntries(tempSet, fileType); + Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for invalid ID"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test bulk add file instance with null correlation type + try { + Set tempSet = new HashSet<>(Arrays.asList(new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment"))); + EamDb.getInstance().bulkInsertReferenceTypeEntries(tempSet, null); + Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for null type"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting reference instances with valid data + try { + List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, inAllSetsHash); + assertTrue("getReferenceInstancesByTypeValue returned " + temp.size() + " instances - expected 3", temp.size() == 3); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting reference instances with non-existent data + try { + List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, "testHash"); + assertTrue("getReferenceInstancesByTypeValue returned " + temp.size() + " instances for non-existent value - expected 0", temp.isEmpty()); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting reference instances an invalid type (the email table is not yet implemented) + try { + List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(emailType, inAllSetsHash); + Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for invalid table"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting reference instances with null type + try { + List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(null, inAllSetsHash); + Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for null type"); + }catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting reference instances with null value + try { + List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, null); + assertTrue("getReferenceInstancesByTypeValue returned non-empty list given null value", temp.isEmpty()); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking existing hash/ID + try { + assertTrue("isFileHashInReferenceSet returned false for valid data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, knownSet1id)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking non-existent (but valid) hash/ID + try { + assertFalse("isFileHashInReferenceSet returned true for non-existent data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, notableSet1id)); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking invalid reference set ID + try { + assertFalse("isFileHashInReferenceSet returned true for invalid data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, 5678)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking null hash + try { + assertFalse("isFileHashInReferenceSet returned true for null hash", EamDb.getInstance().isFileHashInReferenceSet(null, knownSet1id)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking existing hash/ID + try { + assertTrue("isValueInReferenceSet returned false for valid data", + EamDb.getInstance().isValueInReferenceSet(knownHash1, knownSet1id, fileType.getId())); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking non-existent (but valid) hash/ID + try { + assertFalse("isValueInReferenceSet returned true for non-existent data", + EamDb.getInstance().isValueInReferenceSet(knownHash1, notableSet1id, fileType.getId())); + }catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking invalid reference set ID + try { + assertFalse("isValueInReferenceSet returned true for invalid data", + EamDb.getInstance().isValueInReferenceSet(knownHash1, 5678, fileType.getId())); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking null hash + try { + assertFalse("isValueInReferenceSet returned true for null value", + EamDb.getInstance().isValueInReferenceSet(null, knownSet1id, fileType.getId())); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test checking invalid type + try { + EamDb.getInstance().isValueInReferenceSet(knownHash1, knownSet1id, emailType.getId()); + Assert.fail("isValueInReferenceSet failed to throw exception for invalid type"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test known bad with notable data + try { + assertTrue("isArtifactKnownBadByReference returned false for notable value", + EamDb.getInstance().isArtifactKnownBadByReference(fileType, notableHash1)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test known bad with known data + try { + assertFalse("isArtifactKnownBadByReference returned true for known value", + EamDb.getInstance().isArtifactKnownBadByReference(fileType, knownHash1)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test known bad with non-existent data + try { + assertFalse("isArtifactKnownBadByReference returned true for non-existent value", + EamDb.getInstance().isArtifactKnownBadByReference(fileType, "abcdef")); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test known bad with null hash + try { + assertFalse("isArtifactKnownBadByReference returned true for null value", + EamDb.getInstance().isArtifactKnownBadByReference(fileType, null)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test known bad with null type + try { + EamDb.getInstance().isArtifactKnownBadByReference(null, knownHash1); + Assert.fail("isArtifactKnownBadByReference failed to throw exception from null type"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test known bad with invalid type + try { + assertFalse("isArtifactKnownBadByReference returned true for invalid type", EamDb.getInstance().isArtifactKnownBadByReference(emailType, null)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + /** * Test method for the methods related to reference sets (does not include instance testing) + * Only the files type is currently implemented * newReferenceSet(EamGlobalSet eamGlobalSet) tests: * - Test creating notable reference set * - Test creating known reference set @@ -200,16 +571,6 @@ public class CentralRepoDatamodelTest extends TestCase { EamGlobalSet set3; int set3id; - // Store the file type object to save time - CorrelationAttribute.Type fileType; - try{ - fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); - assertTrue("In testReferenceSets, getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); - } catch (EamDbException ex){ - Exceptions.printStackTrace(ex); - Assert.fail(ex); - return; - } // Test creating a notable reference set try { @@ -449,8 +810,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(4567); Assert.fail("getReferenceSetOrganization failed to throw exception for invalid reference set ID"); } catch (EamDbException ex){ - Exceptions.printStackTrace(ex); - Assert.fail(ex); + // This is the expected behavior } } From 36c4e509726343a572eec51d550e76445abf0a3d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 31 Jan 2018 14:48:00 -0500 Subject: [PATCH 47/71] Organizations testing almost complete --- .../datamodel/AbstractSqlEamDb.java | 8 + .../datamodel/CentralRepoDatamodelTest.java | 156 ++++++++++++++++++ 2 files changed, 164 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index e3b61fbece..b8942b1b8d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1439,6 +1439,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public long newOrganization(EamOrganization eamOrg) throws EamDbException { + if(eamOrg == null) { + throw new EamDbException("EamOrganization is null"); + } + Connection conn = connect(); ResultSet generatedKeys = null; PreparedStatement preparedStatement = null; @@ -1560,6 +1564,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException { + if(updatedOrganization == null) { + throw new EamDbException("null updatedOrganization"); + } + Connection conn = connect(); PreparedStatement preparedStatement = null; String sql = "UPDATE organizations SET org_name = ?, poc_name = ?, poc_email = ?, poc_phone = ? WHERE id = ?"; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index cd0a0322a4..403181f916 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -161,6 +161,162 @@ public class CentralRepoDatamodelTest extends TestCase { assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } + /** + * + */ + public void testOrganizations() { + + EamOrganization orgA; + String orgAname = "orgA"; + EamOrganization orgB; + String orgBname = "orgB"; + String orgBpocName = "pocName"; + String orgBpocEmail = "pocEmail"; + String orgBpocPhone = "pocPhone"; + + // Test adding a basic organization + try{ + orgA = new EamOrganization(orgAname); + orgA.setOrgID((int) EamDb.getInstance().newOrganization(orgA)); + assertTrue("Organization ID is still -1 after adding to db", orgA.getOrgID() != -1); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test adding an organization with additional fields + try{ + orgB = new EamOrganization(orgBname, orgBpocName, orgBpocEmail, orgBpocPhone); + orgB.setOrgID((int) EamDb.getInstance().newOrganization(orgB)); + assertTrue("Organization ID is still -1 after adding to db", orgB.getOrgID() != -1); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test adding a duplicate organization + try{ + EamOrganization temp = new EamOrganization(orgAname); + EamDb.getInstance().newOrganization(temp); + Assert.fail("newOrganization failed to throw exception for duplicate org name"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding null organization + try{ + EamDb.getInstance().newOrganization(null); + Assert.fail("newOrganization failed to throw exception for null org"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding organization with null name + try{ + EamOrganization temp = new EamOrganization(null); + EamDb.getInstance().newOrganization(temp); + Assert.fail("newOrganization failed to throw exception for null name"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting organizations + // We expect five - the default org, two from setUp, and two from this method + try{ + List orgs = EamDb.getInstance().getOrganizations(); + assertTrue("getOrganizations returned null list", orgs != null); + assertTrue("getOrganizations returned " + orgs.size() + " orgs - expected 5", orgs.size() == 5); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting org with valid ID + try{ + EamOrganization temp = EamDb.getInstance().getOrganizationByID(orgB.getOrgID()); + assertTrue("getOrganizationByID returned null for valid ID", temp != null); + assertTrue("getOrganizationByID returned unexpected name for organization", orgBname.equals(temp.getName())); + assertTrue("getOrganizationByID returned unexpected poc name for organization", orgBpocName.equals(temp.getPocName())); + assertTrue("getOrganizationByID returned unexpected poc email for organization", orgBpocEmail.equals(temp.getPocEmail())); + assertTrue("getOrganizationByID returned unexpected poc phone for organization", orgBpocPhone.equals(temp.getPocPhone())); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting org with invalid ID + try{ + EamOrganization temp = EamDb.getInstance().getOrganizationByID(12345); + Assert.fail("getOrganizationByID failed to throw exception for invalid ID"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test updating valid org + try{ + String newName = "newOrgName"; + String newPocName = "newPocName"; + String newPocEmail = "newPocEmail"; + String newPocPhone = "newPocPhone"; + orgA.setName(newName); + orgA.setPocName(newPocName); + orgA.setPocEmail(newPocEmail); + orgA.setPocPhone(newPocPhone); + + EamDb.getInstance().updateOrganization(orgA); + + EamOrganization copyOfA = EamDb.getInstance().getOrganizationByID(orgA.getOrgID()); + + assertTrue("getOrganizationByID returned null for valid ID", copyOfA != null); + assertTrue("updateOrganization failed to update org name", newName.equals(copyOfA.getName())); + assertTrue("updateOrganization failed to update poc name", newPocName.equals(copyOfA.getPocName())); + assertTrue("updateOrganization failed to update poc email", newPocEmail.equals(copyOfA.getPocEmail())); + assertTrue("updateOrganization failed to update poc phone", newPocPhone.equals(copyOfA.getPocPhone())); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test updating invalid org + // Shouldn't do anything + try{ + EamOrganization temp = new EamOrganization("invalidOrg"); + temp.setOrgID(3434); + EamDb.getInstance().updateOrganization(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test updating null org + try{ + EamDb.getInstance().updateOrganization(null); + Assert.fail("updateOrganization failed to throw exception for null org"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test updating org to null name + try{ + EamOrganization copyOfA = EamDb.getInstance().getOrganizationByID(orgA.getOrgID()); + copyOfA.setName(null); + EamDb.getInstance().updateOrganization(copyOfA); + Assert.fail("updateOrganization failed to throw exception for null name"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test deleting existing org that isn't in use + + // Test deleting existing org that is in use + + // Test deleting non-existent org + + // Test deleting null org + } + /** * Tests for adding / retrieving reference instances * Only the files type is currently implemented From c6ab090fe66f1c8f2d1c647abf56a23c7be0966d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 08:31:07 -0500 Subject: [PATCH 48/71] Finished organizations --- .../datamodel/AbstractSqlEamDb.java | 4 ++ .../datamodel/CentralRepoDatamodelTest.java | 64 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index b8942b1b8d..0a2d91fd02 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1592,6 +1592,10 @@ public abstract class AbstractSqlEamDb implements EamDb { "AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."}) @Override public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException { + if(organizationToDelete == null) { + throw new EamDbException("Organization to delete is null"); + } + Connection conn = connect(); PreparedStatement checkIfUsedStatement = null; ResultSet resultSet = null; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 403181f916..f887c5cb02 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -162,7 +162,28 @@ public class CentralRepoDatamodelTest extends TestCase { } /** - * + * Test the methods related to organizations + * newOrganization(EamOrganization eamOrg) tests: + * - Test with just org name + * - Test with org name and poc info + * - Test adding duplicate org + * - Test adding null org + * - Test adding org with null name + * getOrganizations() tests: + * - Test getting the list of orgs +* getOrganizationByID(int orgID) tests: +* - Test with valid ID +* - Test with invalid ID +* updateOrganization(EamOrganization updatedOrganization) tests: +* - Test updating valid org +* - Test updating invalid org +* - Test updating null org +* - Test updating org to null name +* deleteOrganization(EamOrganization organizationToDelete) tests: +* - Test deleting org that isn't in use +* - Test deleting org that is in use +* - Test deleting invalid org +* - Test deleting null org */ public void testOrganizations() { @@ -309,12 +330,53 @@ public class CentralRepoDatamodelTest extends TestCase { } // Test deleting existing org that isn't in use + try{ + EamOrganization orgToDelete = new EamOrganization("deleteThis"); + orgToDelete.setOrgID((int)EamDb.getInstance().newOrganization(orgToDelete)); + int orgCount = EamDb.getInstance().getOrganizations().size(); + + EamDb.getInstance().deleteOrganization(orgToDelete); + assertTrue("getOrganizations returned unexpected count after deletion", orgCount - 1 == EamDb.getInstance().getOrganizations().size()); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } // Test deleting existing org that is in use + try{ + // Make a new org + EamOrganization inUseOrg = new EamOrganization("inUseOrg"); + inUseOrg.setOrgID((int)EamDb.getInstance().newOrganization(inUseOrg)); + + // Make a reference set that uses it + EamGlobalSet tempSet = new EamGlobalSet(inUseOrg.getOrgID(), "inUseOrgTest", "1.0", TskData.FileKnown.BAD, false, fileType); + EamDb.getInstance().newReferenceSet(tempSet); + + // It should now throw an exception if we try to delete it + EamDb.getInstance().deleteOrganization(inUseOrg); + Assert.fail("deleteOrganization failed to throw exception for in use organization"); + } catch (EamDbException ex){ + // This is the expected behavior + } // Test deleting non-existent org + // Should do nothing + try{ + EamOrganization temp = new EamOrganization("temp"); + temp.setOrgID(9876); + EamDb.getInstance().deleteOrganization(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } // Test deleting null org + try{ + EamDb.getInstance().deleteOrganization(null); + Assert.fail("deleteOrganization failed to throw exception for null organization"); + } catch (EamDbException ex){ + // This is the expected behavior + } } /** From 205f8e820ce2a64883d9458eec26a7e7aa06f194 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 09:17:08 -0500 Subject: [PATCH 49/71] Starting type tests --- .../datamodel/CorrelationAttribute.java | 6 +-- .../datamodel/CentralRepoDatamodelTest.java | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java index 39968d7922..57bafc4ff2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java @@ -181,7 +181,7 @@ public class CorrelationAttribute implements Serializable { * Must start with a lowercase letter and only contain * lowercase letters, numbers, and '_' characters. * @param supported Is this Type currently supported - * @param enabled Is this Type currentl enabled. + * @param enabled Is this Type currently enabled. */ public Type(int id, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException { this.id = id; @@ -195,7 +195,7 @@ public class CorrelationAttribute implements Serializable { } /** - * Constructior for custom types where we do not know the Type ID until + * Constructor for custom types where we do not know the Type ID until * the row has been entered into the correlation_types table * in the central repository. * @@ -204,7 +204,7 @@ public class CorrelationAttribute implements Serializable { * Must start with a lowercase letter and only contain * lowercase letters, numbers, and '_' characters. * @param supported Is this Type currently supported - * @param enabled Is this Type currentl enabled. + * @param enabled Is this Type currently enabled. */ public Type(String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException { this(-1, displayName, dbTableName, supported, enabled); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index f887c5cb02..a66a3476ad 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.TskData; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; /** * @@ -161,6 +163,56 @@ public class CentralRepoDatamodelTest extends TestCase { assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } + /** + * newCorrelationType(CorrelationAttribute.Type newType) +getDefinedCorrelationTypes() +getEnabledCorrelationTypes() +getSupportedCorrelationTypes() +* getCorrelationTypeById(int typeId) +updateCorrelationType(CorrelationAttribute.Type aType) + */ + public void testCorrelationTypes() { + + CorrelationAttribute.Type customType; + + // Test new type with valid data + try{ + customType = new CorrelationAttribute.Type("customType", "custom_type", false, false); + customType.setId(EamDb.getInstance().newCorrelationType(customType)); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test new type with duplicate data + try{ + CorrelationAttribute.Type temp = new CorrelationAttribute.Type("customType", "custom_type", false, false); + EamDb.getInstance().newCorrelationType(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test new type with null name + try{ + CorrelationAttribute.Type temp = new CorrelationAttribute.Type(null, "temp_type", false, false); + EamDb.getInstance().newCorrelationType(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test new type with null db name + try{ + CorrelationAttribute.Type temp = new CorrelationAttribute.Type("temp", null, false, false); + EamDb.getInstance().newCorrelationType(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + /** * Test the methods related to organizations * newOrganization(EamOrganization eamOrg) tests: From 49e0c8d28c12fde63b101cb7e1f33a3a713bfee0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 09:18:39 -0500 Subject: [PATCH 50/71] Remove Details button from Ingest History screenshot --- .../images/case-properties-history-tab.PNG | Bin 29219 -> 28533 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doxygen-user/images/case-properties-history-tab.PNG b/docs/doxygen-user/images/case-properties-history-tab.PNG index fc0a4da441bf5cfce71ddfab700066e8b2196994..0a5d8f25b75fbc102bb21cbe921c518f4417e6de 100644 GIT binary patch delta 8454 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 Date: Thu, 1 Feb 2018 09:25:49 -0500 Subject: [PATCH 51/71] Merge pull request #3395 from zhhl/3366-EditDetailsButtonMisplaced 3366 Edit Details button is misplaced --- .../sleuthkit/autopsy/casemodule/CaseInformationPanel.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index b03a17920e..2a460d5c46 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +61,11 @@ class CaseInformationPanel extends javax.swing.JPanel { @Override public void stateChanged(ChangeEvent e) { tabbedPane.getSelectedComponent().setSize(tabbedPane.getSelectedComponent().getPreferredSize()); + if (tabbedPane.getSelectedComponent() instanceof CasePropertiesPanel) { + editDetailsButton.setVisible(true); + } else { + editDetailsButton.setVisible(false); + } } }); } From 806d7a5bd914e89bf42a5acfb8a32e3aa1ee133b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 1 Feb 2018 10:56:16 -0500 Subject: [PATCH 52/71] 3470: Add comments for the Excel Cell size. --- Core/src/org/sleuthkit/autopsy/report/ReportExcel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index 81045dc5b1..9158790ad6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -34,7 +34,7 @@ class ReportExcel implements TableReportModule { private static final Logger logger = Logger.getLogger(ReportExcel.class.getName()); private static ReportExcel instance; - private static final int EXCEL_CELL_MAXIMUM_SIZE = 36767; + private static final int EXCEL_CELL_MAXIMUM_SIZE = 36767; //Specified at:https://poi.apache.org/apidocs/org/apache/poi/ss/SpreadsheetVersion.html private Workbook wb; private Sheet sheet; From d825b36ef2fa2d8581c3d5d6ef58f8f112e2f42e Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 11:27:15 -0500 Subject: [PATCH 53/71] Finished org testing --- .../datamodel/AbstractSqlEamDb.java | 17 +- .../datamodel/CorrelationAttribute.java | 3 + .../datamodel/CentralRepoDatamodelTest.java | 154 ++++++++++++++++-- 3 files changed, 156 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 0a2d91fd02..bb8393034f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1325,7 +1325,7 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException { EamGlobalSet refSet = this.getReferenceSetByID(referenceSetID); - if (refSet == null) { + if(refSet == null) { return false; } @@ -1941,6 +1941,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException { + if (newType == null) { + throw new EamDbException("null correlation type"); + } + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -1955,7 +1959,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } else { insertSql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)"; } - querySql = "SELECT id FROM correlation_types WHERE display_name=? AND db_table_name=?"; + querySql = "SELECT * FROM correlation_types WHERE display_name=? AND db_table_name=?"; try { preparedStatement = conn.prepareStatement(insertSql); @@ -2145,9 +2149,12 @@ public abstract class AbstractSqlEamDb implements EamDb { preparedStatement = conn.prepareStatement(sql); preparedStatement.setInt(1, typeId); resultSet = preparedStatement.executeQuery(); - resultSet.next(); - aType = getCorrelationTypeFromResultSet(resultSet); - return aType; + if(resultSet.next()) { + aType = getCorrelationTypeFromResultSet(resultSet); + return aType; + } else { + throw new EamDbException("Failed to find entry for correlation type ID = " + typeId); + } } catch (SQLException ex) { throw new EamDbException("Error getting correlation type by id.", ex); // NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java index 57bafc4ff2..3a18932aa2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java @@ -184,6 +184,9 @@ public class CorrelationAttribute implements Serializable { * @param enabled Is this Type currently enabled. */ public Type(int id, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException { + if(dbTableName == null) { + throw new EamDbException("dbTableName is null"); + } this.id = id; this.displayName = displayName; this.dbTableName = dbTableName; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index a66a3476ad..5b639cec14 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -164,20 +164,37 @@ public class CentralRepoDatamodelTest extends TestCase { } /** - * newCorrelationType(CorrelationAttribute.Type newType) -getDefinedCorrelationTypes() -getEnabledCorrelationTypes() -getSupportedCorrelationTypes() -* getCorrelationTypeById(int typeId) -updateCorrelationType(CorrelationAttribute.Type aType) + * Test methods related to correlation types + * newCorrelationType(CorrelationAttribute.Type newType) tests: + * - Test with valid data + * - Test with duplicate data + * - Test with null name + * - Test with null db name + * - Test with null type + * getDefinedCorrelationTypes() tests: + * - Test that the expected number are returned + * getEnabledCorrelationTypes() tests: + * - Test that the expected number are returned + * getSupportedCorrelationTypes() tests: + * - Test that the expected number are returned + * getCorrelationTypeById(int typeId) tests: + * - Test with valid ID + * - Test with invalid ID + * updateCorrelationType(CorrelationAttribute.Type aType) tests: + * - Test with existing type + * - Test with non-existent type + * - Test updating to null name + * - Test with null type */ public void testCorrelationTypes() { CorrelationAttribute.Type customType; + String customTypeName = "customType"; + String customTypeDb = "custom_type"; // Test new type with valid data try{ - customType = new CorrelationAttribute.Type("customType", "custom_type", false, false); + customType = new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); customType.setId(EamDb.getInstance().newCorrelationType(customType)); } catch (EamDbException ex){ Exceptions.printStackTrace(ex); @@ -187,30 +204,141 @@ updateCorrelationType(CorrelationAttribute.Type aType) // Test new type with duplicate data try{ - CorrelationAttribute.Type temp = new CorrelationAttribute.Type("customType", "custom_type", false, false); + CorrelationAttribute.Type temp = new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); EamDb.getInstance().newCorrelationType(temp); + Assert.fail("newCorrelationType failed to throw exception for duplicate name/db table"); } catch (EamDbException ex){ - Exceptions.printStackTrace(ex); - Assert.fail(ex); + // This is the expected behavior } // Test new type with null name try{ CorrelationAttribute.Type temp = new CorrelationAttribute.Type(null, "temp_type", false, false); EamDb.getInstance().newCorrelationType(temp); + Assert.fail("newCorrelationType failed to throw exception for null name table"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test new type with null db name + // The constructor should fail in this case + try{ + CorrelationAttribute.Type temp = new CorrelationAttribute.Type("temp", null, false, false); + Assert.fail("CorrelationAttribute.Type failed to throw exception for null db table name"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test new type with null type + try{ + EamDb.getInstance().newCorrelationType(null); + Assert.fail("newCorrelationType failed to throw exception for null type"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting all correlation types + try{ + List types = EamDb.getInstance().getDefinedCorrelationTypes(); + + // We expect 6 total - 5 default and the custom one made earlier + assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 6", types.size() == 6); } catch (EamDbException ex){ Exceptions.printStackTrace(ex); Assert.fail(ex); } - // Test new type with null db name + // Test getting enabled correlation types try{ - CorrelationAttribute.Type temp = new CorrelationAttribute.Type("temp", null, false, false); - EamDb.getInstance().newCorrelationType(temp); + List types = EamDb.getInstance().getEnabledCorrelationTypes(); + + // We expect 5 - the custom type is disabled + assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 5", types.size() == 5); } catch (EamDbException ex){ Exceptions.printStackTrace(ex); Assert.fail(ex); } + + // Test getting supported correlation types + try{ + List types = EamDb.getInstance().getSupportedCorrelationTypes(); + + // We expect 5 - the custom type is not supported + assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 5", types.size() == 5); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting the type with a valid ID + try{ + CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(customType.getId()); + assertTrue("getCorrelationTypeById returned type with unexpected name " + temp.getDisplayName(), customTypeName.equals(temp.getDisplayName())); + assertTrue("getCorrelationTypeById returned type with unexpected db table name " + temp.getDbTableName(), customTypeDb.equals(temp.getDbTableName())); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting the type with a invalid ID + try{ + CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(5555); + Assert.fail("getCorrelationTypeById failed to throw exception for invalid ID"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test updating a valid type + try{ + String newName = "newName"; + String newDbTable = "new_db_table"; + customType.setDisplayName(newName); + customType.setDbTableName(newDbTable); + customType.setEnabled(true); // These were originally false + customType.setSupported(true); + + EamDb.getInstance().updateCorrelationType(customType); + + // Get a fresh copy from the database + CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(customType.getId()); + + assertTrue("updateCorrelationType failed to update name", newName.equals(temp.getDisplayName())); + assertTrue("updateCorrelationType failed to update db table name", newDbTable.equals(temp.getDbTableName())); + assertTrue("updateCorrelationType failed to update enabled status", temp.isEnabled()); + assertTrue("updateCorrelationType failed to update supported status", temp.isSupported()); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test updating a type with an invalid ID + // Nothing should happen + try{ + CorrelationAttribute.Type temp= new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); + temp.setId(12345); + EamDb.getInstance().updateCorrelationType(temp); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test updating a type to a null name + try{ + customType.setDisplayName(null); + EamDb.getInstance().updateCorrelationType(customType); + Assert.fail("updateCorrelationType failed to throw exception for null name"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test updating a null type + try{ + customType.setDisplayName(null); + EamDb.getInstance().updateCorrelationType(customType); + Assert.fail("updateCorrelationType failed to throw exception for null type"); + } catch (EamDbException ex){ + // This is the expected behavior + } } /** From 1c5ecb3487e4316368f7c973347f9871e3becb81 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 1 Feb 2018 11:47:46 -0500 Subject: [PATCH 54/71] mark temp code --- .../datamodel/CentralRepoDatamodelTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 5b639cec14..035db25e6f 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -28,8 +28,6 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.TskData; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; /** * @@ -39,7 +37,7 @@ public class CentralRepoDatamodelTest extends TestCase { private static final String PROPERTIES_FILE = "CentralRepository"; private static final String CR_DB_NAME = "testcentralrepo.db"; //private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); - private static final Path testDirectory = Paths.get("C:", "Work", "CRDatamodelTest"); + private static final Path testDirectory = Paths.get("C:", "Work", "CRDatamodelTest"); // TEMP EASIER FOR TESTING SqliteEamDbSettings dbSettingsSqlite; private CorrelationCase case1; @@ -156,13 +154,20 @@ public class CentralRepoDatamodelTest extends TestCase { FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | IOException ex) { - // } catch (EamDbException ex) { + // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB Exceptions.printStackTrace(ex); Assert.fail(ex); } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } + /** + * Test the module settings + */ + public void testSettings(){ + // Maybe + } + /** * Test methods related to correlation types * newCorrelationType(CorrelationAttribute.Type newType) tests: From 85e5a8c56234cfc19950cdbb81708207a883c898 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 1 Feb 2018 13:41:17 -0500 Subject: [PATCH 55/71] 3499: A full Ruleset list. --- ruleset.xml | 283 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 255 insertions(+), 28 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index 2b4e66b821..c85bbe4dcd 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -6,33 +6,260 @@ Ruleset used by Autopsy - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4c11cb8acb1fa387936dc5beaefb8f1ba85966f3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 1 Feb 2018 14:24:27 -0500 Subject: [PATCH 56/71] Merge pull request #3392 from zhhl/3469_3470-FixExcelAndHMTLReportIssues 3469 3470 fix Excel and HTML report issues --- .../sleuthkit/autopsy/report/ReportExcel.java | 17 ++++++++++++++++- .../sleuthkit/autopsy/report/ReportHTML.java | 4 +++- .../autopsy/report/TableReportGenerator.java | 6 ++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java index 19526a0a19..9158790ad6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportExcel.java @@ -34,6 +34,7 @@ class ReportExcel implements TableReportModule { private static final Logger logger = Logger.getLogger(ReportExcel.class.getName()); private static ReportExcel instance; + private static final int EXCEL_CELL_MAXIMUM_SIZE = 36767; //Specified at:https://poi.apache.org/apidocs/org/apache/poi/ss/SpreadsheetVersion.html private Workbook wb; private Sheet sheet; @@ -236,10 +237,24 @@ class ReportExcel implements TableReportModule { * @param row cells to add */ @Override + @NbBundle.Messages({ + "ReportExcel.exceptionMessage.dataTooLarge=Value is too long to fit into an Excel cell. ", + "ReportExcel.exceptionMessage.errorText=Error showing data into an Excel cell." + }) + public void addRow(List rowData) { Row row = sheet.createRow(rowIndex); for (int i = 0; i < rowData.size(); ++i) { - row.createCell(i).setCellValue(rowData.get(i)); + Cell excelCell = row.createCell(i); + try { + excelCell.setCellValue(rowData.get(i)); + } catch (Exception e) { + if (e instanceof java.lang.IllegalArgumentException && rowData.get(i).length() > EXCEL_CELL_MAXIMUM_SIZE) { + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_dataTooLarge() + e.getMessage()); + } else { + excelCell.setCellValue(Bundle.ReportExcel_exceptionMessage_errorText()); + } + } } ++rowIndex; } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 88d9fe44b1..01a179a370 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -51,6 +51,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Services; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; @@ -554,7 +555,8 @@ class ReportHTML implements TableReportModule { StringBuilder builder = new StringBuilder(); builder.append("\t\n"); //NON-NLS for (String cell : row) { - builder.append("\t\t").append(cell).append("\n"); //NON-NLS + String escapeHTMLCell = EscapeUtil.escapeHtml(cell); + builder.append("\t\t").append(escapeHTMLCell).append("\n"); //NON-NLS } builder.append("\t\n"); //NON-NLS rowCount++; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 93894022b3..499f848d54 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,6 @@ import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.TagsManager; -import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; @@ -666,8 +665,7 @@ class TableReportGenerator { tableModule.startTable(columnHeaderNames); } - String previewreplace = EscapeUtil.escapeHtml(preview); - tableModule.addRow(Arrays.asList(new String[]{previewreplace.replaceAll(" Date: Thu, 1 Feb 2018 16:00:12 -0500 Subject: [PATCH 57/71] 3499: Fix typo. --- ruleset.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruleset.xml b/ruleset.xml index c85bbe4dcd..3576173d64 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -184,7 +184,7 @@ - + From 481bec66c11f780db1417fee5fb094e0f09b3387 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 1 Feb 2018 17:08:09 -0500 Subject: [PATCH 58/71] Address review comments --- .../autopsy/contentviewers/SQLiteViewer.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index d5b5afdb68..627d30e87c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -40,6 +40,7 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JComboBox; import javax.swing.SwingWorker; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; @@ -457,7 +458,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { return resultSetToArrayList(resultSet); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Failed to get data for table.", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Failed to get data for table " + tableName, ex); //NON-NLS } //NON-NLS return null; @@ -479,7 +480,7 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { selectedTableView.setupTable(Collections.emptyList()); } } catch (InterruptedException | ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Unexpected exception while reading table " + tableName, ex); //NON-NLS } } }; @@ -487,26 +488,27 @@ public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { worker.execute(); } + @NbBundle.Messages("SQLiteViewer.BlobNotShown.message=BLOB Data not shown") private ArrayList> resultSetToArrayList(ResultSet rs) throws SQLException { - ResultSetMetaData md = rs.getMetaData(); - int columns = md.getColumnCount(); - ArrayList> arraylist = new ArrayList<>(); + ResultSetMetaData metaData = rs.getMetaData(); + int columns = metaData.getColumnCount(); + ArrayList> rowlist = new ArrayList<>(); while (rs.next()) { Map row = new LinkedHashMap<>(columns); for (int i = 1; i <= columns; ++i) { if (rs.getObject(i) == null) { - row.put(md.getColumnName(i), ""); + row.put(metaData.getColumnName(i), ""); } else { - if (md.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) { - row.put(md.getColumnName(i), "BLOB Data not shown..."); + if (metaData.getColumnTypeName(i).compareToIgnoreCase("blob") == 0) { + row.put(metaData.getColumnName(i), Bundle.SQLiteViewer_BlobNotShown_message()); } else { - row.put(md.getColumnName(i), rs.getObject(i)); + row.put(metaData.getColumnName(i), rs.getObject(i)); } } } - arraylist.add(row); + rowlist.add(row); } - return arraylist; + return rowlist; } } From ef74c1f30ac52df5e8c6b8cd4d20bb32542fd5e0 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Feb 2018 11:56:04 -0500 Subject: [PATCH 59/71] 3522 change case path used by Multi-User case panel to be real path instead of all caps --- .../src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 24aaf70d2e..e283ef91d9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -29,6 +29,8 @@ import org.netbeans.swing.outline.Outline; import org.openide.nodes.Node; import java.awt.EventQueue; import java.io.File; +import java.io.IOException; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -222,10 +224,10 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider String name = file.getName().toLowerCase(); if (autFilePath == null && name.endsWith(".aut")) { try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); - } catch (CaseMetadata.CaseMetadataException ex) { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()).toRealPath(LinkOption.NOFOLLOW_LINKS))); + } catch (CaseMetadata.CaseMetadataException | IOException ex) { LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } + } break; } } From dda0e03e7f1f327dd39ca32cdda642a03778b8fa Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Feb 2018 12:02:00 -0500 Subject: [PATCH 60/71] 3402 compare nodes themeselves instead of node names for view file in directory --- .../org/sleuthkit/autopsy/directorytree/ViewContextAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 95c5f50c23..792252605a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -202,7 +202,7 @@ public class ViewContextAction extends AbstractAction { undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content)); TreeView treeView = treeViewTopComponent.getTree(); treeView.expandNode(parentTreeViewNode); - if (treeViewTopComponent.getSelectedNode().getDisplayName().equals(parentTreeViewNode.getDisplayName())) { + if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) { //In the case where our tree view already has the destination directory selected //due to an optimization in the ExplorerManager.setExploredContextAndSelection method //the property change we listen for to call DirectoryTreeTopComponent.respondSelection From 703cb9f223fd378402e29d33befcb035c23f75ec Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Feb 2018 12:47:32 -0500 Subject: [PATCH 61/71] 3522 fix location of real path conversion to before any file access --- .../autopsy/casemodule/CaseBrowser.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index e283ef91d9..4f25030fd3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -209,28 +209,34 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider List nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES); for (String node : nodeList) { - Path casePath = Paths.get(node); - File caseFolder = casePath.toFile(); - if (caseFolder.exists()) { - /* - * Search for '*.aut' files. - */ - File[] fileArray = caseFolder.listFiles(); - if (fileArray == null) { - continue; - } - String autFilePath = null; - for (File file : fileArray) { - String name = file.getName().toLowerCase(); - if (autFilePath == null && name.endsWith(".aut")) { - try { - caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()).toRealPath(LinkOption.NOFOLLOW_LINKS))); - } catch (CaseMetadata.CaseMetadataException | IOException ex) { - LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); - } - break; + Path casePath; + try { + casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS); + + File caseFolder = casePath.toFile(); + if (caseFolder.exists()) { + /* + * Search for '*.aut' files. + */ + File[] fileArray = caseFolder.listFiles(); + if (fileArray == null) { + continue; + } + String autFilePath = null; + for (File file : fileArray) { + String name = file.getName().toLowerCase(); + if (autFilePath == null && name.endsWith(".aut")) { + try { + caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath()))); + } catch (CaseMetadata.CaseMetadataException ex) { + LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex); + } + break; + } } } + } catch (IOException ignore) { + //if a path could not be resolved to a real path do add it to the caseList } } return caseList; From eeaa0bd0b142c132e500d8b035f9705398a5d1f5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 2 Feb 2018 13:02:49 -0500 Subject: [PATCH 62/71] Partway through artifact tests --- .../DataContentViewerOtherCases.java | 16 +- .../datamodel/AbstractSqlEamDb.java | 43 +- .../datamodel/CorrelationAttribute.java | 7 +- .../CorrelationAttributeInstance.java | 14 +- .../datamodel/EamArtifactUtil.java | 3 +- .../datamodel/CentralRepoDatamodelTest.java | 368 +++++++++++++++++- 6 files changed, 417 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index ad642a9cbc..c098112efa 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -489,12 +489,16 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D corAttrInstances.addAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); corAttrInstances.forEach((corAttrInstance) -> { - CorrelationAttribute newCeArtifact = new CorrelationAttribute( - corAttr.getCorrelationType(), - corAttr.getCorrelationValue() - ); - newCeArtifact.addInstance(corAttrInstance); - tableModel.addEamArtifact(newCeArtifact); + try { + CorrelationAttribute newCeArtifact = new CorrelationAttribute( + corAttr.getCorrelationType(), + corAttr.getCorrelationValue() + ); + newCeArtifact.addInstance(corAttrInstance); + tableModel.addEamArtifact(newCeArtifact); + } catch (EamDbException ex){ + LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex); + } }); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index bb8393034f..a26c0e2a3b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -521,6 +521,16 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException { + if(eamArtifact == null) { + throw new EamDbException("CorrelationAttribute is null"); + } + if(eamArtifact.getCorrelationType() == null) { + throw new EamDbException("Correlation type is null"); + } + if(eamArtifact.getCorrelationValue() == null) { + throw new EamDbException("Correlation value is null"); + } + Connection conn = connect(); List eamInstances = eamArtifact.getInstances(); @@ -534,11 +544,21 @@ public abstract class AbstractSqlEamDb implements EamDb { sql.append("(case_id, data_source_id, value, file_path, known_status, comment) "); sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "); sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?)"); - + try { preparedStatement = conn.prepareStatement(sql.toString()); for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { + if(eamInstance.getCorrelationCase() == null) { + throw new EamDbException("CorrelationAttributeInstance has null case"); + } + if(eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("CorrelationAttributeInstance has null data source"); + } + if(eamInstance.getKnownStatus() == null) { + throw new EamDbException("CorrelationAttributeInstance has null known status"); + } + preparedStatement.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); preparedStatement.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); preparedStatement.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); @@ -575,6 +595,9 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -627,6 +650,12 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + if(filePath == null) { + throw new EamDbException("Correlation value is null"); + } Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -649,7 +678,7 @@ public abstract class AbstractSqlEamDb implements EamDb { try { preparedStatement = conn.prepareStatement(sql.toString()); - preparedStatement.setString(1, filePath); + preparedStatement.setString(1, filePath.toLowerCase()); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { artifactInstance = getEamArtifactInstanceFromResultSet(resultSet); @@ -678,6 +707,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); Long instanceCount = 0L; @@ -2210,8 +2243,8 @@ public abstract class AbstractSqlEamDb implements EamDb { } CorrelationDataSource eamDataSource = new CorrelationDataSource( - resultSet.getInt("id"), resultSet.getInt("case_id"), + resultSet.getInt("id"), resultSet.getString("device_id"), resultSet.getString("name") ); @@ -2245,13 +2278,13 @@ public abstract class AbstractSqlEamDb implements EamDb { * * @throws SQLException when an expected column name is not in the resultSet */ - private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet) throws SQLException { + private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { if (null == resultSet) { return null; } CorrelationAttributeInstance eamArtifactInstance = new CorrelationAttributeInstance( new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")), - new CorrelationDataSource(-1, resultSet.getInt("case_id"), resultSet.getString("device_id"), resultSet.getString("name")), + new CorrelationDataSource(resultSet.getInt("case_id"), -1, resultSet.getString("device_id"), resultSet.getString("name")), resultSet.getString("file_path"), resultSet.getString("comment"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java index 3a18932aa2..427bbc97bb 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttribute.java @@ -28,7 +28,7 @@ import org.openide.util.NbBundle.Messages; /** * Represents a type and value pair that can be used for correlation. * CorrelationAttributeInstances store information about the actual - * occurences of the attribute. + * occurrences of the attribute. */ public class CorrelationAttribute implements Serializable { @@ -66,7 +66,10 @@ public class CorrelationAttribute implements Serializable { return DEFAULT_CORRELATION_TYPES; } - public CorrelationAttribute(Type correlationType, String correlationValue) { + public CorrelationAttribute(Type correlationType, String correlationValue) throws EamDbException { + if(correlationValue == null) { + throw new EamDbException ("Correlation value is null"); + } this.ID = ""; this.correlationType = correlationType; // Lower-case all values to normalize and improve correlation hits, going forward make sure this makes sense for all correlation types diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index f75364c580..5ca99abc7a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -46,7 +46,7 @@ public class CorrelationAttributeInstance implements Serializable { public CorrelationAttributeInstance( CorrelationCase eamCase, CorrelationDataSource eamDataSource - ) { + ) throws EamDbException { this(-1, eamCase, eamDataSource, "", null, TskData.FileKnown.UNKNOWN); } @@ -54,7 +54,7 @@ public class CorrelationAttributeInstance implements Serializable { CorrelationCase eamCase, CorrelationDataSource eamDataSource, String filePath - ) { + ) throws EamDbException { this(-1, eamCase, eamDataSource, filePath, null, TskData.FileKnown.UNKNOWN); } @@ -63,7 +63,7 @@ public class CorrelationAttributeInstance implements Serializable { CorrelationDataSource eamDataSource, String filePath, String comment - ) { + ) throws EamDbException { this(-1, eamCase, eamDataSource, filePath, comment, TskData.FileKnown.UNKNOWN); } @@ -73,7 +73,7 @@ public class CorrelationAttributeInstance implements Serializable { String filePath, String comment, TskData.FileKnown knownStatus - ) { + ) throws EamDbException { this(-1, eamCase, eamDataSource, filePath, comment, knownStatus); } @@ -84,7 +84,11 @@ public class CorrelationAttributeInstance implements Serializable { String filePath, String comment, TskData.FileKnown knownStatus - ) { + ) throws EamDbException { + if(filePath == null) { + throw new EamDbException("file path is null"); + } + this.ID = ID; this.correlationCase = eamCase; this.correlationDataSource = eamDataSource; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 171444fa42..c0810b52d3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -135,7 +135,8 @@ public class EamArtifactUtil { * @return the new EamArtifact, or null if one was not created because * bbArtifact did not contain the needed data */ - private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, BlackboardArtifact bbArtifact) { + private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, + BlackboardArtifact bbArtifact) throws EamDbException { String value = null; int artifactTypeID = bbArtifact.getArtifactTypeID(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 035db25e6f..f6587d1412 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -1,7 +1,20 @@ /* - * 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. + * Central Repository + * + * 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.centralrepository.datamodel; @@ -44,6 +57,7 @@ public class CentralRepoDatamodelTest extends TestCase { private CorrelationCase case2; private CorrelationDataSource dataSource1fromCase1; private CorrelationDataSource dataSource2fromCase1; + private CorrelationDataSource dataSource1fromCase2; private EamOrganization org1; private EamOrganization org2; CorrelationAttribute.Type fileType; @@ -123,6 +137,11 @@ public class CentralRepoDatamodelTest extends TestCase { EamDb.getInstance().newDataSource(dataSource2fromCase1); dataSource2fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource2fromCase1.getDeviceID()); assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); + + dataSource1fromCase2 = new CorrelationDataSource(case2.getID(), "dataSource3_deviceID", "dataSource3"); + EamDb.getInstance().newDataSource(dataSource1fromCase2); + dataSource1fromCase2 = EamDb.getInstance().getDataSource(case2, dataSource1fromCase2.getDeviceID()); + assertTrue("Failed to create test object dataSource1fromCase2", dataSource1fromCase2 != null); org1 = new EamOrganization("org1"); org1.setOrgID((int) EamDb.getInstance().newOrganization(org1)); @@ -154,7 +173,7 @@ public class CentralRepoDatamodelTest extends TestCase { FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | IOException ex) { - // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB + // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -168,6 +187,324 @@ public class CentralRepoDatamodelTest extends TestCase { // Maybe } + /** + * Test methods related to artifacts + */ + public void testArtifacts() { + + String inAllDataSourcesHash = "6cddb0e31787b79cfdcc0676b98a71ce"; + String inAllDataSourcesPath = "C:\\files\\path0.txt"; + String inDataSource1twiceHash = "b2f5ff47436671b6e533d8dc3614845d"; + String inDataSource1twicePath1 = "C:\\files\\path1.txt"; + String inDataSource1twicePath2 = "C:\\files\\path2.txt"; + String onlyInDataSource3Hash = "2af54305f183778d87de0c70c591fae4"; + String onlyInDataSource3Path = "C:\\files\\path3.txt"; + + // These will all go in dataSource1fromCase1 + String emailValue = "test@gmail.com"; + String emailPath = "C:\\files\\emailPath.txt"; + String phoneValue = "202-555-1234"; + String phonePath = "C:\\files\\phonePath.txt"; + String domainValue = "www.mozilla.com"; + String domainPath = "C:\\files\\domainPath.txt"; + String devIdValue = "94B21234"; + String devIdPath = "C:\\files\\devIdPath.txt"; + + // Test adding attribute with one instance + try{ + CorrelationAttribute attr = new CorrelationAttribute(fileType, onlyInDataSource3Hash); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case2, dataSource1fromCase2, onlyInDataSource3Path); + attr.addInstance(inst); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding attribute with an instance in each data source + try{ + CorrelationAttribute attr = new CorrelationAttribute(fileType, inAllDataSourcesHash); + CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inAllDataSourcesPath); + attr.addInstance(inst1); + CorrelationAttributeInstance inst2 = new CorrelationAttributeInstance(case1, dataSource2fromCase1, inAllDataSourcesPath); + attr.addInstance(inst2); + CorrelationAttributeInstance inst3 = new CorrelationAttributeInstance(case2, dataSource1fromCase2, inAllDataSourcesPath); + attr.addInstance(inst3); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding attribute with two instances in one data source + try{ + CorrelationAttribute attr = new CorrelationAttribute(fileType, inDataSource1twiceHash); + CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inDataSource1twicePath1); + attr.addInstance(inst1); + CorrelationAttributeInstance inst2 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inDataSource1twicePath2); + attr.addInstance(inst2); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding the other types + // Test adding an email artifact + try{ + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID), + emailValue); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, emailPath); + attr.addInstance(inst); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding a phone artifact + try{ + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.PHONE_TYPE_ID), + phoneValue); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, phonePath); + attr.addInstance(inst); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding a domain artifact + try{ + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.DOMAIN_TYPE_ID), + domainValue); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, domainPath); + attr.addInstance(inst); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test adding a device ID artifact + try{ + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.USBID_TYPE_ID), + devIdValue); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, devIdPath); + attr.addInstance(inst); + EamDb.getInstance().addArtifact(attr); + } catch (EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test CorrelationAttributeInstance failure cases + + // Create an attribute to use in the next few tests + CorrelationAttribute failAttr; + try { + failAttr = new CorrelationAttribute(fileType, "badInstances"); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Test adding instance with null case + try{ + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(null, dataSource1fromCase2, "badPath"); + failAttr.addInstance(inst); + EamDb.getInstance().addArtifact(failAttr); + Assert.fail("addArtifact failed to throw exception for null case"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding instance with invalid case ID + try{ + CorrelationCase badCase = new CorrelationCase("badCaseUuid", "badCaseName"); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(badCase, dataSource1fromCase2, "badPath"); + failAttr.addInstance(inst); + EamDb.getInstance().addArtifact(failAttr); + Assert.fail("addArtifact failed to throw exception for invalid case"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding instance with null data source + try{ + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, null, "badPath"); + failAttr.addInstance(inst); + EamDb.getInstance().addArtifact(failAttr); + Assert.fail("addArtifact failed to throw exception for null data source"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding instance with invalid data source ID + try{ + CorrelationDataSource badDS = new CorrelationDataSource(case1.getID(), "badDSUuid", "badDSName"); + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, badDS, "badPath"); + failAttr.addInstance(inst); + EamDb.getInstance().addArtifact(failAttr); + Assert.fail("addArtifact failed to throw exception for invalid data source"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding instance with null path + // This will fail in the CorrelationAttributeInstance constructor + try{ + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, null); + Assert.fail("CorrelationAttributeInstance failed to throw exception for null path"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test adding instance with null known status + try{ + CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, null, "comment", null); + failAttr.addInstance(inst); + EamDb.getInstance().addArtifact(failAttr); + Assert.fail("addArtifact failed to throw exception for null known status"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test CorrelationAttribute failure cases + + // Test null type + try{ + CorrelationAttribute attr = new CorrelationAttribute(null, "badInstances"); + EamDb.getInstance().addArtifact(attr); + Assert.fail("addArtifact failed to throw exception for null type"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test null value + // This will fail in the CorrelationAttribute constructor + try{ + CorrelationAttribute attr = new CorrelationAttribute(fileType, null); + Assert.fail("addArtifact failed to throw exception for null value"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + // Test getting instances with expected resuls + try { + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, inAllDataSourcesHash); + assertTrue("getArtifactInstancesByTypeValue returned " + instances.size() + " results - expected 3", instances.size() == 3); + + // This test works because all the instances of this hash were set to the same path + for(CorrelationAttributeInstance inst:instances) { + assertTrue("getArtifactInstancesByTypeValue returned instance with unexpected path " + inst.getFilePath(), + inAllDataSourcesPath.equalsIgnoreCase(inst.getFilePath())); + } + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instances expecting no results + try { + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue( + EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID), inAllDataSourcesHash); + assertTrue("getArtifactInstancesByTypeValue returned " + instances.size() + " results - expected 0", instances.isEmpty()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instances with null type + try { + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(null, inAllDataSourcesHash); + Assert.fail("getArtifactInstancesByTypeValue failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting instances with null value + // Should just return nothing + try { + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, null); + assertTrue("getArtifactInstancesByTypeValue returned non-empty list for null value", instances.isEmpty()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instances with path that should produce results + try { + List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, inAllDataSourcesPath); + assertTrue("getArtifactInstancesByPath returned " + instances.size() + " objects - expected 3", instances.size() == 3); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instances with path that should not produce results + try { + List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, "xyz"); + assertTrue("getArtifactInstancesByPath returned " + instances.size() + " objects - expected 0", instances.isEmpty()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instances with null type + try { + List instances = EamDb.getInstance().getArtifactInstancesByPath(null, inAllDataSourcesPath); + Assert.fail("getArtifactInstancesByPath failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting instances with null value + try { + List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, null); + Assert.fail("getArtifactInstancesByPath failed to throw exception for null value"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting instance count with path that should produce results + try { + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, inAllDataSourcesPath); + assertTrue("getCountArtifactInstancesByTypeValue returned " + count + " - expected 3", count == 3); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instance count with path that should not produce results + try { + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, "xyz"); + assertTrue("getCountArtifactInstancesByTypeValue returned " + count + " - expected 0", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting instance count with null type + try { + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesPath); + Assert.fail("getCountArtifactInstancesByTypeValue failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting instance count with null value + try { + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, null); + Assert.fail("getCountArtifactInstancesByTypeValue failed to throw exception for null value"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + + } + /** * Test methods related to correlation types * newCorrelationType(CorrelationAttribute.Type newType) tests: @@ -191,7 +528,7 @@ public class CentralRepoDatamodelTest extends TestCase { * - Test updating to null name * - Test with null type */ - public void testCorrelationTypes() { + public void atestCorrelationTypes() { CorrelationAttribute.Type customType; String customTypeName = "customType"; @@ -370,7 +707,7 @@ public class CentralRepoDatamodelTest extends TestCase { * - Test deleting invalid org * - Test deleting null org */ - public void testOrganizations() { + public void atestOrganizations() { EamOrganization orgA; String orgAname = "orgA"; @@ -603,7 +940,7 @@ public class CentralRepoDatamodelTest extends TestCase { * - Test null type * - Test invalid type */ - public void testReferenceSetInstances(){ + public void atestReferenceSetInstances(){ // After the two initial testing blocks, the reference sets should contain: // notableSet1 - notableHash1, inAllSetsHash @@ -963,7 +1300,7 @@ public class CentralRepoDatamodelTest extends TestCase { * - Test on valid reference set ID * - Test on invalid reference set ID */ - public void testReferenceSets() { + public void atestReferenceSets() { String set1name = "referenceSet1"; String set1version = "1.0"; EamGlobalSet set1; @@ -1236,7 +1573,7 @@ public class CentralRepoDatamodelTest extends TestCase { * Long getCountUniqueDataSources() tests: * - Test that the result is as expected */ - public void testDataSources() { + public void atestDataSources() { final String dataSourceAname = "dataSourceA"; final String dataSourceAid = "dataSourceA_deviceID"; CorrelationDataSource dataSourceA; @@ -1334,16 +1671,17 @@ public class CentralRepoDatamodelTest extends TestCase { } // Test getting the list of data sources - // There should be three data sources, and we'll check for the expected device IDs + // There should be five data sources, and we'll check for the expected device IDs try { List dataSources = EamDb.getInstance().getDataSources(); List devIdList = dataSources.stream().map(c -> c.getDeviceID()).collect(Collectors.toList()); - assertTrue("getDataSources returned unexpected number of data sources", dataSources.size() == 4); + assertTrue("getDataSources returned unexpected number of data sources", dataSources.size() == 5); assertTrue("getDataSources is missing expected data sources", devIdList.contains(dataSourceAid) && devIdList.contains(dataSource1fromCase1.getDeviceID()) - && devIdList.contains(dataSource2fromCase1.getDeviceID())); + && devIdList.contains(dataSource2fromCase1.getDeviceID()) + && devIdList.contains(dataSource1fromCase2.getDeviceID())); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -1352,7 +1690,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test the data source count try { assertTrue("getCountUniqueDataSources returned unexpected number of data sources", - EamDb.getInstance().getCountUniqueDataSources() == 4); + EamDb.getInstance().getCountUniqueDataSources() == 5); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -1372,7 +1710,7 @@ public class CentralRepoDatamodelTest extends TestCase { * bulkInsertCases(List cases) - Test on a list of cases * larger than the bulk insert threshold. - Test on a null list */ - public void testCases() { + public void atestCases() { final String caseAname = "caseA"; final String caseAuuid = "caseA_uuid"; CorrelationCase caseA; @@ -1609,7 +1947,7 @@ public class CentralRepoDatamodelTest extends TestCase { * to null value - Test updating null name - Test updating non-existing name * to new value */ - public void testDbInfo() { + public void atestDbInfo() { final String name1 = "testName1"; final String name2 = "testName2"; final String name3 = "testName3"; From 699995fd638551ed45203faf664aa05e4f93ec6d Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 2 Feb 2018 14:07:29 -0500 Subject: [PATCH 63/71] disabled some rulesets from initial commit --- ruleset.xml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index 3576173d64..6ff40194e3 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -53,10 +53,18 @@ - - + + + + + + + + @@ -122,7 +131,9 @@ - + @@ -182,7 +193,8 @@ - + @@ -258,7 +270,8 @@ - + From 3aedf68dd29c04f4555bc079a605b344a6e548c0 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 2 Feb 2018 14:31:10 -0500 Subject: [PATCH 64/71] more pmd changes --- ruleset.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ruleset.xml b/ruleset.xml index 6ff40194e3..9c8f7e34c2 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -92,7 +92,8 @@ - + @@ -176,7 +177,8 @@ - + From 5d0cb87f5fab1693272d9ddeb22428a9ed41009a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 2 Feb 2018 16:33:55 -0500 Subject: [PATCH 65/71] Artifact testing partially done. --- .../datamodel/AbstractSqlEamDb.java | 16 +- .../datamodel/CentralRepoDatamodelTest.java | 237 +++++++++++++++++- 2 files changed, 245 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index a26c0e2a3b..1517466116 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -710,6 +710,9 @@ public abstract class AbstractSqlEamDb implements EamDb { if(aType == null) { throw new EamDbException("Correlation type is null"); } + if(value == null) { + throw new EamDbException("Correlation value is null"); + } Connection conn = connect(); @@ -725,7 +728,7 @@ public abstract class AbstractSqlEamDb implements EamDb { try { preparedStatement = conn.prepareStatement(sql.toString()); - preparedStatement.setString(1, value); + preparedStatement.setString(1, value.toLowerCase()); resultSet = preparedStatement.executeQuery(); resultSet.next(); instanceCount = resultSet.getLong(1); @@ -742,6 +745,9 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException { + if (corAttr == null) { + throw new EamDbException("Correlation attribute is null"); + } Double uniqueTypeValueTuples = getCountUniqueCaseDataSourceTuplesHavingTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).doubleValue(); Double uniqueCaseDataSourceTuples = getCountUniqueDataSources().doubleValue(); Double commonalityPercentage = uniqueTypeValueTuples / uniqueCaseDataSourceTuples * 100; @@ -760,6 +766,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); Long instanceCount = 0L; @@ -881,6 +891,10 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public void prepareBulkArtifact(CorrelationAttribute eamArtifact) throws EamDbException { + if(eamArtifact.getCorrelationType() == null) { + throw new EamDbException("Correlation type is null"); + } + synchronized (bulkArtifacts) { bulkArtifacts.get(eamArtifact.getCorrelationType().getDbTableName()).add(eamArtifact); bulkArtifactsCount++; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index f6587d1412..e0177f12f0 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -173,7 +173,7 @@ public class CentralRepoDatamodelTest extends TestCase { FileUtils.deleteDirectory(testDirectory.toFile()); } catch (EamDbException | IOException ex) { - // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB + // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -183,10 +183,97 @@ public class CentralRepoDatamodelTest extends TestCase { /** * Test the module settings */ - public void testSettings(){ + public void atestSettings(){ // Maybe } + /** + * Test the methods associated with bulk artifacts + * First test the normal use case of a large number of valid artifacts getting added + * Next test the error conditions: + * + */ + public void testBulkArtifacts() { + + // Test bulk artifacts + // Steps: + // - Make a list of artifacts roughly half the threshold size + // - Call prepareBulkArtifact on all of them + // - Verify that nothing has been written to the database + // - Make a list of artifacts equal to the threshold size + // - Call prepareBulkArtifact on all of them + // - Verify that the bulk threshold number of them were written to the database + // - Call bulkInsertArtifacts to insert the remainder + // - Verify that the database now has all the artifacts + try { + // Make sure there are no artifacts in the database to start + long originalArtifactCount = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); + assertTrue("getCountArtifactInstancesByCaseDataSource returned non-zero count", originalArtifactCount == 0); + + // Create the first list, which will have (bulkThreshold / 2) entries + List list1 = new ArrayList<>(); + for(int i = 0; i < dbSettingsSqlite.getBulkThreshold() / 2;i++) { + String value = "bulkInsertValue1_" + String.valueOf(i); + String path = "C:\\bulkInsertPath1\\file" + String.valueOf(i); + + CorrelationAttribute attr = new CorrelationAttribute(fileType, value); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, path)); + list1.add(attr); + } + + // Queue up the current list. There should not be enough to trigger the insert + for(CorrelationAttribute attr:list1){ + EamDb.getInstance().prepareBulkArtifact(attr); + } + + // Check that nothing has been written yet + assertTrue("Artifacts written to database before threshold was reached", + originalArtifactCount == EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID())); + + // Make a second list with length equal to bulkThreshold + List list2 = new ArrayList<>(); + for(int i = 0; i < dbSettingsSqlite.getBulkThreshold();i++) { + String value = "bulkInsertValue2_" + String.valueOf(i); + String path = "C:\\bulkInsertPath2\\file" + String.valueOf(i); + + CorrelationAttribute attr = new CorrelationAttribute(fileType, value); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, path)); + list2.add(attr); + } + + // Queue up the current list. This will trigger an insert partway through + for(CorrelationAttribute attr:list2){ + EamDb.getInstance().prepareBulkArtifact(attr); + } + + // There should now be bulkThreshold artifacts in the database + long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); + assertTrue("Artifact count " + count + " does not match bulkThreshold " + dbSettingsSqlite.getBulkThreshold(), count == dbSettingsSqlite.getBulkThreshold()); + + // Now call bulkInsertArtifacts() to insert the rest of queue + EamDb.getInstance().bulkInsertArtifacts(); + count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); + int expectedCount = list1.size() + list2.size(); + assertTrue("Artifact count " + count + " does not match expected count " + expectedCount, count == expectedCount); + + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test preparing artifact with null type + try{ + CorrelationAttribute attr = new CorrelationAttribute(null, "value"); + EamDb.getInstance().prepareBulkArtifact(attr); + Assert.fail("prepareBulkArtifact failed to throw exception for null type"); + } catch (EamDbException ex){ + // This is the expected behavior + } + + + + } + /** * Test methods related to artifacts */ @@ -210,6 +297,17 @@ public class CentralRepoDatamodelTest extends TestCase { String devIdValue = "94B21234"; String devIdPath = "C:\\files\\devIdPath.txt"; + // Store the email type + CorrelationAttribute.Type emailType; + + try { + emailType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + // Test adding attribute with one instance try{ CorrelationAttribute attr = new CorrelationAttribute(fileType, onlyInDataSource3Hash); @@ -252,8 +350,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test adding the other types // Test adding an email artifact try{ - CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID), - emailValue); + CorrelationAttribute attr = new CorrelationAttribute(emailType, emailValue); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, emailPath); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); @@ -409,7 +506,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instances expecting no results try { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue( - EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID), inAllDataSourcesHash); + emailType, inAllDataSourcesHash); assertTrue("getArtifactInstancesByTypeValue returned " + instances.size() + " results - expected 0", instances.isEmpty()); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -470,7 +567,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instance count with path that should produce results try { - long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, inAllDataSourcesPath); + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, inAllDataSourcesHash); assertTrue("getCountArtifactInstancesByTypeValue returned " + count + " - expected 3", count == 3); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -488,7 +585,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instance count with null type try { - long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesPath); + long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesHash); Assert.fail("getCountArtifactInstancesByTypeValue failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -502,7 +599,133 @@ public class CentralRepoDatamodelTest extends TestCase { // This is the expected behavior } + // Test getting frequency of value that is in all three data sources + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, inAllDataSourcesHash); + int freq = EamDb.getInstance().getFrequencyPercentage(attr); + assertTrue("getFrequencyPercentage returned " + freq + " - expected 100", freq == 100); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + // Test getting frequency of value that appears twice in a single data source + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, inDataSource1twiceHash); + int freq = EamDb.getInstance().getFrequencyPercentage(attr); + assertTrue("getFrequencyPercentage returned " + freq + " - expected 33", freq == 33); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting frequency of non-file type + try { + CorrelationAttribute attr = new CorrelationAttribute(emailType, emailValue); + int freq = EamDb.getInstance().getFrequencyPercentage(attr); + assertTrue("getFrequencyPercentage returned " + freq + " - expected 33", freq == 33); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting frequency of non-existent value + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "randomValue"); + int freq = EamDb.getInstance().getFrequencyPercentage(attr); + assertTrue("getFrequencyPercentage returned " + freq + " - expected 0", freq == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting frequency with null type + try { + CorrelationAttribute attr = new CorrelationAttribute(null, "randomValue"); + int freq = EamDb.getInstance().getFrequencyPercentage(attr); + Assert.fail("getFrequencyPercentage failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting frequency with null attribute + try { + int freq = EamDb.getInstance().getFrequencyPercentage(null); + Assert.fail("getFrequencyPercentage failed to throw exception for null attribute"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting count for dataSource1fromCase1 (includes all types) + try { + long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); + assertTrue("getCountArtifactInstancesByCaseDataSource returned " + count + " - expected 7", count == 7); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting count with null case UUID + try { + long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(null, dataSource1fromCase1.getDeviceID()); + assertTrue("getCountArtifactInstancesByCaseDataSource returned " + count + " - expected 0", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting count with null data source ID + try { + long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), null); + assertTrue("getCountArtifactInstancesByCaseDataSource returned " + count + " - expected 0", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting data source count for entry that is in all three + try { + long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, inAllDataSourcesHash); + assertTrue("getCountUniqueCaseDataSourceTuplesHavingTypeValue returned " + count + " - expected 3", count == 3); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting data source count for entry that is in one data source twice + try { + long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, inDataSource1twiceHash); + assertTrue("getCountUniqueCaseDataSourceTuplesHavingTypeValue returned " + count + " - expected 1", count == 1); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting data source count for entry that is not in any data sources + try { + long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, "abcdef"); + assertTrue("getCountUniqueCaseDataSourceTuplesHavingTypeValue returned " + count + " - expected 0", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting data source count for null type + try { + long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(null, "abcdef"); + Assert.fail("getCountUniqueCaseDataSourceTuplesHavingTypeValue failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting data source count for null value + try { + long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, null); + assertTrue("getCountUniqueCaseDataSourceTuplesHavingTypeValue returned " + count + " - expected 0", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } } /** From 3bfe08ccf918240ea6826f68bd4e8279eaeb45df Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 6 Feb 2018 14:14:29 -0500 Subject: [PATCH 66/71] Finished notable artifact tests --- .../datamodel/AbstractSqlEamDb.java | 40 +- .../datamodel/CentralRepoDatamodelTest.java | 1463 ++++++++++------- 2 files changed, 949 insertions(+), 554 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 1517466116..18af7f6c81 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -948,6 +948,17 @@ public abstract class AbstractSqlEamDb implements EamDb { for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { + + if(eamInstance.getCorrelationCase() == null) { + throw new EamDbException("Correlation attribute instance has null case"); + } + if(eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("Correlation attribute instance has null data source"); + } + if(eamInstance.getKnownStatus()== null) { + throw new EamDbException("Correlation attribute instance has null known known status"); + } + bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); @@ -1071,15 +1082,28 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException { + if(eamArtifact == null) { + throw new EamDbException("Correlation attribute is null"); + } + if(knownStatus == null) { + throw new EamDbException("Known status is null"); + } if (1 != eamArtifact.getInstances().size()) { throw new EamDbException("Error: Artifact must have exactly one (1) Artifact Instance to set as notable."); // NON-NLS } - Connection conn = connect(); - List eamInstances = eamArtifact.getInstances(); CorrelationAttributeInstance eamInstance = eamInstances.get(0); + if(eamInstance.getCorrelationCase() == null) { + throw new EamDbException("Correlation case is null"); + } + if(eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("Correlation data source is null"); + } + + Connection conn = connect(); + PreparedStatement preparedUpdate = null; PreparedStatement preparedQuery = null; ResultSet resultSet = null; @@ -1162,6 +1186,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -1212,6 +1240,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); Long badInstances = 0L; @@ -1256,6 +1288,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { + if(aType == null) { + throw new EamDbException("Correlation type is null"); + } + Connection conn = connect(); Collection caseNames = new LinkedHashSet<>(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index e0177f12f0..b2e05d8694 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -49,8 +49,7 @@ public class CentralRepoDatamodelTest extends TestCase { private static final String PROPERTIES_FILE = "CentralRepository"; private static final String CR_DB_NAME = "testcentralrepo.db"; - //private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); - private static final Path testDirectory = Paths.get("C:", "Work", "CRDatamodelTest"); // TEMP EASIER FOR TESTING + private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); SqliteEamDbSettings dbSettingsSqlite; private CorrelationCase case1; @@ -90,7 +89,7 @@ public class CentralRepoDatamodelTest extends TestCase { assertTrue("Unable to create test directory", testDirectory.toFile().exists()); // Save the current central repo settings - propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); + propertiesMap = ModuleSettings.getConfigSettings(PROPERTIES_FILE); try { dbSettingsSqlite.setDbName(CR_DB_NAME); @@ -137,7 +136,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDb.getInstance().newDataSource(dataSource2fromCase1); dataSource2fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource2fromCase1.getDeviceID()); assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); - + dataSource1fromCase2 = new CorrelationDataSource(case2.getID(), "dataSource3_deviceID", "dataSource3"); EamDb.getInstance().newDataSource(dataSource1fromCase2); dataSource1fromCase2 = EamDb.getInstance().getDataSource(case2, dataSource1fromCase2.getDeviceID()); @@ -148,7 +147,7 @@ public class CentralRepoDatamodelTest extends TestCase { org2 = new EamOrganization("org2"); org2.setOrgID((int) EamDb.getInstance().newOrganization(org2)); - + // Store the file type object for later use fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); assertTrue("getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); @@ -169,33 +168,291 @@ public class CentralRepoDatamodelTest extends TestCase { // Close and delete the test case and central repo db try { EamDb.getInstance().shutdownConnections(); - FileUtils.deleteDirectory(testDirectory.toFile()); - } catch (EamDbException | IOException ex) { - // } catch (EamDbException ex) { // TEMP FOR LOOKING AT DB Exceptions.printStackTrace(ex); Assert.fail(ex); } assertFalse("Error deleting test directory " + testDirectory.toString(), testDirectory.toFile().exists()); } - + /** - * Test the module settings + * Test the notable status of artifacts + * addArtifact(CorrelationAttribute eamArtifact) tests: + * - Test that two artifacts created with BAD status still have it when fetched from the database + * - Test that two artifacts created with BAD and KNOWN status still have the correct status when fetched from the database + * setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) tests: + * - Test updating status + * - Test updating artifact with two instances + * - Test updating null artifact + * - Test updating artifact with null known status + * - Test updating artifact with null case + * - Test updating artifact with null data source + * getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) tests: + * - Test getting two notable instances + * - Test getting notable instances where one instance is notable and the other is known + * - Test getting notable instances with null type + * - Test getting notable instances with null value + * getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) tests: + * - Test getting count of two notable instances + * - Test getting notable instance count where one instance is notable and the other is known + * - Test getting notable instance count with null type + * - Test getting notable instance count with null value + * getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) tests: + * - Test getting cases with notable instances (all instances are notable) + * - Test getting cases with notable instances (only one instance is notable) + * - Test getting cases with null type + * - Test getting cases with null value */ - public void atestSettings(){ - // Maybe + public void testNotableArtifactStatus() { + + String notableHashInBothCases = "e34a8899ef6468b74f8a1048419ccc8b"; + String notableHashInOneCaseKnownOther = "d293f2f5cebcb427cde3bb95db5e1797"; + String hashToChangeToNotable = "23bd4ea37ec6304e75ac723527472a0f"; + + // Add two instances with notable status + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, notableHashInBothCases); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, "path1", + "", TskData.FileKnown.BAD)); + attr.addInstance(new CorrelationAttributeInstance(case2, dataSource1fromCase2, "path2", + "", TskData.FileKnown.BAD)); + EamDb.getInstance().addArtifact(attr); + + List attrs = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, notableHashInBothCases); + assertTrue("getArtifactInstancesByTypeValue returned " + attrs.size() + " values - expected 2", attrs.size() == 2); + for (CorrelationAttributeInstance a : attrs) { + assertTrue("Artifact did not have expected BAD status", a.getKnownStatus().equals(TskData.FileKnown.BAD)); + } + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Add two instances with one notable, one known + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, notableHashInOneCaseKnownOther); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, "path3", + "", TskData.FileKnown.BAD)); + attr.addInstance(new CorrelationAttributeInstance(case2, dataSource1fromCase2, "path4", + "", TskData.FileKnown.KNOWN)); + EamDb.getInstance().addArtifact(attr); + + List attrs = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, notableHashInOneCaseKnownOther); + assertTrue("getArtifactInstancesByTypeValue returned " + attrs.size() + " values - expected 2", attrs.size() == 2); + for (CorrelationAttributeInstance a : attrs) { + if (case1.getCaseUUID().equals(a.getCorrelationCase().getCaseUUID())) { + assertTrue("Artifact did not have expected BAD status", a.getKnownStatus().equals(TskData.FileKnown.BAD)); + } else if (case2.getCaseUUID().equals(a.getCorrelationCase().getCaseUUID())) { + assertTrue("Artifact did not have expected KNOWN status", a.getKnownStatus().equals(TskData.FileKnown.KNOWN)); + } else { + Assert.fail("getArtifactInstancesByTypeValue returned unexpected case"); + } + } + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Add an artifact and then update its status + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, hashToChangeToNotable); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase2, "path5", + "", TskData.FileKnown.KNOWN)); + EamDb.getInstance().addArtifact(attr); + + EamDb.getInstance().setArtifactInstanceKnownStatus(attr, TskData.FileKnown.BAD); + + List attrs = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, hashToChangeToNotable); + assertTrue("getArtifactInstancesByTypeValue returned " + attrs.size() + " values - expected 1", attrs.size() == 1); + assertTrue("Artifact status did not change to BAD", attrs.get(0).getKnownStatus().equals(TskData.FileKnown.BAD)); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + return; + } + + // Try to update artifact with two CorrelationAttributeInstance instances + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "badHash"); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, "badPath", + "", TskData.FileKnown.KNOWN)); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase2, "badPath", + "", TskData.FileKnown.KNOWN)); + + EamDb.getInstance().setArtifactInstanceKnownStatus(attr, TskData.FileKnown.BAD); + Assert.fail("setArtifactInstanceKnownStatus failed to throw exception for multiple Correlation Attribute Instances"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try to update null artifact + try { + EamDb.getInstance().setArtifactInstanceKnownStatus(null, TskData.FileKnown.BAD); + Assert.fail("setArtifactInstanceKnownStatus failed to throw exception for null correlation attribute"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try to update artifact with null known status + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "badHash"); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, "badPath", + "", TskData.FileKnown.KNOWN)); + + EamDb.getInstance().setArtifactInstanceKnownStatus(attr, null); + Assert.fail("setArtifactInstanceKnownStatus failed to throw exception for null known status"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try to update artifact with null case + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "badHash"); + attr.addInstance(new CorrelationAttributeInstance(null, dataSource1fromCase1, "badPath", + "", TskData.FileKnown.KNOWN)); + + EamDb.getInstance().setArtifactInstanceKnownStatus(attr, TskData.FileKnown.BAD); + Assert.fail("setArtifactInstanceKnownStatus failed to throw exception for null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Try to update artifact with null data source + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "badHash"); + attr.addInstance(new CorrelationAttributeInstance(case1, null, "badPath", + "", TskData.FileKnown.KNOWN)); + + EamDb.getInstance().setArtifactInstanceKnownStatus(attr, TskData.FileKnown.BAD); + Assert.fail("setArtifactInstanceKnownStatus failed to throw exception for null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting two notable instances + try { + List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(fileType, notableHashInBothCases); + assertTrue("getArtifactInstancesKnownBad returned " + attrs.size() + " values - expected 2", attrs.size() == 2); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting notable instances where one instance is notable and the other is known + try { + List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(fileType, notableHashInOneCaseKnownOther); + assertTrue("getArtifactInstancesKnownBad returned " + attrs.size() + " values - expected 1", attrs.size() == 1); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting notable instances with null type + try { + List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(null, notableHashInOneCaseKnownOther); + Assert.fail("getArtifactInstancesKnownBad failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting notable instances with null value (should work fine) + try { + List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(fileType, null); + assertTrue("getArtifactInstancesKnownBad returned " + attrs.size() + " values - expected ", attrs.size() == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting count of two notable instances + try { + long count = EamDb.getInstance().getCountArtifactInstancesKnownBad(fileType, notableHashInBothCases); + assertTrue("getCountArtifactInstancesKnownBad returned " + count + " values - expected 2", count == 2); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting notable instance count where one instance is notable and the other is known + try { + long count = EamDb.getInstance().getCountArtifactInstancesKnownBad(fileType, notableHashInOneCaseKnownOther); + assertTrue("getCountArtifactInstancesKnownBad returned " + count + " values - expected 1", count == 1); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting notable instance count with null type + try { + EamDb.getInstance().getCountArtifactInstancesKnownBad(null, notableHashInOneCaseKnownOther); + Assert.fail("getCountArtifactInstancesKnownBad failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting notable instance count with null value (should work fine) + try { + long count = EamDb.getInstance().getCountArtifactInstancesKnownBad(fileType, null); + assertTrue("getCountArtifactInstancesKnownBad returned " + count + " values - expected ", count == 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting cases with notable instances (all instances are notable) + try { + List cases = EamDb.getInstance().getListCasesHavingArtifactInstancesKnownBad(fileType, notableHashInBothCases); + assertTrue("getListCasesHavingArtifactInstancesKnownBad returned " + cases.size() + " values - expected 2", cases.size() == 2); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting cases with notable instances (only one instance is notable) + try { + List cases = EamDb.getInstance().getListCasesHavingArtifactInstancesKnownBad(fileType, notableHashInOneCaseKnownOther); + assertTrue("getListCasesHavingArtifactInstancesKnownBad returned " + cases.size() + " values - expected 1", cases.size() == 1); + assertTrue("getListCasesHavingArtifactInstancesKnownBad returned unexpected case " + cases.get(0), case1.getDisplayName().equals(cases.get(0))); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + // Test getting cases with null type + try { + EamDb.getInstance().getListCasesHavingArtifactInstancesKnownBad(null, notableHashInOneCaseKnownOther); + Assert.fail("getListCasesHavingArtifactInstancesKnownBad failed to throw exception for null type"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test getting cases with null value (should work fine) + try { + List cases = EamDb.getInstance().getListCasesHavingArtifactInstancesKnownBad(fileType, null); + assertTrue("getListCasesHavingArtifactInstancesKnownBad returned " + cases.size() + " values - expected ", cases.isEmpty()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } } - + /** - * Test the methods associated with bulk artifacts - * First test the normal use case of a large number of valid artifacts getting added + * Test the methods associated with bulk artifacts (prepareBulkArtifact and + * bulkInsertArtifacts). + * First test the normal use case of a large number of valid artifacts getting added. * Next test the error conditions: - * + * - Test preparing artifact with null type + * - Test preparing artifact with null case + * - Test preparing artifact with null data source + * - Test preparing artifact with null path + * - Test preparing artifact with null known status */ public void testBulkArtifacts() { - - // Test bulk artifacts + + // Test normal addition of bulk artifacts // Steps: // - Make a list of artifacts roughly half the threshold size // - Call prepareBulkArtifact on all of them @@ -209,76 +466,164 @@ public class CentralRepoDatamodelTest extends TestCase { // Make sure there are no artifacts in the database to start long originalArtifactCount = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); assertTrue("getCountArtifactInstancesByCaseDataSource returned non-zero count", originalArtifactCount == 0); - + // Create the first list, which will have (bulkThreshold / 2) entries List list1 = new ArrayList<>(); - for(int i = 0; i < dbSettingsSqlite.getBulkThreshold() / 2;i++) { + for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() / 2; i++) { String value = "bulkInsertValue1_" + String.valueOf(i); String path = "C:\\bulkInsertPath1\\file" + String.valueOf(i); - + CorrelationAttribute attr = new CorrelationAttribute(fileType, value); attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, path)); list1.add(attr); } - + // Queue up the current list. There should not be enough to trigger the insert - for(CorrelationAttribute attr:list1){ + for (CorrelationAttribute attr : list1) { EamDb.getInstance().prepareBulkArtifact(attr); } - + // Check that nothing has been written yet - assertTrue("Artifacts written to database before threshold was reached", + assertTrue("Artifacts written to database before threshold was reached", originalArtifactCount == EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID())); // Make a second list with length equal to bulkThreshold List list2 = new ArrayList<>(); - for(int i = 0; i < dbSettingsSqlite.getBulkThreshold();i++) { + for (int i = 0; i < dbSettingsSqlite.getBulkThreshold(); i++) { String value = "bulkInsertValue2_" + String.valueOf(i); String path = "C:\\bulkInsertPath2\\file" + String.valueOf(i); - + CorrelationAttribute attr = new CorrelationAttribute(fileType, value); attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, path)); list2.add(attr); } - + // Queue up the current list. This will trigger an insert partway through - for(CorrelationAttribute attr:list2){ + for (CorrelationAttribute attr : list2) { EamDb.getInstance().prepareBulkArtifact(attr); } - + // There should now be bulkThreshold artifacts in the database long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); assertTrue("Artifact count " + count + " does not match bulkThreshold " + dbSettingsSqlite.getBulkThreshold(), count == dbSettingsSqlite.getBulkThreshold()); - + // Now call bulkInsertArtifacts() to insert the rest of queue EamDb.getInstance().bulkInsertArtifacts(); count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); int expectedCount = list1.size() + list2.size(); assertTrue("Artifact count " + count + " does not match expected count " + expectedCount, count == expectedCount); - + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test preparing artifact with null type - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(null, "value"); EamDb.getInstance().prepareBulkArtifact(attr); Assert.fail("prepareBulkArtifact failed to throw exception for null type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test preparing artifact with null case + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "value"); + attr.addInstance(new CorrelationAttributeInstance(null, dataSource1fromCase1, "path")); + EamDb.getInstance().prepareBulkArtifact(attr); + EamDb.getInstance().bulkInsertArtifacts(); + Assert.fail("bulkInsertArtifacts failed to throw exception for null case"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test preparing artifact with null data source + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "value"); + attr.addInstance(new CorrelationAttributeInstance(case1, null, "path")); + EamDb.getInstance().prepareBulkArtifact(attr); + EamDb.getInstance().bulkInsertArtifacts(); + Assert.fail("prepareBulkArtifact failed to throw exception for null data source"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test preparing artifact with null path + // CorrelationAttributeInstance will throw an exception + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "value"); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, null)); + Assert.fail("CorrelationAttributeInstance failed to throw exception for null path"); + } catch (EamDbException ex) { + // This is the expected behavior + } + + // Test preparing artifact with null known status + try { + CorrelationAttribute attr = new CorrelationAttribute(fileType, "value"); + attr.addInstance(new CorrelationAttributeInstance(case1, dataSource1fromCase1, "path", "comment", null)); + EamDb.getInstance().prepareBulkArtifact(attr); + EamDb.getInstance().bulkInsertArtifacts(); + Assert.fail("prepareBulkArtifact failed to throw exception for null known status"); + } catch (EamDbException ex) { // This is the expected behavior } - - - } - + /** - * Test methods related to artifacts + * Test most methods related to artifacts + * addArtifact(CorrelationAttribute eamArtifact) tests: + * - Test adding artifact with one instance + * - Test adding artifact with one instance in each data source + * - Test adding artifact with two instances in the same data source + * - Test adding email artifact + * - Test adding phone artifact + * - Test adding domain artifact + * - Test adding device artifact + * - Test adding artifact with null case + * - Test adding artifact with invalid case ID + * - Test adding artifact with null data source + * - Test adding artifact with invalid data source ID + * - Test adding artifact with null path + * - Test adding artifact with null known status + * - Test adding artifact with null correlation type + * - Test adding artifact with null value + * getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) tests: + * - Test getting three expected instances + * - Test getting no expected instances + * - Test with null type + * - Test with null value + * getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) tests: + * - Test with existing path + * - Test with non-existent path + * - Test with null type + * - Test with null path + * getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) tests: + * - Test getting three expected instances + * - Test getting no expected instances + * - Test with null type + * - Test with null value + * getFrequencyPercentage(CorrelationAttribute corAttr) tests: + * - Test value in every data source + * - Test value in one data source twice + * - Test email + * - Test value in no data sources + * - Test with null type + * - Test with null attribute + * getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) tests: + * - Test data source with seven instances + * - Test with null case UUID + * - Test with null device ID + * getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) tests: + * - Test value in every data source + * - Test value in one data source twice + * - Test value in no data sources + * - Test with null type + * - Test with null value */ public void testArtifacts() { - + String inAllDataSourcesHash = "6cddb0e31787b79cfdcc0676b98a71ce"; String inAllDataSourcesPath = "C:\\files\\path0.txt"; String inDataSource1twiceHash = "b2f5ff47436671b6e533d8dc3614845d"; @@ -286,7 +631,7 @@ public class CentralRepoDatamodelTest extends TestCase { String inDataSource1twicePath2 = "C:\\files\\path2.txt"; String onlyInDataSource3Hash = "2af54305f183778d87de0c70c591fae4"; String onlyInDataSource3Path = "C:\\files\\path3.txt"; - + // These will all go in dataSource1fromCase1 String emailValue = "test@gmail.com"; String emailPath = "C:\\files\\emailPath.txt"; @@ -296,10 +641,9 @@ public class CentralRepoDatamodelTest extends TestCase { String domainPath = "C:\\files\\domainPath.txt"; String devIdValue = "94B21234"; String devIdPath = "C:\\files\\devIdPath.txt"; - + // Store the email type CorrelationAttribute.Type emailType; - try { emailType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID); } catch (EamDbException ex) { @@ -307,20 +651,20 @@ public class CentralRepoDatamodelTest extends TestCase { Assert.fail(ex); return; } - + // Test adding attribute with one instance - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(fileType, onlyInDataSource3Hash); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case2, dataSource1fromCase2, onlyInDataSource3Path); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding attribute with an instance in each data source - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(fileType, inAllDataSourcesHash); CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inAllDataSourcesPath); attr.addInstance(inst1); @@ -329,74 +673,73 @@ public class CentralRepoDatamodelTest extends TestCase { CorrelationAttributeInstance inst3 = new CorrelationAttributeInstance(case2, dataSource1fromCase2, inAllDataSourcesPath); attr.addInstance(inst3); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding attribute with two instances in one data source - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(fileType, inDataSource1twiceHash); CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inDataSource1twicePath1); attr.addInstance(inst1); CorrelationAttributeInstance inst2 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, inDataSource1twicePath2); attr.addInstance(inst2); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding the other types // Test adding an email artifact - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(emailType, emailValue); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, emailPath); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding a phone artifact - try{ - CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.PHONE_TYPE_ID), + try { + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.PHONE_TYPE_ID), phoneValue); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, phonePath); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding a domain artifact - try{ - CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.DOMAIN_TYPE_ID), + try { + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.DOMAIN_TYPE_ID), domainValue); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, domainPath); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding a device ID artifact - try{ - CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.USBID_TYPE_ID), + try { + CorrelationAttribute attr = new CorrelationAttribute(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.USBID_TYPE_ID), devIdValue); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, devIdPath); attr.addInstance(inst); EamDb.getInstance().addArtifact(attr); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test CorrelationAttributeInstance failure cases - // Create an attribute to use in the next few tests CorrelationAttribute failAttr; try { @@ -406,103 +749,102 @@ public class CentralRepoDatamodelTest extends TestCase { Assert.fail(ex); return; } - + // Test adding instance with null case - try{ + try { CorrelationAttributeInstance inst = new CorrelationAttributeInstance(null, dataSource1fromCase2, "badPath"); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); Assert.fail("addArtifact failed to throw exception for null case"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding instance with invalid case ID - try{ + try { CorrelationCase badCase = new CorrelationCase("badCaseUuid", "badCaseName"); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(badCase, dataSource1fromCase2, "badPath"); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); Assert.fail("addArtifact failed to throw exception for invalid case"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding instance with null data source - try{ + try { CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, null, "badPath"); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); Assert.fail("addArtifact failed to throw exception for null data source"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding instance with invalid data source ID - try{ + try { CorrelationDataSource badDS = new CorrelationDataSource(case1.getID(), "badDSUuid", "badDSName"); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, badDS, "badPath"); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); Assert.fail("addArtifact failed to throw exception for invalid data source"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding instance with null path // This will fail in the CorrelationAttributeInstance constructor - try{ + try { CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, null); Assert.fail("CorrelationAttributeInstance failed to throw exception for null path"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding instance with null known status - try{ + try { CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, null, "comment", null); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); Assert.fail("addArtifact failed to throw exception for null known status"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test CorrelationAttribute failure cases - // Test null type - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(null, "badInstances"); EamDb.getInstance().addArtifact(attr); Assert.fail("addArtifact failed to throw exception for null type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test null value // This will fail in the CorrelationAttribute constructor - try{ + try { CorrelationAttribute attr = new CorrelationAttribute(fileType, null); Assert.fail("addArtifact failed to throw exception for null value"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting instances with expected resuls try { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, inAllDataSourcesHash); assertTrue("getArtifactInstancesByTypeValue returned " + instances.size() + " results - expected 3", instances.size() == 3); - + // This test works because all the instances of this hash were set to the same path - for(CorrelationAttributeInstance inst:instances) { - assertTrue("getArtifactInstancesByTypeValue returned instance with unexpected path " + inst.getFilePath(), + for (CorrelationAttributeInstance inst : instances) { + assertTrue("getArtifactInstancesByTypeValue returned instance with unexpected path " + inst.getFilePath(), inAllDataSourcesPath.equalsIgnoreCase(inst.getFilePath())); } } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instances expecting no results try { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue( @@ -512,7 +854,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instances with null type try { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(null, inAllDataSourcesHash); @@ -520,7 +862,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting instances with null value // Should just return nothing try { @@ -530,7 +872,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instances with path that should produce results try { List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, inAllDataSourcesPath); @@ -539,7 +881,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instances with path that should not produce results try { List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, "xyz"); @@ -548,7 +890,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instances with null type try { List instances = EamDb.getInstance().getArtifactInstancesByPath(null, inAllDataSourcesPath); @@ -556,15 +898,15 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - - // Test getting instances with null value + + // Test getting instances with null path try { List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, null); - Assert.fail("getArtifactInstancesByPath failed to throw exception for null value"); + Assert.fail("getArtifactInstancesByPath failed to throw exception for null path"); } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting instance count with path that should produce results try { long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, inAllDataSourcesHash); @@ -573,7 +915,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instance count with path that should not produce results try { long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, "xyz"); @@ -582,7 +924,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting instance count with null type try { long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesHash); @@ -590,7 +932,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting instance count with null value try { long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, null); @@ -598,7 +940,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting frequency of value that is in all three data sources try { CorrelationAttribute attr = new CorrelationAttribute(fileType, inAllDataSourcesHash); @@ -608,7 +950,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting frequency of value that appears twice in a single data source try { CorrelationAttribute attr = new CorrelationAttribute(fileType, inDataSource1twiceHash); @@ -618,7 +960,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting frequency of non-file type try { CorrelationAttribute attr = new CorrelationAttribute(emailType, emailValue); @@ -628,7 +970,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting frequency of non-existent value try { CorrelationAttribute attr = new CorrelationAttribute(fileType, "randomValue"); @@ -638,7 +980,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting frequency with null type try { CorrelationAttribute attr = new CorrelationAttribute(null, "randomValue"); @@ -647,7 +989,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting frequency with null attribute try { int freq = EamDb.getInstance().getFrequencyPercentage(null); @@ -655,7 +997,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting count for dataSource1fromCase1 (includes all types) try { long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); @@ -664,7 +1006,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting count with null case UUID try { long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(null, dataSource1fromCase1.getDeviceID()); @@ -673,8 +1015,8 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - - // Test getting count with null data source ID + + // Test getting count with null device ID try { long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), null); assertTrue("getCountArtifactInstancesByCaseDataSource returned " + count + " - expected 0", count == 0); @@ -682,7 +1024,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting data source count for entry that is in all three try { long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, inAllDataSourcesHash); @@ -691,7 +1033,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting data source count for entry that is in one data source twice try { long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, inDataSource1twiceHash); @@ -700,7 +1042,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting data source count for entry that is not in any data sources try { long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, "abcdef"); @@ -709,7 +1051,7 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting data source count for null type try { long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(null, "abcdef"); @@ -717,7 +1059,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting data source count for null value try { long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(fileType, null); @@ -727,211 +1069,211 @@ public class CentralRepoDatamodelTest extends TestCase { Assert.fail(ex); } } - + /** * Test methods related to correlation types - * newCorrelationType(CorrelationAttribute.Type newType) tests: - * - Test with valid data - * - Test with duplicate data - * - Test with null name - * - Test with null db name + * newCorrelationType(CorrelationAttribute.Type newType) tests: + * - Test with valid data + * - Test with duplicate data + * - Test with null name + * - Test with null db name + * - Test with null type + * getDefinedCorrelationTypes() tests: + * - Test that the expected number are returned + * getEnabledCorrelationTypes() tests: + * - Test that the expected number are returned + * getSupportedCorrelationTypes() tests: + * - Test that the expected number are returned + * getCorrelationTypeById(int typeId) tests: + * - Test with valid ID + * - Test with invalid ID + * updateCorrelationType(CorrelationAttribute.Type aType) tests: + * - Test with existing type + * - Test with non-existent type + * - Test updating to null name * - Test with null type - * getDefinedCorrelationTypes() tests: - * - Test that the expected number are returned - * getEnabledCorrelationTypes() tests: - * - Test that the expected number are returned - * getSupportedCorrelationTypes() tests: - * - Test that the expected number are returned - * getCorrelationTypeById(int typeId) tests: - * - Test with valid ID - * - Test with invalid ID - * updateCorrelationType(CorrelationAttribute.Type aType) tests: - * - Test with existing type - * - Test with non-existent type - * - Test updating to null name - * - Test with null type */ - public void atestCorrelationTypes() { - + public void testCorrelationTypes() { + CorrelationAttribute.Type customType; String customTypeName = "customType"; String customTypeDb = "custom_type"; - + // Test new type with valid data - try{ + try { customType = new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); customType.setId(EamDb.getInstance().newCorrelationType(customType)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test new type with duplicate data - try{ + try { CorrelationAttribute.Type temp = new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); EamDb.getInstance().newCorrelationType(temp); Assert.fail("newCorrelationType failed to throw exception for duplicate name/db table"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test new type with null name - try{ + try { CorrelationAttribute.Type temp = new CorrelationAttribute.Type(null, "temp_type", false, false); EamDb.getInstance().newCorrelationType(temp); Assert.fail("newCorrelationType failed to throw exception for null name table"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test new type with null db name // The constructor should fail in this case - try{ + try { CorrelationAttribute.Type temp = new CorrelationAttribute.Type("temp", null, false, false); Assert.fail("CorrelationAttribute.Type failed to throw exception for null db table name"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test new type with null type - try{ + try { EamDb.getInstance().newCorrelationType(null); Assert.fail("newCorrelationType failed to throw exception for null type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting all correlation types - try{ + try { List types = EamDb.getInstance().getDefinedCorrelationTypes(); - + // We expect 6 total - 5 default and the custom one made earlier assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 6", types.size() == 6); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting enabled correlation types - try{ + try { List types = EamDb.getInstance().getEnabledCorrelationTypes(); - + // We expect 5 - the custom type is disabled assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 5", types.size() == 5); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting supported correlation types - try{ + try { List types = EamDb.getInstance().getSupportedCorrelationTypes(); - + // We expect 5 - the custom type is not supported assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 5", types.size() == 5); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting the type with a valid ID - try{ + try { CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(customType.getId()); assertTrue("getCorrelationTypeById returned type with unexpected name " + temp.getDisplayName(), customTypeName.equals(temp.getDisplayName())); assertTrue("getCorrelationTypeById returned type with unexpected db table name " + temp.getDbTableName(), customTypeDb.equals(temp.getDbTableName())); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting the type with a invalid ID - try{ + try { CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(5555); Assert.fail("getCorrelationTypeById failed to throw exception for invalid ID"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } // Test updating a valid type - try{ + try { String newName = "newName"; String newDbTable = "new_db_table"; customType.setDisplayName(newName); customType.setDbTableName(newDbTable); customType.setEnabled(true); // These were originally false customType.setSupported(true); - + EamDb.getInstance().updateCorrelationType(customType); - + // Get a fresh copy from the database CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(customType.getId()); - + assertTrue("updateCorrelationType failed to update name", newName.equals(temp.getDisplayName())); assertTrue("updateCorrelationType failed to update db table name", newDbTable.equals(temp.getDbTableName())); assertTrue("updateCorrelationType failed to update enabled status", temp.isEnabled()); assertTrue("updateCorrelationType failed to update supported status", temp.isSupported()); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test updating a type with an invalid ID // Nothing should happen - try{ - CorrelationAttribute.Type temp= new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); + try { + CorrelationAttribute.Type temp = new CorrelationAttribute.Type(customTypeName, customTypeDb, false, false); temp.setId(12345); EamDb.getInstance().updateCorrelationType(temp); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test updating a type to a null name - try{ + try { customType.setDisplayName(null); EamDb.getInstance().updateCorrelationType(customType); Assert.fail("updateCorrelationType failed to throw exception for null name"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test updating a null type - try{ + try { customType.setDisplayName(null); EamDb.getInstance().updateCorrelationType(customType); Assert.fail("updateCorrelationType failed to throw exception for null type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } } - + /** - * Test the methods related to organizations + * Test the methods related to organizations * newOrganization(EamOrganization eamOrg) tests: * - Test with just org name - * - Test with org name and poc info - * - Test adding duplicate org - * - Test adding null org - * - Test adding org with null name - * getOrganizations() tests: - * - Test getting the list of orgs -* getOrganizationByID(int orgID) tests: -* - Test with valid ID -* - Test with invalid ID -* updateOrganization(EamOrganization updatedOrganization) tests: -* - Test updating valid org -* - Test updating invalid org -* - Test updating null org -* - Test updating org to null name -* deleteOrganization(EamOrganization organizationToDelete) tests: -* - Test deleting org that isn't in use -* - Test deleting org that is in use -* - Test deleting invalid org -* - Test deleting null org + * - Test with org name and poc info + * - Test adding duplicate org + * - Test adding null org + * - Test adding org with null name + * getOrganizations() tests: + * - Test getting the list of orgs + * getOrganizationByID(int orgID) tests: + * - Test with valid ID + * - Test with invalid ID + * updateOrganization(EamOrganization updatedOrganization) tests: + * - Test updating valid org + * - Test updating invalid org + * - Test updating null org + * - Test updating org to null name + * deleteOrganization(EamOrganization organizationToDelete) tests: + * - Test deleting org that isn't in use + * - Test deleting org that is in use + * - Test deleting invalid org + * - Test deleting null org */ - public void atestOrganizations() { - + public void testOrganizations() { + EamOrganization orgA; String orgAname = "orgA"; EamOrganization orgB; @@ -939,89 +1281,89 @@ public class CentralRepoDatamodelTest extends TestCase { String orgBpocName = "pocName"; String orgBpocEmail = "pocEmail"; String orgBpocPhone = "pocPhone"; - + // Test adding a basic organization - try{ + try { orgA = new EamOrganization(orgAname); orgA.setOrgID((int) EamDb.getInstance().newOrganization(orgA)); assertTrue("Organization ID is still -1 after adding to db", orgA.getOrgID() != -1); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test adding an organization with additional fields - try{ + try { orgB = new EamOrganization(orgBname, orgBpocName, orgBpocEmail, orgBpocPhone); orgB.setOrgID((int) EamDb.getInstance().newOrganization(orgB)); assertTrue("Organization ID is still -1 after adding to db", orgB.getOrgID() != -1); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test adding a duplicate organization - try{ + try { EamOrganization temp = new EamOrganization(orgAname); EamDb.getInstance().newOrganization(temp); Assert.fail("newOrganization failed to throw exception for duplicate org name"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding null organization - try{ + try { EamDb.getInstance().newOrganization(null); Assert.fail("newOrganization failed to throw exception for null org"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding organization with null name - try{ + try { EamOrganization temp = new EamOrganization(null); EamDb.getInstance().newOrganization(temp); Assert.fail("newOrganization failed to throw exception for null name"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting organizations // We expect five - the default org, two from setUp, and two from this method - try{ + try { List orgs = EamDb.getInstance().getOrganizations(); assertTrue("getOrganizations returned null list", orgs != null); assertTrue("getOrganizations returned " + orgs.size() + " orgs - expected 5", orgs.size() == 5); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting org with valid ID - try{ + try { EamOrganization temp = EamDb.getInstance().getOrganizationByID(orgB.getOrgID()); assertTrue("getOrganizationByID returned null for valid ID", temp != null); assertTrue("getOrganizationByID returned unexpected name for organization", orgBname.equals(temp.getName())); assertTrue("getOrganizationByID returned unexpected poc name for organization", orgBpocName.equals(temp.getPocName())); assertTrue("getOrganizationByID returned unexpected poc email for organization", orgBpocEmail.equals(temp.getPocEmail())); assertTrue("getOrganizationByID returned unexpected poc phone for organization", orgBpocPhone.equals(temp.getPocPhone())); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting org with invalid ID - try{ + try { EamOrganization temp = EamDb.getInstance().getOrganizationByID(12345); Assert.fail("getOrganizationByID failed to throw exception for invalid ID"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test updating valid org - try{ + try { String newName = "newOrgName"; String newPocName = "newPocName"; String newPocEmail = "newPocEmail"; @@ -1030,141 +1372,141 @@ public class CentralRepoDatamodelTest extends TestCase { orgA.setPocName(newPocName); orgA.setPocEmail(newPocEmail); orgA.setPocPhone(newPocPhone); - + EamDb.getInstance().updateOrganization(orgA); - + EamOrganization copyOfA = EamDb.getInstance().getOrganizationByID(orgA.getOrgID()); - + assertTrue("getOrganizationByID returned null for valid ID", copyOfA != null); assertTrue("updateOrganization failed to update org name", newName.equals(copyOfA.getName())); assertTrue("updateOrganization failed to update poc name", newPocName.equals(copyOfA.getPocName())); assertTrue("updateOrganization failed to update poc email", newPocEmail.equals(copyOfA.getPocEmail())); assertTrue("updateOrganization failed to update poc phone", newPocPhone.equals(copyOfA.getPocPhone())); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test updating invalid org // Shouldn't do anything - try{ + try { EamOrganization temp = new EamOrganization("invalidOrg"); temp.setOrgID(3434); EamDb.getInstance().updateOrganization(temp); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test updating null org - try{ + try { EamDb.getInstance().updateOrganization(null); Assert.fail("updateOrganization failed to throw exception for null org"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test updating org to null name - try{ + try { EamOrganization copyOfA = EamDb.getInstance().getOrganizationByID(orgA.getOrgID()); copyOfA.setName(null); EamDb.getInstance().updateOrganization(copyOfA); Assert.fail("updateOrganization failed to throw exception for null name"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test deleting existing org that isn't in use - try{ + try { EamOrganization orgToDelete = new EamOrganization("deleteThis"); - orgToDelete.setOrgID((int)EamDb.getInstance().newOrganization(orgToDelete)); + orgToDelete.setOrgID((int) EamDb.getInstance().newOrganization(orgToDelete)); int orgCount = EamDb.getInstance().getOrganizations().size(); - + EamDb.getInstance().deleteOrganization(orgToDelete); assertTrue("getOrganizations returned unexpected count after deletion", orgCount - 1 == EamDb.getInstance().getOrganizations().size()); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test deleting existing org that is in use - try{ + try { // Make a new org EamOrganization inUseOrg = new EamOrganization("inUseOrg"); - inUseOrg.setOrgID((int)EamDb.getInstance().newOrganization(inUseOrg)); - + inUseOrg.setOrgID((int) EamDb.getInstance().newOrganization(inUseOrg)); + // Make a reference set that uses it EamGlobalSet tempSet = new EamGlobalSet(inUseOrg.getOrgID(), "inUseOrgTest", "1.0", TskData.FileKnown.BAD, false, fileType); EamDb.getInstance().newReferenceSet(tempSet); - + // It should now throw an exception if we try to delete it EamDb.getInstance().deleteOrganization(inUseOrg); Assert.fail("deleteOrganization failed to throw exception for in use organization"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior - } - + } + // Test deleting non-existent org // Should do nothing - try{ + try { EamOrganization temp = new EamOrganization("temp"); temp.setOrgID(9876); EamDb.getInstance().deleteOrganization(temp); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } - + } + // Test deleting null org - try{ + try { EamDb.getInstance().deleteOrganization(null); Assert.fail("deleteOrganization failed to throw exception for null organization"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior - } + } } - + /** - * Tests for adding / retrieving reference instances - * Only the files type is currently implemented + * Tests for adding / retrieving reference instances + * Only the files type is currently implemented * addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) tests: - * - Test adding multiple valid entries - * - Test invalid reference set ID - * - Test null hash (EamGlobalFileInstance constructor) - * - Test null known status (EamGlobalFileInstance constructor) + * - Test adding multiple valid entries + * - Test invalid reference set ID + * - Test null hash (EamGlobalFileInstance constructor) + * - Test null known status (EamGlobalFileInstance constructor) * - Test null correlation type - * bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) tests: - * - Test with large valid list - * - Test with null list - * - Test with invalid reference set ID - * - Test with null correlation type - * getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) tests: - * - Test with valid entries - * - Test with non-existent value - * - Test with invalid type - * - Test with null type - * - Test with null value - * isFileHashInReferenceSet(String hash, int referenceSetID)tests: - * - Test existing hash/ID - * - Test non-existent (but valid) hash/ID - * - Test invalid ID - * - Test null hash - * isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) tests: - * - Test existing value/ID - * - Test non-existent (but valid) value/ID - * - Test invalid ID - * - Test null value - * - Test invalid type ID - * isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) tests: - * - Test notable value - * - Test known value - * - Test non-existent value - * - Test null value - * - Test null type - * - Test invalid type + * bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) tests: + * - Test with large valid list + * - Test with null list + * - Test with invalid reference set ID + * - Test with null correlation type + * getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) tests: + * - Test with valid entries + * - Test with non-existent value + * - Test with invalid type + * - Test with null type + * - Test with null value + * isFileHashInReferenceSet(String hash, int referenceSetID)tests: + * - Test existing hash/ID + * - Test non-existent (but valid) hash/ID + * - Test invalid ID + * - Test null hash + * isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) tests: + * - Test existing value/ID + * - Test non-existent (but valid) value/ID + * - Test invalid ID + * - Test null value + * - Test invalid type ID + * isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) tests: + * - Test notable value + * - Test known value + * - Test non-existent value + * - Test null value + * - Test null type + * - Test invalid type */ - public void atestReferenceSetInstances(){ - + public void testReferenceSetInstances() { + // After the two initial testing blocks, the reference sets should contain: // notableSet1 - notableHash1, inAllSetsHash // notableSet2 - inAllSetsHash @@ -1175,23 +1517,23 @@ public class CentralRepoDatamodelTest extends TestCase { int notableSet2id; EamGlobalSet knownSet1; int knownSet1id; - - String notableHash1 = "d46feecd663c41648dbf690d9343cf4b"; - String knownHash1 = "39c844daee70485143da4ff926601b5b"; + + String notableHash1 = "d46feecd663c41648dbf690d9343cf4b"; + String knownHash1 = "39c844daee70485143da4ff926601b5b"; String inAllSetsHash = "6449b39bb23c42879fa0c243726e27f7"; - + CorrelationAttribute.Type emailType; - + // Store the email type object for later use - try{ + try { emailType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID); assertTrue("getCorrelationTypeById(EMAIL_TYPE_ID) returned null", emailType != null); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Set up a few reference sets try { notableSet1 = new EamGlobalSet(org1.getOrgID(), "notable set 1", "1.0", TskData.FileKnown.BAD, false, fileType); @@ -1200,71 +1542,71 @@ public class CentralRepoDatamodelTest extends TestCase { notableSet2id = EamDb.getInstance().newReferenceSet(notableSet2); knownSet1 = new EamGlobalSet(org1.getOrgID(), "known set 1", "5.5.4", TskData.FileKnown.KNOWN, false, fileType); knownSet1id = EamDb.getInstance().newReferenceSet(knownSet1); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test adding file instances with valid data try { EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment1"); EamDb.getInstance().addReferenceInstance(temp, fileType); - + temp = new EamGlobalFileInstance(notableSet2id, inAllSetsHash, TskData.FileKnown.BAD, "comment2"); EamDb.getInstance().addReferenceInstance(temp, fileType); - + temp = new EamGlobalFileInstance(knownSet1id, inAllSetsHash, TskData.FileKnown.KNOWN, "comment3"); EamDb.getInstance().addReferenceInstance(temp, fileType); - + temp = new EamGlobalFileInstance(notableSet1id, notableHash1, TskData.FileKnown.BAD, "comment4"); EamDb.getInstance().addReferenceInstance(temp, fileType); - + temp = new EamGlobalFileInstance(knownSet1id, knownHash1, TskData.FileKnown.KNOWN, "comment5"); - EamDb.getInstance().addReferenceInstance(temp, fileType); - }catch (EamDbException ex){ + EamDb.getInstance().addReferenceInstance(temp, fileType); + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test adding file instance with invalid reference set ID try { EamGlobalFileInstance temp = new EamGlobalFileInstance(2345, inAllSetsHash, TskData.FileKnown.BAD, "comment"); - EamDb.getInstance().addReferenceInstance(temp, fileType); + EamDb.getInstance().addReferenceInstance(temp, fileType); Assert.fail("addReferenceInstance failed to throw exception for invalid ID"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating file instance with null hash // Since it isn't possible to get a null hash into the EamGlobalFileInstance, skip trying to // call addReferenceInstance and just test the EamGlobalFileInstance constructor try { EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, null, TskData.FileKnown.BAD, "comment"); Assert.fail("EamGlobalFileInstance failed to throw exception for null hash"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding file instance with null known status // Since it isn't possible to get a null known status into the EamGlobalFileInstance, skip trying to // call addReferenceInstance and just test the EamGlobalFileInstance constructor try { - EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, null, "comment"); + EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, null, "comment"); Assert.fail("EamGlobalFileInstance failed to throw exception for null type"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test adding file instance with null correlation type try { EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment"); - EamDb.getInstance().addReferenceInstance(temp, null); + EamDb.getInstance().addReferenceInstance(temp, null); Assert.fail("addReferenceInstance failed to throw exception for null type"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test bulk insert with large valid set try { // Create a list of global file instances. Make enough that the bulk threshold should be hit once. @@ -1274,256 +1616,256 @@ public class CentralRepoDatamodelTest extends TestCase { String hash = bulkTestHash + String.valueOf(i); instances.add(new EamGlobalFileInstance(notableSet2id, hash, TskData.FileKnown.BAD, null)); } - + // Insert the list EamDb.getInstance().bulkInsertReferenceTypeEntries(instances, fileType); - + // There's no way to get a count of the number of entries in the database, so just do a spot check - if(dbSettingsSqlite.getBulkThreshold() > 10){ + if (dbSettingsSqlite.getBulkThreshold() > 10) { String hash = bulkTestHash + "10"; assertTrue("Sample bulk insert instance not found", EamDb.getInstance().isFileHashInReferenceSet(hash, notableSet2id)); } - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test bulk add file instance with null list try { - EamDb.getInstance().bulkInsertReferenceTypeEntries(null, fileType); + EamDb.getInstance().bulkInsertReferenceTypeEntries(null, fileType); Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for null list"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test bulk add file instance with invalid reference set ID try { Set tempSet = new HashSet<>(Arrays.asList(new EamGlobalFileInstance(2345, inAllSetsHash, TskData.FileKnown.BAD, "comment"))); EamDb.getInstance().bulkInsertReferenceTypeEntries(tempSet, fileType); Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for invalid ID"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test bulk add file instance with null correlation type try { Set tempSet = new HashSet<>(Arrays.asList(new EamGlobalFileInstance(notableSet1id, inAllSetsHash, TskData.FileKnown.BAD, "comment"))); - EamDb.getInstance().bulkInsertReferenceTypeEntries(tempSet, null); + EamDb.getInstance().bulkInsertReferenceTypeEntries(tempSet, null); Assert.fail("bulkInsertReferenceTypeEntries failed to throw exception for null type"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting reference instances with valid data try { List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, inAllSetsHash); assertTrue("getReferenceInstancesByTypeValue returned " + temp.size() + " instances - expected 3", temp.size() == 3); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting reference instances with non-existent data try { List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, "testHash"); assertTrue("getReferenceInstancesByTypeValue returned " + temp.size() + " instances for non-existent value - expected 0", temp.isEmpty()); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting reference instances an invalid type (the email table is not yet implemented) try { List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(emailType, inAllSetsHash); Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for invalid table"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting reference instances with null type try { List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(null, inAllSetsHash); Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for null type"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test getting reference instances with null value try { List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(fileType, null); assertTrue("getReferenceInstancesByTypeValue returned non-empty list given null value", temp.isEmpty()); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking existing hash/ID try { assertTrue("isFileHashInReferenceSet returned false for valid data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, knownSet1id)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking non-existent (but valid) hash/ID try { assertFalse("isFileHashInReferenceSet returned true for non-existent data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, notableSet1id)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking invalid reference set ID try { assertFalse("isFileHashInReferenceSet returned true for invalid data", EamDb.getInstance().isFileHashInReferenceSet(knownHash1, 5678)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking null hash try { assertFalse("isFileHashInReferenceSet returned true for null hash", EamDb.getInstance().isFileHashInReferenceSet(null, knownSet1id)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking existing hash/ID try { - assertTrue("isValueInReferenceSet returned false for valid data", + assertTrue("isValueInReferenceSet returned false for valid data", EamDb.getInstance().isValueInReferenceSet(knownHash1, knownSet1id, fileType.getId())); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking non-existent (but valid) hash/ID try { - assertFalse("isValueInReferenceSet returned true for non-existent data", + assertFalse("isValueInReferenceSet returned true for non-existent data", EamDb.getInstance().isValueInReferenceSet(knownHash1, notableSet1id, fileType.getId())); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking invalid reference set ID try { - assertFalse("isValueInReferenceSet returned true for invalid data", + assertFalse("isValueInReferenceSet returned true for invalid data", EamDb.getInstance().isValueInReferenceSet(knownHash1, 5678, fileType.getId())); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking null hash try { - assertFalse("isValueInReferenceSet returned true for null value", + assertFalse("isValueInReferenceSet returned true for null value", EamDb.getInstance().isValueInReferenceSet(null, knownSet1id, fileType.getId())); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test checking invalid type try { EamDb.getInstance().isValueInReferenceSet(knownHash1, knownSet1id, emailType.getId()); Assert.fail("isValueInReferenceSet failed to throw exception for invalid type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test known bad with notable data try { - assertTrue("isArtifactKnownBadByReference returned false for notable value", + assertTrue("isArtifactKnownBadByReference returned false for notable value", EamDb.getInstance().isArtifactKnownBadByReference(fileType, notableHash1)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test known bad with known data try { - assertFalse("isArtifactKnownBadByReference returned true for known value", + assertFalse("isArtifactKnownBadByReference returned true for known value", EamDb.getInstance().isArtifactKnownBadByReference(fileType, knownHash1)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test known bad with non-existent data try { - assertFalse("isArtifactKnownBadByReference returned true for non-existent value", + assertFalse("isArtifactKnownBadByReference returned true for non-existent value", EamDb.getInstance().isArtifactKnownBadByReference(fileType, "abcdef")); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test known bad with null hash try { - assertFalse("isArtifactKnownBadByReference returned true for null value", + assertFalse("isArtifactKnownBadByReference returned true for null value", EamDb.getInstance().isArtifactKnownBadByReference(fileType, null)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test known bad with null type try { EamDb.getInstance().isArtifactKnownBadByReference(null, knownHash1); Assert.fail("isArtifactKnownBadByReference failed to throw exception from null type"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test known bad with invalid type try { assertFalse("isArtifactKnownBadByReference returned true for invalid type", EamDb.getInstance().isArtifactKnownBadByReference(emailType, null)); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } } - + /** - * Test method for the methods related to reference sets (does not include instance testing) - * Only the files type is currently implemented - * newReferenceSet(EamGlobalSet eamGlobalSet) tests: - * - Test creating notable reference set - * - Test creating known reference set - * - Test creating duplicate reference set - * - Test creating almost duplicate reference set - * - Test with invalid org ID - * - Test with null name - * - Test with null version - * - Test with null known status + * Test method for the methods related to reference sets (does not include + * instance testing) Only the files type is currently implemented + * newReferenceSet(EamGlobalSet eamGlobalSet) tests: + * - Test creating notable reference set + * - Test creating known reference set + * - Test creating duplicate reference set + * - Test creating almost duplicate reference set + * - Test with invalid org ID + * - Test with null name + * - Test with null version + * - Test with null known status * - Test with null file type - * referenceSetIsValid(int referenceSetID, String referenceSetName, String version) tests: - * - Test on existing reference set - * - Test on invalid reference set - * - Test with null name - * - Test with null version - * referenceSetExists(String referenceSetName, String version) tests: - * - Test on existing reference set - * - Test on invalid reference set - * - Test with null name - * - Test with null version - * getReferenceSetByID(int globalSetID) tests: - * - Test with valid ID - * - Test with invalid ID - * getAllReferenceSets(CorrelationAttribute.Type correlationType) tests: - * - Test getting all file sets - * - Test getting all email sets - * - Test with null type parameter - * deleteReferenceSet(int referenceSetID) tests: - * - Test on valid reference set ID - * - Test on invalid reference set ID - * getReferenceSetOrganization(int referenceSetID) tests: - * - Test on valid reference set ID - * - Test on invalid reference set ID + * referenceSetIsValid(int referenceSetID, String referenceSetName, String version) tests: + * - Test on existing reference set + * - Test on invalid reference set + * - Test with null name + * - Test with null version + * referenceSetExists(String referenceSetName, String version) tests: + * - Test on existing reference set + * - Test on invalid reference set + * - Test with null name + * - Test with null version + * getReferenceSetByID(int globalSetID) tests: + * - Test with valid ID + * - Test with invalid ID + * getAllReferenceSets(CorrelationAttribute.Type correlationType) tests: + * - Test getting all file sets + * - Test getting all email sets + * - Test with null type parameter + * deleteReferenceSet(int referenceSetID) tests: + * - Test on valid reference set ID + * - Test on invalid reference set ID + * getReferenceSetOrganization(int referenceSetID) tests: + * - Test on valid reference set ID + * - Test on invalid reference set ID */ - public void atestReferenceSets() { + public void testReferenceSets() { String set1name = "referenceSet1"; String set1version = "1.0"; EamGlobalSet set1; @@ -1533,202 +1875,201 @@ public class CentralRepoDatamodelTest extends TestCase { int set2id; EamGlobalSet set3; int set3id; - - + // Test creating a notable reference set try { set1 = new EamGlobalSet(org1.getOrgID(), set1name, set1version, TskData.FileKnown.BAD, false, fileType); set1id = EamDb.getInstance().newReferenceSet(set1); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test creating a known reference set try { set2 = new EamGlobalSet(org2.getOrgID(), set2name, "", TskData.FileKnown.KNOWN, false, fileType); set2id = EamDb.getInstance().newReferenceSet(set2); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test creating a reference set with the same name and version try { EamGlobalSet temp = new EamGlobalSet(org1.getOrgID(), set1name, "1.0", TskData.FileKnown.BAD, false, fileType); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from duplicate name/version pair"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a reference set with the same name but different version try { set3 = new EamGlobalSet(org1.getOrgID(), set1name, "2.0", TskData.FileKnown.BAD, false, fileType); set3id = EamDb.getInstance().newReferenceSet(set3); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); return; } - + // Test creating a reference set with invalid org ID try { EamGlobalSet temp = new EamGlobalSet(5000, "tempName", "", TskData.FileKnown.BAD, false, fileType); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from invalid org ID"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a reference set with null name try { EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), null, "", TskData.FileKnown.BAD, false, fileType); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from null name"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a reference set with null version try { EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", null, TskData.FileKnown.BAD, false, fileType); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from null version"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a reference set with null file known status try { EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", "", null, false, fileType); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from null file known status"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a reference set with null file type try { EamGlobalSet temp = new EamGlobalSet(org2.getOrgID(), "tempName", "", TskData.FileKnown.BAD, false, null); EamDb.getInstance().newReferenceSet(temp); Assert.fail("newReferenceSet failed to throw exception from null file type"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior } - + // Test validation with a valid reference set try { assertTrue("referenceSetIsValid returned false for valid reference set", EamDb.getInstance().referenceSetIsValid(set1id, set1name, set1version)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test validation with an invalid reference set try { assertFalse("referenceSetIsValid returned true for invalid reference set", EamDb.getInstance().referenceSetIsValid(5000, set1name, set1version)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test validation with a null name try { assertFalse("referenceSetIsValid returned true with null name", EamDb.getInstance().referenceSetIsValid(set1id, null, set1version)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test validation with a null version try { assertFalse("referenceSetIsValid returned true with null version", EamDb.getInstance().referenceSetIsValid(set1id, set1name, null)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test existence with a valid reference set try { assertTrue("referenceSetExists returned false for valid reference set", EamDb.getInstance().referenceSetExists(set1name, set1version)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test existence with an invalid reference set try { assertFalse("referenceSetExists returned true for invalid reference set", EamDb.getInstance().referenceSetExists(set1name, "5.5")); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test existence with null name try { assertFalse("referenceSetExists returned true for null name", EamDb.getInstance().referenceSetExists(null, "1.0")); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test existence with null version try { assertFalse("referenceSetExists returned true for null version", EamDb.getInstance().referenceSetExists(set1name, null)); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting global set with valid ID try { EamGlobalSet temp = EamDb.getInstance().getReferenceSetByID(set1id); assertTrue("getReferenceSetByID returned null for valid ID", temp != null); - assertTrue("getReferenceSetByID returned set with incorrect name and/or version", + assertTrue("getReferenceSetByID returned set with incorrect name and/or version", set1name.equals(temp.getSetName()) && set1version.equals(temp.getVersion())); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting global set with invalid ID try { EamGlobalSet temp = EamDb.getInstance().getReferenceSetByID(1234); assertTrue("getReferenceSetByID returned non-null result for invalid ID", temp == null); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting all file reference sets try { List referenceSets = EamDb.getInstance().getAllReferenceSets(fileType); assertTrue("getAllReferenceSets(FILES) returned unexpected number", referenceSets.size() == 3); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test getting all email reference sets try { List referenceSets = EamDb.getInstance().getAllReferenceSets(EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.EMAIL_TYPE_ID)); assertTrue("getAllReferenceSets(EMAIL) returned unexpected number", referenceSets.isEmpty()); - }catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } - + } + // Test null argument to getAllReferenceSets try { EamDb.getInstance().getAllReferenceSets(null); Assert.fail("getAllReferenceSets failed to throw exception from null type argument"); - }catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior - } - + } + // Test deleting an existing reference set // First: create a new reference set, check that it's in the database, and get the number of reference sets // Second: delete the reference set, check that it is no longer in the database, and the total number of sets decreased by one @@ -1737,66 +2078,66 @@ public class CentralRepoDatamodelTest extends TestCase { int setToDeleteID = EamDb.getInstance().newReferenceSet(setToDelete); assertTrue("setToDelete wasn't found in database", EamDb.getInstance().referenceSetIsValid(setToDeleteID, setToDelete.getSetName(), setToDelete.getVersion())); int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); - - EamDb.getInstance().deleteReferenceSet(setToDeleteID); + + EamDb.getInstance().deleteReferenceSet(setToDeleteID); assertFalse("Deleted reference set was found in database", EamDb.getInstance().referenceSetIsValid(setToDeleteID, setToDelete.getSetName(), setToDelete.getVersion())); assertTrue("Unexpected number of reference sets in database after deletion", currentCount - 1 == EamDb.getInstance().getAllReferenceSets(fileType).size()); - - } catch (EamDbException ex){ + + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } - + } + // Test deleting a non-existent reference set // The expectation is that nothing will happen try { - int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); - EamDb.getInstance().deleteReferenceSet(1234); - assertTrue("Number of reference sets changed after deleting non-existent set", currentCount == EamDb.getInstance().getAllReferenceSets(fileType).size()); - } catch (EamDbException ex){ + int currentCount = EamDb.getInstance().getAllReferenceSets(fileType).size(); + EamDb.getInstance().deleteReferenceSet(1234); + assertTrue("Number of reference sets changed after deleting non-existent set", currentCount == EamDb.getInstance().getAllReferenceSets(fileType).size()); + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } - + } + // Test getting reference set organization for valid ID with org set try { EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(set1id); assertTrue("getReferenceSetOrganization returned null for valid set", org != null); assertTrue("getReferenceSetOrganization returned the incorrect organization", org.getOrgID() == org1.getOrgID()); - } catch (EamDbException ex){ + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - } - + } + // Test getting reference set organization for non-existent reference set try { EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(4567); Assert.fail("getReferenceSetOrganization failed to throw exception for invalid reference set ID"); - } catch (EamDbException ex){ + } catch (EamDbException ex) { // This is the expected behavior - } + } } - + /** * Test method for the methods related to the data sources table - * newDataSource(CorrelationDataSource eamDataSource) tests: + * newDataSource(CorrelationDataSource eamDataSource) tests: + * - Test with valid data + * - Test with duplicate data + * - Test with duplicate device ID and name but different case + * - Test with invalid case ID + * - Test with null device ID + * - Test with null name + * getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) tests: * - Test with valid data - * - Test with duplicate data - * - Test with duplicate device ID and name but different case - * - Test with invalid case ID + * - Test with non-existent data + * - Test with null correlationCase * - Test with null device ID - * - Test with null name - * getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) tests: - * - Test with valid data - * - Test with non-existent data - * - Test with null correlationCase - * - Test with null device ID - * List getDataSources()tests: - * - Test that the count and device IDs are as expected - * Long getCountUniqueDataSources() tests: - * - Test that the result is as expected - */ - public void atestDataSources() { + * getDataSources()tests: + * - Test that the count and device IDs are as expected + * getCountUniqueDataSources() tests: + * - Test that the result is as expected + */ + public void testDataSources() { final String dataSourceAname = "dataSourceA"; final String dataSourceAid = "dataSourceA_deviceID"; CorrelationDataSource dataSourceA; @@ -1811,7 +2152,7 @@ public class CentralRepoDatamodelTest extends TestCase { Assert.fail(ex); return; } - + // Test creating a data source with the same case, name, and ID try { CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), dataSourceAid, dataSourceAname); @@ -1820,7 +2161,7 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } - + // Test creating a data source with the same name and ID but different case try { dataSourceB = new CorrelationDataSource(case1.getID(), dataSourceAid, dataSourceAname); @@ -1909,10 +2250,10 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } - + // Test the data source count try { - assertTrue("getCountUniqueDataSources returned unexpected number of data sources", + assertTrue("getCountUniqueDataSources returned unexpected number of data sources", EamDb.getInstance().getCountUniqueDataSources() == 5); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -1922,23 +2263,35 @@ public class CentralRepoDatamodelTest extends TestCase { /** * Test method for the methods related to the cases table - * newCase(CorrelationCase eamCase) tests: - Test valid data - Test null - * UUID - Test null case name - Test repeated UUID newCase(Case autopsyCase) - * tests: - Test valid data - Test null autopsyCase - * updateCase(CorrelationCase eamCase) tests: - Test with valid data, - * checking all fields - Test null eamCase getCase(Case autopsyCase) tests: - * - Test with current Autopsy case getCaseByUUID(String caseUUID) - * getCases() - Test with UUID that is in the database - Test with UUID that - * is not in the database - Test with null UUID - * bulkInsertCases(List cases) - Test on a list of cases - * larger than the bulk insert threshold. - Test on a null list + * newCase(CorrelationCase eamCase) tests: + * - Test valid data + * - Test null UUID + * - Test null case name + * - Test repeated UUID + * newCase(Case autopsyCase) tests: + * - Test valid data + * - Test null autopsyCase + * updateCase(CorrelationCase eamCase) tests: + * - Test with valid data, checking all fields + * - Test null eamCase + * getCase(Case autopsyCase) tests: + * - Test with current Autopsy case + * getCaseByUUID(String caseUUID) + * - Test with UUID that is in the database + * - Test with UUID that is not in the database + * - Test with null UUID + * getCases() tests: + * - Test getting all cases, checking the count and fields + * bulkInsertCases(List cases) + * - Test on a list of cases larger than the bulk insert threshold. + * - Test on a null list */ - public void atestCases() { + public void testCases() { final String caseAname = "caseA"; final String caseAuuid = "caseA_uuid"; CorrelationCase caseA; CorrelationCase caseB; - + try { // Set up an Autopsy case for testing try { @@ -2148,12 +2501,12 @@ public class CentralRepoDatamodelTest extends TestCase { try { Case.closeCurrentCase(); // This seems to help in allowing the Autopsy case to be deleted - try{ - Thread.sleep(2000); - } catch (Exception ex){ + try { + Thread.sleep(2000); + } catch (Exception ex) { } - } catch (CaseActionException ex){ + } catch (CaseActionException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -2162,15 +2515,21 @@ public class CentralRepoDatamodelTest extends TestCase { /** * Test method for the three methods related to the db_info table - * newDbInfo(String name, String value) tests: - Test valid data - Test null - * name - Test null value getDbInfo(String name) - Test getting value for - * existing name - Test getting value for non-existing name - Test getting - * value for null name updateDbInfo(String name, String value) - Test - * updating existing name to valid new value - Test updating existing name - * to null value - Test updating null name - Test updating non-existing name - * to new value + * newDbInfo(String name, String value) tests: + * - Test valid data + * - Test null name + * - Test null value + * getDbInfo(String name) + * - Test getting value for existing name + * - Test getting value for non-existing name + * - Test getting value for null name + * updateDbInfo(String name, String value) + * - Test updating existing name to valid new value + * - Test updating existing name to null value + * - Test updating null name + * - Test updating non-existing name to new value */ - public void atestDbInfo() { + public void testDbInfo() { final String name1 = "testName1"; final String name2 = "testName2"; final String name3 = "testName3"; From 66d3694159df5b393c54c30144b44ecf7e11fa9a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 6 Feb 2018 14:51:14 -0500 Subject: [PATCH 67/71] Some codacy fixes --- .../datamodel/CentralRepoDatamodelTest.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index b2e05d8694..29a3abee71 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -352,7 +352,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting notable instances with null type try { - List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(null, notableHashInOneCaseKnownOther); + EamDb.getInstance().getArtifactInstancesKnownBad(null, notableHashInOneCaseKnownOther); Assert.fail("getArtifactInstancesKnownBad failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -361,7 +361,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting notable instances with null value (should work fine) try { List attrs = EamDb.getInstance().getArtifactInstancesKnownBad(fileType, null); - assertTrue("getArtifactInstancesKnownBad returned " + attrs.size() + " values - expected ", attrs.size() == 0); + assertTrue("getArtifactInstancesKnownBad returned " + attrs.size() + " values - expected ", attrs.isEmpty()); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -857,7 +857,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instances with null type try { - List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(null, inAllDataSourcesHash); + EamDb.getInstance().getArtifactInstancesByTypeValue(null, inAllDataSourcesHash); Assert.fail("getArtifactInstancesByTypeValue failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -901,7 +901,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instances with null path try { - List instances = EamDb.getInstance().getArtifactInstancesByPath(fileType, null); + EamDb.getInstance().getArtifactInstancesByPath(fileType, null); Assert.fail("getArtifactInstancesByPath failed to throw exception for null path"); } catch (EamDbException ex) { // This is the expected behavior @@ -927,7 +927,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instance count with null type try { - long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesHash); + EamDb.getInstance().getCountArtifactInstancesByTypeValue(null, inAllDataSourcesHash); Assert.fail("getCountArtifactInstancesByTypeValue failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -935,7 +935,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instance count with null value try { - long count = EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, null); + EamDb.getInstance().getCountArtifactInstancesByTypeValue(fileType, null); Assert.fail("getCountArtifactInstancesByTypeValue failed to throw exception for null value"); } catch (EamDbException ex) { // This is the expected behavior @@ -984,7 +984,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting frequency with null type try { CorrelationAttribute attr = new CorrelationAttribute(null, "randomValue"); - int freq = EamDb.getInstance().getFrequencyPercentage(attr); + EamDb.getInstance().getFrequencyPercentage(attr); Assert.fail("getFrequencyPercentage failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -992,7 +992,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting frequency with null attribute try { - int freq = EamDb.getInstance().getFrequencyPercentage(null); + EamDb.getInstance().getFrequencyPercentage(null); Assert.fail("getFrequencyPercentage failed to throw exception for null attribute"); } catch (EamDbException ex) { // This is the expected behavior @@ -1054,7 +1054,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting data source count for null type try { - long count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(null, "abcdef"); + EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(null, "abcdef"); Assert.fail("getCountUniqueCaseDataSourceTuplesHavingTypeValue failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -1189,7 +1189,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting the type with a invalid ID try { - CorrelationAttribute.Type temp = EamDb.getInstance().getCorrelationTypeById(5555); + EamDb.getInstance().getCorrelationTypeById(5555); Assert.fail("getCorrelationTypeById failed to throw exception for invalid ID"); } catch (EamDbException ex) { // This is the expected behavior @@ -1356,7 +1356,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting org with invalid ID try { - EamOrganization temp = EamDb.getInstance().getOrganizationByID(12345); + EamDb.getInstance().getOrganizationByID(12345); Assert.fail("getOrganizationByID failed to throw exception for invalid ID"); } catch (EamDbException ex) { // This is the expected behavior @@ -1676,7 +1676,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting reference instances an invalid type (the email table is not yet implemented) try { - List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(emailType, inAllSetsHash); + EamDb.getInstance().getReferenceInstancesByTypeValue(emailType, inAllSetsHash); Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for invalid table"); } catch (EamDbException ex) { // This is the expected behavior @@ -1684,7 +1684,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting reference instances with null type try { - List temp = EamDb.getInstance().getReferenceInstancesByTypeValue(null, inAllSetsHash); + EamDb.getInstance().getReferenceInstancesByTypeValue(null, inAllSetsHash); Assert.fail("getReferenceInstancesByTypeValue failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior @@ -1872,9 +1872,7 @@ public class CentralRepoDatamodelTest extends TestCase { int set1id; String set2name = "referenceSet2"; EamGlobalSet set2; - int set2id; EamGlobalSet set3; - int set3id; // Test creating a notable reference set try { @@ -1889,7 +1887,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a known reference set try { set2 = new EamGlobalSet(org2.getOrgID(), set2name, "", TskData.FileKnown.KNOWN, false, fileType); - set2id = EamDb.getInstance().newReferenceSet(set2); + EamDb.getInstance().newReferenceSet(set2); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -1908,7 +1906,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a reference set with the same name but different version try { set3 = new EamGlobalSet(org1.getOrgID(), set1name, "2.0", TskData.FileKnown.BAD, false, fileType); - set3id = EamDb.getInstance().newReferenceSet(set3); + EamDb.getInstance().newReferenceSet(set3); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -2111,7 +2109,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting reference set organization for non-existent reference set try { - EamOrganization org = EamDb.getInstance().getReferenceSetOrganization(4567); + EamDb.getInstance().getReferenceSetOrganization(4567); Assert.fail("getReferenceSetOrganization failed to throw exception for invalid reference set ID"); } catch (EamDbException ex) { // This is the expected behavior @@ -2219,7 +2217,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting a data source with a null case try { - CorrelationDataSource temp = EamDb.getInstance().getDataSource(null, dataSourceAid); + EamDb.getInstance().getDataSource(null, dataSourceAid); Assert.fail("getDataSource did not throw exception from null case"); } catch (EamDbException ex) { // This is the expected behavior @@ -2316,7 +2314,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test null uuid try { CorrelationCase tempCase = new CorrelationCase(null, "nullUuidCase"); - tempCase = EamDb.getInstance().newCase(tempCase); + EamDb.getInstance().newCase(tempCase); Assert.fail("newCase did not throw expected exception from null uuid"); } catch (EamDbException ex) { // This is the expected behavior @@ -2325,7 +2323,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test null name try { CorrelationCase tempCase = new CorrelationCase("nullCaseUuid", null); - tempCase = EamDb.getInstance().newCase(tempCase); + EamDb.getInstance().newCase(tempCase); Assert.fail("newCase did not throw expected exception from null name"); } catch (EamDbException ex) { // This is the expected behavior @@ -2359,7 +2357,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test null Autopsy case try { Case nullCase = null; - CorrelationCase tempCase = EamDb.getInstance().newCase(nullCase); + EamDb.getInstance().newCase(nullCase); Assert.fail("newCase did not throw expected exception from null case"); } catch (EamDbException ex) { // This is the expected behavior From b58a7b18536930b36e4b27305e0b5933f169251a Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 7 Feb 2018 07:50:20 -0500 Subject: [PATCH 68/71] codacy fix and removed a few unnecessary return statements. --- .../datamodel/CentralRepoDatamodelTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 29a3abee71..644372feab 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -227,7 +227,6 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - return; } // Add two instances with one notable, one known @@ -253,7 +252,6 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - return; } // Add an artifact and then update its status @@ -271,7 +269,6 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); - return; } // Try to update artifact with two CorrelationAttributeInstance instances @@ -893,7 +890,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test getting instances with null type try { - List instances = EamDb.getInstance().getArtifactInstancesByPath(null, inAllDataSourcesPath); + EamDb.getInstance().getArtifactInstancesByPath(null, inAllDataSourcesPath); Assert.fail("getArtifactInstancesByPath failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior From 4cd548e456532e502172f4b7bcaa8138a16e7da6 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Wed, 7 Feb 2018 22:43:56 -0500 Subject: [PATCH 69/71] 3536 - Look at all Content in node so that artifacts are not shown --- .../corecomponents/DataContentViewerHex.java | 2 +- .../DataContentViewerString.java | 3 +- .../DataContentViewerUtility.java | 54 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java index a7a97e4b75..7a4df69e9d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java @@ -453,7 +453,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont return; } - Content content = (selectedNode).getLookup().lookup(Content.class); + Content content = DataContentViewerUtility.getDefaultContent(selectedNode); if (content == null) { resetComponent(); return; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java index 14cb2eb2c1..936ff3c46b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java @@ -452,8 +452,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC return; } - Lookup lookup = selectedNode.getLookup(); - Content content = lookup.lookup(Content.class); + Content content = DataContentViewerUtility.getDefaultContent(selectedNode); if (content != null) { this.setDataView(content, 0); return; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java new file mode 100755 index 0000000000..53491b407e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -0,0 +1,54 @@ +/* + * 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.corecomponents; + +import org.sleuthkit.datamodel.Content; +import org.openide.nodes.Node; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Utility classes for content viewers. + * In theory, this would live in the contentviewer package, + * but the initial method was needed only be viewers in + * corecomponents and therefore can stay out of public API. + */ +class DataContentViewerUtility { + /** + * Returns the first non-Blackboard Artifact from a Node. + * Needed for (at least) Hex and Strings that want to view + * all types of content (not just AbstractFile), but don't want + * to display an artifact unless that's the only thing there. + * Scenario is hash hit or interesting item hit. + * + * @param node Node passed into content viewer + * @return highest priority content or null if there is no content + */ + static Content getDefaultContent(Node node) { + Content bbContentSeen = null; + for (Content content : (node).getLookup().lookupAll(Content.class)) { + if (content instanceof BlackboardArtifact) { + bbContentSeen = content; + } + else { + return content; + } + } + return bbContentSeen; + } +} From 4bcd518f4bb7f71b3e42b3499344e969a5a1b31c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 8 Feb 2018 15:04:29 -0500 Subject: [PATCH 70/71] Disalbe the run time test in nightly. --- test/script/regression.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index afe1ea4976..53f8810ff8 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -810,7 +810,7 @@ class TestConfiguration(object): if parsed_config.getElementsByTagName("singleUser_golddir"): self.singleUser_gold = parsed_config.getElementsByTagName("singleUser_golddir")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("timing"): - self.timing = parsed_config.getElementsByTagName("timing")[0].getAttribute("value").encode().decode("utf_8") + self.timing = ("True" == parsed_config.getElementsByTagName("timing")[0].getAttribute("value").encode().decode("utf_8")) if parsed_config.getElementsByTagName("autopsyPlatform"): self.autopsyPlatform = parsed_config.getElementsByTagName("autopsyPlatform")[0].getAttribute("value").encode().decode("utf_8") # Multi-user settings @@ -1392,7 +1392,7 @@ class Logs(object): try: Logs._fill_ingest_data(test_data) except Exception as e: - Errors.print_error("Error: Unknown fatal error when filling test_config data.") + Errors.print_error("Error when filling test_config data.") Errors.print_error(str(e) + "\n") logging.critical(traceback.format_exc()) # If running in verbose mode (-v) @@ -1469,7 +1469,6 @@ class Logs(object): test_data.heap_space = search_logs("Heap memory usage:", test_data)[0].rstrip().split(": ")[1] ingest_line = search_logs("Ingest (including enqueue)", test_data)[0] test_data.total_ingest_time = get_word_at(ingest_line, 6).rstrip() - message_line_count = find_msg_in_log_set("Ingest messages count:", test_data) test_data.indexed_files = message_line_count From 5cf44c5b5523f99e6ed5f54f1545596b6a74d3f8 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 9 Feb 2018 11:04:44 -0500 Subject: [PATCH 71/71] Fix codacy warnings --- .../datamodel/CentralRepoDatamodelTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 644372feab..cce1e807e1 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -792,7 +792,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test adding instance with null path // This will fail in the CorrelationAttributeInstance constructor try { - CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, dataSource1fromCase1, null); + new CorrelationAttributeInstance(case1, dataSource1fromCase1, null); Assert.fail("CorrelationAttributeInstance failed to throw exception for null path"); } catch (EamDbException ex) { // This is the expected behavior @@ -821,7 +821,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test null value // This will fail in the CorrelationAttribute constructor try { - CorrelationAttribute attr = new CorrelationAttribute(fileType, null); + new CorrelationAttribute(fileType, null); Assert.fail("addArtifact failed to throw exception for null value"); } catch (EamDbException ex) { // This is the expected behavior @@ -1127,7 +1127,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test new type with null db name // The constructor should fail in this case try { - CorrelationAttribute.Type temp = new CorrelationAttribute.Type("temp", null, false, false); + new CorrelationAttribute.Type("temp", null, false, false); Assert.fail("CorrelationAttribute.Type failed to throw exception for null db table name"); } catch (EamDbException ex) { // This is the expected behavior @@ -1579,7 +1579,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Since it isn't possible to get a null hash into the EamGlobalFileInstance, skip trying to // call addReferenceInstance and just test the EamGlobalFileInstance constructor try { - EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, null, TskData.FileKnown.BAD, "comment"); + new EamGlobalFileInstance(notableSet1id, null, TskData.FileKnown.BAD, "comment"); Assert.fail("EamGlobalFileInstance failed to throw exception for null hash"); } catch (EamDbException ex) { // This is the expected behavior @@ -1589,7 +1589,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Since it isn't possible to get a null known status into the EamGlobalFileInstance, skip trying to // call addReferenceInstance and just test the EamGlobalFileInstance constructor try { - EamGlobalFileInstance temp = new EamGlobalFileInstance(notableSet1id, inAllSetsHash, null, "comment"); + new EamGlobalFileInstance(notableSet1id, inAllSetsHash, null, "comment"); Assert.fail("EamGlobalFileInstance failed to throw exception for null type"); } catch (EamDbException ex) { // This is the expected behavior