diff --git a/Core/build.xml b/Core/build.xml index f38f7732b2..d246edaa54 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -86,7 +86,7 @@ - + @@ -97,6 +97,12 @@ + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index ef7e853d68..cf0c27fb27 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -24,10 +24,8 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.logging.Level; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -57,7 +55,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private final List tagNamesList = new ArrayList<>(); private final List standardTagNamesList = new ArrayList<>(); private TagNameAndComment tagNameAndComment = null; - + public static class TagNameAndComment { private final TagName tagName; @@ -105,7 +103,16 @@ public class GetTagNameAndCommentDialog extends JDialog { public static TagNameAndComment doDialog(Window owner) { GetTagNameAndCommentDialog dialog = new GetTagNameAndCommentDialog(owner); dialog.display(); - return dialog.tagNameAndComment; + return dialog.getTagNameAndComment(); + } + + /** + * Get the TagNameAndComment. + * + * @return the tagNameAndComment + */ + private TagNameAndComment getTagNameAndComment() { + return tagNameAndComment; } private GetTagNameAndCommentDialog(Window owner) { @@ -114,14 +121,14 @@ public class GetTagNameAndCommentDialog extends JDialog { ModalityType.APPLICATION_MODAL); } - private void display() { initComponents(); tagCombo.setRenderer(new DefaultListCellRenderer() { private static final long serialVersionUID = 1L; + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - String status = ((TagName) value).getKnownStatus() == TskData.FileKnown.BAD ?TagsManager.getNotableTagLabel() : ""; + String status = ((TagName) value).getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; String newValue = ((TagName) value).getDisplayName() + status; return super.getListCellRendererComponent(list, newValue, index, isSelected, cellHasFocus); } @@ -151,7 +158,7 @@ public class GetTagNameAndCommentDialog extends JDialog { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); List standardTagNames = TagsManager.getStandardTagNames(); Map tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); - + tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> { if (standardTagNames.contains(tagName.getDisplayName())) { standardTagNamesList.add(tagName); @@ -159,7 +166,6 @@ public class GetTagNameAndCommentDialog extends JDialog { tagNamesList.add(tagName); } }); - } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class @@ -320,4 +326,5 @@ public class GetTagNameAndCommentDialog extends JDialog { private javax.swing.JComboBox tagCombo; private javax.swing.JLabel tagLabel; // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 9494b3288a..4154a913b9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -146,6 +146,8 @@ UpdateRecentCases.menuItem.clearRecentCases.text=Clear Recent Cases UpdateRecentCases.menuItem.empty=-Empty- AddImageWizardIngestConfigPanel.CANCEL_BUTTON.text=Cancel NewCaseVisualPanel1.CaseFolderOnCDriveError.text=Warning: Path to multi-user case folder is on \"C:\" drive +NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text=Warning: Path to case folder is on \"C:\" drive. Case folder is created on the target system +NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text=Warning: Path to case folder is on the target system. Create case folder in mounted drive. CollaborationMonitor.addingDataSourceStatus.msg={0} adding data source CollaborationMonitor.analyzingDataSourceStatus.msg={0} analyzing {1} MissingImageDialog.lbWarning.text= diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 1d2b1ce1e2..a095341c37 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -319,7 +319,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { // Display warning if there is one (but don't disable "next" button) try { - if (false == PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { + if (false == PathValidator.isValidForMultiUserCase(path, Case.getCurrentCaseThrows().getCaseType())) { pathErrorLabel.setVisible(true); pathErrorLabel.setText(Bundle.ImageFilePanel_pathValidation_dataSourceOnCDriveError()); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index ef985db2dd..6703b16dc5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -290,7 +290,7 @@ final class LocalFilesPanel extends javax.swing.JPanel { final Case.CaseType currentCaseType = Case.getCurrentCaseThrows().getCaseType(); for (String currentPath : pathsList) { - if (!PathValidator.isValid(currentPath, currentCaseType)) { + if (!PathValidator.isValidForMultiUserCase(currentPath, currentCaseType)) { errorLabel.setVisible(true); errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_dataSourceOnCDriveError()); return; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java index 5e11d5dfa0..103fd8fd6c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java @@ -191,7 +191,7 @@ final class LogicalEvidenceFilePanel extends javax.swing.JPanel implements Docum } // display warning if there is one (but don't disable "next" button) try { - if (!PathValidator.isValid(path, Case.getCurrentCaseThrows().getCaseType())) { + if (!PathValidator.isValidForMultiUserCase(path, Case.getCurrentCaseThrows().getCaseType())) { errorLabel.setVisible(true); errorLabel.setText(Bundle.LogicalEvidenceFilePanel_pathValidation_dataSourceOnCDriveError()); return false; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 6f365af86e..8b551ce4ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -29,6 +29,7 @@ import javax.swing.event.DocumentListener; import org.sleuthkit.autopsy.casemodule.Case.CaseType; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.PathValidator; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * The JPanel for the first page of the new case wizard. @@ -151,10 +152,23 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { */ caseParentDirWarningLabel.setVisible(false); String parentDir = getCaseParentDir(); - if (!PathValidator.isValid(parentDir, getCaseType())) { + if (!PathValidator.isValidForMultiUserCase(parentDir, getCaseType())) { caseParentDirWarningLabel.setVisible(true); caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnCDriveError.text")); } + + /** + * Check the base case directory if it can persist data and show a + * warning if it is a wrong choice + */ + if(!PathValidator.isValidForRunningOnTarget(parentDir)){ + caseParentDirWarningLabel.setVisible(true); + if(PlatformUtil.isWindowsOS()){ + caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnInternalDriveWindowsError.text" )); + } else if(System.getProperty("os.name").toLowerCase().contains("nux")) { + caseParentDirWarningLabel.setText(NbBundle.getMessage(this.getClass(), "NewCaseVisualPanel1.CaseFolderOnInternalDriveLinuxError.text")); + } + } /** * Enable the "Next" button for the wizard if there is text entered for diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 46727fc570..95dfad8c5d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -859,13 +859,14 @@ public class SingleUserCaseConverter { if (value > biggestPK) { biggestPK = value; } - outputStatement.executeUpdate("INSERT INTO content_tags (tag_id, obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset) VALUES (" //NON-NLS + outputStatement.executeUpdate("INSERT INTO content_tags (tag_id, obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset, user_name) VALUES (" //NON-NLS + value + "," + inputResultSet.getLong(2) + "," + inputResultSet.getLong(3) + ",'" + inputResultSet.getString(4) + "'," + inputResultSet.getLong(5) + "," - + inputResultSet.getLong(6) + ")"); //NON-NLS + + inputResultSet.getLong(6) + ",'" + + inputResultSet.getString(7)+ "')"); //NON-NLS } catch (SQLException ex) { if (ex.getErrorCode() != 0) { // 0 if the entry already exists @@ -892,7 +893,8 @@ public class SingleUserCaseConverter { + value + "," + inputResultSet.getLong(2) + "," + inputResultSet.getLong(3) + ",'" - + inputResultSet.getString(4) + "')"); //NON-NLS + + inputResultSet.getString(4) + "','" + + inputResultSet.getString(5) + "')"); //NON-NLS } catch (SQLException ex) { if (ex.getErrorCode() != 0) { // 0 if the entry already exists diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java index 58b4f41d6e..6e954ce725 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; import org.openide.util.Lookup; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -38,7 +37,7 @@ import org.sleuthkit.datamodel.TskDataException; public final class Blackboard implements Closeable { private SleuthkitCase caseDb; - + /** * Constructs a representation of the blackboard, a place where artifacts * and their attributes are posted. @@ -80,8 +79,8 @@ public final class Blackboard implements Closeable { * * @return A type object representing the artifact type. * - * @throws BlackboardBlackboardException If there is a problem getting or - * adding the artifact type. + * @throws BlackboardException If there is a problem getting or adding the + * artifact type. */ public synchronized BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName) throws BlackboardException { if (null == caseDb) { @@ -110,8 +109,8 @@ public final class Blackboard implements Closeable { * * @return A type object representing the attribute type. * - * @throws BlackboardBlackboardException If there is a problem getting or - * adding the attribute type. + * @throws BlackboardException If there is a problem getting or adding the + * attribute type. */ public synchronized BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException { if (null == caseDb) { @@ -140,7 +139,6 @@ public final class Blackboard implements Closeable { caseDb = null; } - /** * A blackboard exception. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index d80feed87a..896298c2bd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -47,7 +47,6 @@ import org.sleuthkit.datamodel.TskData; public class TagsManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); - private final SleuthkitCase caseDb; /** @@ -71,13 +70,14 @@ public class TagsManager implements Closeable { || tagDisplayName.contains(";")); } + @NbBundle.Messages({"TagsManager.notableTagEnding.text= (Notable)"}) /** - * Get String of text which is used to label tags as notable to the user. - * + * Get String of text which is used to label tags as notable to the user. + * * @return Bundle message TagsManager.notableTagEnding.text */ - public static String getNotableTagLabel(){ + public static String getNotableTagLabel() { return Bundle.TagsManager_notableTagEnding_text(); } @@ -123,13 +123,13 @@ public class TagsManager implements Closeable { /** * Returns a list of names of standard/predefined tags - * + * * @return list of predefined tag names */ public static List getStandardTagNames() { return TagNameDefinition.getStandardTagNames(); } - + /** * Constructs a per case Autopsy service that manages the addition of * content and artifact tags to the case database. @@ -166,21 +166,79 @@ public class TagsManager implements Closeable { return caseDb.getTagNamesInUse(); } + /** + * Gets a list of all tag names currently in use in the case database for + * tagging content or artifacts by the specified user. + * + * @param userName - the user name that you want to get tags for + * + * @return A list, possibly empty, of TagName objects. + * + * @throws TskCoreException If there is an error querying the case database. + */ + public List getTagNamesInUseForUser(String userName) throws TskCoreException { + Set tagNameSet = new HashSet<>(); + List artifactTags = caseDb.getAllBlackboardArtifactTags(); + for (BlackboardArtifactTag tag : artifactTags) { + if (tag.getUserName().equals(userName)) { + tagNameSet.add(tag.getName()); + } + } + List contentTags = caseDb.getAllContentTags(); + for (ContentTag tag : contentTags) { + if (tag.getUserName().equals(userName)) { + tagNameSet.add(tag.getName()); + } + } + return new ArrayList<>(tagNameSet); + } + /** * Selects all of the rows from the tag_names table in the case database for * which there is at least one matching row in the content_tags or * blackboard_artifact_tags tables, for the given data source object id. * * @param dsObjId data source object id - * + * * @return A list, possibly empty, of TagName data transfer objects (DTOs) - * for the rows. + * for the rows. * * @throws TskCoreException */ public List getTagNamesInUse(long dsObjId) throws TskCoreException { return caseDb.getTagNamesInUse(dsObjId); } + + /** + * Selects all of the rows from the tag_names table in the case database for + * which there is at least one matching row in the content_tags or + * blackboard_artifact_tags tables, for the given data source object id and user. + * + * @param dsObjId data source object id + * @param userName - the user name that you want to get tags for + * + * @return A list, possibly empty, of TagName data transfer objects (DTOs) + * for the rows. + * + * @throws TskCoreException + */ + public List getTagNamesInUseForUser(long dsObjId, String userName) throws TskCoreException { + Set tagNameSet = new HashSet<>(); + List artifactTags = caseDb.getAllBlackboardArtifactTags(); + for (BlackboardArtifactTag tag : artifactTags) { + if (tag.getUserName().equals(userName) && tag.getArtifact().getDataSource().getId() == dsObjId) { + tagNameSet.add(tag.getName()); + } + } + List contentTags = caseDb.getAllContentTags(); + for (ContentTag tag : contentTags) { + if (tag.getUserName().equals(userName) && tag.getContent().getDataSource().getId() == dsObjId) { + tagNameSet.add(tag.getName()); + } + } + return new ArrayList<>(tagNameSet); + } + /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current @@ -416,24 +474,77 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsCountByTagName(tagName); } + /** + * Gets content tags count by tag name for the specified user. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param userName - the user name that you want to get tags for + * + * @return A count of the content tags with the specified tag name for the + * specified user. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagNameForUser(TagName tagName, String userName) throws TskCoreException { + long count = 0; + List contentTags = getContentTagsByTagName(tagName); + for (ContentTag tag : contentTags) { + if (userName.equals(tag.getUserName())) { + count++; + } + } + return count; + } + /** * Gets content tags count by tag name, for the given data source * * @param tagName The representation of the desired tag type in the case - * database, which can be obtained by calling getTagNames and/or addTagName. - * + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * * @param dsObjId data source object id * * @return A count of the content tags with the specified tag name, and for - * the given data source + * the given data source * * @throws TskCoreException If there is an error getting the tags count from - * the case database. + * the case database. */ public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { return caseDb.getContentTagsCountByTagName(tagName, dsObjId); } - + + /** + * Gets content tags count by tag name, for the given data source and user + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * + * @param dsObjId data source object id + * @param userName - the user name that you want to get tags for + * + * @return A count of the content tags with the specified tag name, and for + * the given data source and user + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagNameForUser(TagName tagName, long dsObjId, String userName) throws TskCoreException { + long count = 0; + List contentTags = getContentTagsByTagName(tagName, dsObjId); + for (ContentTag tag : contentTags) { + if (userName.equals(tag.getUserName())) { + count++; + } + } + return count; + } + /** * Gets a content tag by tag id. * @@ -463,11 +574,11 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsByTagName(tagName); } - /** + /** * Gets content tags by tag name, for the given data source. * * @param tagName The tag name of interest. - * + * * @param dsObjId data source object id * * @return A list, possibly empty, of the content tags with the specified @@ -479,7 +590,7 @@ public class TagsManager implements Closeable { public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { return caseDb.getContentTagsByTagName(tagName, dsObjId); } - + /** * Gets content tags count by content. * @@ -581,6 +692,31 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } + /** + * Gets an artifact tags count by tag name for a specific user. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param userName - the user name that you want to get tags for + * + * @return A count of the artifact tags with the specified tag name for the + * specified user. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getBlackboardArtifactTagsCountByTagNameForUser(TagName tagName, String userName) throws TskCoreException { + long count = 0; + List artifactTags = getBlackboardArtifactTagsByTagName(tagName); + for (BlackboardArtifactTag tag : artifactTags) { + if (userName.equals(tag.getUserName())) { + count++; + } + } + return count; + } + /** * Gets an artifact tags count by tag name, for the given data source. * @@ -589,8 +725,8 @@ public class TagsManager implements Closeable { * and/or addTagName. * @param dsObjId data source object id * - * @return A count of the artifact tags with the specified tag name, - * for the given data source. + * @return A count of the artifact tags with the specified tag name, for the + * given data source. * * @throws TskCoreException If there is an error getting the tags count from * the case database. @@ -598,7 +734,34 @@ public class TagsManager implements Closeable { public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName, dsObjId); } - + + /** + * Gets an artifact tags count by tag name, for the given data source and + * user. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId data source object id + * @param userName - the user name that you want to get tags for + * + * @return A count of the artifact tags with the specified tag name, for the + * given data source and user. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getBlackboardArtifactTagsCountByTagNameForUser(TagName tagName, long dsObjId, String userName) throws TskCoreException { + long count = 0; + List artifactTags = getBlackboardArtifactTagsByTagName(tagName, dsObjId); + for (BlackboardArtifactTag tag : artifactTags) { + if (userName.equals(tag.getUserName())) { + count++; + } + } + return count; + } + /** * Gets an artifact tag by tag id. * @@ -647,7 +810,7 @@ public class TagsManager implements Closeable { public List getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { return caseDb.getBlackboardArtifactTagsByTagName(tagName, dsObjId); } - + /** * Gets artifact tags for a particular artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties index 3223583037..1c7e6c2d7e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties @@ -5,8 +5,6 @@ OpenIDE-Module-Long-Description=\ Correlation Engine ingest module and central database. \n\n\ The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\ Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest. -CentralRepoCommentDialog.fileLabel.text=File: CentralRepoCommentDialog.commentLabel.text=Comment: -CentralRepoCommentDialog.pathLabel.text= CentralRepoCommentDialog.okButton.text=&OK CentralRepoCommentDialog.cancelButton.text=C&ancel diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form index 5a9882d4cf..8f471230d0 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form @@ -31,14 +31,7 @@ - - - - - - - - + @@ -55,21 +48,16 @@ - - - - - - + - + - - - - + + + + - + @@ -113,20 +101,6 @@ - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java index 529ffb8529..5325c0fc2f 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -52,7 +52,6 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { currentComment = instance.getComment(); } - pathLabel.setText(instance.getFilePath()); commentTextArea.setText(instance.getComment()); this.correlationAttribute = correlationAttribute; @@ -103,8 +102,6 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { commentTextArea = new javax.swing.JTextArea(); okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); - fileLabel = new javax.swing.JLabel(); - pathLabel = new javax.swing.JLabel(); commentLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -131,10 +128,6 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { } }); - org.openide.awt.Mnemonics.setLocalizedText(fileLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.fileLabel.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.pathLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.commentLabel.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); @@ -146,12 +139,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(fileLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pathLabel)) - .addComponent(commentLabel)) + .addComponent(commentLabel) .addGap(0, 451, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) @@ -164,17 +152,13 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(fileLabel) - .addComponent(pathLabel)) - .addGap(19, 19, 19) .addComponent(commentLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(okButton) - .addComponent(cancelButton)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(okButton)) .addContainerGap()) ); @@ -197,9 +181,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { private javax.swing.JButton cancelButton; private javax.swing.JLabel commentLabel; private javax.swing.JTextArea commentTextArea; - private javax.swing.JLabel fileLabel; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JButton okButton; - private javax.swing.JLabel pathLabel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 60667bae46..9c42be16a8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - + @@ -106,7 +106,7 @@ - + @@ -133,23 +133,18 @@ - - - + - - - - - - - - + + + + + @@ -230,13 +225,6 @@ - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 26ceed5500..8a1ce6864e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.centralrepository.contentviewer; import java.awt.Component; +import java.awt.FontMetrics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedWriter; @@ -85,7 +86,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private static final long serialVersionUID = -1L; - private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + private static final Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + + private static final int DEFAULT_MIN_CELL_WIDTH = 15; + private static final int CELL_TEXT_WIDTH_PADDING = 5; private final DataContentViewerOtherCasesTableModel tableModel; private final Collection correlationAttributes; @@ -125,7 +129,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi showCommonalityDetails(); } else if (jmi.equals(addCommentMenuItem)) { try { - OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); + OtherOccurrenceNodeInstanceData selectedNode = (OtherOccurrenceNodeInstanceData) tableModel.getRow(otherCasesTable.getSelectedRow()); AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(selectedNode.createCorrelationAttribute()); action.actionPerformed(null); String currentComment = action.getComment(); @@ -149,7 +153,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // Set background of every nth row as light grey. TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer(); otherCasesTable.setDefaultRenderer(Object.class, renderer); - tableStatusPanelLabel.setVisible(false); } @@ -207,7 +210,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (-1 != selectedRowViewIdx) { EamDb dbManager = EamDb.getInstance(); int selectedRowModelIdx = otherCasesTable.convertRowIndexToModel(selectedRowViewIdx); - OtherOccurrenceNodeData nodeData = (OtherOccurrenceNodeData) tableModel.getRow(selectedRowModelIdx); + OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) tableModel.getRow(selectedRowModelIdx); CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); if (eamCasePartial == null) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, @@ -462,12 +465,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Not Enabled."}) /** - * Gets the list of Eam Cases and determines the earliest case creation date. - * Sets the label to display the earliest date string to the user. + * Gets the list of Eam Cases and determines the earliest case creation + * date. Sets the label to display the earliest date string to the user. */ - private void setEarliestCaseDate() { - String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); - + private void setEarliestCaseDate() { + String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); + if (EamDb.isEnabled()) { LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC); DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); @@ -475,15 +478,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi EamDb dbManager = EamDb.getInstance(); List cases = dbManager.getCases(); for (CorrelationCase aCase : cases) { - LocalDateTime caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); - - if (caseDate.isBefore(earliestDate)) { + LocalDateTime caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); + + if (caseDate.isBefore(earliestDate)) { earliestDate = caseDate; dateStringDisplay = aCase.getCreationDate(); - } + } } - + } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS } catch (ParseException ex) { @@ -495,10 +498,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Query the central repo database (if enabled) and the case database to find all - * artifact instances correlated to the given central repository artifact. If the - * central repo is not enabled, this will only return files from the current case - * with matching MD5 hashes. + * Query the central repo database (if enabled) and the case database to + * find all artifact instances correlated to the given central repository + * artifact. If the central repo is not enabled, this will only return files + * from the current case with matching MD5 hashes. * * @param corAttr CorrelationAttribute to query for * @param dataSourceName Data source to filter results @@ -506,19 +509,19 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * * @return A collection of correlated artifact instances */ - private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { + private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { final Case openCase = Case.getCurrentCase(); String caseUUID = openCase.getName(); - HashMap nodeDataMap = new HashMap<>(); + HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - for (CorrelationAttributeInstance artifactInstance:instances) { - + for (CorrelationAttributeInstance artifactInstance : instances) { + // Only add the attribute if it isn't the object the user selected. // We consider it to be a different object if at least one of the following is true: // - the case UUID is different @@ -530,14 +533,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId) || !artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName())) { - OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); UniquePathKey uniquePathKey = new UniquePathKey(newNode); nodeDataMap.put(uniquePathKey, newNode); } } } - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { + if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { List caseDbFiles = getCaseDbMatches(corAttr, openCase); for (AbstractFile caseDbFile : caseDbFiles) { @@ -560,13 +563,17 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Get all other abstract files in the current case with the same MD5 as the selected node. + * Get all other abstract files in the current case with the same MD5 as the + * selected node. + * * @param corAttr The CorrelationAttribute containing the MD5 to search for * @param openCase The current case + * * @return List of matching AbstractFile objects + * * @throws NoCurrentCaseException * @throws TskCoreException - * @throws EamDbException + * @throws EamDbException */ private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); @@ -586,18 +593,18 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi /** * Adds the file to the nodeDataMap map if it does not already exist - * - * @param autopsyCase + * + * @param autopsyCase * @param nodeDataMap * @param newFile * * @throws TskCoreException * @throws EamDbException */ - private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { - - OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(newFile, autopsyCase); - + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { + + OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(newFile, autopsyCase); + // If the caseDB object has a notable tag associated with it, update // the known status to BAD if (newNode.getKnown() != TskData.FileKnown.BAD) { @@ -613,13 +620,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // Make a key to see if the file is already in the map UniquePathKey uniquePathKey = new UniquePathKey(newNode); - + // If this node is already in the list, the only thing we need to do is // update the known status to BAD if the caseDB version had known status BAD. // Otherwise this is a new node so add the new node to the map. if (nodeDataMap.containsKey(uniquePathKey)) { if (newNode.getKnown() == TskData.FileKnown.BAD) { - OtherOccurrenceNodeData prevInstance = nodeDataMap.get(uniquePathKey); + OtherOccurrenceNodeInstanceData prevInstance = nodeDataMap.get(uniquePathKey); prevInstance.updateKnown(newNode.getKnown()); } } else { @@ -642,7 +649,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else { return this.file != null && this.file.getSize() > 0 - && ((this.file.getMd5Hash() != null) && ( ! this.file.getMd5Hash().isEmpty())); + && ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty())); } } @@ -665,8 +672,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * * @param node The node being viewed. */ - @Messages({"DataContentViewerOtherCases.table.isempty=There are no associated artifacts or files from other occurrences to display.", - "DataContentViewerOtherCases.table.noArtifacts=Correlation cannot be performed on the selected file."}) + @Messages({ + "DataContentViewerOtherCases.table.noArtifacts=Item has no attributes with which to search.", + "DataContentViewerOtherCases.table.noResultsFound=No results found." + }) private void populateTable(Node node) { String dataSourceName = ""; String deviceId = ""; @@ -684,7 +693,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get the attributes we can correlate on correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); for (CorrelationAttribute corAttr : correlationAttributes) { - Map correlatedNodeDataMap = new HashMap<>(0); + Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); @@ -696,36 +705,45 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } if (correlationAttributes.isEmpty()) { - // @@@ BC: We should have a more descriptive message than this. Mention that the file didn't have a MD5, etc. - displayMessageOnTableStatusPanel(Bundle.DataContentViewerOtherCases_table_noArtifacts()); + tableModel.addNodeData(new OtherOccurrenceNodeMessageData(Bundle.DataContentViewerOtherCases_table_noArtifacts())); + setColumnWidthToText(0, Bundle.DataContentViewerOtherCases_table_noArtifacts()); } else if (0 == tableModel.getRowCount()) { - displayMessageOnTableStatusPanel(Bundle.DataContentViewerOtherCases_table_isempty()); + tableModel.addNodeData(new OtherOccurrenceNodeMessageData(Bundle.DataContentViewerOtherCases_table_noResultsFound())); + setColumnWidthToText(0, Bundle.DataContentViewerOtherCases_table_noResultsFound()); } else { - clearMessageOnTableStatusPanel(); setColumnWidths(); } setEarliestCaseDate(); } + /** + * Adjust a given column for the text provided. + * + * @param columnIndex The index of the column to adjust. + * @param text The text whose length will be used to adjust the + * column width. + */ + private void setColumnWidthToText(int columnIndex, String text) { + TableColumn column = otherCasesTable.getColumnModel().getColumn(columnIndex); + FontMetrics fontMetrics = otherCasesTable.getFontMetrics(otherCasesTable.getFont()); + int stringWidth = fontMetrics.stringWidth(text); + column.setMinWidth(stringWidth + CELL_TEXT_WIDTH_PADDING); + } + + /** + * Adjust column widths to their preferred values. + */ private void setColumnWidths() { for (int idx = 0; idx < tableModel.getColumnCount(); idx++) { TableColumn column = otherCasesTable.getColumnModel().getColumn(idx); - int colWidth = tableModel.getColumnPreferredWidth(idx); - if (0 < colWidth) { - column.setPreferredWidth(colWidth); + column.setMinWidth(DEFAULT_MIN_CELL_WIDTH); + int columnWidth = tableModel.getColumnPreferredWidth(idx); + if (columnWidth > 0) { + column.setPreferredWidth(columnWidth); } } } - private void displayMessageOnTableStatusPanel(String message) { - tableStatusPanelLabel.setText(message); - tableStatusPanelLabel.setVisible(true); - } - - private void clearMessageOnTableStatusPanel() { - tableStatusPanelLabel.setVisible(false); - } - /** * 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 @@ -749,7 +767,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi earliestCaseLabel = new javax.swing.JLabel(); earliestCaseDate = new javax.swing.JLabel(); tableStatusPanel = new javax.swing.JPanel(); - tableStatusPanelLabel = new javax.swing.JLabel(); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { @@ -811,8 +828,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addGap(0, 16, Short.MAX_VALUE) ); - tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); - javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); tableContainerPanelLayout.setHorizontalGroup( @@ -825,20 +840,16 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addComponent(earliestCaseLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(earliestCaseDate) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addContainerGap()) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 176, Short.MAX_VALUE) - .addGap(0, 0, 0) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(earliestCaseLabel) - .addComponent(earliestCaseDate)) - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 27, Short.MAX_VALUE) + .addGap(2, 2, 2) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate)) .addGap(0, 0, 0) .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0)) @@ -857,7 +868,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addGap(0, 483, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 59, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -869,7 +880,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 59, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -879,8 +890,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { int rowIndex = otherCasesTable.getSelectedRow(); OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); - if (selectedNode.isCentralRepoNode()) { - enableCentralRepoActions = true; + if (selectedNode instanceof OtherOccurrenceNodeInstanceData) { + OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedNode; + enableCentralRepoActions = instanceData.isCentralRepoNode(); } } @@ -904,20 +916,19 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private javax.swing.JPanel tableContainerPanel; private javax.swing.JScrollPane tableScrollPane; private javax.swing.JPanel tableStatusPanel; - private javax.swing.JLabel tableStatusPanelLabel; // End of variables declaration//GEN-END:variables /** * Used as a key to ensure we eliminate duplicates from the result set by * not overwriting CR correlation instances. */ - static final class UniquePathKey { + private static final class UniquePathKey { private final String dataSourceID; private final String filePath; private final String type; - UniquePathKey(OtherOccurrenceNodeData nodeData) { + UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) { super(); dataSourceID = nodeData.getDeviceID(); if (nodeData.getFilePath() != null) { @@ -931,10 +942,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Override public boolean equals(Object other) { if (other instanceof UniquePathKey) { - UniquePathKey otherKey = (UniquePathKey)(other); - return ( Objects.equals(otherKey.dataSourceID, this.dataSourceID) - && Objects.equals(otherKey.filePath, this.filePath) - && Objects.equals(otherKey.type, this.type)); + UniquePathKey otherKey = (UniquePathKey) (other); + return (Objects.equals(otherKey.getDataSourceID(), this.getDataSourceID()) + && Objects.equals(otherKey.getFilePath(), this.getFilePath()) + && Objects.equals(otherKey.getType(), this.getType())); } return false; } @@ -944,7 +955,34 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi //int hash = 7; //hash = 67 * hash + this.dataSourceID.hashCode(); //hash = 67 * hash + this.filePath.hashCode(); - return Objects.hash(dataSourceID, filePath, type); + return Objects.hash(getDataSourceID(), getFilePath(), getType()); + } + + /** + * Get the type of this UniquePathKey. + * + * @return the type + */ + String getType() { + return type; + } + + /** + * Get the file path for the UniquePathKey. + * + * @return the filePath + */ + String getFilePath() { + return filePath; + } + + /** + * Get the data source id for the UniquePathKey. + * + * @return the dataSourceID + */ + String getDataSourceID() { + return dataSourceID; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java index 5febf88dc3..1a3527d940 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,20 +22,20 @@ import java.util.ArrayList; import java.util.List; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; /** * Model for cells in data content viewer table */ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { + private static final long serialVersionUID = 1L; + @Messages({"DataContentViewerOtherCasesTableModel.case=Case", "DataContentViewerOtherCasesTableModel.device=Device", "DataContentViewerOtherCasesTableModel.dataSource=Data Source", "DataContentViewerOtherCasesTableModel.path=Path", - "DataContentViewerOtherCasesTableModel.type=Correlation Type", - "DataContentViewerOtherCasesTableModel.value=Correlation Value", + "DataContentViewerOtherCasesTableModel.attribute=Matched Attribute", + "DataContentViewerOtherCasesTableModel.value=Attribute Value", "DataContentViewerOtherCasesTableModel.known=Known", "DataContentViewerOtherCasesTableModel.comment=Comment", "DataContentViewerOtherCasesTableModel.noData=No Data.",}) @@ -44,7 +44,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { // If order is changed, update the CellRenderer to ensure correct row coloring. CASE_NAME(Bundle.DataContentViewerOtherCasesTableModel_case(), 100), DATA_SOURCE(Bundle.DataContentViewerOtherCasesTableModel_dataSource(), 100), - TYPE(Bundle.DataContentViewerOtherCasesTableModel_type(), 100), + ATTRIBUTE(Bundle.DataContentViewerOtherCasesTableModel_attribute(), 125), VALUE(Bundle.DataContentViewerOtherCasesTableModel_value(), 200), KNOWN(Bundle.DataContentViewerOtherCasesTableModel_known(), 50), FILE_PATH(Bundle.DataContentViewerOtherCasesTableModel_path(), 450), @@ -68,7 +68,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } }; - List nodeDataList; + private final List nodeDataList; DataContentViewerOtherCasesTableModel() { nodeDataList = new ArrayList<>(); @@ -109,26 +109,41 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { return Bundle.DataContentViewerOtherCasesTableModel_noData(); } - return mapValueById(rowIdx, TableColumns.values()[colIdx]); - } - - Object getRow(int rowIdx) { - return nodeDataList.get(rowIdx); + OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); + TableColumns columnId = TableColumns.values()[colIdx]; + if (nodeData instanceof OtherOccurrenceNodeMessageData) { + return mapNodeMessageData((OtherOccurrenceNodeMessageData) nodeData, columnId); + } + return mapNodeInstanceData((OtherOccurrenceNodeInstanceData) nodeData, columnId); } /** - * Map a rowIdx and colId to the value in that cell. + * Map a column ID to the value in that cell for node message data. * - * @param rowIdx Index of row to search - * @param colId ID of column to search + * @param nodeData The node message data. + * @param columnId The ID of the cell column. * - * @return value in the cell + * @return The value in the cell. */ - private Object mapValueById(int rowIdx, TableColumns colId) { - OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); + private Object mapNodeMessageData(OtherOccurrenceNodeMessageData nodeData, TableColumns columnId) { + if (columnId == TableColumns.CASE_NAME) { + return nodeData.getDisplayMessage(); + } + return ""; + } + + /** + * Map a column ID to the value in that cell for node instance data. + * + * @param nodeData The node instance data. + * @param columnId The ID of the cell column. + * + * @return The value in the cell. + */ + private Object mapNodeInstanceData(OtherOccurrenceNodeInstanceData nodeData, TableColumns columnId) { String value = Bundle.DataContentViewerOtherCasesTableModel_noData(); - switch (colId) { + switch (columnId) { case CASE_NAME: if (null != nodeData.getCaseName()) { value = nodeData.getCaseName(); @@ -147,7 +162,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { case FILE_PATH: value = nodeData.getFilePath(); break; - case TYPE: + case ATTRIBUTE: value = nodeData.getType(); break; case VALUE: @@ -159,10 +174,16 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { case COMMENT: value = nodeData.getComment(); break; + default: // This shouldn't occur! Use default "No data" value. + break; } return value; } + Object getRow(int rowIdx) { + return nodeDataList.get(rowIdx); + } + @Override public Class getColumnClass(int colIdx) { return String.class; @@ -178,6 +199,9 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { fireTableDataChanged(); } + /** + * Clear the node data table. + */ void clearTable() { nodeDataList.clear(); fireTableDataChanged(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java old mode 100644 new mode 100755 index 958068fb14..c10f078313 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -1,5 +1,5 @@ /* - * Central Repository + * Autopsy Forensic Browser * * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit org @@ -18,216 +18,9 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; - /** - * Class for populating the Other Occurrences tab + * Marker interface for Other Occurrences nodes. */ -class OtherOccurrenceNodeData { +interface OtherOccurrenceNodeData { - // For now hard code the string for the central repo files type, since - // getting it dynamically can fail. - private static final String FILE_TYPE_STR = "Files"; - - private final String caseName; - private String deviceID; - private String dataSourceName; - private final String filePath; - private final String typeStr; - private final CorrelationAttribute.Type type; - private final String value; - private TskData.FileKnown known; - private String comment; - - private AbstractFile originalAbstractFile = null; - private CorrelationAttributeInstance originalCorrelationInstance = null; - - /** - * Create a node from a central repo instance. - * @param instance The central repo instance - * @param type The type of the instance - * @param value The value of the instance - */ - OtherOccurrenceNodeData(CorrelationAttributeInstance instance, CorrelationAttribute.Type type, String value) { - caseName = instance.getCorrelationCase().getDisplayName(); - deviceID = instance.getCorrelationDataSource().getDeviceID(); - dataSourceName = instance.getCorrelationDataSource().getName(); - filePath = instance.getFilePath(); - this.typeStr = type.getDisplayName(); - this.type = type; - this.value = value; - known = instance.getKnownStatus(); - comment = instance.getComment(); - - originalCorrelationInstance = instance; - } - - /** - * Create a node from an abstract file. - * @param newFile The abstract file - * @param autopsyCase The current case - * @throws EamDbException - */ - OtherOccurrenceNodeData(AbstractFile newFile, Case autopsyCase) throws EamDbException { - caseName = autopsyCase.getDisplayName(); - try { - DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); - deviceID = dataSource.getDeviceId(); - dataSourceName = dataSource.getName(); - } catch (TskDataException | TskCoreException ex) { - throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); - } - - filePath = newFile.getParentPath() + newFile.getName(); - typeStr = FILE_TYPE_STR; - this.type = null; - value = newFile.getMd5Hash(); - known = newFile.getKnown(); - comment = ""; - - originalAbstractFile = newFile; - } - - /** - * Check if this node is a "file" type - * @return true if it is a file type - */ - boolean isFileType() { - return FILE_TYPE_STR.equals(typeStr); - } - - /** - * Update the known status for this node - * @param newKnownStatus The new known status - */ - void updateKnown(TskData.FileKnown newKnownStatus) { - known = newKnownStatus; - } - - /** - * Update the comment for this node - * @param newComment The new comment - */ - void updateComment(String newComment) { - comment = newComment; - } - - /** - * Check if this is a central repo node. - * @return true if this node was created from a central repo instance, false otherwise - */ - boolean isCentralRepoNode() { - return (originalCorrelationInstance != null); - } - - /** - * Uses the saved instance plus type and value to make a new CorrelationAttribute. - * Should only be called if isCentralRepoNode() is true. - * @return the newly created CorrelationAttribute - */ - CorrelationAttribute createCorrelationAttribute() throws EamDbException { - if (! isCentralRepoNode() ) { - throw new EamDbException("Can not create CorrelationAttribute for non central repo node"); - } - CorrelationAttribute attr = new CorrelationAttribute(type, value); - attr.addInstance(originalCorrelationInstance); - return attr; - } - - /** - * Get the case name - * @return the case name - */ - String getCaseName() { - return caseName; - } - - /** - * Get the device ID - * @return the device ID - */ - String getDeviceID() { - return deviceID; - } - - /** - * Get the data source name - * @return the data source name - */ - String getDataSourceName() { - return dataSourceName; - } - - /** - * Get the file path - * @return the file path - */ - String getFilePath() { - return filePath; - } - - /** - * Get the type (as a string) - * @return the type - */ - String getType() { - return typeStr; - } - - /** - * Get the value (MD5 hash for files) - * @return the value - */ - String getValue() { - return value; - } - - /** - * Get the known status - * @return the known status - */ - TskData.FileKnown getKnown() { - return known; - } - - /** - * Get the comment - * @return the comment - */ - String getComment() { - return comment; - } - - /** - * Get the backing abstract file. - * Should only be called if isCentralRepoNode() is false - * @return the original abstract file - */ - AbstractFile getAbstractFile() throws EamDbException { - if (originalCorrelationInstance == null) { - throw new EamDbException("AbstractFile is null"); - } - return originalAbstractFile; - } - - /** - * Get the backing CorrelationAttributeInstance. - * Should only be called if isCentralRepoNode() is true - * @return the original CorrelationAttributeInstance - * @throws EamDbException - */ - CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { - if (originalCorrelationInstance == null) { - throw new EamDbException("CorrelationAttributeInstance is null"); - } - return originalCorrelationInstance; - } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java new file mode 100644 index 0000000000..7eb907aba8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeInstanceData.java @@ -0,0 +1,233 @@ +/* + * 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.contentviewer; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; + +/** + * Class for populating the Other Occurrences tab + */ +class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData { + + // For now hard code the string for the central repo files type, since + // getting it dynamically can fail. + private static final String FILE_TYPE_STR = "Files"; + + private final String caseName; + private String deviceID; + private String dataSourceName; + private final String filePath; + private final String typeStr; + private final CorrelationAttribute.Type type; + private final String value; + private TskData.FileKnown known; + private String comment; + + private AbstractFile originalAbstractFile = null; + private CorrelationAttributeInstance originalCorrelationInstance = null; + + /** + * Create a node from a central repo instance. + * @param instance The central repo instance + * @param type The type of the instance + * @param value The value of the instance + */ + OtherOccurrenceNodeInstanceData(CorrelationAttributeInstance instance, CorrelationAttribute.Type type, String value) { + caseName = instance.getCorrelationCase().getDisplayName(); + deviceID = instance.getCorrelationDataSource().getDeviceID(); + dataSourceName = instance.getCorrelationDataSource().getName(); + filePath = instance.getFilePath(); + this.typeStr = type.getDisplayName(); + this.type = type; + this.value = value; + known = instance.getKnownStatus(); + comment = instance.getComment(); + + originalCorrelationInstance = instance; + } + + /** + * Create a node from an abstract file. + * @param newFile The abstract file + * @param autopsyCase The current case + * @throws EamDbException + */ + OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws EamDbException { + caseName = autopsyCase.getDisplayName(); + try { + DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); + deviceID = dataSource.getDeviceId(); + dataSourceName = dataSource.getName(); + } catch (TskDataException | TskCoreException ex) { + throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); + } + + filePath = newFile.getParentPath() + newFile.getName(); + typeStr = FILE_TYPE_STR; + this.type = null; + value = newFile.getMd5Hash(); + known = newFile.getKnown(); + comment = ""; + + originalAbstractFile = newFile; + } + + /** + * Check if this node is a "file" type + * @return true if it is a file type + */ + boolean isFileType() { + return FILE_TYPE_STR.equals(typeStr); + } + + /** + * Update the known status for this node + * @param newKnownStatus The new known status + */ + void updateKnown(TskData.FileKnown newKnownStatus) { + known = newKnownStatus; + } + + /** + * Update the comment for this node + * @param newComment The new comment + */ + void updateComment(String newComment) { + comment = newComment; + } + + /** + * Check if this is a central repo node. + * @return true if this node was created from a central repo instance, false otherwise + */ + boolean isCentralRepoNode() { + return (originalCorrelationInstance != null); + } + + /** + * Uses the saved instance plus type and value to make a new CorrelationAttribute. + * Should only be called if isCentralRepoNode() is true. + * @return the newly created CorrelationAttribute + */ + CorrelationAttribute createCorrelationAttribute() throws EamDbException { + if (! isCentralRepoNode() ) { + throw new EamDbException("Can not create CorrelationAttribute for non central repo node"); + } + CorrelationAttribute attr = new CorrelationAttribute(type, value); + attr.addInstance(originalCorrelationInstance); + return attr; + } + + /** + * Get the case name + * @return the case name + */ + String getCaseName() { + return caseName; + } + + /** + * Get the device ID + * @return the device ID + */ + String getDeviceID() { + return deviceID; + } + + /** + * Get the data source name + * @return the data source name + */ + String getDataSourceName() { + return dataSourceName; + } + + /** + * Get the file path + * @return the file path + */ + String getFilePath() { + return filePath; + } + + /** + * Get the type (as a string) + * @return the type + */ + String getType() { + return typeStr; + } + + /** + * Get the value (MD5 hash for files) + * @return the value + */ + String getValue() { + return value; + } + + /** + * Get the known status + * @return the known status + */ + TskData.FileKnown getKnown() { + return known; + } + + /** + * Get the comment + * @return the comment + */ + String getComment() { + return comment; + } + + /** + * Get the backing abstract file. + * Should only be called if isCentralRepoNode() is false + * @return the original abstract file + */ + AbstractFile getAbstractFile() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("AbstractFile is null"); + } + return originalAbstractFile; + } + + /** + * Get the backing CorrelationAttributeInstance. + * Should only be called if isCentralRepoNode() is true + * @return the original CorrelationAttributeInstance + * @throws EamDbException + */ + CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("CorrelationAttributeInstance is null"); + } + return originalCorrelationInstance; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java new file mode 100755 index 0000000000..99e530349a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeMessageData.java @@ -0,0 +1,34 @@ +/* + * 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.centralrepository.contentviewer; + +/** + * Class for populating the Other Occurrences tab with a single message. + */ +final class OtherOccurrenceNodeMessageData implements OtherOccurrenceNodeData { + private final String displayMessage; + + OtherOccurrenceNodeMessageData(String displayMessage) { + this.displayMessage = displayMessage; + } + + String getDisplayMessage() { + return displayMessage; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index f535d58ccd..991821bebf 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -387,6 +387,46 @@ abstract class AbstractSqlEamDb implements EamDb { return eamCaseResult; } + /** + * Retrieves Case details based on Case ID + * + * @param caseID unique identifier for a case + * + * @return The retrieved case + */ + @Override + public CorrelationCase getCaseById(int caseId) throws EamDbException { + // @@@ We should have a cache here... + + Connection conn = connect(); + + CorrelationCase eamCaseResult = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + String sql = "SELECT cases.id as case_id, case_uid, case_name, creation_date, case_number, examiner_name, " + + "examiner_email, examiner_phone, notes, organizations.id as org_id, org_name, poc_name, poc_email, poc_phone " + + "FROM cases " + + "LEFT JOIN organizations ON cases.org_id=organizations.id " + + "WHERE cases.id=?"; + + try { + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setInt(1, caseId); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + eamCaseResult = getEamCaseFromResultSet(resultSet); + } + } catch (SQLException ex) { + throw new EamDbException("Error getting case details.", ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + + return eamCaseResult; + } /** * Retrieves cases that are in DB. @@ -502,6 +542,48 @@ abstract class AbstractSqlEamDb implements EamDb { return eamDataSourceResult; } + + /** + * Retrieves Data Source details based on data source ID + * + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource + * @param dataSourceId the data source ID number + * + * @return The data source + */ + @Override + public CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException { + if (correlationCase == null) { + throw new EamDbException("Correlation case is null"); + } + + Connection conn = connect(); + + CorrelationDataSource eamDataSourceResult = null; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + String sql = "SELECT * FROM data_sources WHERE id=? AND case_id=?"; // NON-NLS + + try { + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setInt(1, dataSourceId); + preparedStatement.setInt(2, correlationCase.getID()); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + eamDataSourceResult = getEamDataSourceFromResultSet(resultSet); + } + } catch (SQLException ex) { + throw new EamDbException("Error getting data source.", ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + + return eamDataSourceResult; + } /** * Return a list of data sources in the DB @@ -670,7 +752,7 @@ abstract class AbstractSqlEamDb implements EamDb { return artifactInstances; } - + /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath @@ -1814,6 +1896,52 @@ abstract class AbstractSqlEamDb implements EamDb { } } + /** + * Process the Artifact instance in the EamDb give a where clause + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @param whereClause query string to execute + * @throws EamDbException + */ + @Override + public void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException { + if (type == null) { + throw new EamDbException("Correlation type is null"); + } + + if (instanceTableCallback == null) { + throw new EamDbException("Callback interface is null"); + } + + if(whereClause == null) { + throw new EamDbException("Where clause is null"); + } + + Connection conn = connect(); + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); + StringBuilder sql = new StringBuilder(300); + sql.append("select * from ") + .append(tableName) + .append(" WHERE ") + .append(whereClause); + + try { + preparedStatement = conn.prepareStatement(sql.toString()); + resultSet = preparedStatement.executeQuery(); + instanceTableCallback.process(resultSet); + } catch (SQLException ex) { + throw new EamDbException("Error getting all artifact instances from instances table", ex); + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + } + + @Override public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException { if (eamOrg == null) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java index 7ced4a1d7d..e4fb30583e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java @@ -175,13 +175,21 @@ public interface EamDb { */ CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException; + /** + * Retrieves Case details based on Case ID + * + * @param caseID unique identifier for a case + * + * @return The retrieved case + */ + CorrelationCase getCaseById(int caseId) throws EamDbException; /** * Retrieves cases that are in DB. * * @return List of cases */ List getCases() throws EamDbException; - + /** * Creates new Data Source in the database * @@ -200,6 +208,18 @@ public interface EamDb { */ CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException; + + /** + * Retrieves Data Source details based on data source ID + * + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource + * @param dataSourceId the data source ID number + * + * @return The data source + */ + CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException; + /** * Retrieves data sources that are in DB * @@ -225,7 +245,7 @@ public interface EamDb { * @return List of artifact instances for a given type/value */ List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException; - + /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath @@ -685,4 +705,15 @@ public interface EamDb { * @throws EamDbException */ void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException; + + /** + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @param whereClause query string to execute + * @throws EamDbException + */ + void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException; + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index 4a3ef36530..74d0c36a66 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -33,9 +33,9 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; /** - * Sqlite implementation of the Central Repository database. - * All methods in AbstractSqlEamDb that read or write to the database should - * be overriden here and use appropriate locking. + * Sqlite implementation of the Central Repository database. All methods in + * AbstractSqlEamDb that read or write to the database should be overriden here + * and use appropriate locking. */ final class SqliteEamDb extends AbstractSqlEamDb { @@ -46,17 +46,18 @@ final class SqliteEamDb extends AbstractSqlEamDb { private BasicDataSource connectionPool = null; private final SqliteEamDbSettings dbSettings; - + // While the Sqlite database should only be used for single users, it is still // possible for multiple threads to attempt to write to the database simultaneously. private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); /** * Get the singleton instance of SqliteEamDb - * + * * @return the singleton instance of SqliteEamDb - * - * @throws EamDbException if one or more default correlation type(s) have an invalid db table name. + * + * @throws EamDbException if one or more default correlation type(s) have an + * invalid db table name. */ public synchronized static SqliteEamDb getInstance() throws EamDbException { if (instance == null) { @@ -67,9 +68,9 @@ final class SqliteEamDb extends AbstractSqlEamDb { } /** - * - * @throws EamDbException if the AbstractSqlEamDb class has one or more default - * correlation type(s) having an invalid db table name. + * + * @throws EamDbException if the AbstractSqlEamDb class has one or more + * default correlation type(s) having an invalid db table name. */ private SqliteEamDb() throws EamDbException { dbSettings = new SqliteEamDbSettings(); @@ -79,7 +80,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { @Override public void shutdownConnections() throws EamDbException { try { - synchronized(this) { + synchronized (this) { if (null != connectionPool) { connectionPool.close(); connectionPool = null; // force it to be re-created on next connect() @@ -89,7 +90,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { throw new EamDbException("Failed to close existing database connections.", ex); // NON-NLS } } - + @Override public void updateSettings() { synchronized (this) { @@ -107,9 +108,9 @@ final class SqliteEamDb extends AbstractSqlEamDb { @Override public void reset() throws EamDbException { - try{ + try { acquireExclusiveLock(); - + Connection conn = connect(); try { @@ -150,11 +151,11 @@ final class SqliteEamDb extends AbstractSqlEamDb { * */ private void setupConnectionPool() throws EamDbException { - + if (dbSettings.dbFileExists() == false) { throw new EamDbException("Central repository database missing"); } - + connectionPool = new BasicDataSource(); connectionPool.setDriverClassName(dbSettings.getDriver()); connectionPool.setUrl(dbSettings.getConnectionURL()); @@ -200,25 +201,24 @@ final class SqliteEamDb extends AbstractSqlEamDb { return ""; } - /** * Add a new name/value pair in the db_info table. * - * @param name Key to set + * @param name Key to set * @param value Value to set * * @throws EamDbException */ @Override public void newDbInfo(String name, String value) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.newDbInfo(name, value); } finally { releaseExclusiveLock(); } } - + /** * Get the value for the given name from the name/value db_info table. * @@ -230,47 +230,47 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public String getDbInfo(String name) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getDbInfo(name); } finally { releaseSharedLock(); - } + } } - + /** * Update the value for a name in the name/value db_info table. * - * @param name Name to find + * @param name Name to find * @param value Value to assign to name. * * @throws EamDbException */ @Override public void updateDbInfo(String name, String value) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.updateDbInfo(name, value); } finally { releaseExclusiveLock(); - } + } } - - /** + + /** * Creates new Case in the database from the given case - * + * * @param autopsyCase The case to add */ @Override public CorrelationCase newCase(Case autopsyCase) throws EamDbException { - try{ + try { acquireExclusiveLock(); return super.newCase(autopsyCase); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Creates new Case in the database * @@ -280,14 +280,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException { - try{ + try { acquireExclusiveLock(); return super.newCase(eamCase); } finally { releaseExclusiveLock(); - } + } } - + /** * Updates an existing Case in the database * @@ -295,14 +295,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public void updateCase(CorrelationCase eamCase) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.updateCase(eamCase); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Retrieves Case details based on Case UUID * @@ -312,14 +312,32 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public CorrelationCase getCaseByUUID(String caseUUID) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCaseByUUID(caseUUID); } finally { releaseSharedLock(); - } - } - + } + } + + /** + * Retrieves Case details based on Case ID + * + * @param caseID unique identifier for a case + * + * @return The retrieved case + */ + @Override + public CorrelationCase getCaseById(int caseId) throws EamDbException { + try { + acquireSharedLock(); + return super.getCaseById(caseId); + } finally { + releaseSharedLock(); + } + + } + /** * Retrieves cases that are in DB. * @@ -327,14 +345,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public List getCases() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCases(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Creates new Data Source in the database * @@ -342,32 +360,52 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.newDataSource(eamDataSource); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Retrieves Data Source details based on data source device ID * - * @param correlationCase the current CorrelationCase used for ensuring uniqueness of DataSource + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource * @param dataSourceDeviceId the data source device ID number * * @return The data source */ @Override public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getDataSource(correlationCase, dataSourceDeviceId); } finally { releaseSharedLock(); - } - } + } + } + /** + * Retrieves Data Source details based on data source ID + * + * @param correlationCase the current CorrelationCase used for ensuring + * uniqueness of DataSource + * @param dataSourceId the data source ID number + * + * @return The data source + */ + @Override + public CorrelationDataSource getDataSourceById(CorrelationCase correlationCase, int dataSourceId) throws EamDbException { + try { + acquireSharedLock(); + return super.getDataSourceById(correlationCase, dataSourceId); + } finally { + releaseSharedLock(); + } + } + /** * Return a list of data sources in the DB * @@ -375,14 +413,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public List getDataSources() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getDataSources(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Inserts new Artifact(s) into the database. Should add associated Case and * Data Source first. @@ -391,38 +429,38 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.addArtifact(eamArtifact); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Retrieves eamArtifact instances from the database that are associated * with the eamArtifactType and eamArtifactValue of the given eamArtifact. * - * @param aType The type of the artifact - * @param value The correlation value + * @param aType The type of the artifact + * @param value The correlation value * * @return List of artifact instances for a given type/value */ @Override public List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getArtifactInstancesByTypeValue(aType, value); } finally { releaseSharedLock(); - } + } } /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath * - * @param aType EamArtifact.Type to search for + * @param aType EamArtifact.Type to search for * @param filePath File path to search for * * @return List of 0 or more EamArtifactInstances @@ -431,14 +469,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public List getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getArtifactInstancesByPath(aType, filePath); } finally { releaseSharedLock(); - } - } - + } + } + /** * Retrieves number of artifact instances in the database that are * associated with the ArtifactType and artifactValue of the given artifact. @@ -447,29 +485,29 @@ final class SqliteEamDb extends AbstractSqlEamDb { * @param value The value to search for * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. - * @throws EamDbException + * ArtifactValue. + * @throws EamDbException */ @Override public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCountArtifactInstancesByTypeValue(aType, value); } finally { releaseSharedLock(); - } + } } - + @Override public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getFrequencyPercentage(corAttr); } finally { releaseSharedLock(); - } - } - + } + } + /** * Retrieves number of unique caseDisplayName / dataSource tuples in the * database that are associated with the artifactType and artifactValue of @@ -483,130 +521,130 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCountUniqueCaseDataSourceTuplesHavingTypeValue(aType, value); } finally { releaseSharedLock(); - } - } - + } + } @Override public Long getCountUniqueDataSources() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCountUniqueDataSources(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Retrieves number of eamArtifact instances in the database that are * associated with the caseDisplayName and dataSource of the given * eamArtifact instance. * - * @param caseUUID Case ID to search for + * @param caseUUID Case ID to search for * @param dataSourceID Data source ID to search for * * @return Number of artifact instances having caseDisplayName and - * dataSource + * dataSource */ @Override public Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCountArtifactInstancesByCaseDataSource(caseUUID, dataSourceID); } finally { releaseSharedLock(); - } - } - + } + } + /** * Executes a bulk insert of the eamArtifacts added from the * prepareBulkArtifact() method */ @Override public void bulkInsertArtifacts() throws EamDbException { - try{ + try { acquireExclusiveLock(); super.bulkInsertArtifacts(); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Executes a bulk insert of the cases */ @Override public void bulkInsertCases(List cases) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.bulkInsertCases(cases); } finally { releaseExclusiveLock(); - } - } - + } + } + /** - * Sets an eamArtifact instance to the given knownStatus. - * knownStatus should be BAD if the file has been tagged with a notable tag and - * UNKNOWN otherwise. If eamArtifact - * exists, it is updated. If eamArtifact does not exist it is added with the - * given status. + * Sets an eamArtifact instance to the given knownStatus. knownStatus should + * be BAD if the file has been tagged with a notable tag and UNKNOWN + * otherwise. If eamArtifact exists, it is updated. If eamArtifact does not + * exist it is added with the given status. * * @param eamArtifact Artifact containing exactly one (1) ArtifactInstance. - * @param knownStatus The status to change the artifact to. Should never be KNOWN + * @param knownStatus The status to change the artifact to. Should never be + * KNOWN */ @Override public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.setArtifactInstanceKnownStatus(eamArtifact, knownStatus); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Gets list of matching eamArtifact instances that have knownStatus = * "Bad". * * @param aType EamArtifact.Type to search for * @param value Value to search for - * + * * @return List with 0 or more matching eamArtifact instances. */ @Override public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getArtifactInstancesKnownBad(aType, value); } finally { releaseSharedLock(); - } - } - + } + } + /** * * Gets list of matching eamArtifact instances that have knownStatus = * "Bad". + * * @param aType EamArtifact.Type to search for * @return List with 0 or more matching eamArtifact instances. * @throws EamDbException */ @Override public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getArtifactInstancesKnownBad(aType); } finally { releaseSharedLock(); - } + } } - + /** * Count matching eamArtifacts instances that have knownStatus = "Bad". * @@ -617,14 +655,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCountArtifactInstancesKnownBad(aType, value); } finally { releaseSharedLock(); - } - } - + } + } + /** * Gets list of distinct case display names, where each case has 1+ Artifact * Instance matching eamArtifact with knownStatus = "Bad". @@ -633,37 +671,39 @@ final class SqliteEamDb extends AbstractSqlEamDb { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws EamDbException */ @Override public List getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getListCasesHavingArtifactInstancesKnownBad(aType, value); } finally { releaseSharedLock(); - } - } - + } + } + /** * Remove a reference set and all values contained in it. + * * @param referenceSetID - * @throws EamDbException + * @throws EamDbException */ @Override - public void deleteReferenceSet(int referenceSetID) throws EamDbException{ - try{ + public void deleteReferenceSet(int referenceSetID) throws EamDbException { + try { acquireExclusiveLock(); super.deleteReferenceSet(referenceSetID); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Check if the given hash is in a specific reference set + * * @param value * @param referenceSetID * @param correlationTypeID @@ -671,14 +711,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException { - try{ + try { acquireSharedLock(); return super.isValueInReferenceSet(value, referenceSetID, correlationTypeID); } finally { releaseSharedLock(); - } + } } - + /** * Process the Artifact instance in the EamDb * @@ -695,24 +735,44 @@ final class SqliteEamDb extends AbstractSqlEamDb { releaseSharedLock(); } } + /** - * Check whether a reference set with the given name/version is in the central repo. - * Used to check for name collisions when creating reference sets. + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @throws EamDbException + */ + @Override + public void processInstanceTableWhere(CorrelationAttribute.Type type, String whereClause, InstanceTableCallback instanceTableCallback) throws EamDbException { + try { + acquireSharedLock(); + super.processInstanceTableWhere(type, whereClause, instanceTableCallback); + } finally { + releaseSharedLock(); + } + } + + /** + * Check whether a reference set with the given name/version is in the + * central repo. Used to check for name collisions when creating reference + * sets. + * * @param referenceSetName * @param version * @return true if a matching set is found - * @throws EamDbException + * @throws EamDbException */ @Override public boolean referenceSetExists(String referenceSetName, String version) throws EamDbException { - try{ + try { acquireSharedLock(); return super.referenceSetExists(referenceSetName, version); } finally { releaseSharedLock(); - } + } } - + /** * Is the artifact known as bad according to the reference entries? * @@ -722,34 +782,34 @@ final class SqliteEamDb extends AbstractSqlEamDb { * @return Global known status of the artifact */ @Override - public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException { - try{ + public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException { + try { acquireSharedLock(); return super.isArtifactKnownBadByReference(aType, value); } finally { releaseSharedLock(); - } + } } - + /** * Add a new organization * * @return the Organization ID of the newly created organization. - * + * * @param eamOrg The organization to add * * @throws EamDbException */ @Override public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException { - try{ + try { acquireExclusiveLock(); return super.newOrganization(eamOrg); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Get all organizations * @@ -759,14 +819,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public List getOrganizations() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getOrganizations(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Get an organization having the given ID * @@ -778,33 +838,34 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public EamOrganization getOrganizationByID(int orgID) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getOrganizationByID(orgID); } finally { releaseSharedLock(); - } - } - + } + } + @Override public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.updateOrganization(updatedOrganization); } finally { releaseExclusiveLock(); - } + } } - + @Override public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.deleteOrganization(organizationToDelete); } finally { releaseExclusiveLock(); - } + } } + /** * Add a new Global Set * @@ -816,14 +877,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException { - try{ + try { acquireExclusiveLock(); return super.newReferenceSet(eamGlobalSet); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Get a reference set by ID * @@ -835,52 +896,51 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public EamGlobalSet getReferenceSetByID(int referenceSetID) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getReferenceSetByID(referenceSetID); } finally { releaseSharedLock(); - } - } - + } + } + /** * Get all reference sets * * @param correlationType Type of sets to return - * + * * @return List of all reference sets in the central repository * * @throws EamDbException */ @Override public List getAllReferenceSets(CorrelationAttribute.Type correlationType) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getAllReferenceSets(correlationType); } finally { releaseSharedLock(); - } + } } - + /** * Add a new reference instance * * @param eamGlobalFileInstance The reference instance to add - * @param correlationType Correlation Type that this Reference - * Instance is + * @param correlationType Correlation Type that this Reference Instance is * * @throws EamDbException */ @Override public void addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.addReferenceInstance(eamGlobalFileInstance, correlationType); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Insert the bulk collection of Reference Type Instances * @@ -888,18 +948,18 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public void bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.bulkInsertReferenceTypeEntries(globalInstances, contentType); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Get all reference entries having a given correlation type and value * - * @param aType Type to use for matching + * @param aType Type to use for matching * @param aValue Value to use for matching * * @return List of all global file instances with a type and value @@ -908,14 +968,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public List getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getReferenceInstancesByTypeValue(aType, aValue); } finally { releaseSharedLock(); - } - } - + } + } + /** * Add a new EamArtifact.Type to the db. * @@ -927,71 +987,71 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException { - try{ + try { acquireExclusiveLock(); return super.newCorrelationType(newType); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Get the list of EamArtifact.Type's that will be used to correlate * artifacts. * * @return List of EamArtifact.Type's. If none are defined in the database, - * the default list will be returned. + * the default list will be returned. * * @throws EamDbException */ @Override public List getDefinedCorrelationTypes() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getDefinedCorrelationTypes(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Get the list of enabled EamArtifact.Type's that will be used to correlate * artifacts. * * @return List of enabled EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @Override public List getEnabledCorrelationTypes() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getEnabledCorrelationTypes(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Get the list of supported EamArtifact.Type's that can be used to * correlate artifacts. * * @return List of supported EamArtifact.Type's. If none are defined in the - * database, the default list will be returned. + * database, the default list will be returned. * * @throws EamDbException */ @Override public List getSupportedCorrelationTypes() throws EamDbException { - try{ + try { acquireSharedLock(); return super.getSupportedCorrelationTypes(); } finally { releaseSharedLock(); - } - } - + } + } + /** * Update a EamArtifact.Type. * @@ -1001,14 +1061,14 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public void updateCorrelationType(CorrelationAttribute.Type aType) throws EamDbException { - try{ + try { acquireExclusiveLock(); super.updateCorrelationType(aType); } finally { releaseExclusiveLock(); - } - } - + } + } + /** * Get the EamArtifact.Type that has the given Type.Id. * @@ -1020,73 +1080,76 @@ final class SqliteEamDb extends AbstractSqlEamDb { */ @Override public CorrelationAttribute.Type getCorrelationTypeById(int typeId) throws EamDbException { - try{ + try { acquireSharedLock(); return super.getCorrelationTypeById(typeId); } finally { releaseSharedLock(); - } - } - + } + } + /** * Upgrade the schema of the database (if needed) - * @throws EamDbException + * + * @throws EamDbException */ @Override public void upgradeSchema() throws EamDbException, SQLException { - try{ + try { acquireExclusiveLock(); super.upgradeSchema(); } finally { releaseExclusiveLock(); - } + } } - + /** - * Gets an exclusive lock (if applicable). - * Will return the lock if successful, null if unsuccessful because locking - * isn't supported, and throw an exception if we should have been able to get the - * lock but failed (meaning the database is in use). + * Gets an exclusive lock (if applicable). Will return the lock if + * successful, null if unsuccessful because locking isn't supported, and + * throw an exception if we should have been able to get the lock but failed + * (meaning the database is in use). + * * @return the lock, or null if locking is not supported - * @throws EamDbException if the coordination service is running but we fail to get the lock + * @throws EamDbException if the coordination service is running but we fail + * to get the lock */ @Override - public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException{ + public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException { // Multiple users are not supported for SQLite return null; } - + /** - * Acquire the lock that provides exclusive access to the case database. - * Call this method in a try block with a call to - * the lock release method in an associated finally block. + * Acquire the lock that provides exclusive access to the case database. + * Call this method in a try block with a call to the lock release method in + * an associated finally block. */ private void acquireExclusiveLock() { rwLock.writeLock().lock(); } /** - * Release the lock that provides exclusive access to the database. - * This method should always be called in the finally - * block of a try block in which the lock was acquired. + * Release the lock that provides exclusive access to the database. This + * method should always be called in the finally block of a try block in + * which the lock was acquired. */ private void releaseExclusiveLock() { rwLock.writeLock().unlock(); } /** - * Acquire the lock that provides shared access to the case database. - * Call this method in a try block with a call to the - * lock release method in an associated finally block. + * Acquire the lock that provides shared access to the case database. Call + * this method in a try block with a call to the lock release method in an + * associated finally block. */ private void acquireSharedLock() { rwLock.readLock().lock(); } /** - * Release the lock that provides shared access to the database. - * This method should always be called in the finally block - * of a try block in which the lock was acquired. + * Release the lock that provides shared access to the database. This method + * should always be called in the finally block of a try block in which the + * lock was acquired. */ private void releaseSharedLock() { rwLock.readLock().unlock(); diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java new file mode 100644 index 0000000000..7732c5d393 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeInstance.java @@ -0,0 +1,163 @@ +/* + * + * 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.commonfilesearch; + +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Represents an instance in either the CaseDB or CR that had a common match. + * Different implementations know how to get the instance details from either + * CaseDB or CR. + * + * Defines leaf-type nodes used in the Common Files Search results tree. Leaf + * nodes, may describe common attributes which exist in the current case DB or + * in the Central Repo. When a reference to the AbstractFile is lacking (such as + * in the case that a common attribute is found in the Central Repo) not all + * features of the Content Viewer, and context menu can be supported. Thus, + * multiple types of leaf nodes are required to represent Common Attribute + * Instance nodes. + */ +public abstract class AbstractCommonAttributeInstance { + + private final Long abstractFileObjectId; + private final String caseName; + private final String dataSource; + + /** + * Create a leaf node for attributes found in files in the current case db. + * + * @param abstractFileReference file from which the common attribute was + * found + * @param cachedFiles storage for abstract files which have been used + * already so we can avoid extra roundtrips to the case db + * @param dataSource datasource where this attribute appears + * @param caseName case where this attribute appears + */ + AbstractCommonAttributeInstance(Long abstractFileReference, String dataSource, String caseName) { + this.abstractFileObjectId = abstractFileReference; + this.caseName = caseName; + this.dataSource = dataSource; + } + + /** + * Create a leaf node for attributes found in the central repo and not + * available in the current data case. + * + * @param cachedFiles storage for abstract files which have been used + * already so we can avoid extra roundtrips to the case db + */ + AbstractCommonAttributeInstance() { + this.abstractFileObjectId = -1L; + this.caseName = ""; + this.dataSource = ""; + } + + /** + * Get an AbstractFile for this instance if it can be retrieved from the + * CaseDB. + * + * @return AbstractFile corresponding to this common attribute or null if it + * cannot be found (for example, in the event that this is a central repo + * file) + */ + abstract AbstractFile getAbstractFile(); + + /** + * Create a list of leaf nodes, to be used to display a row in the tree + * table + * + * @return leaf nodes for tree + */ + abstract DisplayableItemNode[] generateNodes(); + + /** + * The name of the case where this common attribute is found. + * + * @return case name + */ + String getCaseName() { + return this.caseName; + } + + /** + * Get string name of the data source where this common attribute appears. + * + * @return data source name + */ + public String getDataSource() { + + /** + * Even though we could get this from the CR record or the AbstractFile, + * we want to avoid getting it from the AbstractFile because it would be + * an extra database roundtrip. + */ + return this.dataSource; + } + + /** + * ObjectId of the AbstractFile that is equivalent to the file from which + * this common attribute instance + * + * @return the abstractFileObjectId + */ + public Long getAbstractFileObjectId() { + return abstractFileObjectId; + } + + /** + * Use this to create an AbstractCommonAttributeInstanceNode of the + * appropriate type. In any case, we'll get something which extends + * DisplayableItemNode which can be used to populate the tree. + * + * If the common attribute in question could be derived from an AbstractFile + * in the present SleuthkitCase, we can use an + * IntraCaseCommonAttributeInstanceNode which enables extended functionality + * in the context menu and in the content viewer. + * + * Otherwise, we will get an InterCaseCommonAttributeInstanceNode which + * supports only baseline functionality. + * + * @param attributeInstance common file attribute instance form the central + * repo + * @param abstractFile an AbstractFile from which the attribute instance was + * found - applies to CaseDbCommonAttributeInstance only + * @param currentCaseName + * @return the appropriate leaf node for the results tree + * @throws TskCoreException + */ + static DisplayableItemNode createNode(CorrelationAttribute attribute, AbstractFile abstractFile, String currentCaseName) throws TskCoreException { + + DisplayableItemNode leafNode; + CorrelationAttributeInstance attributeInstance = attribute.getInstances().get(0); + + if (abstractFile == null) { + leafNode = new CentralRepoCommonAttributeInstanceNode(attributeInstance); + } else { + final String abstractFileDataSourceName = abstractFile.getDataSource().getName(); + leafNode = new CaseDBCommonAttributeInstanceNode(abstractFile, currentCaseName, abstractFileDataSourceName); + } + + return leafNode; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java new file mode 100644 index 0000000000..97456bda04 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java @@ -0,0 +1,212 @@ +/* + * + * 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.commonfilesearch; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Prototype for an object which finds files with common attributes. + * Subclass this and implement findFiles in order + */ +public abstract class AbstractCommonAttributeSearcher { + + private final Map dataSourceIdToNameMap; + private boolean filterByMedia; + private boolean filterByDoc; + + AbstractCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMedia, boolean filterByDoc){ + this.filterByDoc = filterByDoc; + this.filterByMedia = filterByMedia; + this.dataSourceIdToNameMap = dataSourceIdMap; + } + + Map getDataSourceIdToNameMap(){ + return Collections.unmodifiableMap(this.dataSourceIdToNameMap); + } + + /** + * Implement this to search for files with common attributes. Creates an + * object (CommonAttributeSearchResults) which contains all of the information + * required to display a tree view in the UI. The view will contain 3 layers: + * a top level node, indicating the number matches each of it's children possess, + * a mid level node indicating the matched attribute, + * @return + * @throws TskCoreException + * @throws NoCurrentCaseException + * @throws SQLException + * @throws EamDbException + */ + public abstract CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException; + + /** + * Implement this to create a descriptive string for the tab which will display + * this data. + * @return an informative string + */ + @NbBundle.Messages({ + "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraAll=Common Files (All Data Sources, %s)", + "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraSingle=Common Files (Data Source: %s, %s)", + "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterAll=Common Files (All Central Repository Cases, %s)", + "AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterSingle=Common Files (Central Repository Case: %s, %s)", + }) + abstract String buildTabTitle(); + + @NbBundle.Messages({ + "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.doc=Documents", + "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.media=Media", + "AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.all=All File Categories" + }) + String buildCategorySelectionString() { + if (!this.isFilterByDoc() && !this.isFilterByMedia()) { + return Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_all(); + } else { + List filters = new ArrayList<>(); + if (this.isFilterByDoc()) { + filters.add(Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_doc()); + } + if (this.isFilterByMedia()) { + filters.add(Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_media()); + } + return String.join(", ", filters); + } + } + + static Map> collateMatchesByNumberOfInstances(Map commonFiles) { + //collate matches by number of matching instances - doing this in sql doesnt seem efficient + Map> instanceCollatedCommonFiles = new TreeMap<>(); + for(CommonAttributeValue md5Metadata : commonFiles.values()){ + Integer size = md5Metadata.getInstanceCount(); + + if(instanceCollatedCommonFiles.containsKey(size)){ + instanceCollatedCommonFiles.get(size).add(md5Metadata); + } else { + ArrayList value = new ArrayList<>(); + value.add(md5Metadata); + instanceCollatedCommonFiles.put(size, value); + } + } + return instanceCollatedCommonFiles; + } + + /* + * The set of the MIME types that will be checked for extension mismatches + * when checkType is ONLY_MEDIA. + * ".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec" + * ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS + * ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf" + */ + static final Set MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of( + "image/bmp", //NON-NLS + "image/gif", //NON-NLS + "image/jpeg", //NON-NLS + "image/png", //NON-NLS + "image/tiff", //NON-NLS + "image/vnd.adobe.photoshop", //NON-NLS + "image/x-raw-nikon", //NON-NLS + "image/x-ms-bmp", //NON-NLS + "image/x-icon", //NON-NLS + "video/webm", //NON-NLS + "video/3gpp", //NON-NLS + "video/3gpp2", //NON-NLS + "video/ogg", //NON-NLS + "video/mpeg", //NON-NLS + "video/mp4", //NON-NLS + "video/quicktime", //NON-NLS + "video/x-msvideo", //NON-NLS + "video/x-flv", //NON-NLS + "video/x-m4v", //NON-NLS + "video/x-ms-wmv", //NON-NLS + "application/vnd.ms-asf", //NON-NLS + "application/vnd.rn-realmedia", //NON-NLS + "application/x-shockwave-flash" //NON-NLS + ).collect(Collectors.toSet()); + + /* + * The set of the MIME types that will be checked for extension mismatches + * when checkType is ONLY_TEXT_FILES. + * ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx" + * ".txt", ".rtf", ".log", ".text", ".xml" + * ".html", ".htm", ".css", ".js", ".php", ".aspx" + * ".pdf" + */ + static final Set TEXT_FILES_MIME_TYPES = Stream.of( + "text/plain", //NON-NLS + "application/rtf", //NON-NLS + "application/pdf", //NON-NLS + "text/css", //NON-NLS + "text/html", //NON-NLS + "text/csv", //NON-NLS + "application/json", //NON-NLS + "application/javascript", //NON-NLS + "application/xml", //NON-NLS + "text/calendar", //NON-NLS + "application/x-msoffice", //NON-NLS + "application/x-ooxml", //NON-NLS + "application/msword", //NON-NLS + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS + "application/vnd.ms-powerpoint", //NON-NLS + "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS + "application/vnd.ms-excel", //NON-NLS + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS + "application/vnd.oasis.opendocument.presentation", //NON-NLS + "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS + "application/vnd.oasis.opendocument.text" //NON-NLS + ).collect(Collectors.toSet()); + + /** + * @return the filterByMedia + */ + boolean isFilterByMedia() { + return filterByMedia; + } + + /** + * @param filterByMedia the filterByMedia to set + */ + void setFilterByMedia(boolean filterByMedia) { + this.filterByMedia = filterByMedia; + } + + /** + * @return the filterByDoc + */ + boolean isFilterByDoc() { + return filterByDoc; + } + + /** + * @param filterByDoc the filterByDoc to set + */ + void setFilterByDoc(boolean filterByDoc) { + this.filterByDoc = filterByDoc; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java new file mode 100644 index 0000000000..ca436b7809 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllInterCaseCommonAttributeSearcher.java @@ -0,0 +1,61 @@ +/* + * + * 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.commonfilesearch; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Algorithm which finds files anywhere in the Central Repo which also occur in + * present case. + */ +public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttributeSearcher { + + /** + * + * @param filterByMediaMimeType match only on files whose mime types can be + * broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be + * broadly categorized as document types + * @throws EamDbException + */ + public AllInterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + } + + @Override + public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { + InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap()); + Map> interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase()); + return new CommonAttributeSearchResults(interCaseCommonFiles); + } + + @Override + String buildTabTitle() { + final String buildCategorySelectionString = this.buildCategorySelectionString(); + final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleInterAll(); + return String.format(titleTemplate, new Object[]{buildCategorySelectionString}); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java similarity index 79% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java index 111cf4aed9..eed61b0fc1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java @@ -25,9 +25,9 @@ import org.sleuthkit.datamodel.TskData.FileKnown; /** * Provides logic for selecting common files from all data sources. */ -final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilder { +final public class AllIntraCaseCommonAttributeSearcher extends IntraCaseCommonAttributeSearcher { - private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where (known != "+ FileKnown.KNOWN.getFileKnownValue() + " OR known IS NULL)%s GROUP BY md5 HAVING COUNT(DISTINCT data_source_obj_id) > 1) order by md5"; //NON-NLS + private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where (known != "+ FileKnown.KNOWN.getFileKnownValue() + " OR known IS NULL)%s GROUP BY md5 HAVING COUNT(DISTINCT data_source_obj_id) > 1) order by md5"; //NON-NLS /** * Implements the algorithm for getting common files across all data @@ -37,7 +37,7 @@ final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadat * @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types * @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types */ - public AllDataSourcesCommonFilesAlgorithm(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + public AllIntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); } @@ -49,9 +49,9 @@ final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadat } @Override - protected String buildTabTitle() { + String buildTabTitle() { final String buildCategorySelectionString = this.buildCategorySelectionString(); - final String titleTemplate = Bundle.CommonFilesMetadataBuilder_buildTabTitle_titleAll(); + final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleIntraAll(); return String.format(titleTemplate, new Object[]{buildCategorySelectionString}); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties index a2b42155d7..23598648ea 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -1,15 +1,25 @@ -CommonFilesPanel.searchButton.text=Search -CommonFilesPanel.withinDataSourceRadioButton.text=At least one instance of a given MD5 must appear in the data source selected below: -CommonFilesPanel.allDataSourcesRadioButton.text=Files can be in any data source -CommonFilesPanel.cancelButton.text=Cancel -CommonFilesPanel.cancelButton.actionCommand=Cancel -CommonFilesPanel.selectedFileCategoriesButton.text=Match on the following file categories: -CommonFilesPanel.selectedFileCategoriesButton.toolTipText=Select from the options below... -CommonFilesPanel.pictureVideoCheckbox.text=Pictures and Videos -CommonFilesPanel.documentsCheckbox.text=Documents CommonFilesPanel.commonFilesSearchLabel.text=Find files in multiple data sources in the current case. -CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... -CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates: -CommonFilesPanel.categoriesLabel.text=Indicate which file types to include in results: -CommonFilesPanel.errorText.text=In order to search, you must select a file category. +CommonFilesPanel.jRadioButton1.text=jRadioButton1 +CommonFilesPanel.jRadioButton2.text=With previous cases in the Central Repository +CommonFilesPanel.intraCaseRadio.label=Correlate within current case only +CommonFilesPanel.interCaseRadio.label=Correlate amongst all known cases (uses Central Repo) +IntraCasePanel.allDataSourcesRadioButton.text=Matches may be from any data source +IntraCasePanel.withinDataSourceRadioButton.text=At least one match must appear in the data source selected below: +IntraCasePanel.selectDataSourceComboBox.actionCommand= +InterCasePanel.specificCentralRepoCaseRadio.text=Matches must be from the following Central Repo case: +InterCasePanel.anyCentralRepoCaseRadio.text=Matches may be from any Central Repo case +CommonAttributePanel.selectedFileCategoriesButton.toolTipText=Select from the options below... +CommonAttributePanel.selectedFileCategoriesButton.text=Only the selected file types: +CommonAttributePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... +CommonAttributePanel.allFileCategoriesRadioButton.text=All file types +CommonAttributePanel.cancelButton.actionCommand=Cancel +CommonAttributePanel.cancelButton.text=Cancel +CommonAttributePanel.searchButton.text=Search +CommonAttributePanel.commonFilesSearchLabel2.text=Scope of Search +CommonAttributePanel.intraCaseRadio.text=Within current case +CommonAttributePanel.commonFilesSearchLabel1.text=Find common files to correlate data soures or cases. +CommonAttributePanel.errorText.text=In order to search, you must select a file category. +CommonAttributePanel.categoriesLabel.text=File Types To Include: +CommonAttributePanel.documentsCheckbox.text=Documents +CommonAttributePanel.pictureVideoCheckbox.text=Pictures and Videos diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java new file mode 100644 index 0000000000..9221a5dc86 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java @@ -0,0 +1,73 @@ +/* + * + * 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.commonfilesearch; + +import java.util.Arrays; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Encapsulates data required to instantiate a FileInstanceNode for an instance in the CaseDB + */ +final public class CaseDBCommonAttributeInstance extends AbstractCommonAttributeInstance { + + private static final Logger LOGGER = Logger.getLogger(CaseDBCommonAttributeInstance.class.getName()); + + + /** + * Create meta data required to find an abstract file and build a + * FileInstanceNode. + * + * @param objectId id of abstract file to find + * @param dataSourceName name of datasource where the object is found + */ + CaseDBCommonAttributeInstance(Long abstractFileReference, String dataSource, String caseName) { + super(abstractFileReference, dataSource, caseName); + } + + @Override + public DisplayableItemNode[] generateNodes() { + final CaseDBCommonAttributeInstanceNode intraCaseCommonAttributeInstanceNode = new CaseDBCommonAttributeInstanceNode(this.getAbstractFile(), this.getCaseName(), this.getDataSource()); + return Arrays.asList(intraCaseCommonAttributeInstanceNode).toArray(new DisplayableItemNode[1]); + } + + @Override + AbstractFile getAbstractFile() { + + Case currentCase; + try { + currentCase = Case.getCurrentCaseThrows(); + + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + + return tskDb.findAllFilesWhere(String.format("obj_id in (%s)", this.getAbstractFileObjectId())).get(0); + + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to find AbstractFile for record with obj_id: %s. Node not created.", new Object[]{this.getAbstractFileObjectId()}), ex); + return null; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java similarity index 81% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java index 899aa5abbc..f105d72498 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java @@ -20,18 +20,18 @@ package org.sleuthkit.autopsy.commonfilesearch; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.AbstractFile; /** - * Used by the Common Files search feature to encapsulate instances of a given - MD5s matched in the search. These nodes will be children of Md5Nodes. + * Node that wraps CaseDBCommonAttributeInstance to represent a file instance stored + * in the CaseDB. */ -public class FileInstanceNode extends FileNode { +public class CaseDBCommonAttributeInstanceNode extends FileNode { + private final String caseName; private final String dataSource; /** @@ -41,11 +41,10 @@ public class FileInstanceNode extends FileNode { * @param fsContent * @param dataSource */ - public FileInstanceNode(AbstractFile fsContent, String dataSource) { + public CaseDBCommonAttributeInstanceNode(AbstractFile fsContent, String caseName, String dataSource) { super(fsContent); + this.caseName = caseName; this.dataSource = dataSource; - - this.setDisplayName(fsContent.getName()); } @Override @@ -59,11 +58,14 @@ public class FileInstanceNode extends FileNode { return visitor.visit(this); } - String getDataSource() { + public String getCase(){ + return this.caseName; + } + + public String getDataSource() { return this.dataSource; } - @NbBundle.Messages({"FileInstanceNode.createSheet.noDescription= "}) @Override protected Sheet createSheet() { Sheet sheet = new Sheet(); @@ -73,13 +75,14 @@ public class FileInstanceNode extends FileNode { sheet.put(sheetSet); } - final String NO_DESCR = Bundle.FileInstanceNode_createSheet_noDescription(); + final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName())); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath())); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsCsvList(this.getContent()))); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource())); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, StringUtils.defaultString(this.getContent().getMIMEType()))); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName)); this.addTagProperty(sheetSet); diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java new file mode 100644 index 0000000000..0e70722b6d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstance.java @@ -0,0 +1,137 @@ +/* + * + * 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.commonfilesearch; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Represents that a row in the CR was found in multiple cases. + * + * Generates a DisplayableItmeNode using a CentralRepositoryFile. + */ +final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttributeInstance { + + private static final Logger LOGGER = Logger.getLogger(CentralRepoCommonAttributeInstance.class.getName()); + private final Integer crFileId; + private CorrelationAttribute currentAttribute; + private final Map dataSourceNameToIdMap; + + CentralRepoCommonAttributeInstance(Integer attrInstId, Map dataSourceIdToNameMap) { + super(); + this.crFileId = attrInstId; + this.dataSourceNameToIdMap = invertMap(dataSourceIdToNameMap); + } + + void setCurrentAttributeInst(CorrelationAttribute attribute) { + this.currentAttribute = attribute; + } + + @Override + AbstractFile getAbstractFile() { + + Case currentCase; + if (this.currentAttribute != null) { + + final CorrelationAttributeInstance currentAttributeInstance = this.currentAttribute.getInstances().get(0); + + String currentFullPath = currentAttributeInstance.getFilePath(); + String currentDataSource = currentAttributeInstance.getCorrelationDataSource().getName(); + + + if(this.dataSourceNameToIdMap.containsKey(currentDataSource)){ + Long dataSourceObjectId = this.dataSourceNameToIdMap.get(currentDataSource); + + try { + currentCase = Case.getCurrentCaseThrows(); + + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + + File fileFromPath = new File(currentFullPath); + String fileName = fileFromPath.getName(); + String parentPath = (fileFromPath.getParent() + File.separator).replace("\\", "/"); + + final String whereClause = String.format("lower(name) = '%s' AND md5 = '%s' AND lower(parent_path) = '%s' AND data_source_obj_id = %s", fileName, currentAttribute.getCorrelationValue(), parentPath, dataSourceObjectId); + List potentialAbstractFiles = tskDb.findAllFilesWhere(whereClause); + + if(potentialAbstractFiles.isEmpty()){ + return null; + } else if(potentialAbstractFiles.size() > 1){ + LOGGER.log(Level.WARNING, String.format("Unable to find an exact match for AbstractFile for record with filePath: %s. May have returned the wrong file.", new Object[]{currentFullPath})); + return potentialAbstractFiles.get(0); + } else { + return potentialAbstractFiles.get(0); + } + + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to find AbstractFile for record with filePath: %s. Node not created.", new Object[]{currentFullPath}), ex); + return null; + } + } else { + return null; + } + } + return null; + } + + @Override + public DisplayableItemNode[] generateNodes() { + + // @@@ We should be doing more of this work in teh generateKeys method. We want to do as little as possible in generateNodes + InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(); + CorrelationAttribute corrAttr = eamDbAttrInst.findSingleCorrelationAttribute(crFileId); + List attrInstNodeList = new ArrayList<>(0); + String currCaseDbName = Case.getCurrentCase().getDisplayName(); + + try { + this.setCurrentAttributeInst(corrAttr); + + AbstractFile abstractFileForAttributeInstance = this.getAbstractFile(); + DisplayableItemNode generatedInstNode = AbstractCommonAttributeInstance.createNode(corrAttr, abstractFileForAttributeInstance, currCaseDbName); + attrInstNodeList.add(generatedInstNode); + + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to get DataSource for record with md5: %s. Node not created.", new Object[]{corrAttr.getCorrelationValue()}), ex); + } + + return attrInstNodeList.toArray(new DisplayableItemNode[attrInstNodeList.size()]); + } + + private Map invertMap(Map dataSourceIdToNameMap) { + HashMap invertedMap = new HashMap<>(); + for (Map.Entry entry : dataSourceIdToNameMap.entrySet()){ + invertedMap.put(entry.getValue(), entry.getKey()); + } + return invertedMap; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java new file mode 100644 index 0000000000..e0b8e928d7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CentralRepoCommonAttributeInstanceNode.java @@ -0,0 +1,116 @@ +/* + * + * 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.commonfilesearch; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.Action; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * Used by the Common Files search feature to encapsulate instances of a given + * MD5s matched in the search. These nodes will be children of Md5Nodes. + * + * Use this type for files which are not in the current case, but from the + * Central Repo. Contrast with SleuthkitCase which should be used + * when the FileInstance was found in the case presently open in Autopsy. + */ +public class CentralRepoCommonAttributeInstanceNode extends DisplayableItemNode { + + private final CorrelationAttributeInstance crFile; + + CentralRepoCommonAttributeInstanceNode(CorrelationAttributeInstance content) { + super(Children.LEAF, Lookups.fixed(content)); + this.crFile = content; + this.setDisplayName(new File(this.crFile.getFilePath()).getName()); + } + + public CorrelationAttributeInstance getCorrelationAttributeInstance(){ + return this.crFile; + } + + @Override + public Action[] getActions(boolean context){ + List actionsList = new ArrayList<>(); + + actionsList.addAll(Arrays.asList(super.getActions(true))); + + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public String getItemType() { + //objects of type FileNode will co-occur in the treetable with objects + // of this type and they will need to provide the same key + return CaseDBCommonAttributeInstanceNode.class.getName(); + } + + @Override + protected Sheet createSheet(){ + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + + if(sheetSet == null){ + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + final CorrelationAttributeInstance centralRepoFile = this.getCorrelationAttributeInstance(); + + final String fullPath = centralRepoFile.getFilePath(); + final File file = new File(fullPath); + + final String caseName = centralRepoFile.getCorrelationCase().getDisplayName(); + + final String name = file.getName(); + final String parent = file.getParent(); + + final String dataSourceName = centralRepoFile.getCorrelationDataSource().getName(); + + final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText(); + + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, name)); + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, parent)); + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, dataSourceName)); + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), org.sleuthkit.autopsy.commonfilesearch.Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName)); + + return sheet; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form new file mode 100644 index 0000000000..c08c3c2d78 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.form @@ -0,0 +1,301 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java new file mode 100644 index 0000000000..bf1765e310 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java @@ -0,0 +1,720 @@ +/* + * 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.commonfilesearch; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.openide.explorer.ExplorerManager; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Panel used for common files search configuration and configuration business + * logic. Nested within CommonFilesDialog. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public final class CommonAttributePanel extends javax.swing.JDialog { + + private static final long serialVersionUID = 1L; + + private static final Long NO_DATA_SOURCE_SELECTED = -1L; + + private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName()); + private boolean pictureViewCheckboxState; + private boolean documentsCheckboxState; + + /** + * Creates new form CommonFilesPanel + */ + @NbBundle.Messages({ + "CommonFilesPanel.title=Common Files Panel", + "CommonFilesPanel.exception=Unexpected Exception loading DataSources.", + "CommonFilesPanel.frame.title=Find Common Files", + "CommonFilesPanel.frame.msg=Find Common Files"}) + public CommonAttributePanel() { + super(new JFrame(Bundle.CommonFilesPanel_frame_title()), + Bundle.CommonFilesPanel_frame_msg(), true); + initComponents(); + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + this.errorText.setVisible(false); + this.setupDataSources(); + + if (CommonAttributePanel.isEamDbAvailable()) { + this.setupCases(); + } else { + this.disableIntercaseSearch(); + } + } + + private static boolean isEamDbAvailable() { + try { + return EamDb.isEnabled() && + EamDb.getInstance() != null && + EamDb.getInstance().getCases().size() > 1 && + Case.isCaseOpen() && + Case.getCurrentCase() != null && + EamDb.getInstance().getCase(Case.getCurrentCase()) != null; + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while checking for EamDB enabled.", ex); + } + return false; + } + + private void disableIntercaseSearch() { + this.intraCaseRadio.setSelected(true); + this.interCaseRadio.setEnabled(false); + } + + @NbBundle.Messages({ + "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)", + "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)", + "CommonFilesPanel.search.results.pathText=Common Files Search Results", + "CommonFilesPanel.search.done.searchProgressGathering=Gathering Common Files Search Results.", + "CommonFilesPanel.search.done.searchProgressDisplay=Displaying Common Files Search Results.", + "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.", + "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.", + "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.", + "CommonFilesPanel.search.done.interupted=Something went wrong finding common files.", + "CommonFilesPanel.search.done.sqlException=Unable to query db for files or data sources."}) + private void search() { + String pathText = Bundle.CommonFilesPanel_search_results_pathText(); + + new SwingWorker() { + + private String tabTitle; + private ProgressHandle progress; + + private void setTitleForAllDataSources() { + this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll(); + } + + private void setTitleForSingleSource(Long dataSourceId) { + final String CommonFilesPanel_search_results_titleSingle = Bundle.CommonFilesPanel_search_results_titleSingle(); + final Object[] dataSourceName = new Object[]{intraCasePanel.getDataSourceMap().get(dataSourceId)}; + + this.tabTitle = String.format(CommonFilesPanel_search_results_titleSingle, dataSourceName); + } + + @Override + @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) + protected CommonAttributeSearchResults doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { + progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering()); + progress.start(); + progress.switchToIndeterminate(); + + Long dataSourceId = intraCasePanel.getSelectedDataSourceId(); + Integer caseId = interCasePanel.getSelectedCaseId(); + + AbstractCommonAttributeSearcher builder; + CommonAttributeSearchResults metadata; + + boolean filterByMedia = false; + boolean filterByDocuments = false; + if (selectedFileCategoriesButton.isSelected()) { + if (pictureVideoCheckbox.isSelected()) { + filterByMedia = true; + } + if (documentsCheckbox.isSelected()) { + filterByDocuments = true; + } + } + + if (CommonAttributePanel.this.interCaseRadio.isSelected()) { + + if (caseId == InterCasePanel.NO_CASE_SELECTED) { + builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + } else { + builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + } + } else { + if (dataSourceId == CommonAttributePanel.NO_DATA_SOURCE_SELECTED) { + builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + + setTitleForAllDataSources(); + } else { + builder = new SingleIntraCaseCommonAttributeSearcher(dataSourceId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments); + + setTitleForSingleSource(dataSourceId); + } + } + metadata = builder.findFiles(); + this.tabTitle = builder.buildTabTitle(); + + return metadata; + } + + @Override + protected void done() { + try { + super.done(); + + CommonAttributeSearchResults metadata = this.get(); + + CommonAttributeSearchResultRootNode commonFilesNode = new CommonAttributeSearchResultRootNode(metadata); + + // -3969 + DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this)); + + TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); + + DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable(); + + Collection viewers = new ArrayList<>(1); + viewers.add(table); + + progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay()); + DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); + progress.finish(); + + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted()); + } catch (ExecutionException ex) { + String errorMessage; + Throwable inner = ex.getCause(); + if (inner instanceof TskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to load files from database.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_tskCoreException(); + } else if (inner instanceof NoCurrentCaseException) { + LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_noCurrentCaseException(); + } else if (inner instanceof SQLException) { + LOGGER.log(Level.SEVERE, "Unable to query db for files.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_sqlException(); + } else { + LOGGER.log(Level.SEVERE, "Unexpected exception while running Common Files Search.", ex); + errorMessage = Bundle.CommonFilesPanel_search_done_exception(); + } + MessageNotifyUtil.Message.error(errorMessage); + } + } + }.execute(); + } + + + /** + * Sets up the data sources dropdown and returns the data sources map for + * future usage. + * + * @return a mapping of data correlationCase ids to data correlationCase + * names + */ + @NbBundle.Messages({ + "CommonFilesPanel.setupDataSources.done.tskCoreException=Unable to run query against DB.", + "CommonFilesPanel.setupDataSources.done.noCurrentCaseException=Unable to open case file.", + "CommonFilesPanel.setupDataSources.done.exception=Unexpected exception loading data sources.", + "CommonFilesPanel.setupDataSources.done.interupted=Something went wrong building the Common Files Search dialog box.", + "CommonFilesPanel.setupDataSources.done.sqlException=Unable to query db for data sources.", + "CommonFilesPanel.setupDataSources.updateUi.noDataSources=No data sources were found."}) + private void setupDataSources() { + + new SwingWorker, Void>() { + + private void updateUi() { + + final Map dataSourceMap = CommonAttributePanel.this.intraCasePanel.getDataSourceMap(); + + String[] dataSourcesNames = new String[dataSourceMap.size()]; + + //only enable all this stuff if we actually have datasources + if (dataSourcesNames.length > 0) { + dataSourcesNames = dataSourceMap.values().toArray(dataSourcesNames); + CommonAttributePanel.this.intraCasePanel.setDataModel(new DataSourceComboBoxModel(dataSourcesNames)); + + boolean multipleDataSources = this.caseHasMultipleSources(); + CommonAttributePanel.this.intraCasePanel.rigForMultipleDataSources(multipleDataSources); + + //TODO this should be attached to the intra/inter radio buttons + CommonAttributePanel.this.setSearchButtonEnabled(true); + } + } + + private boolean caseHasMultipleSources() { + return CommonAttributePanel.this.intraCasePanel.getDataSourceMap().size() > 2; + } + + @Override + protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, SQLException { + DataSourceLoader loader = new DataSourceLoader(); + return loader.getDataSourceMap(); + } + + @Override + protected void done() { + + try { + CommonAttributePanel.this.intraCasePanel.setDataSourceMap(this.get()); + updateUi(); + + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupDataSources_done_interupted()); + } catch (ExecutionException ex) { + String errorMessage; + Throwable inner = ex.getCause(); + if (inner instanceof TskCoreException) { + LOGGER.log(Level.SEVERE, "Failed to load data sources from database.", ex); + errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_tskCoreException(); + } else if (inner instanceof NoCurrentCaseException) { + LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); + errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_noCurrentCaseException(); + } else if (inner instanceof SQLException) { + LOGGER.log(Level.SEVERE, "Unable to query db for data sources.", ex); + errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_sqlException(); + } else { + LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog panel.", ex); + errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_exception(); + } + MessageNotifyUtil.Message.error(errorMessage); + } + } + }.execute(); + } + + @NbBundle.Messages({ + "CommonFilesPanel.setupCases.done.interruptedException=Something went wrong building the Common Files Search dialog box.", + "CommonFilesPanel.setupCases.done.exeutionException=Unexpected exception loading cases."}) + private void setupCases() { + + new SwingWorker, Void>() { + + private void updateUi() { + + final Map caseMap = CommonAttributePanel.this.interCasePanel.getCaseMap(); + + String[] caseNames = new String[caseMap.size()]; + + if (caseNames.length > 0) { + caseNames = caseMap.values().toArray(caseNames); + CommonAttributePanel.this.interCasePanel.setCaseList(new DataSourceComboBoxModel(caseNames)); + + boolean multipleCases = this.centralRepoHasMultipleCases(); + CommonAttributePanel.this.interCasePanel.rigForMultipleCases(multipleCases); + + } else { + CommonAttributePanel.this.disableIntercaseSearch(); + } + } + + private Map mapDataSources(List cases) throws EamDbException { + Map casemap = new HashMap<>(); + CorrelationCase currentCorCase = EamDb.getInstance().getCase(Case.getCurrentCase()); + for (CorrelationCase correlationCase : cases) { + if (currentCorCase.getID() != correlationCase.getID()) { // if not the current Case + casemap.put(correlationCase.getID(), correlationCase.getDisplayName()); + } + } + + return casemap; + } + + @Override + protected Map doInBackground() throws EamDbException { + + List dataSources = EamDb.getInstance().getCases(); + Map caseMap = mapDataSources(dataSources); + + return caseMap; + } + + @Override + protected void done() { + try { + Map cases = this.get(); + CommonAttributePanel.this.interCasePanel.setCaseMap(cases); + this.updateUi(); + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupCases_done_interruptedException()); + } catch (ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog.", ex); + MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupCases_done_exeutionException()); + } + } + + private boolean centralRepoHasMultipleCases() { + return CommonAttributePanel.this.interCasePanel.centralRepoHasMultipleCases(); + } + + }.execute(); + } + + /** + * 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() { + + fileTypeFilterButtonGroup = new javax.swing.ButtonGroup(); + interIntraButtonGroup = new javax.swing.ButtonGroup(); + jPanel1 = new javax.swing.JPanel(); + commonFilesSearchLabel2 = new javax.swing.JLabel(); + searchButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + allFileCategoriesRadioButton = new javax.swing.JRadioButton(); + selectedFileCategoriesButton = new javax.swing.JRadioButton(); + pictureVideoCheckbox = new javax.swing.JCheckBox(); + documentsCheckbox = new javax.swing.JCheckBox(); + categoriesLabel = new javax.swing.JLabel(); + errorText = new javax.swing.JLabel(); + commonFilesSearchLabel1 = new javax.swing.JLabel(); + intraCaseRadio = new javax.swing.JRadioButton(); + interCaseRadio = new javax.swing.JRadioButton(); + layoutPanel = new java.awt.Panel(); + intraCasePanel = new org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel(); + interCasePanel = new org.sleuthkit.autopsy.commonfilesearch.InterCasePanel(); + + setMinimumSize(new java.awt.Dimension(412, 350)); + setPreferredSize(new java.awt.Dimension(412, 350)); + setResizable(false); + addWindowListener(new java.awt.event.WindowAdapter() { + public void windowClosed(java.awt.event.WindowEvent evt) { + formWindowClosed(evt); + } + }); + + jPanel1.setPreferredSize(new java.awt.Dimension(412, 350)); + + org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel2, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel2.text")); // NOI18N + commonFilesSearchLabel2.setFocusable(false); + + org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.searchButton.text")); // NOI18N + searchButton.setEnabled(false); + searchButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + searchButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.cancelButton.text")); // NOI18N + cancelButton.setActionCommand(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.cancelButton.actionCommand")); // NOI18N + cancelButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + fileTypeFilterButtonGroup.add(allFileCategoriesRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.allFileCategoriesRadioButton.text")); // NOI18N + allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N + allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allFileCategoriesRadioButtonActionPerformed(evt); + } + }); + + fileTypeFilterButtonGroup.add(selectedFileCategoriesButton); + selectedFileCategoriesButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.selectedFileCategoriesButton.text")); // NOI18N + selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N + selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + selectedFileCategoriesButtonActionPerformed(evt); + } + }); + + pictureVideoCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.pictureVideoCheckbox.text")); // NOI18N + pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pictureVideoCheckboxActionPerformed(evt); + } + }); + + documentsCheckbox.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.documentsCheckbox.text")); // NOI18N + documentsCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + documentsCheckboxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.categoriesLabel.text")); // NOI18N + categoriesLabel.setName(""); // NOI18N + + errorText.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorText, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.errorText.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel1, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel1.text")); // NOI18N + commonFilesSearchLabel1.setFocusable(false); + + interIntraButtonGroup.add(intraCaseRadio); + intraCaseRadio.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(intraCaseRadio, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.intraCaseRadio.text")); // NOI18N + intraCaseRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + intraCaseRadioActionPerformed(evt); + } + }); + + interIntraButtonGroup.add(interCaseRadio); + org.openide.awt.Mnemonics.setLocalizedText(interCaseRadio, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonFilesPanel.jRadioButton2.text")); // NOI18N + interCaseRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + interCaseRadioActionPerformed(evt); + } + }); + + layoutPanel.setLayout(new java.awt.CardLayout()); + layoutPanel.add(intraCasePanel, "card3"); + layoutPanel.add(interCasePanel, "card2"); + + 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(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(searchButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorText)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(commonFilesSearchLabel2) + .addComponent(intraCaseRadio) + .addComponent(interCaseRadio) + .addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(categoriesLabel) + .addComponent(selectedFileCategoriesButton))) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(35, 35, 35) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(documentsCheckbox) + .addComponent(pictureVideoCheckbox))) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(allFileCategoriesRadioButton))) + .addContainerGap()) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(20, 20, 20) + .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(10, 10, 10))) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(commonFilesSearchLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(intraCaseRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(interCaseRadio) + .addGap(79, 79, 79) + .addComponent(categoriesLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectedFileCategoriesButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pictureVideoCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(documentsCheckbox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(allFileCategoriesRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(searchButton) + .addComponent(cancelButton) + .addComponent(errorText)) + .addContainerGap()) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGap(98, 98, 98) + .addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(180, 180, 180))) + ); + + getContentPane().add(jPanel1, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed + search(); + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_searchButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed + this.manageCheckBoxState(); + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed + + private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed + this.manageCheckBoxState(); + }//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed + + private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_pictureVideoCheckboxActionPerformed + + private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed + this.toggleErrorTextAndSearchBox(); + }//GEN-LAST:event_documentsCheckboxActionPerformed + + private void intraCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_intraCaseRadioActionPerformed + ((java.awt.CardLayout) this.layoutPanel.getLayout()).first(this.layoutPanel); + handleIntraCaseSearchCriteriaChanged(); + }//GEN-LAST:event_intraCaseRadioActionPerformed + + public void handleIntraCaseSearchCriteriaChanged() { + if (this.areIntraCaseSearchCriteriaMet()) { + this.searchButton.setEnabled(true); + this.hideErrorMessages(); + } else { + this.searchButton.setEnabled(false); + this.hideErrorMessages(); + this.showIntraCaseErrorMessage(); + } + } + + private void interCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_interCaseRadioActionPerformed + ((java.awt.CardLayout) this.layoutPanel.getLayout()).last(this.layoutPanel); + handleInterCaseSearchCriteriaChanged(); + }//GEN-LAST:event_interCaseRadioActionPerformed + + private void formWindowClosed(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosed + SwingUtilities.windowForComponent(this).dispose(); + }//GEN-LAST:event_formWindowClosed + + public void handleInterCaseSearchCriteriaChanged() { + if (this.areInterCaseSearchCriteriaMet()) { + this.searchButton.setEnabled(true); + this.hideErrorMessages(); + } else { + this.searchButton.setEnabled(false); + this.hideErrorMessages(); + this.showInterCaseErrorMessage(); + } + } + + private void toggleErrorTextAndSearchBox() { + if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) { + this.searchButton.setEnabled(false); + this.errorText.setVisible(true); + } else { + this.searchButton.setEnabled(true); + this.errorText.setVisible(false); + } + } + + private void manageCheckBoxState() { + + this.pictureViewCheckboxState = this.pictureVideoCheckbox.isSelected(); + this.documentsCheckboxState = this.documentsCheckbox.isSelected(); + + if (this.allFileCategoriesRadioButton.isSelected()) { + this.pictureVideoCheckbox.setEnabled(false); + this.documentsCheckbox.setEnabled(false); + } + + if (this.selectedFileCategoriesButton.isSelected()) { + + this.pictureVideoCheckbox.setSelected(this.pictureViewCheckboxState); + this.documentsCheckbox.setSelected(this.documentsCheckboxState); + + this.pictureVideoCheckbox.setEnabled(true); + this.documentsCheckbox.setEnabled(true); + + this.toggleErrorTextAndSearchBox(); + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allFileCategoriesRadioButton; + private javax.swing.JButton cancelButton; + private javax.swing.JLabel categoriesLabel; + private javax.swing.JLabel commonFilesSearchLabel1; + private javax.swing.JLabel commonFilesSearchLabel2; + private javax.swing.JCheckBox documentsCheckbox; + private javax.swing.JLabel errorText; + private javax.swing.ButtonGroup fileTypeFilterButtonGroup; + private org.sleuthkit.autopsy.commonfilesearch.InterCasePanel interCasePanel; + private javax.swing.JRadioButton interCaseRadio; + private javax.swing.ButtonGroup interIntraButtonGroup; + private org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel intraCasePanel; + private javax.swing.JRadioButton intraCaseRadio; + private javax.swing.JPanel jPanel1; + private java.awt.Panel layoutPanel; + private javax.swing.JCheckBox pictureVideoCheckbox; + private javax.swing.JButton searchButton; + private javax.swing.JRadioButton selectedFileCategoriesButton; + // End of variables declaration//GEN-END:variables + + void setSearchButtonEnabled(boolean enabled) { + this.searchButton.setEnabled(enabled); + } + + private boolean areIntraCaseSearchCriteriaMet() { + return this.intraCasePanel.areSearchCriteriaMet(); + } + + private boolean areInterCaseSearchCriteriaMet() { + return this.interCasePanel.areSearchCriteriaMet(); + } + + private void hideErrorMessages() { + this.errorText.setVisible(false); + } + + private void showIntraCaseErrorMessage() { + this.errorText.setText(this.intraCasePanel.getErrorMessage()); + this.errorText.setVisible(true); + } + + private void showInterCaseErrorMessage() { + this.errorText.setText(this.interCasePanel.getErrorMessage()); + this.errorText.setVisible(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java similarity index 68% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java index a07aa59d36..8e33401af7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java @@ -27,13 +27,14 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; /** - * Wrapper node for Md5Node used to display common files search - * results in the top right pane. Calls InstanceCountNodeFactory. + * Top-level node to store common file search results. Current structure is: + * - node for number of matches + * -- node for MD5/commmon attribute + * --- node for instance. */ -final public class CommonFilesNode extends DisplayableItemNode { - +final public class CommonAttributeSearchResultRootNode extends DisplayableItemNode { - CommonFilesNode(CommonFilesMetadata metadataList) { + CommonAttributeSearchResultRootNode(CommonAttributeSearchResults metadataList) { super(Children.create(new InstanceCountNodeFactory(metadataList), true)); } @@ -64,27 +65,27 @@ final public class CommonFilesNode extends DisplayableItemNode { */ static class InstanceCountNodeFactory extends ChildFactory{ - private final CommonFilesMetadata metadata; + private final CommonAttributeSearchResults searchResults; /** - * Build a factory which converts a CommonFilesMetadata + * Build a factory which converts a CommonAttributeSearchResults * object into DisplayableItemNodes. - * @param metadata + * @param searchResults */ - InstanceCountNodeFactory(CommonFilesMetadata metadata){ - this.metadata = metadata; + InstanceCountNodeFactory(CommonAttributeSearchResults searchResults){ + this.searchResults = searchResults; } - + @Override protected boolean createKeys(List list) { - list.addAll(this.metadata.getMetadata().keySet()); + list.addAll(this.searchResults.getMetadata().keySet()); return true; } @Override protected Node createNodeForKey(Integer instanceCount){ - List md5Metadata = this.metadata.getMetadataForMd5(instanceCount); - return new InstanceCountNode(instanceCount, md5Metadata); - } + List attributeValues = this.searchResults.getAttributeValuesForInstanceCount(instanceCount); + return new InstanceCountNode(instanceCount, attributeValues); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java new file mode 100644 index 0000000000..8ff13e87f1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResults.java @@ -0,0 +1,82 @@ +/* + * + * 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.commonfilesearch; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Stores the results from the various types of common attribute searching + * Stores results based on how they are currently displayed in the UI + */ +final public class CommonAttributeSearchResults { + + // maps instance count to list of attribute values. + private final Map> instanceCountToAttributeValues; + + /** + * Create a values object which can be handed off to the node factories. + * + * @param values list of CommonAttributeValue indexed by size of + * CommonAttributeValue + */ + CommonAttributeSearchResults(Map> metadata){ + this.instanceCountToAttributeValues = metadata; + } + + /** + * Find the child node whose children have the specified number of children. + * + * This is a convenience method - you can also iterate over + * getValues(). + * + * @param isntanceCound key + * @return list of values which represent matches + */ + List getAttributeValuesForInstanceCount(Integer instanceCount) { + return this.instanceCountToAttributeValues.get(instanceCount); + } + + /** + * Get an unmodifiable collection of values, indexed by number of + * grandchildren, which represents the common attributes found in the + * search. + * @return map of sizes of children to list of matches + */ +public Map> getMetadata() { + return Collections.unmodifiableMap(this.instanceCountToAttributeValues); + } + + /** + * How many distinct common files exist for this search results? + * @return number of common files + */ + public int size() { + + int count = 0; + for (List data : this.instanceCountToAttributeValues.values()) { + for(CommonAttributeValue md5 : data){ + count += md5.getInstanceCount(); + } + } + return count; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java similarity index 54% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java index 39c2cf8040..2e9dd4675a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java @@ -19,50 +19,70 @@ */ package org.sleuthkit.autopsy.commonfilesearch; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** - * Encapsulates data required to instantiate an Md5Node. + * Defines a value that was in the common file search results + * as well as information about its instances. */ -final public class Md5Metadata { - +final public class CommonAttributeValue { + private final String md5; - private final List fileInstances; - - Md5Metadata(String md5, List fileInstances){ + private final List fileInstances; + + CommonAttributeValue(String md5, List fileInstances) { this.md5 = md5; this.fileInstances = fileInstances; } - - public String getMd5(){ + + CommonAttributeValue(String md5) { + this.md5 = md5; + this.fileInstances = new ArrayList<>(); + } + + public String getValue() { return this.md5; } - - void addFileInstanceMetadata(FileInstanceMetadata metadata){ - this.fileInstances.add(metadata); - } - - public Collection getMetadata(){ - return Collections.unmodifiableCollection(this.fileInstances); - } - + /** - * How many distinct file instances exist for the MD5 represented by this object? - * @return number of instances + * concatenate cases this value was seen into a single string + * + * @return */ - public int size(){ - return this.fileInstances.size(); + public String getCases() { + return this.fileInstances.stream().map(AbstractCommonAttributeInstance::getCaseName).collect(Collectors.joining(", ")); } public String getDataSources() { - Set sources = new HashSet<> (); - for(FileInstanceMetadata data : this.fileInstances){ - sources.add(data.getDataSourceName()); + Set sources = new HashSet<>(); + for (AbstractCommonAttributeInstance data : this.fileInstances) { + sources.add(data.getDataSource()); } + return String.join(", ", sources); } + + void addInstance(AbstractCommonAttributeInstance metadata) { + this.fileInstances.add(metadata); + } + + public Collection getInstances() { + return Collections.unmodifiableCollection(this.fileInstances); + } + + /** + * How many distinct file instances exist for the MD5 represented by this + * object? + * + * @return number of instances + */ + public int getInstanceCount() { + return this.fileInstances.size(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java similarity index 64% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java index 0b10d73e30..c7c5dea58b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java @@ -20,34 +20,24 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.List; -import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** - * Represents a common files match - two or more files which appear to be the - * same file and appear as children of this node. This node will simply contain - * the MD5 of the matched files, the data sources those files were found within, - * and a count of the instances represented by the md5. + * Represents the layer in the tree for the value (such as MD5) that was in multiple places. + * Children are instances of that value. */ -public class Md5Node extends DisplayableItemNode { - - private static final Logger LOGGER = Logger.getLogger(Md5Node.class.getName()); - - private final String md5Hash; +public class CommonAttributeValueNode extends DisplayableItemNode { + + private final String value; private final int commonFileCount; + private final String cases; private final String dataSources; @NbBundle.Messages({ @@ -57,15 +47,17 @@ public class Md5Node extends DisplayableItemNode { * Create a Match node whose children will all have this object in common. * @param data the common feature, and the children */ - public Md5Node(Md5Metadata data) { + public CommonAttributeValueNode(CommonAttributeValue data) { super(Children.create( new FileInstanceNodeFactory(data), true)); - this.commonFileCount = data.size(); + this.commonFileCount = data.getInstanceCount(); + this.cases = data.getCases(); + // @@ We seem to be doing this string concat twice. We also do it in getDataSources() this.dataSources = String.join(", ", data.getDataSources()); - this.md5Hash = data.getMd5(); + this.value = data.getValue(); - this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.md5Hash)); + this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.value)); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } @@ -76,6 +68,10 @@ public class Md5Node extends DisplayableItemNode { int getCommonFileCount() { return this.commonFileCount; } + + String getCases(){ + return this.cases; + } /** * Datasources where these matches occur. @@ -89,8 +85,8 @@ public class Md5Node extends DisplayableItemNode { * MD5 which is common to these matches * @return string md5 hash */ - public String getMd5() { - return this.md5Hash; + public String getValue() { + return this.value; } @NbBundle.Messages({"Md5Node.createSheet.noDescription= "}) @@ -129,35 +125,28 @@ public class Md5Node extends DisplayableItemNode { } /** - * Child generator for FileInstanceNode of Md5Node. + * Child generator for SleuthkitCaseFileInstanceNode of + * CommonAttributeValueNode. */ - static class FileInstanceNodeFactory extends ChildFactory { + static class FileInstanceNodeFactory extends ChildFactory { - private final Md5Metadata descendants; + private final CommonAttributeValue descendants; - FileInstanceNodeFactory(Md5Metadata descendants) { + FileInstanceNodeFactory(CommonAttributeValue descendants) { this.descendants = descendants; } @Override - protected Node createNodeForKey(FileInstanceMetadata file) { - try { - Case currentCase = Case.getCurrentCaseThrows(); - SleuthkitCase tskDb = currentCase.getSleuthkitCase(); - AbstractFile abstractFile = tskDb.findAllFilesWhere(String.format("obj_id in (%s)", file.getObjectId())).get(0); - - return new FileInstanceNode(abstractFile, file.getDataSourceName()); - } catch (NoCurrentCaseException | TskCoreException ex) { - LOGGER.log(Level.SEVERE, String.format("Unable to create node for file with obj_id: %s.", new Object[]{file.getObjectId()}), ex); - } - return null; - } - - @Override - protected boolean createKeys(List list) { - list.addAll(this.descendants.getMetadata()); + protected boolean createKeys(List list) { + list.addAll(this.descendants.getInstances()); return true; } - } + + @Override + protected Node[] createNodesForKey(AbstractCommonAttributeInstance searchResult) { + return searchResult.generateNodes(); + } -} \ No newline at end of file + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java similarity index 86% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java index cacbdd83f9..c7478f3fc4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java @@ -31,18 +31,18 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; /** - * DataResultViewerTable which overrides the default column header - * width calculations. The CommonFilesSearchResultsViewerTable - * presents multiple tiers of data which are not always present and it may not - * make sense to try to calculate the column widths for such tables by sampling - * rows and looking for wide cells. Rather, we just pick some reasonable values. + * DataResultViewerTable which overrides the default column + * header width calculations. The CommonAttributesSearchResultsViewerTable + * presents multiple tiers of data which are not always present and it may not + * make sense to try to calculate the column widths for such tables by sampling + * rows and looking for wide cells. Rather, we just pick some reasonable values. */ -public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { +public class CommonAttributesSearchResultsViewerTable extends DataResultViewerTable { private static final Map COLUMN_WIDTHS; private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(CommonFilesSearchResultsViewerTable.class.getName()); + private static final Logger LOGGER = Logger.getLogger(CommonAttributesSearchResultsViewerTable.class.getName()); private static final int DEFAULT_WIDTH = 100; @@ -51,6 +51,7 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { map.put(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), 260); map.put(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), 65); map.put(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), 300); + map.put(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), 200); map.put(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), 200); map.put(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), 100); map.put(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), 130); @@ -60,11 +61,13 @@ public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { } @NbBundle.Messages({ + "CommonFilesSearchResultsViewerTable.noDescText= ", "CommonFilesSearchResultsViewerTable.filesColLbl=Files", "CommonFilesSearchResultsViewerTable.instancesColLbl=Instances", "CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path", "CommonFilesSearchResultsViewerTable.hashsetHitsColLbl=Hash Set Hits", - "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source(s)", + "CommonFilesSearchResultsViewerTable.caseColLbl1=Case", + "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source", "CommonFilesSearchResultsViewerTable.mimeTypeColLbl=MIME Type", "CommonFilesSearchResultsViewerTable.tagsColLbl1=Tags" }) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form deleted file mode 100644 index 7a6e3dabe5..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.form +++ /dev/null @@ -1,51 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java deleted file mode 100644 index 204b3136a9..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesDialog.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.commonfilesearch; - -import javax.swing.JFrame; -import javax.swing.SwingUtilities; -import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; - -/** - * Dialog box for configuring and running common files search. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class CommonFilesDialog extends javax.swing.JDialog { - - private static final long serialVersionUID = 1L; - - /** - * Creates new form CommonFilesDialog - */ - @NbBundle.Messages({ - "CommonFilesDialog.frame.title=Find Common Files", - "CommonFilesDialog.frame.msg=Find Common Files"}) - public CommonFilesDialog() { - super(new JFrame(Bundle.CommonFilesDialog_frame_title()), - Bundle.CommonFilesDialog_frame_msg(), true); - initComponents(); - - this.setResizable(false); - this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - } - - /** - * 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() { - - commonFilesPanel1 = new org.sleuthkit.autopsy.commonfilesearch.CommonFilesPanel(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setSize(new java.awt.Dimension(340, 320)); - addWindowListener(new java.awt.event.WindowAdapter() { - public void windowClosed(java.awt.event.WindowEvent evt) { - formWindowClosed(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() - .addComponent(commonFilesPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(commonFilesPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) - ); - - pack(); - setLocationRelativeTo(null); - }// //GEN-END:initComponents - - private void formWindowClosed(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosed - SwingUtilities.windowForComponent(this).dispose(); - }//GEN-LAST:event_formWindowClosed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private org.sleuthkit.autopsy.commonfilesearch.CommonFilesPanel commonFilesPanel1; - // End of variables declaration//GEN-END:variables -} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java deleted file mode 100644 index b04792fb6b..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.commonfilesearch; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Utility and wrapper model around data required for Common Files Search results. - * Subclass this to implement different selections of files from the case. - */ -final public class CommonFilesMetadata { - - private final Map> metadata; - - /** - * Create a metadata object which can be handed off to the node - * factories. - * - * @param metadata list of Md5Metadata indexed by size of Md5Metadata - */ - CommonFilesMetadata(Map> metadata){ - this.metadata = metadata; - } - - /** - * Find the meta data for the given md5. - * - * This is a convenience method - you can also iterate over - * getMetadata(). - * - * @param md5 key - * @return - */ - List getMetadataForMd5(Integer instanceCount) { - return this.metadata.get(instanceCount); - } - - public Map> getMetadata() { - return Collections.unmodifiableMap(this.metadata); - } - - /** - * How many distinct file instances exist for this metadata? - * @return number of file instances - */ - public int size() { - - int count = 0; - for (List data : this.metadata.values()) { - for(Md5Metadata md5 : data){ - count += md5.size(); - } - } - return count; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java deleted file mode 100644 index 0272278440..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.commonfilesearch; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.datamodel.HashUtility; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - * Generates a List when - * findCommonFiles() is called, which organizes files by md5 to - * prepare to display in viewer. - * - * This entire thing runs on a background thread where exceptions are handled. - */ -@SuppressWarnings("PMD.AbstractNaming") -public abstract class CommonFilesMetadataBuilder { - - private final Map dataSourceIdToNameMap; - private final boolean filterByMedia; - private final boolean filterByDoc; - private static final String FILTER_BY_MIME_TYPES_WHERE_CLAUSE = " and mime_type in (%s)"; //NON-NLS // where %s is csv list of mime_types to filter on - - /* - * The set of the MIME types that will be checked for extension mismatches - * when checkType is ONLY_MEDIA. - * ".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec" - * ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS - * ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf" - */ - private static final Set MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of( - "image/bmp", //NON-NLS - "image/gif", //NON-NLS - "image/jpeg", //NON-NLS - "image/png", //NON-NLS - "image/tiff", //NON-NLS - "image/vnd.adobe.photoshop", //NON-NLS - "image/x-raw-nikon", //NON-NLS - "image/x-ms-bmp", //NON-NLS - "image/x-icon", //NON-NLS - "video/webm", //NON-NLS - "video/3gpp", //NON-NLS - "video/3gpp2", //NON-NLS - "video/ogg", //NON-NLS - "video/mpeg", //NON-NLS - "video/mp4", //NON-NLS - "video/quicktime", //NON-NLS - "video/x-msvideo", //NON-NLS - "video/x-flv", //NON-NLS - "video/x-m4v", //NON-NLS - "video/x-ms-wmv", //NON-NLS - "application/vnd.ms-asf", //NON-NLS - "application/vnd.rn-realmedia", //NON-NLS - "application/x-shockwave-flash" //NON-NLS - ).collect(Collectors.toSet()); - - /* - * The set of the MIME types that will be checked for extension mismatches - * when checkType is ONLY_TEXT_FILES. - * ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx" - * ".txt", ".rtf", ".log", ".text", ".xml" - * ".html", ".htm", ".css", ".js", ".php", ".aspx" - * ".pdf" - */ - private static final Set TEXT_FILES_MIME_TYPES = Stream.of( - "text/plain", //NON-NLS - "application/rtf", //NON-NLS - "application/pdf", //NON-NLS - "text/css", //NON-NLS - "text/html", //NON-NLS - "text/csv", //NON-NLS - "application/json", //NON-NLS - "application/javascript", //NON-NLS - "application/xml", //NON-NLS - "text/calendar", //NON-NLS - "application/x-msoffice", //NON-NLS - "application/x-ooxml", //NON-NLS - "application/msword", //NON-NLS - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS - "application/vnd.ms-powerpoint", //NON-NLS - "application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS - "application/vnd.ms-excel", //NON-NLS - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS - "application/vnd.oasis.opendocument.presentation", //NON-NLS - "application/vnd.oasis.opendocument.spreadsheet", //NON-NLS - "application/vnd.oasis.opendocument.text" //NON-NLS - ).collect(Collectors.toSet()); - - /** - * Subclass this to implement different algorithms for getting common files. - * - * @param dataSourceIdMap a map of obj_id to datasource name - * @param filterByMediaMimeType match only on files whose mime types can be - * broadly categorized as media types - * @param filterByDocMimeType match only on files whose mime types can be - * broadly categorized as document types - */ - CommonFilesMetadataBuilder(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { - dataSourceIdToNameMap = dataSourceIdMap; - filterByMedia = filterByMediaMimeType; - filterByDoc = filterByDocMimeType; - } - - /** - * Use this as a prefix when building the SQL select statement. - * - *
    - *
  • You only have to specify the WHERE clause if you use this.
  • - *
  • If you do not use this string, you must use at least the columns - * selected below, in that order.
  • - *
- */ - static final String SELECT_PREFIX = "SELECT obj_id, md5, data_source_obj_id from tsk_files where"; //NON-NLS - - /** - * Should build a SQL SELECT statement to be passed to - * SleuthkitCase.executeQuery(sql) which will select the desired file ids - * and MD5 hashes. - * - * The statement should select obj_id, md5, data_source_obj_id in that - * order. - * - * @return sql string select statement - */ - protected abstract String buildSqlSelectStatement(); - - /** - * Generate a meta data object which encapsulates everything need to add the - * tree table tab to the top component. - * - * @return a data object with all of the matched files in a hierarchical - * format - * @throws TskCoreException - * @throws NoCurrentCaseException - * @throws SQLException - */ - public CommonFilesMetadata findCommonFiles() throws TskCoreException, NoCurrentCaseException, SQLException { - - Map commonFiles = new HashMap<>(); - - SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - String selectStatement = this.buildSqlSelectStatement(); - - try ( - CaseDbQuery query = sleuthkitCase.executeQuery(selectStatement); - ResultSet resultSet = query.getResultSet()) { - - while (resultSet.next()) { - Long objectId = resultSet.getLong(1); - String md5 = resultSet.getString(2); - Long dataSourceId = resultSet.getLong(3); - String dataSource = this.dataSourceIdToNameMap.get(dataSourceId); - - if (md5 == null || HashUtility.isNoDataMd5(md5)) { - continue; - } - - if (commonFiles.containsKey(md5)) { - final Md5Metadata md5Metadata = commonFiles.get(md5); - md5Metadata.addFileInstanceMetadata(new FileInstanceMetadata(objectId, dataSource)); - } else { - final List fileInstances = new ArrayList<>(); - fileInstances.add(new FileInstanceMetadata(objectId, dataSource)); - Md5Metadata md5Metadata = new Md5Metadata(md5, fileInstances); - commonFiles.put(md5, md5Metadata); - } - } - } - - Map> instanceCollatedCommonFiles = new TreeMap<>(); - - for(Md5Metadata md5Metadata : commonFiles.values()){ - Integer size = md5Metadata.size(); - - if(instanceCollatedCommonFiles.containsKey(size)){ - instanceCollatedCommonFiles.get(size).add(md5Metadata); - } else { - ArrayList value = new ArrayList<>(); - value.add(md5Metadata); - instanceCollatedCommonFiles.put(size, value); - } - } - - return new CommonFilesMetadata(instanceCollatedCommonFiles); - } - - /** - * Should be used by subclasses, in their - * buildSqlSelectStatement() function to create an SQL boolean - * expression which will filter our matches based on mime type. The - * expression will be conjoined to base query with an AND operator. - * - * @return sql fragment of the form: - * 'and "mime_type" in ( [comma delimited list of mime types] )' - * or empty string in the event that no types to filter on were given. - */ - String determineMimeTypeFilter() { - - Set mimeTypesToFilterOn = new HashSet<>(); - String mimeTypeString = ""; - if (filterByMedia) { - mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES); - } - if (filterByDoc) { - mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES); - } - StringBuilder mimeTypeFilter = new StringBuilder(mimeTypesToFilterOn.size()); - if (!mimeTypesToFilterOn.isEmpty()) { - for (String mimeType : mimeTypesToFilterOn) { - mimeTypeFilter.append("'").append(mimeType).append("',"); - } - mimeTypeString = mimeTypeFilter.toString().substring(0, mimeTypeFilter.length() - 1); - mimeTypeString = String.format(FILTER_BY_MIME_TYPES_WHERE_CLAUSE, new Object[]{mimeTypeString}); - } - return mimeTypeString; - } - - @NbBundle.Messages({ - "CommonFilesMetadataBuilder.buildTabTitle.titleAll=Common Files (All Data Sources, %s)", - "CommonFilesMetadataBuilder.buildTabTitle.titleSingle=Common Files (Match Within Data Source: %s, %s)" - }) - protected abstract String buildTabTitle(); - - @NbBundle.Messages({ - "CommonFilesMetadataBuilder.buildCategorySelectionString.doc=Documents", - "CommonFilesMetadataBuilder.buildCategorySelectionString.media=Media", - "CommonFilesMetadataBuilder.buildCategorySelectionString.all=All File Categories" - }) - - protected String buildCategorySelectionString() { - if (!this.filterByDoc && !this.filterByMedia) { - return Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_all(); - } else { - List filters = new ArrayList<>(); - if (this.filterByDoc) { - filters.add(Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_doc()); - } - if (this.filterByMedia) { - filters.add(Bundle.CommonFilesMetadataBuilder_buildCategorySelectionString_media()); - } - return String.join(", ", filters); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form deleted file mode 100644 index c9d08c0536..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.form +++ /dev/null @@ -1,261 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java deleted file mode 100644 index 0e94e561b2..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.commonfilesearch; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import javax.swing.ComboBoxModel; -import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; -import org.netbeans.api.progress.ProgressHandle; -import org.openide.explorer.ExplorerManager; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.TableFilterNode; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Panel used for common files search configuration and configuration business - * logic. Nested within CommonFilesDialog. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class CommonFilesPanel extends javax.swing.JPanel { - - private static final long serialVersionUID = 1L; - - private static final Long NO_DATA_SOURCE_SELECTED = -1L; - - private ComboBoxModel dataSourcesList = new DataSourceComboBoxModel(); - private Map dataSourceMap; - - private static final Logger LOGGER = Logger.getLogger(CommonFilesPanel.class.getName()); - private boolean singleDataSource = false; - private String selectedDataSource = ""; - private boolean pictureViewCheckboxState; - private boolean documentsCheckboxState; - - /** - * Creates new form CommonFilesPanel - */ - @NbBundle.Messages({ - "CommonFilesPanel.title=Common Files Panel", - "CommonFilesPanel.exception=Unexpected Exception loading DataSources."}) - public CommonFilesPanel() { - initComponents(); - - this.setupDataSources(); - - this.errorText.setVisible(false); - } - - /** - * Sets up the data sources dropdown and returns the data sources map for - * future usage. - * - * @return a mapping of data source ids to data source names - */ - @NbBundle.Messages({ - "CommonFilesPanel.buildDataSourceMap.done.tskCoreException=Unable to run query against DB.", - "CommonFilesPanel.buildDataSourceMap.done.noCurrentCaseException=Unable to open case file.", - "CommonFilesPanel.buildDataSourceMap.done.exception=Unexpected exception building data sources map.", - "CommonFilesPanel.buildDataSourceMap.done.interupted=Something went wrong building the Common Files Search dialog box.", - "CommonFilesPanel.buildDataSourceMap.done.sqlException=Unable to query db for data sources.", - "CommonFilesPanel.buildDataSourcesMap.updateUi.noDataSources=No data sources were found."}) - private void setupDataSources() { - - new SwingWorker, Void>() { - - private void updateUi() { - - String[] dataSourcesNames = new String[CommonFilesPanel.this.dataSourceMap.size()]; - - //only enable all this stuff if we actually have datasources - if (dataSourcesNames.length > 0) { - dataSourcesNames = CommonFilesPanel.this.dataSourceMap.values().toArray(dataSourcesNames); - CommonFilesPanel.this.dataSourcesList = new DataSourceComboBoxModel(dataSourcesNames); - CommonFilesPanel.this.selectDataSourceComboBox.setModel(CommonFilesPanel.this.dataSourcesList); - - boolean multipleDataSources = this.caseHasMultipleSources(); - - CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(true); - CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(true); - - if (!multipleDataSources) { - CommonFilesPanel.this.withinDataSourceRadioButton.setEnabled(false); - CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(false); - withinDataSourceSelected(false); - CommonFilesPanel.this.selectDataSourceComboBox.setEnabled(false); - } - - CommonFilesPanel.this.searchButton.setEnabled(true); - } else { - MessageNotifyUtil.Message.info(Bundle.CommonFilesPanel_buildDataSourcesMap_updateUi_noDataSources()); - CommonFilesPanel.this.cancelButtonActionPerformed(null); - } - } - - private boolean caseHasMultipleSources() { - return CommonFilesPanel.this.dataSourceMap.size() >= 3; - } - - @Override - protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, SQLException { - DataSourceLoader loader = new DataSourceLoader(); - return loader.getDataSourceMap(); - } - - @Override - protected void done() { - - try { - CommonFilesPanel.this.dataSourceMap = this.get(); - - updateUi(); - - } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex); - MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_buildDataSourceMap_done_interupted()); - } catch (ExecutionException ex) { - String errorMessage; - Throwable inner = ex.getCause(); - if (inner instanceof TskCoreException) { - LOGGER.log(Level.SEVERE, "Failed to load data sources from database.", ex); - errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_tskCoreException(); - } else if (inner instanceof NoCurrentCaseException) { - LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); - errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_noCurrentCaseException(); - } else if (inner instanceof SQLException) { - LOGGER.log(Level.SEVERE, "Unable to query db for data sources.", ex); - errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_sqlException(); - } else { - LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog panel.", ex); - errorMessage = Bundle.CommonFilesPanel_buildDataSourceMap_done_exception(); - } - MessageNotifyUtil.Message.error(errorMessage); - } - } - }.execute(); - } - - @NbBundle.Messages({ - "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)", - "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)", - "CommonFilesPanel.search.results.pathText=Common Files Search Results", - "CommonFilesPanel.search.done.searchProgressGathering=Gathering Common Files Search Results.", - "CommonFilesPanel.search.done.searchProgressDisplay=Displaying Common Files Search Results.", - "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.", - "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.", - "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.", - "CommonFilesPanel.search.done.interupted=Something went wrong finding common files.", - "CommonFilesPanel.search.done.sqlException=Unable to query db for files or data sources."}) - private void search() { - String pathText = Bundle.CommonFilesPanel_search_results_pathText(); - - new SwingWorker() { - - private String tabTitle; - private ProgressHandle progress; - - private void setTitleForAllDataSources() { - this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll(); - } - - private void setTitleForSingleSource(Long dataSourceId) { - final String CommonFilesPanel_search_results_titleSingle = Bundle.CommonFilesPanel_search_results_titleSingle(); - final Object[] dataSourceName = new Object[]{dataSourceMap.get(dataSourceId)}; - - this.tabTitle = String.format(CommonFilesPanel_search_results_titleSingle, dataSourceName); - } - - private Long determineDataSourceId() { - Long selectedObjId = CommonFilesPanel.NO_DATA_SOURCE_SELECTED; - if (CommonFilesPanel.this.singleDataSource) { - for (Entry dataSource : CommonFilesPanel.this.dataSourceMap.entrySet()) { - if (dataSource.getValue().equals(CommonFilesPanel.this.selectedDataSource)) { - selectedObjId = dataSource.getKey(); - break; - } - } - } - return selectedObjId; - } - - @Override - @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) - protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { - progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering()); - progress.start(); - progress.switchToIndeterminate(); - - Long dataSourceId = determineDataSourceId(); - - CommonFilesMetadataBuilder builder; - boolean filterByMedia = false; - boolean filterByDocuments = false; - if (selectedFileCategoriesButton.isSelected()) { - if (pictureVideoCheckbox.isSelected()) { - filterByMedia = true; - } - if (documentsCheckbox.isSelected()) { - filterByDocuments = true; - } - } - if (dataSourceId == CommonFilesPanel.NO_DATA_SOURCE_SELECTED) { - builder = new AllDataSourcesCommonFilesAlgorithm(CommonFilesPanel.this.dataSourceMap, filterByMedia, filterByDocuments); - - setTitleForAllDataSources(); - } else { - builder = new SingleDataSource(dataSourceId, CommonFilesPanel.this.dataSourceMap, filterByMedia, filterByDocuments); - - setTitleForSingleSource(dataSourceId); - } - - this.tabTitle = builder.buildTabTitle(); - - CommonFilesMetadata metadata = builder.findCommonFiles(); - - return metadata; - } - - @Override - protected void done() { - try { - super.done(); - CommonFilesMetadata metadata = get(); - - CommonFilesNode commonFilesNode = new CommonFilesNode(metadata); - - //TODO this could be enumerating the children!!! - DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonFilesPanel.this)); - - TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); - - DataResultViewerTable table = new CommonFilesSearchResultsViewerTable(); - - Collection viewers = new ArrayList<>(1); - viewers.add(table); - progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay()); - DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); - } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex); - MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted()); - } catch (ExecutionException ex) { - String errorMessage; - Throwable inner = ex.getCause(); - if (inner instanceof TskCoreException) { - LOGGER.log(Level.SEVERE, "Failed to load files from database.", ex); - errorMessage = Bundle.CommonFilesPanel_search_done_tskCoreException(); - } else if (inner instanceof NoCurrentCaseException) { - LOGGER.log(Level.SEVERE, "Current case has been closed.", ex); - errorMessage = Bundle.CommonFilesPanel_search_done_noCurrentCaseException(); - } else if (inner instanceof SQLException) { - LOGGER.log(Level.SEVERE, "Unable to query db for files.", ex); - errorMessage = Bundle.CommonFilesPanel_search_done_sqlException(); - } else { - LOGGER.log(Level.SEVERE, "Unexpected exception while running Common Files Search.", ex); - errorMessage = Bundle.CommonFilesPanel_search_done_exception(); - } - MessageNotifyUtil.Message.error(errorMessage); - } finally { - progress.finish(); - } - } - }.execute(); - } - - /** - * 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() { - - dataSourcesButtonGroup = new javax.swing.ButtonGroup(); - fileTypeFilterButtonGroup = new javax.swing.ButtonGroup(); - searchButton = new javax.swing.JButton(); - allDataSourcesRadioButton = new javax.swing.JRadioButton(); - withinDataSourceRadioButton = new javax.swing.JRadioButton(); - selectDataSourceComboBox = new javax.swing.JComboBox<>(); - commonFilesSearchLabel = new javax.swing.JLabel(); - cancelButton = new javax.swing.JButton(); - allFileCategoriesRadioButton = new javax.swing.JRadioButton(); - selectedFileCategoriesButton = new javax.swing.JRadioButton(); - pictureVideoCheckbox = new javax.swing.JCheckBox(); - documentsCheckbox = new javax.swing.JCheckBox(); - dataSourceLabel = new javax.swing.JLabel(); - categoriesLabel = new javax.swing.JLabel(); - errorText = new javax.swing.JLabel(); - - org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.searchButton.text")); // NOI18N - searchButton.setEnabled(false); - searchButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); - searchButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - searchButtonActionPerformed(evt); - } - }); - - dataSourcesButtonGroup.add(allDataSourcesRadioButton); - allDataSourcesRadioButton.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(allDataSourcesRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allDataSourcesRadioButton.text")); // NOI18N - allDataSourcesRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - allDataSourcesRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - allDataSourcesRadioButtonActionPerformed(evt); - } - }); - - dataSourcesButtonGroup.add(withinDataSourceRadioButton); - org.openide.awt.Mnemonics.setLocalizedText(withinDataSourceRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.withinDataSourceRadioButton.text")); // NOI18N - withinDataSourceRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - withinDataSourceRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - withinDataSourceRadioButtonActionPerformed(evt); - } - }); - - selectDataSourceComboBox.setModel(dataSourcesList); - selectDataSourceComboBox.setEnabled(false); - selectDataSourceComboBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - selectDataSourceComboBoxActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.commonFilesSearchLabel.text")); // NOI18N - commonFilesSearchLabel.setFocusable(false); - - org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.cancelButton.text")); // NOI18N - cancelButton.setActionCommand(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.cancelButton.actionCommand")); // NOI18N - cancelButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING); - cancelButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cancelButtonActionPerformed(evt); - } - }); - - fileTypeFilterButtonGroup.add(allFileCategoriesRadioButton); - org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allFileCategoriesRadioButton.text")); // NOI18N - allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N - allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - allFileCategoriesRadioButtonActionPerformed(evt); - } - }); - - fileTypeFilterButtonGroup.add(selectedFileCategoriesButton); - selectedFileCategoriesButton.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.selectedFileCategoriesButton.text")); // NOI18N - selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.selectedFileCategoriesButton.toolTipText")); // NOI18N - selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - selectedFileCategoriesButtonActionPerformed(evt); - } - }); - - pictureVideoCheckbox.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.pictureVideoCheckbox.text")); // NOI18N - pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - pictureVideoCheckboxActionPerformed(evt); - } - }); - - documentsCheckbox.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.documentsCheckbox.text")); // NOI18N - documentsCheckbox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - documentsCheckboxActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(dataSourceLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.text")); // NOI18N - dataSourceLabel.setName(""); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.categoriesLabel.text")); // NOI18N - categoriesLabel.setName(""); // NOI18N - - errorText.setForeground(new java.awt.Color(255, 0, 0)); - org.openide.awt.Mnemonics.setLocalizedText(errorText, org.openide.util.NbBundle.getMessage(CommonFilesPanel.class, "CommonFilesPanel.errorText.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() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(errorText) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(searchButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton) - .addContainerGap()) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(categoriesLabel) - .addComponent(commonFilesSearchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(dataSourceLabel) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(29, 29, 29) - .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(withinDataSourceRadioButton) - .addComponent(allDataSourcesRadioButton) - .addComponent(allFileCategoriesRadioButton) - .addComponent(selectedFileCategoriesButton) - .addGroup(layout.createSequentialGroup() - .addGap(21, 21, 21) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pictureVideoCheckbox) - .addComponent(documentsCheckbox)))))) - .addGap(19, 19, 19)))) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addComponent(commonFilesSearchLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(dataSourceLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(allDataSourcesRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(withinDataSourceRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(categoriesLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(selectedFileCategoriesButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pictureVideoCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(documentsCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(allFileCategoriesRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cancelButton) - .addComponent(searchButton) - .addComponent(errorText))) - ); - }// //GEN-END:initComponents - - private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed - search(); - SwingUtilities.windowForComponent(this).dispose(); - }//GEN-LAST:event_searchButtonActionPerformed - - private void allDataSourcesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allDataSourcesRadioButtonActionPerformed - selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected()); - singleDataSource = false; - }//GEN-LAST:event_allDataSourcesRadioButtonActionPerformed - - private void selectDataSourceComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectDataSourceComboBoxActionPerformed - final Object selectedItem = selectDataSourceComboBox.getSelectedItem(); - if (selectedItem != null) { - selectedDataSource = selectedItem.toString(); - } else { - selectedDataSource = ""; - } - }//GEN-LAST:event_selectDataSourceComboBoxActionPerformed - - private void withinDataSourceRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_withinDataSourceRadioButtonActionPerformed - withinDataSourceSelected(withinDataSourceRadioButton.isSelected()); - }//GEN-LAST:event_withinDataSourceRadioButtonActionPerformed - - private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - SwingUtilities.windowForComponent(this).dispose(); - }//GEN-LAST:event_cancelButtonActionPerformed - - private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed - this.manageCheckBoxState(); - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed - - private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed - this.manageCheckBoxState(); - }//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed - - private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_pictureVideoCheckboxActionPerformed - - private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed - this.toggleErrorTextAndSearchBox(); - }//GEN-LAST:event_documentsCheckboxActionPerformed - - private void toggleErrorTextAndSearchBox() { - if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) { - this.searchButton.setEnabled(false); - this.errorText.setVisible(true); - } else { - this.searchButton.setEnabled(true); - this.errorText.setVisible(false); - } - } - - private void withinDataSourceSelected(boolean selected) { - selectDataSourceComboBox.setEnabled(selected); - if (selectDataSourceComboBox.isEnabled()) { - selectDataSourceComboBox.setSelectedIndex(0); - singleDataSource = true; - } - } - - private void manageCheckBoxState() { - - if (this.allFileCategoriesRadioButton.isSelected()) { - - this.pictureViewCheckboxState = this.pictureVideoCheckbox.isSelected(); - this.documentsCheckboxState = this.documentsCheckbox.isSelected(); - - this.pictureVideoCheckbox.setEnabled(false); - this.documentsCheckbox.setEnabled(false); - } - - if (this.selectedFileCategoriesButton.isSelected()) { - - this.pictureVideoCheckbox.setSelected(this.pictureViewCheckboxState); - this.documentsCheckbox.setSelected(this.documentsCheckboxState); - - this.pictureVideoCheckbox.setEnabled(true); - this.documentsCheckbox.setEnabled(true); - - this.toggleErrorTextAndSearchBox(); - } - } - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JRadioButton allDataSourcesRadioButton; - private javax.swing.JRadioButton allFileCategoriesRadioButton; - private javax.swing.JButton cancelButton; - private javax.swing.JLabel categoriesLabel; - private javax.swing.JLabel commonFilesSearchLabel; - private javax.swing.JLabel dataSourceLabel; - private javax.swing.ButtonGroup dataSourcesButtonGroup; - private javax.swing.JCheckBox documentsCheckbox; - private javax.swing.JLabel errorText; - private javax.swing.ButtonGroup fileTypeFilterButtonGroup; - private javax.swing.JCheckBox pictureVideoCheckbox; - private javax.swing.JButton searchButton; - private javax.swing.JComboBox selectDataSourceComboBox; - private javax.swing.JRadioButton selectedFileCategoriesButton; - private javax.swing.JRadioButton withinDataSourceRadioButton; - // End of variables declaration//GEN-END:variables -} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java index 5a3f887564..6cc00256dd 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -24,6 +24,8 @@ import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -32,9 +34,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; */ final public class CommonFilesSearchAction extends CallableSystemAction { + private static final Logger LOGGER = Logger.getLogger(CommonFilesSearchAction.class.getName()); + private static CommonFilesSearchAction instance = null; private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(CommonFilesSearchAction.class.getName()); CommonFilesSearchAction() { super(); @@ -45,9 +48,22 @@ final public class CommonFilesSearchAction extends CallableSystemAction { public boolean isEnabled(){ boolean shouldBeEnabled = false; try { - shouldBeEnabled = Case.isCaseOpen() && Case.getCurrentCase().getDataSources().size() > 1; + //dont refactor any of this to pull out common expressions - order of evaluation of each expression is significant + shouldBeEnabled = + (Case.isCaseOpen() && + Case.getCurrentCase().getDataSources().size() > 1) + || + (EamDb.isEnabled() && + EamDb.getInstance() != null && + EamDb.getInstance().getCases().size() > 1 && + Case.isCaseOpen() && + Case.getCurrentCase() != null && + EamDb.getInstance().getCase(Case.getCurrentCase()) != null); + } catch(TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting data sources for action enabled check", ex); + LOGGER.log(Level.SEVERE, "Error getting data sources for action enabled check", ex); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error getting CR cases for action enabled check", ex); } return super.isEnabled() && shouldBeEnabled; } @@ -61,12 +77,12 @@ final public class CommonFilesSearchAction extends CallableSystemAction { @Override public void actionPerformed(ActionEvent event) { - new CommonFilesDialog().setVisible(true); + new CommonAttributePanel().setVisible(true); } @Override public void performAction() { - new CommonFilesDialog().setVisible(true); + new CommonAttributePanel().setVisible(true); } @NbBundle.Messages({ diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java index 657fedc53b..1b55e6bdc3 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceComboBoxModel.java @@ -30,7 +30,7 @@ public class DataSourceComboBoxModel extends AbstractListModel implement private static final long serialVersionUID = 1L; private final String[] dataSourceList; - String selection = null; + private String selection = null; /** * Use this to initialize the panel diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java deleted file mode 100644 index 0e53cc6992..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceMetadata.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.commonfilesearch; - -/** - * Encapsulates data required to instantiate a FileInstanceNode. - */ -final public class FileInstanceMetadata { - - private final Long objectId; - private final String dataSourceName; - - /** - * Create meta data required to find an abstract file and build a FileInstanceNode. - * @param objectId id of abstract file to find - * @param dataSourceName name of datasource where the object is found - */ - FileInstanceMetadata(Long objectId, String dataSourceName) { - this.objectId = objectId; - this.dataSourceName = dataSourceName; - } - - /** - * obj_id for the file represented by this object - * @return - */ - public Long getObjectId(){ - return this.objectId; - } - - /** - * Name of datasource where this instance was found. - * @return - */ - public String getDataSourceName(){ - return this.dataSourceName; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java index 987e6faa08..dbc3d362e9 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -40,24 +40,24 @@ import org.sleuthkit.autopsy.datamodel.NodeProperty; final public class InstanceCountNode extends DisplayableItemNode { final private int instanceCount; - final private List metadataList; + final private List attributeValues; /** * Create a node with the given number of instances, and the given * selection of metadata. * @param instanceCount - * @param md5Metadata + * @param attributeValues */ @NbBundle.Messages({ "InstanceCountNode.displayName=Files with %s instances (%s)" }) - public InstanceCountNode(int instanceCount, List md5Metadata) { - super(Children.create(new Md5NodeFactory(md5Metadata), true)); + public InstanceCountNode(int instanceCount, List attributeValues) { + super(Children.create(new CommonAttributeValueNodeFactory(attributeValues), true)); this.instanceCount = instanceCount; - this.metadataList = md5Metadata; + this.attributeValues = attributeValues; - this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), md5Metadata.size())); + this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), attributeValues.size())); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } @@ -73,8 +73,8 @@ final public class InstanceCountNode extends DisplayableItemNode { * Get a list of metadata for the MD5s which are children of this object. * @return List */ - List getMetadata() { - return Collections.unmodifiableList(this.metadataList); + List getAttributeValues() { + return Collections.unmodifiableList(this.attributeValues); } @Override @@ -113,34 +113,36 @@ final public class InstanceCountNode extends DisplayableItemNode { * ChildFactory which builds CommonFileParentNodes from the * CommonFilesMetaaData models. */ - static class Md5NodeFactory extends ChildFactory { + static class CommonAttributeValueNodeFactory extends ChildFactory { /** * List of models, each of which is a parent node matching a single md5, * containing children FileNodes. */ - private final Map metadata; + // maps sting version of value to value Object (??) + private final Map metadata; - Md5NodeFactory(List metadata) { + CommonAttributeValueNodeFactory(List attributeValues) { this.metadata = new HashMap<>(); - Iterator iterator = metadata.iterator(); + Iterator iterator = attributeValues.iterator(); while (iterator.hasNext()) { - Md5Metadata md5Metadata = iterator.next(); - this.metadata.put(md5Metadata.getMd5(), md5Metadata); + CommonAttributeValue attributeValue = iterator.next(); + this.metadata.put(attributeValue.getValue(), attributeValue); } } - @Override - protected Node createNodeForKey(String md5) { - Md5Metadata md5Metadata = this.metadata.get(md5); - return new Md5Node(md5Metadata); - } - @Override protected boolean createKeys(List list) { + // @@@ We should just use CommonAttributeValue as the key... list.addAll(this.metadata.keySet()); return true; } + + @Override + protected Node createNodeForKey(String attributeValue) { + CommonAttributeValue md5Metadata = this.metadata.get(attributeValue); + return new CommonAttributeValueNode(md5Metadata); + } } } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java new file mode 100644 index 0000000000..2c745b5271 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseCommonAttributeSearcher.java @@ -0,0 +1,59 @@ +/* + * + * 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.commonfilesearch; + +import java.util.Map; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; + +/** + * Provides logic for selecting common files from all data sources and all cases + * in the Central Repo. + */ +abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeSearcher { + + private final EamDb dbManager; + + /** + * Implements the algorithm for getting common files across all data sources + * and all cases. Can filter on mime types conjoined by logical AND. + * + * @param filterByMediaMimeType match only on files whose mime types can be + * broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be + * broadly categorized as document types + * + * @throws EamDbException + */ + InterCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + dbManager = EamDb.getInstance(); + } + + protected CorrelationCase getCorrelationCaseFromId(int correlationCaseId) throws EamDbException { + for (CorrelationCase cCase : this.dbManager.getCases()) { + if (cCase.getID() == correlationCaseId) { + return cCase; + } + } + throw new IllegalArgumentException("Cannot locate case."); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.form new file mode 100644 index 0000000000..8c62356803 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.form @@ -0,0 +1,89 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java new file mode 100644 index 0000000000..67aa40134b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java @@ -0,0 +1,204 @@ +/* + * + * 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.commonfilesearch; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.swing.ComboBoxModel; +import org.openide.util.NbBundle; + +/** + * UI controls for Common Files Search scenario where the user intends to find + * common files between cases in addition to the present case. + */ +public class InterCasePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + static final int NO_CASE_SELECTED = -1; + + private ComboBoxModel casesList = new DataSourceComboBoxModel(); + + private final Map caseMap; + + private String errorMessage; + + //True if we are looking in any or all cases, + // false if we must find matches in a given case plus the current case + private boolean anyCase; + + /** + * Creates new form InterCasePanel + */ + public InterCasePanel() { + initComponents(); + this.errorMessage = ""; + this.caseMap = new HashMap<>(); + this.anyCase = true; + } + + private void specificCaseSelected(boolean selected) { + this.specificCentralRepoCaseRadio.setEnabled(selected); + if (this.specificCentralRepoCaseRadio.isEnabled()) { + this.caseComboBox.setEnabled(true); + this.caseComboBox.setSelectedIndex(0); + } + } + + String getErrorMessage(){ + return this.errorMessage; + } + + /** + * 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() { + + buttonGroup = new javax.swing.ButtonGroup(); + anyCentralRepoCaseRadio = new javax.swing.JRadioButton(); + specificCentralRepoCaseRadio = new javax.swing.JRadioButton(); + caseComboBox = new javax.swing.JComboBox<>(); + + buttonGroup.add(anyCentralRepoCaseRadio); + anyCentralRepoCaseRadio.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(anyCentralRepoCaseRadio, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.anyCentralRepoCaseRadio.text")); // NOI18N + anyCentralRepoCaseRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + anyCentralRepoCaseRadioActionPerformed(evt); + } + }); + + buttonGroup.add(specificCentralRepoCaseRadio); + org.openide.awt.Mnemonics.setLocalizedText(specificCentralRepoCaseRadio, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.specificCentralRepoCaseRadio.text")); // NOI18N + specificCentralRepoCaseRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + specificCentralRepoCaseRadioActionPerformed(evt); + } + }); + + caseComboBox.setModel(casesList); + caseComboBox.setEnabled(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(anyCentralRepoCaseRadio) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(caseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(specificCentralRepoCaseRadio)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(anyCentralRepoCaseRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(specificCentralRepoCaseRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(caseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + private void specificCentralRepoCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specificCentralRepoCaseRadioActionPerformed + this.caseComboBox.setEnabled(true); + if(this.caseComboBox.isEnabled() && this.caseComboBox.getSelectedItem() == null){ + this.caseComboBox.setSelectedIndex(0); + } + this.anyCase = false; + }//GEN-LAST:event_specificCentralRepoCaseRadioActionPerformed + + private void anyCentralRepoCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_anyCentralRepoCaseRadioActionPerformed + this.caseComboBox.setEnabled(false); + this.anyCase = true; + }//GEN-LAST:event_anyCentralRepoCaseRadioActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton anyCentralRepoCaseRadio; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JComboBox caseComboBox; + private javax.swing.JRadioButton specificCentralRepoCaseRadio; + // End of variables declaration//GEN-END:variables + + Map getCaseMap() { + return Collections.unmodifiableMap(this.caseMap); + } + + void setCaseList(DataSourceComboBoxModel dataSourceComboBoxModel) { + this.casesList = dataSourceComboBoxModel; + this.caseComboBox.setModel(dataSourceComboBoxModel); + } + + void rigForMultipleCases(boolean multipleCases) { + this.anyCentralRepoCaseRadio.setEnabled(multipleCases); + this.anyCentralRepoCaseRadio.setEnabled(multipleCases); + + if(!multipleCases){ + this.specificCentralRepoCaseRadio.setSelected(true); + this.specificCaseSelected(true); + } + } + + void setCaseMap(Map caseMap) { + this.caseMap.clear(); + this.caseMap.putAll(caseMap); + } + + boolean centralRepoHasMultipleCases() { + return this.caseMap.size() >= 2; + } + + Integer getSelectedCaseId(){ + if(this.anyCase){ + return InterCasePanel.NO_CASE_SELECTED; + } + + for(Entry entry : this.caseMap.entrySet()){ + if(entry.getValue().equals(this.caseComboBox.getSelectedItem())){ + return entry.getKey(); + } + } + + return InterCasePanel.NO_CASE_SELECTED; + } + + @NbBundle.Messages({ + "InterCasePanel.showInterCaseErrorMessage.message=Cannot run intercase correlation search: no cases in Central Repository." + }) + boolean areSearchCriteriaMet() { + if(this.caseMap.isEmpty()){ + this.errorMessage = Bundle.InterCasePanel_showInterCaseErrorMessage_message(); + return false; + } else { + return true; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java new file mode 100644 index 0000000000..571094d14f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java @@ -0,0 +1,241 @@ +/* + * 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.commonfilesearch; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.HashUtility; + +/** + * Used to process and return CorrelationCase md5s from the EamDB for + * CommonFilesSearch. + */ +final class InterCaseSearchResultsProcessor { + + private Map dataSources; + + private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName()); + + private final String interCaseWhereClause = "value IN (SELECT value FROM file_instances" + + " WHERE value IN (SELECT value FROM file_instances" + + " WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)" + + " GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value"; + + private final String singleInterCaseWhereClause = "value IN (SELECT value FROM file_instances " + + "WHERE value IN (SELECT value FROM file_instances " + + "WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value) " + + "AND (case_id=%s OR case_id=%s) GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value"; + + /** + * Used in the InterCaseCommonAttributeSearchers to find common attribute instances and generate nodes at the UI level. + * @param dataSources + */ + InterCaseSearchResultsProcessor(Map dataSources){ + this.dataSources = dataSources; + } + + /** + * Used in the CentralRepoCommonAttributeInstance to find common attribute instances and generate nodes at the UI level. + */ + InterCaseSearchResultsProcessor(){ + //intentionally emtpy - we need a constructor which does not set the data sources field + } + + /** + * Finds a single CorrelationAttribute given an id. + * + * @param attrbuteId Row of CorrelationAttribute to retrieve from the EamDb + * @return CorrelationAttribute object representation of retrieved match + */ + CorrelationAttribute findSingleCorrelationAttribute(int attrbuteId) { + try { + InterCaseCommonAttributeRowCallback instancetableCallback = new InterCaseCommonAttributeRowCallback(); + EamDb DbManager = EamDb.getInstance(); + CorrelationAttribute.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + DbManager.processInstanceTableWhere(fileType, String.format("id = %s", attrbuteId), instancetableCallback); + + return instancetableCallback.getCorrelationAttribute(); + + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error accessing EamDb processing InstanceTable row.", ex); + } + + return null; + } + + /** + * Given the current case, fins all intercase common files from the EamDb + * and builds maps of obj id to md5 and case. + * + * @param currentCase The current TSK Case. + */ + Map> findInterCaseCommonAttributeValues(Case currentCase) { + try { + InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback(); + EamDb DbManager = EamDb.getInstance(); + CorrelationAttribute.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + int caseId = DbManager.getCase(currentCase).getID(); + + DbManager.processInstanceTableWhere(fileType, String.format(interCaseWhereClause, caseId, + TskData.FileKnown.KNOWN.getFileKnownValue()), + instancetableCallback); + + return instancetableCallback.getInstanceCollatedCommonFiles(); + + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex); + } + return new HashMap<>(); + } + + /** + * Given the current case, and a specific case of interest, finds common + * files which exist between cases from the EamDb. Builds maps of obj id to + * md5 and case. + * + * @param currentCase The current TSK Case. + * @param singleCase The case of interest. Matches must exist in this case. + */ + Map> findSingleInterCaseCommonAttributeValues(Case currentCase, CorrelationCase singleCase) { + try { + InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback(); + EamDb DbManager = EamDb.getInstance(); + CorrelationAttribute.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + int caseId = DbManager.getCase(currentCase).getID(); + int targetCaseId = singleCase.getID(); + DbManager.processInstanceTableWhere(fileType, String.format(singleInterCaseWhereClause, caseId, + TskData.FileKnown.KNOWN.getFileKnownValue(), caseId, targetCaseId), instancetableCallback); + return instancetableCallback.getInstanceCollatedCommonFiles(); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex); + } + return new HashMap<>(); + } + + /** + * Callback to use with findInterCaseCommonAttributeValues which generates a + * list of md5s for common files search + */ + private class InterCaseCommonAttributesCallback implements InstanceTableCallback { + + final Map> instanceCollatedCommonFiles = new HashMap<>(); + + private CommonAttributeValue commonAttributeValue = null; + private String previousRowMd5 = ""; + + @Override + public void process(ResultSet resultSet) { + try { + while (resultSet.next()) { + + int resultId = InstanceTableCallback.getId(resultSet); + String md5Value = InstanceTableCallback.getValue(resultSet); + if (previousRowMd5.isEmpty()) { + previousRowMd5 = md5Value; + } + if (md5Value == null || HashUtility.isNoDataMd5(md5Value)) { + continue; + } + + countAndAddCommonAttributes(md5Value, resultId); + + } + } catch (SQLException ex) { + LOGGER.log(Level.WARNING, "Error getting artifact instances from database.", ex); // NON-NLS + } + } + + private void countAndAddCommonAttributes(String md5Value, int resultId) { + if (commonAttributeValue == null) { + commonAttributeValue = new CommonAttributeValue(md5Value); + } + if (!md5Value.equals(previousRowMd5)) { + int size = commonAttributeValue.getInstanceCount(); + if (instanceCollatedCommonFiles.containsKey(size)) { + instanceCollatedCommonFiles.get(size).add(commonAttributeValue); + } else { + ArrayList value = new ArrayList<>(); + value.add(commonAttributeValue); + instanceCollatedCommonFiles.put(size, value); + } + + commonAttributeValue = new CommonAttributeValue(md5Value); + previousRowMd5 = md5Value; + } + // we don't *have* all the information for the rows in the CR, + // so we need to consult the present case via the SleuthkitCase object + // Later, when the FileInstanceNode is built. Therefore, build node generators for now. + AbstractCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, InterCaseSearchResultsProcessor.this.dataSources); + commonAttributeValue.addInstance(searchResult); + } + + Map> getInstanceCollatedCommonFiles() { + return Collections.unmodifiableMap(instanceCollatedCommonFiles); + } + } + + /** + * Callback to use with findSingleCorrelationAttribute which retrieves a + * single CorrelationAttribute from the EamDb. + */ + private class InterCaseCommonAttributeRowCallback implements InstanceTableCallback { + + CorrelationAttribute correlationAttribute = null; + + @Override + public void process(ResultSet resultSet) { + try { + EamDb DbManager = EamDb.getInstance(); + CorrelationAttribute.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + + while (resultSet.next()) { + CorrelationCase correlationCase = DbManager.getCaseById(InstanceTableCallback.getCaseId(resultSet)); + CorrelationDataSource dataSource = DbManager.getDataSourceById(correlationCase, InstanceTableCallback.getDataSourceId(resultSet)); + correlationAttribute = DbManager.getCorrelationAttribute(fileType, + correlationCase, + dataSource, + InstanceTableCallback.getValue(resultSet), + InstanceTableCallback.getFilePath(resultSet)); + + } + } catch (SQLException | EamDbException ex) { + LOGGER.log(Level.WARNING, "Error getting single correlation artifact instance from database.", ex); // NON-NLS + } + } + + CorrelationAttribute getCorrelationAttribute() { + return correlationAttribute; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java new file mode 100644 index 0000000000..172108d5ef --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java @@ -0,0 +1,168 @@ +/* + * + * 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.commonfilesearch; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.HashUtility; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * Generates a List when + * findFiles() is called, which organizes files by md5 to + * prepare to display in viewer. + * + * This entire thing runs on a background thread where exceptions are handled. + */ +@SuppressWarnings("PMD.AbstractNaming") +public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAttributeSearcher { + + private static final String FILTER_BY_MIME_TYPES_WHERE_CLAUSE = " and mime_type in (%s)"; //NON-NLS // where %s is csv list of mime_types to filter on + + /** + * Subclass this to implement different algorithms for getting common files. + * + * @param dataSourceIdMap a map of obj_id to datasource name + * @param filterByMediaMimeType match only on files whose mime types can be + * broadly categorized as media types + * @param filterByDocMimeType match only on files whose mime types can be + * broadly categorized as document types + */ + IntraCaseCommonAttributeSearcher(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); + } + + /** + * Use this as a prefix when building the SQL select statement. + * + *
    + *
  • You only have to specify the WHERE clause if you use this.
  • + *
  • If you do not use this string, you must use at least the columns + * selected below, in that order.
  • + *
+ */ + static final String SELECT_PREFIX = "SELECT obj_id, md5, data_source_obj_id from tsk_files where"; //NON-NLS + + /** + * Should build a SQL SELECT statement to be passed to + * SleuthkitCase.executeQuery(sql) which will select the desired file ids + * and MD5 hashes. + * + * The statement should select obj_id, md5, data_source_obj_id in that + * order. + * + * @return sql string select statement + */ + protected abstract String buildSqlSelectStatement(); + + /** + * Generate a meta data object which encapsulates everything need to add the + * tree table tab to the top component. + * + * @return a data object with all of the matched files in a hierarchical + * format + * @throws TskCoreException + * @throws NoCurrentCaseException + * @throws SQLException + */ + @Override + public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException { + Map commonFiles = new HashMap<>(); + + final Case currentCase = Case.getCurrentCaseThrows(); + final String caseName = currentCase.getDisplayName(); + + SleuthkitCase sleuthkitCase = currentCase.getSleuthkitCase(); + + String selectStatement = this.buildSqlSelectStatement(); + + try ( + CaseDbQuery query = sleuthkitCase.executeQuery(selectStatement); + ResultSet resultSet = query.getResultSet()) { + + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String md5 = resultSet.getString(2); + Long dataSourceId = resultSet.getLong(3); + String dataSource = this.getDataSourceIdToNameMap().get(dataSourceId); + + if (md5 == null || HashUtility.isNoDataMd5(md5)) { + continue; + } + + if (commonFiles.containsKey(md5)) { + final CommonAttributeValue commonAttributeValue = commonFiles.get(md5); + commonAttributeValue.addInstance(new CaseDBCommonAttributeInstance(objectId, dataSource, caseName)); + } else { + final CommonAttributeValue commonAttributeValue = new CommonAttributeValue(md5); + commonAttributeValue.addInstance(new CaseDBCommonAttributeInstance(objectId, dataSource, caseName)); + commonFiles.put(md5, commonAttributeValue); + } + } + } + + Map> instanceCollatedCommonFiles = collateMatchesByNumberOfInstances(commonFiles); + + return new CommonAttributeSearchResults(instanceCollatedCommonFiles); + } + + /** + * Should be used by subclasses, in their + * buildSqlSelectStatement() function to create an SQL boolean + * expression which will filter our matches based on mime type. The + * expression will be conjoined to base query with an AND operator. + * + * @return sql fragment of the form: + * 'and "mime_type" in ( [comma delimited list of mime types] )' + * or empty string in the event that no types to filter on were given. + */ + String determineMimeTypeFilter() { + + Set mimeTypesToFilterOn = new HashSet<>(); + String mimeTypeString = ""; + if (isFilterByMedia()) { + mimeTypesToFilterOn.addAll(MEDIA_PICS_VIDEO_MIME_TYPES); + } + if (isFilterByDoc()) { + mimeTypesToFilterOn.addAll(TEXT_FILES_MIME_TYPES); + } + StringBuilder mimeTypeFilter = new StringBuilder(mimeTypesToFilterOn.size()); + if (!mimeTypesToFilterOn.isEmpty()) { + for (String mimeType : mimeTypesToFilterOn) { + mimeTypeFilter.append(SINGLE_QUOTE).append(mimeType).append(SINGLE_QUTOE_COMMA); + } + mimeTypeString = mimeTypeFilter.toString().substring(0, mimeTypeFilter.length() - 1); + mimeTypeString = String.format(FILTER_BY_MIME_TYPES_WHERE_CLAUSE, new Object[]{mimeTypeString}); + } + return mimeTypeString; + } + static final String SINGLE_QUTOE_COMMA = "',"; + static final String SINGLE_QUOTE = "'"; +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.form b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.form new file mode 100644 index 0000000000..7038ca5926 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.form @@ -0,0 +1,97 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java new file mode 100644 index 0000000000..c62552082b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java @@ -0,0 +1,194 @@ +/* + * + * 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.commonfilesearch; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import javax.swing.ComboBoxModel; +import org.openide.util.NbBundle; + +/** + * UI controls for Common Files Search scenario where the user intends to find + * common files between datasources. It is an inner panel which provides the ability + * to select all datasources or a single datasource from a dropdown list of + * sources in the current case. + */ +public class IntraCasePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + static final long NO_DATA_SOURCE_SELECTED = -1; + + private boolean singleDataSource; + private ComboBoxModel dataSourcesList = new DataSourceComboBoxModel(); + private final Map dataSourceMap; + + private String errorMessage; + + /** + * Creates new form IntraCasePanel + */ + public IntraCasePanel() { + initComponents(); + this.errorMessage = ""; + this.dataSourceMap = new HashMap<>(); + this.singleDataSource = true; + } + + public Map getDataSourceMap(){ + return Collections.unmodifiableMap(this.dataSourceMap); + } + + Long getSelectedDataSourceId(){ + if(!this.singleDataSource){ + return IntraCasePanel.NO_DATA_SOURCE_SELECTED; + } + + for(Entry entry : this.dataSourceMap.entrySet()){ + if(entry.getValue().equals(this.selectDataSourceComboBox.getSelectedItem())){ + return entry.getKey(); + } + } + + return IntraCasePanel.NO_DATA_SOURCE_SELECTED; + } + + /** + * 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() { + + buttonGroup = new javax.swing.ButtonGroup(); + allDataSourcesRadioButton = new javax.swing.JRadioButton(); + withinDataSourceRadioButton = new javax.swing.JRadioButton(); + selectDataSourceComboBox = new javax.swing.JComboBox<>(); + + buttonGroup.add(allDataSourcesRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(allDataSourcesRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allDataSourcesRadioButton.text")); // NOI18N + allDataSourcesRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + allDataSourcesRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allDataSourcesRadioButtonActionPerformed(evt); + } + }); + + buttonGroup.add(withinDataSourceRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(withinDataSourceRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.withinDataSourceRadioButton.text")); // NOI18N + withinDataSourceRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + withinDataSourceRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + withinDataSourceRadioButtonActionPerformed(evt); + } + }); + + selectDataSourceComboBox.setModel(dataSourcesList); + selectDataSourceComboBox.setActionCommand(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectDataSourceComboBox.actionCommand")); // NOI18N + selectDataSourceComboBox.setEnabled(false); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(allDataSourcesRadioButton) + .addComponent(withinDataSourceRadioButton))) + .addGroup(layout.createSequentialGroup() + .addGap(27, 27, 27) + .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(allDataSourcesRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(withinDataSourceRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + private void allDataSourcesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allDataSourcesRadioButtonActionPerformed + selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected()); + singleDataSource = false; + }//GEN-LAST:event_allDataSourcesRadioButtonActionPerformed + + private void withinDataSourceRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_withinDataSourceRadioButtonActionPerformed + withinDataSourceSelected(withinDataSourceRadioButton.isSelected()); + }//GEN-LAST:event_withinDataSourceRadioButtonActionPerformed + + private void withinDataSourceSelected(boolean selected) { + selectDataSourceComboBox.setEnabled(selected); + if (selectDataSourceComboBox.isEnabled()) { + selectDataSourceComboBox.setSelectedIndex(0); + singleDataSource = true; + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton allDataSourcesRadioButton; + private javax.swing.ButtonGroup buttonGroup; + private javax.swing.JComboBox selectDataSourceComboBox; + private javax.swing.JRadioButton withinDataSourceRadioButton; + // End of variables declaration//GEN-END:variables + + void setDataModel(DataSourceComboBoxModel dataSourceComboBoxModel) { + this.dataSourcesList = dataSourceComboBoxModel; + this.selectDataSourceComboBox.setModel(dataSourcesList); + } + + void rigForMultipleDataSources(boolean multipleDataSources) { + this.withinDataSourceRadioButton.setEnabled(multipleDataSources); + this.allDataSourcesRadioButton.setSelected(!multipleDataSources); + this.withinDataSourceRadioButton.setSelected(multipleDataSources); + this.withinDataSourceSelected(multipleDataSources); + + } + + void setDataSourceMap(Map dataSourceMap) { + this.dataSourceMap.clear(); + this.dataSourceMap.putAll(dataSourceMap); + } + + @NbBundle.Messages({ + "IntraCasePanel.areSearchCriteriaMet.message=Cannot run intra-case correlation search." + }) + boolean areSearchCriteriaMet() { + if(this.dataSourceMap.isEmpty()){ + this.errorMessage = Bundle.IntraCasePanel_areSearchCriteriaMet_message(); + return false; + } else { + return true; + } + } + + String getErrorMessage() { + return this.errorMessage; + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java new file mode 100644 index 0000000000..6c06da8b38 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleInterCaseCommonAttributeSearcher.java @@ -0,0 +1,86 @@ +/* + * + * 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.commonfilesearch; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttributeSearcher { + + private final int corrleationCaseId; + private String correlationCaseName; + + /** + * + * @param correlationCaseId + * @param filterByMediaMimeType + * @param filterByDocMimeType + * @throws EamDbException + */ + public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) throws EamDbException { + super(dataSourceIdMap,filterByMediaMimeType, filterByDocMimeType); + + this.corrleationCaseId = correlationCaseId; + this.correlationCaseName = ""; + } + + /** + * Collect metadata required to render the tree table where matches must + * occur in the case with the given ID. + * + * @param correlationCaseId id of case where matches must occur (no other matches will be shown) + * @return business object needed to populate tree table with results + * @throws TskCoreException + * @throws NoCurrentCaseException + * @throws SQLException + * @throws EamDbException + */ + @Override + public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { + + CorrelationCase cCase = this.getCorrelationCaseFromId(this.corrleationCaseId); + correlationCaseName = cCase.getDisplayName(); + return this.findFiles(cCase); + } + + CommonAttributeSearchResults findFiles(CorrelationCase correlationCase) throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { + InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap()); + Map> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseCommonAttributeValues(Case.getCurrentCase(), correlationCase); + + return new CommonAttributeSearchResults(interCaseCommonFiles); + } + + @Override + String buildTabTitle() { + final String buildCategorySelectionString = this.buildCategorySelectionString(); + final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleInterSingle(); + return String.format(titleTemplate, new Object[]{correlationCaseName, buildCategorySelectionString}); + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java similarity index 82% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java index f22715960f..02bd480093 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java @@ -25,7 +25,7 @@ import org.sleuthkit.datamodel.TskData.FileKnown; /** * Provides logic for selecting common files from a single data source. */ -final public class SingleDataSource extends CommonFilesMetadataBuilder { +final public class SingleIntraCaseCommonAttributeSearcher extends IntraCaseCommonAttributeSearcher { private static final String WHERE_CLAUSE = "%s md5 in (select md5 from tsk_files where md5 in (select md5 from tsk_files where (known != "+ FileKnown.KNOWN.getFileKnownValue() + " OR known IS NULL) and data_source_obj_id=%s%s) GROUP BY md5 HAVING COUNT(DISTINCT data_source_obj_id) > 1) order by md5"; //NON-NLS private final Long selectedDataSourceId; @@ -41,7 +41,7 @@ final public class SingleDataSource extends CommonFilesMetadataBuilder { * @param filterByDocMimeType match only on files whose mime types can be * broadly categorized as document types */ - public SingleDataSource(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + public SingleIntraCaseCommonAttributeSearcher(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); this.selectedDataSourceId = dataSourceId; this.dataSourceName = dataSourceIdMap.get(this.selectedDataSourceId); @@ -50,13 +50,13 @@ final public class SingleDataSource extends CommonFilesMetadataBuilder { @Override protected String buildSqlSelectStatement() { Object[] args = new String[]{SELECT_PREFIX, Long.toString(this.selectedDataSourceId), determineMimeTypeFilter()}; - return String.format(SingleDataSource.WHERE_CLAUSE, args); + return String.format(SingleIntraCaseCommonAttributeSearcher.WHERE_CLAUSE, args); } @Override - protected String buildTabTitle() { + public String buildTabTitle() { final String buildCategorySelectionString = this.buildCategorySelectionString(); - final String titleTemplate = Bundle.CommonFilesMetadataBuilder_buildTabTitle_titleSingle(); + final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleIntraSingle(); return String.format(titleTemplate, new Object[]{this.dataSourceName, buildCategorySelectionString}); } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index eb448f4f8a..2fd30addcc 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,7 +46,7 @@ import org.sleuthkit.datamodel.TskCoreException; final class RelationshipNode extends BlackboardArtifactNode { private static final Logger logger = Logger.getLogger(RelationshipNode.class.getName()); - + RelationshipNode(BlackboardArtifact artifact) { super(artifact); final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); @@ -115,7 +115,7 @@ final class RelationshipNode extends BlackboardArtifactNode { } addTagProperty(sheetSet); - + return sheet; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 8f08b248fd..a95dd307f5 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -117,8 +117,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont drp.open(); drpExplorerManager = drp.getExplorerManager(); - drpExplorerManager.addPropertyChangeListener(evt -> - viewInNewWindowButton.setEnabled(drpExplorerManager.getSelectedNodes().length == 1)); + drpExplorerManager.addPropertyChangeListener(evt + -> viewInNewWindowButton.setEnabled(drpExplorerManager.getSelectedNodes().length == 1)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java new file mode 100644 index 0000000000..a6a122da8a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/core/AutopsyOptionProcessor.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.core; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.sendopts.CommandException; +import org.netbeans.spi.sendopts.Env; +import org.netbeans.spi.sendopts.Option; +import org.netbeans.spi.sendopts.OptionProcessor; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * This class can be used to add command line options to Autopsy + * To add more options to autopsy, create a Option variable and add it to the set in getOptions method + * Do your logic for that option in the process method + */ +@ServiceProvider(service=OptionProcessor.class) +public class AutopsyOptionProcessor extends OptionProcessor { + + private static final Logger logger = Logger.getLogger(AutopsyOptionProcessor.class.getName()); + private final Option liveAutopsyOption = Option.withoutArgument('l', "liveAutopsy"); + private final static String PROP_BASECASE = "LBL_BaseCase_PATH"; + + + @Override + protected Set