diff --git a/Core/build.xml b/Core/build.xml index 609fb1367b..0135ad0b1d 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -73,6 +73,8 @@ tofile="${ext.dir}/mchange-commons-java-0.2.9.jar"/> + @@ -84,17 +86,23 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/Core/manifest.mf b/Core/manifest.mf index 67d3366e22..5eb077ef30 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 23 +OpenIDE-Module-Implementation-Version: 24 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 93d0e96015..80bb473653 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -17,7 +17,6 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar -file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar file.reference.bcprov-jdk15on-1.54.jar=release/modules/ext/bcprov-jdk15on-1.54.jar file.reference.jackcess-2.1.8.jar=release/modules/ext/jackcess-2.1.8.jar file.reference.jackcess-encrypt-2.1.2.jar=release/modules/ext/jackcess-encrypt-2.1.2.jar @@ -30,7 +29,7 @@ file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-tran file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar -file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar +file.reference.sleuthkit-postgresql-4.6.2.jar=release/modules/ext/sleuthkit-postgresql-4.6.2.jar file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar @@ -39,6 +38,7 @@ file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8 file.reference.xmpcore-5.1.3.jar=release/modules/ext/xmpcore-5.1.3.jar file.reference.xz-1.6.jar=release/modules/ext/xz-1.6.jar file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar +file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt @@ -46,5 +46,5 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.11 +spec.version.base=10.12 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index f6a841482c..859db9f0d2 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -251,7 +251,7 @@ 3 - 1.1 + 1.2 @@ -412,8 +412,8 @@ release/modules/ext/metadata-extractor-2.10.1.jar - ext/sleuthkit-postgresql-4.6.1.jar - release/modules/ext/sleuthkit-postgresql-4.6.1.jar + ext/sleuthkit-postgresql-4.6.2.jar + release/modules/ext/sleuthkit-postgresql-4.6.2.jar ext/tika-core-1.17.jar @@ -499,6 +499,10 @@ ext/xmpcore-5.1.3.jar release/modules/ext/xmpcore-5.1.3.jar + + ext/SparseBitSet-1.1.jar + release/modules/ext/SparseBitSet-1.1.jar + diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 7ad185ac8e..9015d9a6ad 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -37,8 +37,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to blackboard artifacts. */ @NbBundle.Messages({ - "AddBlackboardArtifactTagAction.singularTagResult=Tag Result", - "AddBlackboardArtifactTagAction.pluralTagResult=Tag Results", + "AddBlackboardArtifactTagAction.singularTagResult=Add Result Tag", + "AddBlackboardArtifactTagAction.pluralTagResult=Add Result Tags", "# {0} - artifactName", "AddBlackboardArtifactTagAction.unableToTag.msg=Unable to tag {0}.", "AddBlackboardArtifactTagAction.taggingErr=Tagging Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index 602789735d..34e7b2a110 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -38,8 +38,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to content. */ @NbBundle.Messages({ - "AddContentTagAction.singularTagFile=Tag File", - "AddContentTagAction.pluralTagFile=Tag Files", + "AddContentTagAction.singularTagFile=Add File Tag", + "AddContentTagAction.pluralTagFile=Add File Tags", "# {0} - fileName", "AddContentTagAction.unableToTag.msg=Unable to tag {0}, not a regular file.", "AddContentTagAction.cannotApplyTagErr=Cannot Apply Tag", diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 6efb30e6a2..6606185e24 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; @@ -92,6 +94,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Get the current set of tag names. Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); @@ -101,6 +104,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Create a menu item for each of the existing and visible tags. // Selecting one of these menu items adds a tag with the associated tag name. + List standardTagMenuitems = new ArrayList<>(); if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -114,15 +118,26 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagNameItem.addActionListener((ActionEvent e) -> { getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); - - add(tagNameItem); + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } if (getItemCount() > 0) { addSeparator(); } - + + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + + addSeparator(); + // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates // a dialog that can be used to create or select a tag name with an // optional comment and adds a tag with the resulting name. diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 1cbc4b70b6..c83c8c2782 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -20,7 +20,7 @@ AddTagAction.newTag=New Tag... AddTagAction.tagAndComment=Tag and Comment... AddBookmarkTagAction.bookmark.text=Bookmark GetTagNameAndCommentDialog.noTags=No Tags -GetTagNameAndCommentDialog.createTag=Create Tag +GetTagNameAndCommentDialog.selectTag=Select Tag GetTagNameAndCommentDialog.cancelName=cancel GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties index d6f865ecf0..10bd2ad0df 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties @@ -23,7 +23,7 @@ AddTagAction.noTags=\u30bf\u30b0\u7121\u3057 AddTagAction.newTag=\u65b0\u898f\u30bf\u30b0\u2026 AddTagAction.tagAndComment=\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8\u3092\u8ffd\u52a0\u2026 GetTagNameAndCommentDialog.noTags=\u30bf\u30b0\u7121\u3057 -GetTagNameAndCommentDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 +GetTagNameAndCommentDialog.selectTag=\u30bf\u30b0\u3092\u9078\u629e GetTagNameAndCommentDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb GetTagNameDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 GetTagNameDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java index 18ae9ac7e5..c3878e6b59 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java @@ -38,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException; * artifacts. */ @NbBundle.Messages({ - "DeleteBlackboardArtifactTagAction.deleteTag=Delete Tag", + "DeleteBlackboardArtifactTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteBlackboardArtifactTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java index 07cce02bfc..b7ff3a73a0 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to delete tags applied to content. */ @NbBundle.Messages({ - "DeleteContentTagAction.deleteTag=Delete Tag", + "DeleteContentTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteContentTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java index 008cb419ff..76f3faa8be 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -138,7 +139,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem */ @NbBundle.Messages({"# {0} - artifactID", "DeleteFileBlackboardArtifactTagAction.deleteTags.alert=Unable to untag artifact {0}."}) - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -153,6 +154,8 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem = selectedBlackboardArtifactsList.iterator().next(); Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + List standardTagMenuitems = new ArrayList<>(); try { // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); @@ -182,7 +185,12 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem tagNameItem.addActionListener((ActionEvent e) -> { deleteTag(tagName, artifactTag, artifact.getArtifactID()); }); - add(tagNameItem); + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } } @@ -192,6 +200,12 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem } } + if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){ + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); if (getItemCount() == 0) { setEnabled(false); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java index f336cd888f..c8a8a69fdc 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -136,7 +137,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen * creating or selecting a tag name for a tag and specifying an optional tag * comment. */ - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -150,6 +151,8 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen AbstractFile file = selectedAbstractFilesList.iterator().next(); Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + List standardTagMenuitems = new ArrayList<>(); try { // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); @@ -179,7 +182,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen tagNameItem.addActionListener((ActionEvent e) -> { deleteTag(tagName, contentTag, file.getId()); }); - add(tagNameItem); + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } } @@ -189,6 +198,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen } } + if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){ + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + if(getItemCount() == 0) { setEnabled(false); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 57c43e964b..818d660ff3 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -24,11 +24,11 @@ - + - + @@ -38,14 +38,14 @@ - + - + - + @@ -58,11 +58,11 @@ - - - + + + - + @@ -125,16 +125,6 @@ - - - - - - - - - - @@ -145,5 +135,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index d2c66f410b..ef7e853d68 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -22,9 +22,13 @@ import java.awt.Component; import java.awt.Window; 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; import javax.swing.DefaultListCellRenderer; @@ -50,7 +54,8 @@ import org.sleuthkit.datamodel.TskData; public class GetTagNameAndCommentDialog extends JDialog { private static final long serialVersionUID = 1L; - private final Set tagNamesSet = new HashSet<>(); + private final List tagNamesList = new ArrayList<>(); + private final List standardTagNamesList = new ArrayList<>(); private TagNameAndComment tagNameAndComment = null; public static class TagNameAndComment { @@ -105,7 +110,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private GetTagNameAndCommentDialog(Window owner) { super(owner, - NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.createTag"), + NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.selectTag"), ModalityType.APPLICATION_MODAL); } @@ -144,16 +149,29 @@ public class GetTagNameAndCommentDialog extends JDialog { // not exist in the database). try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagNamesSet.addAll(tagsManager.getAllTagNames()); + 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); + } else { + tagNamesList.add(tagName); + } + }); + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class .getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - for (TagName tag : tagNamesSet) { - + tagNamesList.forEach((tag) -> { tagCombo.addItem(tag); - } + }); + + standardTagNamesList.forEach((tag) -> { + tagCombo.addItem(tag); + }); // Center and show the dialog box. this.setLocationRelativeTo(this.getOwner()); @@ -174,8 +192,9 @@ public class GetTagNameAndCommentDialog extends JDialog { tagCombo = new javax.swing.JComboBox(); tagLabel = new javax.swing.JLabel(); commentLabel = new javax.swing.JLabel(); - commentText = new javax.swing.JTextField(); newTagButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + commentText = new javax.swing.JTextArea(); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { @@ -203,9 +222,6 @@ public class GetTagNameAndCommentDialog extends JDialog { org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentLabel.text")); // NOI18N - commentText.setText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.text")); // NOI18N - commentText.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.toolTipText")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(newTagButton, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.newTagButton.text")); // NOI18N newTagButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -213,6 +229,12 @@ public class GetTagNameAndCommentDialog extends JDialog { } }); + commentText.setColumns(20); + commentText.setRows(5); + commentText.setText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.text")); // NOI18N + commentText.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.toolTipText")); // NOI18N + jScrollPane1.setViewportView(commentText); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -222,7 +244,7 @@ public class GetTagNameAndCommentDialog extends JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newTagButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 165, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) @@ -232,8 +254,8 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(commentText) - .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 318, Short.MAX_VALUE)))) .addContainerGap()) ); @@ -247,10 +269,10 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(commentLabel) - .addComponent(commentText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 37, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 22, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton) @@ -282,7 +304,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed TagName newTagName = GetTagNameDialog.doDialog(this); if (newTagName != null) { - tagNamesSet.add(newTagName); + tagNamesList.add(newTagName); tagCombo.addItem(newTagName); tagCombo.setSelectedItem(newTagName); } @@ -291,7 +313,8 @@ public class GetTagNameAndCommentDialog extends JDialog { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JLabel commentLabel; - private javax.swing.JTextField commentText; + private javax.swing.JTextArea commentText; + private javax.swing.JScrollPane jScrollPane1; private javax.swing.JButton newTagButton; private javax.swing.JButton okButton; private javax.swing.JComboBox tagCombo; diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java new file mode 100644 index 0000000000..a2895b4d55 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -0,0 +1,127 @@ +/* + * 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.actions; + +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This Action allows users to replace a tag applied to blackboard + * artifacts, with another tag + */ +public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction { + + private static final Logger logger = Logger.getLogger(ReplaceBlackboardArtifactTagAction.class.getName()); + private static final long serialVersionUID = 1L; + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ReplaceBlackboardArtifactTagAction instance; + + public static synchronized ReplaceBlackboardArtifactTagAction getInstance() { + if (null == instance) { + instance = new ReplaceBlackboardArtifactTagAction(); + } + return instance; + } + + private ReplaceBlackboardArtifactTagAction() { + super(MENU_TEXT); + } + + /** + * Replaces the specified tag on the given artifact with the new one + * + * @param oldArtifactTag tag to be replaced + * @param newTagName name of the tag to replace with + * @param newComment the newComment for the tag use an empty string for no newComment + */ + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - artifactID", + "ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."}) + @Override + protected void replaceTag( BlackboardArtifactTag oldArtifactTag, TagName newTagName, String newComment) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldArtifactTag.getName().getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); + tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName, newComment); + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing artifact tag", ex); //NON-NLS + } + } + }.execute(); + } + + /** + * Returns list of tags selected by user to replace + * + * @return a list of tags + */ + @Override + Collection getTagsToReplace() { + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java new file mode 100644 index 0000000000..51b898eb84 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -0,0 +1,120 @@ +/* + * 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.actions; + +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This Action allow users to replace a content tag with another tag + */ +public final class ReplaceContentTagAction extends ReplaceTagAction { + + private static final Logger logger = Logger.getLogger(ReplaceContentTagAction.class.getName()); + + private static final long serialVersionUID = 1L; + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ReplaceContentTagAction instance; + + public static synchronized ReplaceContentTagAction getInstance() { + if (null == instance) { + instance = new ReplaceContentTagAction(); + } + return instance; + } + + private ReplaceContentTagAction() { + super(MENU_TEXT); + } + + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - content obj id", + "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) + @Override + protected void replaceTag(ContentTag oldTag, TagName newTagName, String newComment) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteContentTag(oldTag); + tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment); + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing content tag", ex); //NON-NLS + } + } + }.execute(); + } + + /** + * Returns list of content tags selected by user to replace + * + * @return a list of tags + */ + @Override + Collection getTagsToReplace() { + return Utilities.actionsGlobalContext().lookupAll(ContentTag.class); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java new file mode 100644 index 0000000000..79ca4f4739 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -0,0 +1,201 @@ +/* + * 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.actions; + +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.util.NbBundle; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Abstract class to define context action to replace a tag with another + * + * @param tag type + */ +@NbBundle.Messages({ + "ReplaceTagAction.replaceTag=Replace Selected Tag(s) With" +}) +abstract class ReplaceTagAction extends AbstractAction implements Presenter.Popup { + + private static final long serialVersionUID = 1L; + protected static final String MENU_TEXT = NbBundle.getMessage(ReplaceTagAction.class, + "ReplaceTagAction.replaceTag"); + + ReplaceTagAction(String menuText) { + super(menuText); + } + + /** + * Subclasses of replaceTagAction should not override actionPerformed, + * but instead override replaceTag. + * + * @param event + */ + @Override + @SuppressWarnings("NoopMethodInAbstractClass") + public void actionPerformed(ActionEvent event) { + } + + protected String getActionDisplayName() { + return MENU_TEXT; + } + + /** + * Method to actually replace the selected tag with the given new tag + * + * @param oldTag - the TagName which is being removed from the item + * @param newTagName - the TagName which is being added to the itme + * @param comment the comment associated with the tag, empty string for no comment + */ + abstract protected void replaceTag(T oldTag, TagName newTagName, String comment); + + /** + * Returns elected tags which are to be replaced + * + * @return + */ + abstract Collection getTagsToReplace(); + + + @Override + public JMenuItem getPopupPresenter() { + return new ReplaceTagMenu(); + } + + /** + * Instances of this class implement a context menu user interface for + * selecting a tag name to replace the tag with + */ + private final class ReplaceTagMenu extends JMenu { + + private static final long serialVersionUID = 1L; + + ReplaceTagMenu() { + super(getActionDisplayName()); + + final Collection selectedTags = getTagsToReplace(); + + // Get the current set of tag names. + Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + + List standardTagMenuitems = new ArrayList<>(); + // Ideally we should'nt allow user to pick a replacement tag that's already been applied to an item + // In the very least we don't allow them to pick the same tag as the one they are trying to replace + Set existingTagNames = new HashSet<>(); + if (!selectedTags.isEmpty()) { + T firstTag = selectedTags.iterator().next(); + existingTagNames.add(firstTag.getName().getDisplayName()); + } + + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + String tagDisplayName = entry.getKey(); + String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); + // for the bookmark tag name only, added shortcut label + if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { + tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); + } + + // Add action to replace the tag + tagNameItem.addActionListener((ActionEvent event) -> { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, entry.getValue(), oldtag.getComment()); + }); + }); + + // Don't allow replacing a tag with same tag. + if (existingTagNames.contains(tagDisplayName)) { + tagNameItem.setEnabled(false); + } + + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } + } + } else { + JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); + empty.setEnabled(false); + add(empty); + } + + // + if (this.getItemCount() > 0) { + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + + addSeparator(); + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent event) -> { + TagName newTagName = GetTagNameDialog.doDialog(); + if (null != newTagName) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, newTagName, oldtag.getComment()); + }); + } + }); + add(newTagMenuItem); + // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates + // a dialog that can be used to create or select a tag name with an + // optional comment and adds a tag with the resulting name. + JMenuItem tagAndCommentItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.tagAndComment")); + tagAndCommentItem.addActionListener((ActionEvent event) -> { + GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(); + if (null != tagNameAndComment) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, tagNameAndComment.getTagName(), tagNameAndComment.getComment()); + }); + } + }); + add(tagAndCommentItem); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java index 742cb1750c..a469a36584 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,15 +18,22 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Frame; +import javax.swing.JDialog; + /** * Dialog to show add image error messages */ -public class AddImageErrorsDialog extends javax.swing.JDialog { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class AddImageErrorsDialog extends JDialog { /** * Creates new form AddImageErrorsDialog + * + * @param parent The parent frame. + * @param modal Does this dialog act as a modal? */ - public AddImageErrorsDialog(java.awt.Frame parent, boolean modal) { + public AddImageErrorsDialog(Frame parent, boolean modal) { super(parent, modal); initComponents(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 6c6097717e..15eb0c3c67 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -57,6 +57,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * {@link AddImageWizardIngestConfigPanel} (which is a bit weird if you ask m * -jm) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { private boolean readyToIngest = false; @@ -79,7 +80,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { */ private AddImageWizardAddingProgressVisual component; private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 - private final List newContents = Collections.synchronizedList(new ArrayList()); + private final List newContents = Collections.synchronizedList(new ArrayList<>()); private final DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl(); private IngestJobSettings ingestJobSettings; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java index e4d6d09984..15f1ae86a5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * The "Add Image" wizard panel1 handling the logic of selecting image file(s) * to add to Case, and pick the time zone. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardDataSourceSettingsPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java index 4678e136a7..cfcbdd015c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java @@ -40,10 +40,11 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * TODO: review this for dead code. think about moving logic of adding image to * 3rd panel( {@link AddImageWizardAddingProgressPanel}) separate class -jm */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel { @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules") - private IngestJobSettingsPanel ingestJobSettingsPanel; + private final IngestJobSettingsPanel ingestJobSettingsPanel; /** * The visual component that displays this panel. If you need to access the * component from this class, just use getComponent(). diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java index 86981e9d5d..5db401fd0f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * Create a wizard panel which contains a panel allowing the selection of the * DataSourceProcessor */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardSelectDspPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { @NbBundle.Messages("SelectDataSourceProcessorPanel.name.text=Select Type of Data Source To Add") diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index ee14581bc7..056ff4e3ec 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -36,6 +36,7 @@ import javax.swing.Box.Filler; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; @@ -67,7 +68,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); } - + //add actionlistner to listen for change } @@ -132,7 +133,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); - if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ + if ((Case.getCurrentCaseThrows().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())) { dspButton.setEnabled(false); //disable the button for local disk DSP when this is a multi user case dspButton.setSelected(false); shouldAddMultiUserWarning = true; @@ -172,6 +173,9 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.weighty = 1; gridBagLayout.setConstraints(vertGlue, constraints); jPanel1.setLayout(gridBagLayout); + SwingUtilities.invokeLater(() -> { + jScrollPane1.getVerticalScrollBar().setValue(0); + }); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 9c04508c1f..9494b3288a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -114,7 +114,6 @@ LocalFilesDSProcessor.toString.text=Logical Files LocalFilesPanel.contentType.text=LOCAL LocalFilesPanel.moduleErr=Module Error LocalFilesPanel.moduleErr.msg=A module caused an error listening to LocalFilesPanel updates. See log to determine which module. Some data could be incomplete. -MissingImageDialog.allDesc.text=All Supported Types MissingImageDialog.display.title=Search for Missing Image MissingImageDialog.confDlg.noFileSel.msg=No image file has been selected. Are you sure you\nwould like to exit without finding the image? MissingImageDialog.confDlg.noFileSel.title=Missing Image diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 8e405adec4..90849f4639 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -101,7 +101,6 @@ LocalFilesDSProcessor.toString.text=\u30ed\u30b8\u30ab\u30eb\u30d5\u30a1\u30a4\u LocalFilesPanel.contentType.text=\u30ed\u30fc\u30ab\u30eb LocalFilesPanel.moduleErr=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a8\u30e9\u30fc LocalFilesPanel.moduleErr.msg=LocalFilesPanel\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u78ba\u8a8d\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u8d77\u3053\u3057\u307e\u3057\u305f\u3002\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304b\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u4e0b\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002 -MissingImageDialog.allDesc.text=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u5168\u3066\u306e\u30bf\u30a4\u30d7 MissingImageDialog.display.title=\u6b20\u843d\u30a4\u30e1\u30fc\u30b8\u3092\u691c\u7d22 MissingImageDialog.confDlg.noFileSel.msg=\u30a4\u30e1\u30fc\u30b8\u30d5\u30a1\u30a4\u30eb\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30e1\u30fc\u30b8\u3092\u898b\u3064\u3051\u308b\n\u524d\u306b\u672c\u5f53\u306b\u7d42\u4e86\u3057\u307e\u3059\u304b\uff1f MissingImageDialog.confDlg.noFileSel.title=\u6b20\u843d\u30a4\u30e1\u30fc\u30b8 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 44bf52503f..9019ae162d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -129,6 +129,7 @@ public class Case { private static final String EXPORT_FOLDER = "Export"; //NON-NLS private static final String LOG_FOLDER = "Log"; //NON-NLS private static final String REPORTS_FOLDER = "Reports"; //NON-NLS + private static final String CONFIG_FOLDER = "Config"; // NON-NLS private static final String TEMP_FOLDER = "Temp"; //NON-NLS private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS private static final String CASE_ACTION_THREAD_NAME = "%s-case-action"; @@ -291,9 +292,10 @@ public class Case { */ ADDING_DATA_SOURCE_FAILED, /** - * A new data source has been added to the current case. The old value - * of the PropertyChangeEvent is null, the new value is the newly-added - * data source (type: Content). Cast the PropertyChangeEvent to + * A new data source or series of data sources have been added to the + * current case. The old value of the PropertyChangeEvent is null, the + * new value is the newly-added data source (type: Content). Cast the + * PropertyChangeEvent to * org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent to * access additional event data. */ @@ -1098,6 +1100,10 @@ public class Case { /* * Open the top components (windows within the main application * window). + * + * Note: If the core windows are not opened here, they will be + * opened via the DirectoryTreeTopComponent 'propertyChange()' + * method on a DATA_SOURCE_ADDED event. */ if (newCurrentCase.hasData()) { CoreComponentControl.openCoreWindows(); @@ -1351,6 +1357,16 @@ public class Case { return getOrCreateSubdirectory(REPORTS_FOLDER); } + /** + * Gets the full path to the config directory for this case, creating it if + * it does not exist. + * + * @return The config directory path. + */ + public String getConfigDirectory() { + return getOrCreateSubdirectory(CONFIG_FOLDER); + } + /** * Gets the full path to the module output directory for this case, creating * it if it does not exist. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 1e091db81c..0fb98ea8b8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -89,6 +89,15 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour public ImageDSProcessor() { configPanel = ImageFilePanel.createInstance(ImageDSProcessor.class.getName(), filtersList); } + + /** + * Get the list of file filters supported by this DSP. + * + * @return A list of all supported file filters. + */ + static List getFileFiltersList() { + return filtersList; + } /** * Gets a string that describes the type of data sources this processor is diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index cb1ec5ddfd..c5c8fb32fb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -257,6 +257,7 @@ final class LocalDiskPanel extends JPanel { pathTextField.setEnabled(copyImageCheckbox.isSelected()); browseButton.setEnabled(copyImageCheckbox.isSelected()); changeDatabasePathCheckbox.setEnabled(copyImageCheckbox.isSelected()); + changeDatabasePathCheckbox.setSelected(copyImageCheckbox.isSelected()); fireUpdateEvent(); }//GEN-LAST:event_copyImageCheckboxActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 872c647a94..ef985db2dd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -279,7 +279,10 @@ final class LocalFilesPanel extends javax.swing.JPanel { * * @param paths Absolute paths to the selected data source */ - @NbBundle.Messages("LocalFilesPanel.pathValidation.error=WARNING: Exception while gettting opon case.") + @NbBundle.Messages({ + "LocalFilesPanel.pathValidation.dataSourceOnCDriveError=Warning: Path to multi-user data source is on \"C:\" drive", + "LocalFilesPanel.pathValidation.getOpenCase=WARNING: Exception while gettting open case." + }) private void warnIfPathIsInvalid(final List pathsList) { errorLabel.setVisible(false); @@ -289,13 +292,13 @@ final class LocalFilesPanel extends javax.swing.JPanel { for (String currentPath : pathsList) { if (!PathValidator.isValid(currentPath, currentCaseType)) { errorLabel.setVisible(true); - errorLabel.setText(NbBundle.getMessage(this.getClass(), "DataSourceOnCDriveError.text")); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_dataSourceOnCDriveError()); return; } } } catch (NoCurrentCaseException ex) { errorLabel.setVisible(true); - errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_error()); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_getOpenCase()); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java index 5645e1d5d6..894603fe02 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java @@ -28,6 +28,7 @@ import java.io.File; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; +import javax.swing.filechooser.FileFilter; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.DriveUtils; @@ -44,17 +45,8 @@ class MissingImageDialog extends javax.swing.JDialog { private static final Logger logger = Logger.getLogger(MissingImageDialog.class.getName()); long obj_id; SleuthkitCase db; - static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC); - static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC); - static final List allExt = new ArrayList(); - static { - allExt.addAll(GeneralFilter.RAW_IMAGE_EXTS); - allExt.addAll(GeneralFilter.ENCASE_IMAGE_EXTS); - } - static final String allDesc = NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.allDesc.text"); - static final GeneralFilter allFilter = new GeneralFilter(allExt, allDesc); - private final JFileChooser fc = new JFileChooser(); + private final JFileChooser fileChooser = new JFileChooser(); /** * Instantiate a MissingImageDialog. @@ -68,13 +60,15 @@ class MissingImageDialog extends javax.swing.JDialog { this.db = db; initComponents(); - fc.setDragEnabled(false); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - fc.setMultiSelectionEnabled(false); + fileChooser.setDragEnabled(false); + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(false); - fc.addChoosableFileFilter(rawFilter); - fc.addChoosableFileFilter(encaseFilter); - fc.setFileFilter(allFilter); + List fileFiltersList = ImageDSProcessor.getFileFiltersList(); + for (FileFilter fileFilter : fileFiltersList) { + fileChooser.addChoosableFileFilter(fileFilter); + } + fileChooser.setFileFilter(fileFiltersList.get(0)); selectButton.setEnabled(false); } @@ -286,12 +280,12 @@ class MissingImageDialog extends javax.swing.JDialog { // set the current directory of the FileChooser if the ImagePath Field is valid File currentDir = new File(oldText); if (currentDir.exists()) { - fc.setCurrentDirectory(currentDir); + fileChooser.setCurrentDirectory(currentDir); } - int retval = fc.showOpenDialog(this); + int retval = fileChooser.showOpenDialog(this); if (retval == JFileChooser.APPROVE_OPTION) { - String path = fc.getSelectedFile().getPath(); + String path = fileChooser.getSelectedFile().getPath(); pathNameTextField.setText(path); } //pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString(), false, true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java index 8c90aeccb4..af95b0c5a0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import org.openide.windows.WindowManager; /** * This class extends a JDialog and maintains the MultiUserCasesPanel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MultiUserCasesDialog extends JDialog { private static final long serialVersionUID = 1L; 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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index e9c1b5307c..266db3a64c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -1,6 +1,5 @@ OptionsCategory_Name_TagNamesOptions=Tags OptionsCategory_TagNames=TagNames -Blackboard.unableToIndexArtifact.error.msg=Unable to index blackboard artifact {0} TagNameDialog.title.text=New Tag TagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ; TagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index fb02e7d2d4..33e8f29237 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -54,7 +54,7 @@ final class TagNameDefinition implements Comparable { private static final List STANDARD_TAG_DISPLAY_NAMES = Arrays.asList(Bundle.TagNameDefinition_predefTagNames_bookmark_text(), Bundle.TagNameDefinition_predefTagNames_followUp_text(), Bundle.TagNameDefinition_predefTagNames_notableItem_text(), DhsImageCategory.ONE.getDisplayName(), DhsImageCategory.TWO.getDisplayName(), DhsImageCategory.THREE.getDisplayName(), - DhsImageCategory.FOUR.getDisplayName(), DhsImageCategory.FIVE.getDisplayName()); + DhsImageCategory.FOUR.getDisplayName(), DhsImageCategory.FIVE.getDisplayName(), DhsImageCategory.ZERO.getDisplayName()); private final String displayName; private final String description; private final TagName.HTML_COLOR color; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f113a622fe..d80feed87a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -121,6 +121,15 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * 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. @@ -157,6 +166,21 @@ public class TagsManager implements Closeable { return caseDb.getTagNamesInUse(); } + /** + * 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. + * + * @throws TskCoreException + */ + public List getTagNamesInUse(long dsObjId) throws TskCoreException { + return caseDb.getTagNamesInUse(dsObjId); + } /** * 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 @@ -392,6 +416,24 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsCountByTagName(tagName); } + /** + * 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. + * + * @param dsObjId data source object id + * + * @return A count of the content tags with the specified tag name, and for + * the given data source + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsCountByTagName(tagName, dsObjId); + } + /** * Gets a content tag by tag id. * @@ -421,6 +463,23 @@ 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 + * tag name, and for the given data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getContentTagsByTagName(tagName, dsObjId); + } + /** * Gets content tags count by content. * @@ -522,6 +581,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } + /** + * Gets an artifact 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. + * @param dsObjId data source object id + * + * @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. + */ + public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagsCountByTagName(tagName, dsObjId); + } + /** * Gets an artifact tag by tag id. * @@ -553,6 +630,24 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsByTagName(tagName); } + /** + * Gets artifact tags by tag name, for specified 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. + * @param dsObjId data source object id + * + * @return A list, possibly empty, of the artifact tags with the specified + * tag name, for the specified data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + 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/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java index 8c03883523..df98197428 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -21,51 +21,48 @@ package org.sleuthkit.autopsy.centralrepository; import java.awt.event.ActionEvent; import java.util.logging.Level; import javax.swing.AbstractAction; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskCoreException; /** * An AbstractAction to manage adding and modifying a Central Repository file * instance comment. */ +@Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=Add/Edit Central Repository Comment"}) public final class AddEditCentralRepoCommentAction extends AbstractAction { private static final Logger logger = Logger.getLogger(AddEditCentralRepoCommentAction.class.getName()); private boolean addToDatabase; private CorrelationAttribute correlationAttribute; - String title; + private String comment; /** - * Private constructor to create an instance given a CorrelationAttribute. + * Constructor to create an instance given a CorrelationAttribute. * * @param correlationAttribute The correlation attribute to modify. - * @param title The text for the menu item. */ - private AddEditCentralRepoCommentAction(CorrelationAttribute correlationAttribute, String title) { - super(title); - this.title = title; + public AddEditCentralRepoCommentAction(CorrelationAttribute correlationAttribute) { + super(Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment()); this.correlationAttribute = correlationAttribute; } /** - * Private constructor to create an instance given an AbstractFile. + * Constructor to create an instance given an AbstractFile. * - * @param file The file from which a correlation attribute to modify is - * derived. - * @param title The text for the menu item. + * @param file The file from which a correlation attribute to modify is + * derived. */ - private AddEditCentralRepoCommentAction(AbstractFile file, String title) { - - super(title); - this.title = title; + public AddEditCentralRepoCommentAction(AbstractFile file) { + super(Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment()); correlationAttribute = EamArtifactUtil.getCorrelationAttributeFromContent(file); if (correlationAttribute == null) { addToDatabase = true; @@ -73,21 +70,24 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { } } - @Override - public void actionPerformed(ActionEvent event) { - addEditCentralRepoComment(); - } - /** * Create a Add/Edit dialog for the correlation attribute file instance * comment. The comment will be updated in the database if the file instance * exists there, or a new file instance will be added to the database with * the comment attached otherwise. + * + * The current comment for this instance is saved in case it is needed to + * update the display. If the comment was not changed either due to the + * action being canceled or the occurrence of an error, the comment will be + * null. */ - public void addEditCentralRepoComment() { - CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); + @Override + public void actionPerformed(ActionEvent event) { + CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute); centralRepoCommentDialog.display(); + comment = null; + if (centralRepoCommentDialog.isCommentUpdated()) { EamDb dbManager; @@ -99,44 +99,35 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { } else { dbManager.updateAttributeInstanceComment(correlationAttribute); } + + comment = centralRepoCommentDialog.getComment(); } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); + logger.log(Level.SEVERE, "Error adding comment", ex); + NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message( + "An error occurred while trying to save the comment to the central repository.", + NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDescriptor); } } } /** - * Create an instance labeled "Add/Edit Central Repository Comment" given an - * AbstractFile. This is intended for the result view. + * Retrieve the comment that was last saved. If a comment update was + * canceled or an error occurred while attempting to save the comment, the + * comment will be null. * - * @param file The file from which a correlation attribute to modify is - * derived. - * - * @return The instance. - * - * @throws EamDbException - * @throws NoCurrentCaseException - * @throws TskCoreException + * @return The comment. */ - @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=Add/Edit Central Repository Comment"}) - public static AddEditCentralRepoCommentAction createAddEditCentralRepoCommentAction(AbstractFile file) { - - return new AddEditCentralRepoCommentAction(file, - Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment()); + public String getComment() { + return comment; } - + /** - * Create an instance labeled "Add/Edit Comment" given a - * CorrelationAttribute. This is intended for the content view. - * - * @param correlationAttribute The correlation attribute to modify. - * - * @return The instance. + * Retrieve the associated correlation attribute. + * + * @return The correlation attribute. */ - @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditComment=Add/Edit Comment"}) - public static AddEditCentralRepoCommentAction createAddEditCommentAction(CorrelationAttribute correlationAttribute) { - - return new AddEditCentralRepoCommentAction(correlationAttribute, - Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditComment()); + public CorrelationAttribute getCorrelationAttribute() { + return correlationAttribute; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java index 4e42bfe9d7..529ffb8529 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.centralrepository; +import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; @@ -26,24 +27,30 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeIns * Dialog to allow Central Repository file instance comments to be added and * modified. */ +@Messages({"CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Repository Comment"}) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class CentralRepoCommentDialog extends javax.swing.JDialog { private final CorrelationAttribute correlationAttribute; private boolean commentUpdated = false; + private String currentComment = ""; /** * Create an instance. * * @param correlationAttribute The correlation attribute to be modified. - * @param title The title to assign the dialog. */ - CentralRepoCommentDialog(CorrelationAttribute correlationAttribute, String title) { - super(WindowManager.getDefault().getMainWindow(), title); + CentralRepoCommentDialog(CorrelationAttribute correlationAttribute) { + super(WindowManager.getDefault().getMainWindow(), Bundle.CentralRepoCommentDialog_title_addEditCentralRepoComment()); initComponents(); CorrelationAttributeInstance instance = correlationAttribute.getInstances().get(0); + + // Store the original comment + if (instance.getComment() != null) { + currentComment = instance.getComment(); + } pathLabel.setText(instance.getFilePath()); commentTextArea.setText(instance.getComment()); @@ -71,6 +78,17 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { boolean isCommentUpdated() { return commentUpdated; } + + /** + * Get the current comment. + * If the user hit OK, this will be the new comment. + * If the user canceled, this will be the original comment. + * + * @return the comment + */ + String getComment() { + return currentComment; + } /** * This method is called from within the constructor to initialize the form. @@ -168,8 +186,8 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - String comment = commentTextArea.getText(); - correlationAttribute.getInstances().get(0).setComment(comment); + currentComment = commentTextArea.getText(); + correlationAttribute.getInstances().get(0).setComment(currentComment); commentUpdated = true; dispose(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java new file mode 100755 index 0000000000..65a980558c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -0,0 +1,60 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.Action; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * This creates a single context menu item for adding or editing a Central + * Repository comment. + */ +@ServiceProvider(service = ContextMenuActionsProvider.class) +public class CentralRepoContextMenuActionsProvider implements ContextMenuActionsProvider { + + @Override + public List getActions() { + ArrayList actionsList = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + + if (selectedFiles.size() != 1) { + return actionsList; + } + + for (AbstractFile file : selectedFiles) { + if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { + AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(file); + if (action.getCorrelationAttribute() == null) { + action.setEnabled(false); + } + actionsList.add(action); + } + } + + return actionsList; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index 39759e3eb7..acb3fa42d1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -3,4 +3,7 @@ DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency -DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Comment +DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Central Repository Comment +DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date +DataContentViewerOtherCases.earliestCaseLabel.toolTipText= +DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date: diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 0097313943..60667bae46 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 @@ - + @@ -103,10 +103,10 @@ - + - + @@ -124,17 +124,36 @@ - - + + + + + + + + + + + + + - - - + + + + + + + + + + + - + @@ -174,6 +193,23 @@ + + + + + + + + + + + + + + + + + @@ -185,37 +221,22 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index bf7fef5fa8..26ceed5500 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -25,16 +25,19 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; -import java.util.stream.Collectors; import javax.swing.JFileChooser; import javax.swing.JMenuItem; import javax.swing.JOptionPane; @@ -45,6 +48,8 @@ import javax.swing.JPanel; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; @@ -56,7 +61,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -69,7 +73,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; /** * View correlation results from other cases @@ -80,6 +83,8 @@ import org.sleuthkit.datamodel.TskDataException; "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",}) public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { + private static final long serialVersionUID = -1L; + private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private final DataContentViewerOtherCasesTableModel tableModel; @@ -119,10 +124,18 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); } else if (jmi.equals(addCommentMenuItem)) { - CorrelationAttribute selectedAttribute = (CorrelationAttribute) tableModel.getRow(otherCasesTable.getSelectedRow()); - AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedAttribute); - action.addEditCentralRepoComment(); - otherCasesTable.repaint(); + try { + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); + AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(selectedNode.createCorrelationAttribute()); + action.actionPerformed(null); + String currentComment = action.getComment(); + if (currentComment != null) { + selectedNode.updateComment(action.getComment()); + otherCasesTable.repaint(); + } + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error performing Add/Edit Central Repository Comment action", ex); + } } } }; @@ -137,6 +150,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer(); otherCasesTable.setDefaultRenderer(Object.class, renderer); tableStatusPanelLabel.setVisible(false); + } @Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", @@ -157,7 +171,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } else { - StringBuilder msg = new StringBuilder(); + StringBuilder msg = new StringBuilder(correlationAttributes.size()); int percentage; try { EamDb dbManager = EamDb.getInstance(); @@ -187,23 +201,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi "DataContentViewerOtherCases.caseDetailsDialog.noCaseNameError=Error", "DataContentViewerOtherCases.noOpenCase.errMsg=No open case available."}) private void showCaseDetails(int selectedRowViewIdx) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, - Bundle.DataContentViewerOtherCases_noOpenCase_errMsg(), - Bundle.DataContentViewerOtherCases_noOpenCase_errMsg(), - DEFAULT_OPTION, PLAIN_MESSAGE); - return; - } + String caseDisplayName = Bundle.DataContentViewerOtherCases_caseDetailsDialog_noCaseNameError(); try { if (-1 != selectedRowViewIdx) { EamDb dbManager = EamDb.getInstance(); int selectedRowModelIdx = otherCasesTable.convertRowIndexToModel(selectedRowViewIdx); - CorrelationAttribute eamArtifact = (CorrelationAttribute) tableModel.getRow(selectedRowModelIdx); - CorrelationCase eamCasePartial = eamArtifact.getInstances().get(0).getCorrelationCase(); + OtherOccurrenceNodeData nodeData = (OtherOccurrenceNodeData) tableModel.getRow(selectedRowModelIdx); + CorrelationCase eamCasePartial = nodeData.getCorrelationAttributeInstance().getCorrelationCase(); if (eamCasePartial == null) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetailsReference(), @@ -213,7 +218,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } caseDisplayName = eamCasePartial.getDisplayName(); // query case details - CorrelationCase eamCase = dbManager.getCase(openCase); + CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID()); if (eamCase == null) { JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(), @@ -234,6 +239,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi DEFAULT_OPTION, PLAIN_MESSAGE); } } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error loading case details", ex); JOptionPane.showConfirmDialog(showCaseDetailsMenuItem, Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(), caseDisplayName, @@ -307,6 +313,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // start with empty table tableModel.clearTable(); correlationAttributes.clear(); + earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable()); } @Override @@ -453,10 +460,45 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return ret; } + @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Not Enabled."}) /** - * Query the database for artifact instances from other cases correlated to - * the given central repository artifact. Instances from the same datasource - * / device will also be included. + * 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(); + + if (EamDb.isEnabled()) { + LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC); + DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); + try { + EamDb dbManager = EamDb.getInstance(); + List cases = dbManager.getCases(); + for (CorrelationCase aCase : cases) { + 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) { + logger.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS + } + + } + earliestCaseDate.setText(dateStringDisplay); + } + + /** + * 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 @@ -464,33 +506,46 @@ 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(); - String filePath = (file.getParentPath() + file.getName()).toLowerCase(); - HashMap artifactInstances = new HashMap<>(); + + HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { - EamDb dbManager = EamDb.getInstance(); - artifactInstances.putAll(dbManager.getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).stream() - .filter(artifactInstance -> !artifactInstance.getFilePath().equals(filePath) - || !artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) - || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) - || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) - .collect(Collectors.toMap(correlationAttr -> new UniquePathKey(correlationAttr.getCorrelationDataSource().getDeviceID(), correlationAttr.getFilePath()), - correlationAttr -> correlationAttr))); - } + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = addCaseDbMatches(corAttr, openCase); - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateAttributeInstance(openCase, artifactInstances, caseDbFile); + 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 + // - the data source name is different + // - the data source device ID is different + // - the file path is different + if (!artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) + || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId) + || !artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName())) { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } } } - return artifactInstances; + if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { + List caseDbFiles = getCaseDbMatches(corAttr, openCase); + + for (AbstractFile caseDbFile : caseDbFiles) { + addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); + } + } + + return nodeDataMap; } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { @@ -504,9 +559,17 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return new HashMap<>(0); } - private List addCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { + /** + * 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 + */ + private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); - SleuthkitCase tsk = openCase.getSleuthkitCase(); List matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5})); @@ -522,75 +585,64 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Adds the file to the artifactInstances map if it does not already exist - * - * @param autopsyCase - * @param artifactInstances + * Adds the file to the nodeDataMap map if it does not already exist + * + * @param autopsyCase + * @param nodeDataMap * @param newFile * * @throws TskCoreException * @throws EamDbException */ - private void addOrUpdateAttributeInstance(final Case autopsyCase, Map artifactInstances, AbstractFile newFile) throws TskCoreException, EamDbException { - - // figure out if the casedb file is known via either hash or tags - TskData.FileKnown localKnown = newFile.getKnown(); - - if (localKnown != TskData.FileKnown.BAD) { + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(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) { List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); for (ContentTag tag : fileMatchTags) { TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { - localKnown = TskData.FileKnown.BAD; + newNode.updateKnown(TskData.FileKnown.BAD); break; } } } - // make a key to see if the file is already in the map - String filePath = newFile.getParentPath() + newFile.getName(); - String deviceId; - try { - deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); - } catch (TskDataException | TskCoreException ex) { - logger.log(Level.WARNING, "Error getting data source info: {0}", ex); - return; - } - UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); - - // double check that the CR version is BAD if the caseDB version is BAD. - if (artifactInstances.containsKey(uniquePathKey)) { - if (localKnown == TskData.FileKnown.BAD) { - CorrelationAttributeInstance prevInstance = artifactInstances.get(uniquePathKey); - prevInstance.setKnownStatus(localKnown); + // 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); + prevInstance.updateKnown(newNode.getKnown()); } - } // add the data from the case DB by pushing data into CorrelationAttributeInstance class - else { - // NOTE: If we are in here, it is likely because CR is not enabled. So, we cannot rely - // on any of the methods that query the DB. - CorrelationCase correlationCase = new CorrelationCase(autopsyCase.getName(), autopsyCase.getDisplayName()); - - CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, newFile.getDataSource()); - - CorrelationAttributeInstance caseDbInstance = new CorrelationAttributeInstance(correlationCase, correlationDataSource, filePath, "", localKnown); - artifactInstances.put(uniquePathKey, caseDbInstance); + } else { + nodeDataMap.put(uniquePathKey, newNode); } } @Override public boolean isSupported(Node node) { - this.file = this.getAbstractFileFromNode(node); - // Is supported if this node - // has correlatable content (File, BlackboardArtifact) OR - // other common files across datasources. + // Is supported if one of the following is true: + // - The central repo is enabled and the node has correlatable content + // (either through the MD5 hash of the associated file or through a BlackboardArtifact) + // - The central repo is disabled and the backing file has a valid MD5 hash + this.file = this.getAbstractFileFromNode(node); if (EamDb.isEnabled()) { return this.file != null && this.file.getSize() > 0 && !getCorrelationAttributesFromNode(node).isEmpty(); } else { return this.file != null - && this.file.getSize() > 0; + && this.file.getSize() > 0 + && ((this.file.getMd5Hash() != null) && ( ! this.file.getMd5Hash().isEmpty())); } } @@ -632,22 +684,14 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get the attributes we can correlate on correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); for (CorrelationAttribute corAttr : correlationAttributes) { - Map corAttrInstances = new HashMap<>(0); + Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB - corAttrInstances.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + + correlatedNodeDataMap.values().forEach((nodeData) -> { + tableModel.addNodeData(nodeData); - corAttrInstances.values().forEach((corAttrInstance) -> { - try { - CorrelationAttribute newCeArtifact = new CorrelationAttribute( - corAttr.getCorrelationType(), - corAttr.getCorrelationValue() - ); - newCeArtifact.addInstance(corAttrInstance); - tableModel.addEamArtifact(newCeArtifact); - } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error creating correlation attribute", ex); - } }); } @@ -660,6 +704,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi clearMessageOnTableStatusPanel(); setColumnWidths(); } + setEarliestCaseDate(); } private void setColumnWidths() { @@ -701,6 +746,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableContainerPanel = new javax.swing.JPanel(); tableScrollPane = new javax.swing.JScrollPane(); otherCasesTable = new javax.swing.JTable(); + earliestCaseLabel = new javax.swing.JLabel(); + earliestCaseDate = new javax.swing.JLabel(); tableStatusPanel = new javax.swing.JPanel(); tableStatusPanelLabel = new javax.swing.JLabel(); @@ -746,44 +793,55 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi otherCasesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); tableScrollPane.setViewportView(otherCasesTable); - tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N + earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N - tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N + + tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel); tableStatusPanel.setLayout(tableStatusPanelLayout); tableStatusPanelLayout.setHorizontalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 0, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) - .addContainerGap())) ); tableStatusPanelLayout.setVerticalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 16, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 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( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(tableStatusPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() + .addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1282, Short.MAX_VALUE) + .addGap(218, 218, 218)) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .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()) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .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)) + .addGap(0, 0, 0) .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) + .addGap(0, 0, 0)) ); javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); @@ -796,10 +854,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); otherCasesPanelLayout.setVerticalGroup( otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 60, Short.MAX_VALUE) + .addGap(0, 483, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 52, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -811,28 +869,31 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 52, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) ); }// //GEN-END:initComponents private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible - boolean addCommentMenuItemVisible = false; + boolean enableCentralRepoActions = false; if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { int rowIndex = otherCasesTable.getSelectedRow(); - CorrelationAttribute selectedAttribute = (CorrelationAttribute) tableModel.getRow(rowIndex); - if (selectedAttribute.getInstances().get(0).isDatabaseInstance() - && selectedAttribute.getCorrelationType().getId() == CorrelationAttribute.FILES_TYPE_ID) { - addCommentMenuItemVisible = true; + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); + if (selectedNode.isCentralRepoNode()) { + enableCentralRepoActions = true; } } - addCommentMenuItem.setVisible(addCommentMenuItemVisible); + addCommentMenuItem.setVisible(enableCentralRepoActions); + showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); + showCommonalityMenuItem.setVisible(enableCentralRepoActions); }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JFileChooser CSVFileChooser; private javax.swing.JMenuItem addCommentMenuItem; + private javax.swing.JLabel earliestCaseDate; + private javax.swing.JLabel earliestCaseLabel; private javax.swing.JMenuItem exportToCSVMenuItem; private javax.swing.JPanel otherCasesPanel; private javax.swing.JTable otherCasesTable; @@ -854,33 +915,26 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private final String dataSourceID; private final String filePath; + private final String type; - UniquePathKey(String theDataSource, String theFilePath) { + UniquePathKey(OtherOccurrenceNodeData nodeData) { super(); - dataSourceID = theDataSource; - filePath = theFilePath.toLowerCase(); - } - - /** - * - * @return the dataSourceID device ID - */ - String getDataSourceID() { - return dataSourceID; - } - - /** - * - * @return the filPath including the filename and extension. - */ - String getFilePath() { - return filePath; + dataSourceID = nodeData.getDeviceID(); + if (nodeData.getFilePath() != null) { + filePath = nodeData.getFilePath().toLowerCase(); + } else { + filePath = null; + } + type = nodeData.getType(); } @Override public boolean equals(Object other) { if (other instanceof UniquePathKey) { - return ((UniquePathKey) other).getDataSourceID().equals(dataSourceID) && ((UniquePathKey) other).getFilePath().equals(filePath); + UniquePathKey otherKey = (UniquePathKey)(other); + return ( Objects.equals(otherKey.dataSourceID, this.dataSourceID) + && Objects.equals(otherKey.filePath, this.filePath) + && Objects.equals(otherKey.type, this.type)); } return false; } @@ -890,7 +944,7 @@ 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); + return Objects.hash(dataSourceID, filePath, type); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java index f0ecb31e72..5febf88dc3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java @@ -68,10 +68,10 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } }; - List eamArtifacts; + List nodeDataList; DataContentViewerOtherCasesTableModel() { - eamArtifacts = new ArrayList<>(); + nodeDataList = new ArrayList<>(); } @Override @@ -95,7 +95,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public int getRowCount() { - return eamArtifacts.size(); + return nodeDataList.size(); } @Override @@ -105,15 +105,15 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == eamArtifacts.size()) { + if (0 == nodeDataList.size()) { return Bundle.DataContentViewerOtherCasesTableModel_noData(); } return mapValueById(rowIdx, TableColumns.values()[colIdx]); } - public Object getRow(int rowIdx) { - return eamArtifacts.get(rowIdx); + Object getRow(int rowIdx) { + return nodeDataList.get(rowIdx); } /** @@ -125,40 +125,39 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { * @return value in the cell */ private Object mapValueById(int rowIdx, TableColumns colId) { - CorrelationAttribute eamArtifact = eamArtifacts.get(rowIdx); - CorrelationAttributeInstance eamArtifactInstance = eamArtifact.getInstances().get(0); + OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); String value = Bundle.DataContentViewerOtherCasesTableModel_noData(); switch (colId) { case CASE_NAME: - if (null != eamArtifactInstance.getCorrelationCase()) { - value = eamArtifactInstance.getCorrelationCase().getDisplayName(); + if (null != nodeData.getCaseName()) { + value = nodeData.getCaseName(); } break; case DEVICE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getDeviceID(); + if (null != nodeData.getDeviceID()) { + value = nodeData.getDeviceID(); } break; case DATA_SOURCE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getName(); + if (null != nodeData.getDataSourceName()) { + value = nodeData.getDataSourceName(); } break; case FILE_PATH: - value = eamArtifactInstance.getFilePath(); + value = nodeData.getFilePath(); break; case TYPE: - value = eamArtifact.getCorrelationType().getDisplayName(); + value = nodeData.getType(); break; case VALUE: - value = eamArtifact.getCorrelationValue(); + value = nodeData.getValue(); break; case KNOWN: - value = eamArtifactInstance.getKnownStatus().getName(); + value = nodeData.getKnown().getName(); break; case COMMENT: - value = eamArtifactInstance.getComment(); + value = nodeData.getComment(); break; } return value; @@ -170,18 +169,17 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } /** - * Add one local central repository artifact to the table. + * Add one correlated instance object to the table * - * @param eamArtifact central repository artifact to add to the - * table + * @param newNodeData data to add to the table */ - public void addEamArtifact(CorrelationAttribute eamArtifact) { - eamArtifacts.add(eamArtifact); + void addNodeData(OtherOccurrenceNodeData newNodeData) { + nodeDataList.add(newNodeData); fireTableDataChanged(); } - public void clearTable() { - eamArtifacts.clear(); + 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 new file mode 100644 index 0000000000..958068fb14 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.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 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/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 45bc197283..c2e4651808 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -38,7 +38,7 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil.updateSchemaVersion; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.TskData; @@ -58,6 +58,9 @@ abstract class AbstractSqlEamDb implements EamDb { protected int bulkArtifactsThreshold; private final Map> bulkArtifacts; + // Maximum length for the value column in the instance tables + static final int MAX_VALUE_LENGTH = 256; + // number of instances to keep in bulk queue before doing an insert. // Update Test code if this changes. It's hard coded there. static final int DEFAULT_BULK_THRESHHOLD = 1000; @@ -384,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. @@ -432,7 +475,8 @@ abstract class AbstractSqlEamDb implements EamDb { if (eamDataSource.getCaseID() == -1) { throw new EamDbException("Case ID is -1"); } else if (eamDataSource.getID() != -1) { - throw new EamDbException("Database ID is already set in object"); + // This data source is already in the central repo + return; } Connection conn = connect(); @@ -498,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 @@ -550,6 +636,13 @@ abstract class AbstractSqlEamDb implements EamDb { if (eamArtifact.getCorrelationValue() == null) { throw new EamDbException("Correlation value is null"); } + if (eamArtifact.getCorrelationValue().length() >= MAX_VALUE_LENGTH) { + throw new EamDbException("Artifact value too long for central repository." + + "\nCorrelationArtifact ID: " + eamArtifact.getID() + + "\nCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName() + + "\nCorrelationArtifact Value: " + eamArtifact.getCorrelationValue()); + + } Connection conn = connect(); @@ -659,7 +752,7 @@ abstract class AbstractSqlEamDb implements EamDb { return artifactInstances; } - + /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath @@ -881,7 +974,7 @@ abstract class AbstractSqlEamDb implements EamDb { sql += "+ (SELECT count(*) FROM " + table_name - + " WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) and data_source_id=(SELECT id FROM data_sources WHERE device_id=?))"; + + " WHERE data_source_id=(SELECT data_sources.id FROM cases INNER JOIN data_sources ON cases.id = data_sources.case_id WHERE case_uid=? and device_id=?))"; } try { @@ -975,27 +1068,50 @@ abstract class AbstractSqlEamDb implements EamDb { if (!eamArtifact.getCorrelationValue().isEmpty()) { if (eamInstance.getCorrelationCase() == null) { - throw new EamDbException("CorrelationAttributeInstance case is null"); + throw new EamDbException("CorrelationAttributeInstance case is null for: " + + "\n\tCorrelationArtifact ID: " + eamArtifact.getID() + + "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName() + + "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue()); } if (eamInstance.getCorrelationDataSource() == null) { - throw new EamDbException("CorrelationAttributeInstance data source is null"); + throw new EamDbException("CorrelationAttributeInstance data source is null for: " + + "\n\tCorrelationArtifact ID: " + eamArtifact.getID() + + "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName() + + "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue()); } if (eamInstance.getKnownStatus() == null) { - throw new EamDbException("CorrelationAttributeInstance known status is null"); + throw new EamDbException("CorrelationAttributeInstance known status is null for: " + + "\n\tCorrelationArtifact ID: " + eamArtifact.getID() + + "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName() + + "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue() + + "\n\tEam Instance: " + + "\n\t\tCaseId: " + eamInstance.getCorrelationDataSource().getCaseID() + + "\n\t\tDeviceID: " + eamInstance.getCorrelationDataSource().getDeviceID()); } - bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); - bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); - bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); - bulkPs.setString(4, eamArtifact.getCorrelationValue()); - bulkPs.setString(5, eamInstance.getFilePath()); - bulkPs.setByte(6, eamInstance.getKnownStatus().getFileKnownValue()); - if ("".equals(eamInstance.getComment())) { - bulkPs.setNull(7, Types.INTEGER); + if (eamArtifact.getCorrelationValue().length() < MAX_VALUE_LENGTH) { + bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); + bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); + bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); + bulkPs.setString(4, eamArtifact.getCorrelationValue()); + bulkPs.setString(5, eamInstance.getFilePath()); + bulkPs.setByte(6, eamInstance.getKnownStatus().getFileKnownValue()); + if ("".equals(eamInstance.getComment())) { + bulkPs.setNull(7, Types.INTEGER); + } else { + bulkPs.setString(7, eamInstance.getComment()); + } + bulkPs.addBatch(); } else { - bulkPs.setString(7, eamInstance.getComment()); + logger.log(Level.WARNING, ("Artifact value too long for central repository." + + "\n\tCorrelationArtifact ID: " + eamArtifact.getID() + + "\n\tCorrelationArtifact Type: " + eamArtifact.getCorrelationType().getDisplayName() + + "\n\tCorrelationArtifact Value: " + eamArtifact.getCorrelationValue()) + + "\n\tEam Instance: " + + "\n\t\tCaseId: " + eamInstance.getCorrelationDataSource().getCaseID() + + "\n\t\tDeviceID: " + eamInstance.getCorrelationDataSource().getDeviceID() + + "\n\t\tFilePath: " + eamInstance.getFilePath()); } - bulkPs.addBatch(); } } } @@ -1004,8 +1120,8 @@ abstract class AbstractSqlEamDb implements EamDb { bulkArtifacts.get(type.getDbTableName()).clear(); } - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); - EnterpriseHealthMonitor.submitTimingMetric(timingMetric); + TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); + HealthMonitor.submitTimingMetric(timingMetric); // Reset state bulkArtifactsCount = 0; @@ -1740,11 +1856,13 @@ abstract class AbstractSqlEamDb implements EamDb { return 0 < badInstances; } + /** * Process the Artifact instance in the EamDb * - * @param type EamArtifact.Type to search for + * @param type EamArtifact.Type to search for * @param instanceTableCallback callback to process the instance + * * @throws EamDbException */ @Override @@ -1772,12 +1890,58 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all artifact instances from instances table", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } } + /** + * 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(3); + sql.append("select * from "); + sql.append(tableName); + sql.append(" WHERE "); + sql.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/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 8cf07a42e9..84dce834a1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -67,6 +67,7 @@ public class CorrelationDataSource implements Serializable { /** * Create a CorrelationDataSource object from a TSK Content object. + * This will add it to the central repository. * * @param correlationCase the current CorrelationCase used for ensuring * uniqueness of DataSource diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 6aad75d38e..808b314af0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -235,21 +235,37 @@ public class EamArtifactUtil { return null; } - CorrelationAttribute correlationAttribute = null; - + CorrelationAttribute correlationAttribute; + CorrelationAttribute.Type type; + CorrelationCase correlationCase; + CorrelationDataSource correlationDataSource; + String value; + String filePath; + try { - CorrelationAttribute.Type type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); - CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); + type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows()); if (null == correlationCase) { correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows()); } - CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource()); - String value = file.getMd5Hash(); - String filePath = (file.getParentPath() + file.getName()).toLowerCase(); - - correlationAttribute = EamDb.getInstance().getCorrelationAttribute(type, correlationCase, correlationDataSource, value, filePath); - } catch (TskCoreException | EamDbException | NoCurrentCaseException ex) { + correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource()); + value = file.getMd5Hash(); + filePath = (file.getParentPath() + file.getName()).toLowerCase(); + } catch (TskCoreException | EamDbException ex) { logger.log(Level.SEVERE, "Error retrieving correlation attribute.", ex); + return null; + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Case is closed.", ex); + return null; + } + + try { + correlationAttribute = EamDb.getInstance().getCorrelationAttribute(type, correlationCase, correlationDataSource, value, filePath); + } catch (EamDbException ex) { + logger.log(Level.WARNING, String.format( + "Correlation attribute could not be retrieved for '%s' (id=%d): %s", + content.getName(), content.getId(), ex.getMessage())); + return null; } return correlationAttribute; @@ -300,9 +316,12 @@ public class EamArtifactUtil { af.getParentPath() + af.getName()); eamArtifact.addInstance(cei); return eamArtifact; - } catch (TskCoreException | EamDbException | NoCurrentCaseException ex) { + } catch (TskCoreException | EamDbException ex) { logger.log(Level.SEVERE, "Error making correlation attribute.", ex); return null; + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Case is closed.", ex); + return 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/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java index f008070cea..ee1d297b7d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.apache.commons.dbcp2.BasicDataSource; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java index 1154da273d..32de4e7646 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java @@ -221,7 +221,7 @@ public final class PostgresEamDbSettings { LOGGER.log(Level.SEVERE, "Failed to execute database existance query.", ex); // NON-NLS return false; } finally { - EamDbUtil.closePreparedStatement(ps); + EamDbUtil.closeStatement(ps); EamDbUtil.closeResultSet(rs); EamDbUtil.closeConnection(conn); } 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/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index 55958287d5..a3d49d6ba3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -450,10 +450,10 @@ final class CaseEventListener implements PropertyChangeListener { correlationCase = dbManager.newCase(openCase); } if (null == dbManager.getDataSource(correlationCase, deviceId)) { - dbManager.newDataSource(CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource)); + CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource); } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS } catch (TskCoreException | TskDataException ex) { LOGGER.log(Level.SEVERE, "Error getting data source from DATA_SOURCE_ADDED event content.", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index 46ebe75541..e3cfad9fe8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -276,12 +276,12 @@ public class IngestEventsListener { } } if (FALSE == eamArtifacts.isEmpty()) { - try { - for (CorrelationAttribute eamArtifact : eamArtifacts) { + for (CorrelationAttribute eamArtifact : eamArtifacts) { + try { dbManager.addArtifact(eamArtifact); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error adding artifact to database.", ex); //NON-NLS } - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS } } // DATA_ADDED } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java index 33f790d72e..9c30cf05c5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java @@ -49,7 +49,7 @@ import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListener; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; /** @@ -135,9 +135,9 @@ final class IngestModule implements FileIngestModule { */ if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) { try { - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query"); + TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query"); List caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); - EnterpriseHealthMonitor.submitTimingMetric(timingMetric); + HealthMonitor.submitTimingMetric(timingMetric); if (!caseDisplayNamesList.isEmpty()) { postCorrelatedBadFileToBlackboard(abstractFile, caseDisplayNamesList); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties index 0106566831..5514e3316b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties @@ -65,3 +65,13 @@ GlobalSettingsPanel.manageOrganizationButton.text=Manage Organizations GlobalSettingsPanel.lbCentralRepository.text=A central repository allows you to correlate files and results between cases. GlobalSettingsPanel.pnCorrelationProperties.border.title=Correlation Properties GlobalSettingsPanel.organizationPanel.border.title=Organizations +GlobalSettingsPanel.casesPanel.border.title=Case Details +GlobalSettingsPanel.showCasesButton.text=Show Cases +ShowCasesDialog.closeButton.AccessibleContext.accessibleName=Close +ShowCasesDialog.closeButton.actionCommand=Close +ShowCasesDialog.closeButton.text=Close +ShowCasesDialog.caseDetailsTable.toolTipText=Click column name to sort. Right-click on the table for more options. +ShowCasesDialog.title=Case Details +GlobalSettingsPanel.Case\ Details.AccessibleContext.accessibleName=Cases Details +ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription=Click column name to sort. +GlobalSettingsPanel.casesTextArea.text=Display table that lists central repository case details. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form index 788fef4660..33c94bea0b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form @@ -27,7 +27,7 @@ - + @@ -57,24 +57,26 @@ - + - - - - - - - - - - + - - + + + + + + + + + + + + + @@ -90,8 +92,10 @@ + + - + @@ -328,7 +332,7 @@ - + @@ -398,6 +402,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index a1daad3a71..925edee795 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -124,6 +124,10 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i manageOrganizationButton = new javax.swing.JButton(); organizationScrollPane = new javax.swing.JScrollPane(); organizationTextArea = new javax.swing.JTextArea(); + casesPanel = new javax.swing.JPanel(); + showCasesButton = new javax.swing.JButton(); + casesScrollPane = new javax.swing.JScrollPane(); + casesTextArea = new javax.swing.JTextArea(); tbOops = new javax.swing.JTextField(); setName(""); // NOI18N @@ -276,7 +280,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addGroup(organizationPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(organizationPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(organizationScrollPane) + .addComponent(organizationScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 992, Short.MAX_VALUE) .addGroup(organizationPanelLayout.createSequentialGroup() .addComponent(manageOrganizationButton) .addGap(0, 0, Short.MAX_VALUE))) @@ -292,6 +296,52 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addGap(8, 8, 8)) ); + casesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesPanel.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Tahoma", 0, 12))); // NOI18N + casesPanel.setName("Case Details"); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(showCasesButton, org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.showCasesButton.text")); // NOI18N + showCasesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + showCasesButtonActionPerformed(evt); + } + }); + + casesScrollPane.setBorder(null); + + casesTextArea.setEditable(false); + casesTextArea.setBackground(new java.awt.Color(240, 240, 240)); + casesTextArea.setColumns(20); + casesTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + casesTextArea.setLineWrap(true); + casesTextArea.setRows(2); + casesTextArea.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesTextArea.text")); // NOI18N + casesTextArea.setWrapStyleWord(true); + casesTextArea.setBorder(null); + casesScrollPane.setViewportView(casesTextArea); + + javax.swing.GroupLayout casesPanelLayout = new javax.swing.GroupLayout(casesPanel); + casesPanel.setLayout(casesPanelLayout); + casesPanelLayout.setHorizontalGroup( + casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(casesPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(casesScrollPane) + .addGroup(casesPanelLayout.createSequentialGroup() + .addComponent(showCasesButton) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + casesPanelLayout.setVerticalGroup( + casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, casesPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(casesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(showCasesButton) + .addGap(8, 8, 8)) + ); + tbOops.setEditable(false); tbOops.setFont(tbOops.getFont().deriveFont(tbOops.getFont().getStyle() | java.awt.Font.BOLD, 12)); tbOops.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.tbOops.text")); // NOI18N @@ -301,20 +351,21 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, 1022, Short.MAX_VALUE) .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(cbUseCentralRepo) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 974, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(36, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pnCorrelationProperties, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 1012, Short.MAX_VALUE) - .addComponent(organizationPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnCorrelationProperties, javax.swing.GroupLayout.DEFAULT_SIZE, 1016, Short.MAX_VALUE) + .addComponent(organizationPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(casesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(cbUseCentralRepo) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 974, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -329,10 +380,14 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(organizationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(casesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 92, Short.MAX_VALUE)) + .addContainerGap()) ); + casesPanel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.Case Details.AccessibleContext.accessibleName")); // NOI18N + jScrollPane1.setViewportView(jPanel1); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -343,13 +398,13 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 488, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 517, Short.MAX_VALUE) ); }// //GEN-END:initComponents private void bnManageTypesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnManageTypesActionPerformed store(); - ManageCorrelationPropertiesDialog dialog = new ManageCorrelationPropertiesDialog(); + ManageCorrelationPropertiesDialog manageCorrelationDialog = new ManageCorrelationPropertiesDialog(); firePropertyChange(OptionsPanelController.PROP_VALID, null, null); }//GEN-LAST:event_bnManageTypesActionPerformed @@ -374,9 +429,14 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void manageOrganizationButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manageOrganizationButtonActionPerformed store(); - ManageOrganizationsDialog dialog = new ManageOrganizationsDialog(); + ManageOrganizationsDialog manageOrganizationsDialog = new ManageOrganizationsDialog(); }//GEN-LAST:event_manageOrganizationButtonActionPerformed + private void showCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showCasesButtonActionPerformed + store(); + ShowCasesDialog showCasesDialog = new ShowCasesDialog(); + }//GEN-LAST:event_showCasesButtonActionPerformed + @Override @Messages({"GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module."}) public void load() { @@ -553,12 +613,18 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i organizationPanel.setEnabled(enable && !ingestRunning); organizationTextArea.setEnabled(enable && !ingestRunning); manageOrganizationButton.setEnabled(enable && !ingestRunning); + showCasesButton.setEnabled(enable && !ingestRunning); + casesPanel.setEnabled(enable && !ingestRunning); + casesTextArea.setEnabled(enable && !ingestRunning); return true; } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton bnDbConfigure; private javax.swing.JButton bnManageTypes; + private javax.swing.JPanel casesPanel; + private javax.swing.JScrollPane casesScrollPane; + private javax.swing.JTextArea casesTextArea; private javax.swing.JCheckBox cbUseCentralRepo; private javax.swing.JScrollPane correlationPropertiesScrollPane; private javax.swing.JTextArea correlationPropertiesTextArea; @@ -577,6 +643,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private javax.swing.JTextArea organizationTextArea; private javax.swing.JPanel pnCorrelationProperties; private javax.swing.JPanel pnDatabaseConfiguration; + private javax.swing.JButton showCasesButton; private javax.swing.JTextField tbOops; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java index a7c3016d96..3fa46b3306 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.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"); @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.centralrepository.optionspanel; -import java.awt.Dimension; -import java.awt.Toolkit; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -38,8 +36,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; /** * Dialog to handle management of artifact types handled by the Central - * Repository + * Repository */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ManageCorrelationPropertiesDialog extends javax.swing.JDialog { private static final Logger LOGGER = Logger.getLogger(ManageCorrelationPropertiesDialog.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form new file mode 100644 index 0000000000..c6a18ff739 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form @@ -0,0 +1,169 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java new file mode 100644 index 0000000000..6432c5376f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -0,0 +1,190 @@ +/* + * Central Repository + * + * Copyright 2015-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.optionspanel; + +import java.util.List; +import java.util.logging.Level; +import javax.swing.JDialog; +import javax.swing.JFrame; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Dialog to display table of CorrelationCase information from the CR tab of options. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +final class ShowCasesDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final static Logger logger = Logger.getLogger(ShowCasesDialog.class.getName()); + + private final ShowCasesTableModel tableModel; + @Messages({"ShowCasesDialog.title_text=All Cases Details"}) + /** + * Creates new form ShowCases Panel + */ + ShowCasesDialog() { + super((JFrame) WindowManager.getDefault().getMainWindow(), + Bundle.ShowCasesDialog_title_text(), + true); + tableModel = new ShowCasesTableModel(); + initComponents(); + try { + EamDb dbManager = EamDb.getInstance(); + List eamCases = dbManager.getCases(); + for(CorrelationCase eamCase : eamCases) { + tableModel.addEamCase(eamCase); + } + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS + } + display(); + } + + private void display() { + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); + } + + + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + showCasesPanel = new javax.swing.JPanel(); + showCasesScrollPane = new javax.swing.JScrollPane(); + outCasesPane = new javax.swing.JPanel(); + innerCaseScrollPane = new javax.swing.JScrollPane(); + caseDetailsTable = new javax.swing.JTable(); + closeButton = new javax.swing.JButton(); + + setTitle(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.title")); // NOI18N + setMinimumSize(new java.awt.Dimension(545, 415)); + + showCasesPanel.setPreferredSize(new java.awt.Dimension(527, 407)); + + showCasesScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + showCasesScrollPane.setPreferredSize(new java.awt.Dimension(535, 415)); + + innerCaseScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + caseDetailsTable.setAutoCreateRowSorter(true); + caseDetailsTable.setModel(tableModel); + caseDetailsTable.setToolTipText(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.toolTipText")); // NOI18N + caseDetailsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + caseDetailsTable.getTableHeader().setReorderingAllowed(false); + innerCaseScrollPane.setViewportView(caseDetailsTable); + caseDetailsTable.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription")); // NOI18N + + javax.swing.GroupLayout outCasesPaneLayout = new javax.swing.GroupLayout(outCasesPane); + outCasesPane.setLayout(outCasesPaneLayout); + outCasesPaneLayout.setHorizontalGroup( + outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 1423, Short.MAX_VALUE) + .addGroup(outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1423, Short.MAX_VALUE)) + ); + outCasesPaneLayout.setVerticalGroup( + outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 500, Short.MAX_VALUE) + .addGroup(outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)) + ); + + showCasesScrollPane.setViewportView(outCasesPane); + + javax.swing.GroupLayout showCasesPanelLayout = new javax.swing.GroupLayout(showCasesPanel); + showCasesPanel.setLayout(showCasesPanelLayout); + showCasesPanelLayout.setHorizontalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 1188, Short.MAX_VALUE) + .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 527, Short.MAX_VALUE)) + ); + showCasesPanelLayout.setVerticalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 473, Short.MAX_VALUE) + .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE)) + ); + + org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.text")); // NOI18N + closeButton.setActionCommand(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.actionCommand")); // NOI18N + closeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + closeButtonActionPerformed(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() + .addGap(6, 6, 6) + .addComponent(showCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1188, Short.MAX_VALUE) + .addGap(6, 6, 6)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(closeButton) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addComponent(showCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 473, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(closeButton) + .addContainerGap()) + ); + + closeButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.AccessibleContext.accessibleName")); // NOI18N + + pack(); + }// //GEN-END:initComponents + + private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed + dispose(); + }//GEN-LAST:event_closeButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTable caseDetailsTable; + private javax.swing.JButton closeButton; + private javax.swing.JScrollPane innerCaseScrollPane; + private javax.swing.JPanel outCasesPane; + private javax.swing.JPanel showCasesPanel; + private javax.swing.JScrollPane showCasesScrollPane; + // End of variables declaration//GEN-END:variables + + + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java new file mode 100644 index 0000000000..def6cb15b6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java @@ -0,0 +1,187 @@ +/* + * 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.optionspanel; + +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.CorrelationCase; + +/** + * Model for cells to display correlation case information + */ +class ShowCasesTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + @Messages({"ShowCasesTableModel.case=Case Name", + "ShowCasesTableModel.creationDate=Creation Date", + "ShowCasesTableModel.caseNumber=Case Number", + "ShowCasesTableModel.examinerName=Examiner Name", + "ShowCasesTableModel.examinerEmail=Examiner Email", + "ShowCasesTableModel.examinerPhone=Examiner Phone", + "ShowCasesTableModel.notes=Notes", + "ShowCasesTableModel.noData=No Cases"}) + /** + * Enum which lists columns of interest from CorrelationCase. + */ + enum TableColumns { + // Ordering here determines displayed column order in Content Viewer. + // If order is changed, update the CellRenderer to ensure correct row coloring. + CASE_NAME(Bundle.ShowCasesTableModel_case(), 200), + CREATION_DATE(Bundle.ShowCasesTableModel_creationDate(), 150), + CASE_NUMBER(Bundle.ShowCasesTableModel_caseNumber(), 100), + EXAMINER_NAME(Bundle.ShowCasesTableModel_examinerName(), 200), + EXAMINER_EMAIL(Bundle.ShowCasesTableModel_examinerEmail(), 100), + EXAMINER_PHONE(Bundle.ShowCasesTableModel_examinerPhone(), 100), + NOTES(Bundle.ShowCasesTableModel_notes(), 450); + + private final String columnName; + private final int columnWidth; + + TableColumns(String columnName, int columnWidth) { + this.columnName = columnName; + this.columnWidth = columnWidth; + } + + String columnName() { + return columnName; + } + + int columnWidth() { + return columnWidth; + } + }; + + /** + * list of Eam Cases from central repository. + */ + private List eamCases; + + ShowCasesTableModel() { + eamCases = new ArrayList<>(); + } + + @Override + public int getColumnCount() { + return TableColumns.values().length; + } + + /** + * Get the preferred width that has been configured for this column. + * + * A value of 0 means that no preferred width has been defined for this + * column. + * + * @param colIdx Column index + * + * @return preferred column width >= 0 + */ + int getColumnPreferredWidth(int colIdx) { + return TableColumns.values()[colIdx].columnWidth(); + } + + @Override + public int getRowCount() { + return eamCases.size(); + } + + @Override + public String getColumnName(int colIdx) { + return TableColumns.values()[colIdx].columnName(); + } + + @Override + public Object getValueAt(int rowIdx, int colIdx) { + if (eamCases.isEmpty()) { + return Bundle.ShowCasesTableModel_noData(); + } + + return mapValueById(rowIdx, TableColumns.values()[colIdx]); + } + + Object getRow(int rowIdx) { + return eamCases.get(rowIdx); + } + + /** + * Map a rowIdx and colId to the value in that cell. + * + * @param rowIdx Index of row to search + * @param colId ID of column to search + * + * @return value in the cell + */ + private Object mapValueById(int rowIdx, TableColumns colId) { + CorrelationCase eamCase = eamCases.get(rowIdx); + String value = Bundle.ShowCasesTableModel_noData(); + + switch (colId) { + case CASE_NAME: + value = eamCase.getDisplayName(); + break; + case CREATION_DATE: + value = eamCase.getCreationDate(); + break; + case CASE_NUMBER: + value = eamCase.getCaseNumber(); + break; + case EXAMINER_NAME: + value = eamCase.getExaminerName(); + break; + case EXAMINER_EMAIL: + value = eamCase.getExaminerEmail(); + break; + case EXAMINER_PHONE: + value = eamCase.getExaminerPhone(); + break; + case NOTES: + value = eamCase.getNotes(); + break; + default: + break; + } + return value; + } + + @Override + public Class getColumnClass(int colIdx) { + return String.class; + } + + /** + * Add one local central repository case to the table. + * + * @param eamCase central repository case to add to the + * table + */ + void addEamCase(CorrelationCase eamCase) { + eamCases.add(eamCase); + fireTableDataChanged(); + } + + void clearTable() { + eamCases.clear(); + fireTableDataChanged(); + } + + + +} 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..0eda8c5e6f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AbstractCommonAttributeSearcher.java @@ -0,0 +1,213 @@ +/* + * + * 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).addMetadataToList(md5Metadata); + } else { + CommonAttributeValueList value = new CommonAttributeValueList(); + value.addMetadataToList(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..e11953cfa9 --- /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 74% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java index 044241ccf5..eed61b0fc1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllIntraCaseCommonAttributeSearcher.java @@ -20,13 +20,14 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.Map; +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 != 1 OR known IS NULL)%s GROUP BY md5 HAVING COUNT(*) > 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 @@ -36,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); } @@ -48,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 72ddffb748..fbdc43d143 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -1,15 +1,24 @@ -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 duplicate files in the current case. -CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... -CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types +CommonFilesPanel.commonFilesSearchLabel.text=Find files in multiple data sources in the current case. 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: +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..994df4c66a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstance.java @@ -0,0 +1,75 @@ +/* + * + * 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(); + + AbstractFile abstractFile = tskDb.findAllFilesWhere(String.format("obj_id in (%s)", this.getAbstractFileObjectId())).get(0); + + return abstractFile; + + } 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/CaseDBCommonAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java new file mode 100644 index 0000000000..b44855c51c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java @@ -0,0 +1,90 @@ +/* + * 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.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Node that wraps CaseDBCommonAttributeInstance to represent a file instance stored + * in the CaseDB. + */ +public class CaseDBCommonAttributeInstanceNode extends FileNode { + + private final String caseName; + private final String dataSource; + + /** + * Create a node which can be used in a multilayer tree table and is based + * on an AbstractFile. + * + * @param fsContent + * @param dataSource + */ + public CaseDBCommonAttributeInstanceNode(AbstractFile fsContent, String caseName, String dataSource) { + super(fsContent); + this.caseName = caseName; + this.dataSource = dataSource; + } + + @Override + public boolean isLeafTypeNode(){ + //Not used atm - could maybe be leveraged for better use in Children objects + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + public String getCase(){ + return this.caseName; + } + + public String getDataSource() { + return this.dataSource; + } + + @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 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()))); + + this.addTagProperty(sheetSet); + + return sheet; + } +} 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..1b33bb9422 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributePanel.java @@ -0,0 +1,716 @@ +/* + * 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 { + EamDb DbManager = EamDb.getInstance(); + return DbManager != 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, Exception { + 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 Exception { + 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 Exception { + + 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 60% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java index 67c8c096bc..42ad7979e1 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeSearchResultRootNode.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import org.sleuthkit.autopsy.datamodel.InstanceCountNode; import java.util.List; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -28,12 +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 Md5NodeFactory. + * 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)); } @@ -58,25 +59,33 @@ final public class CommonFilesNode extends DisplayableItemNode { public String getItemType() { return getClass().getName(); } + + /** + * Used to generate InstanceCountNodes. + */ + static class InstanceCountNodeFactory extends ChildFactory{ - static class InstanceCountNodeFactory extends ChildFactory { - - private final CommonFilesMetadata metadata; - - InstanceCountNodeFactory(CommonFilesMetadata metadata) { - this.metadata = metadata; + private final CommonAttributeSearchResults searchResults; + + /** + * Build a factory which converts a CommonAttributeSearchResults + * object into DisplayableItemNodes. + * @param searchResults + */ + 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) { - Md5MetadataList md5Metadata = this.metadata.getMetadataForMd5(instanceCount); - return new InstanceCountNode(instanceCount, md5Metadata); + protected Node createNodeForKey(Integer instanceCount){ + CommonAttributeValueList 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..85417fe831 --- /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 + */ + CommonAttributeValueList 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 (CommonAttributeValueList data : this.instanceCountToAttributeValues.values()) { + for(CommonAttributeValue md5 : data.getMetadataList()){ + 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 50% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java index 501a70e09b..0d388af932 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValue.java @@ -19,52 +19,74 @@ */ 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() { + final String cases = this.fileInstances.stream().map(AbstractCommonAttributeInstance::getCaseName).collect(Collectors.joining(", ")); + return cases; } 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); + + final String dataSources = String.join(", ", sources); + return dataSources; + } + + 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/Md5MetadataList.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java similarity index 81% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5MetadataList.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java index 3e57ec7d55..6f9d7d796e 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5MetadataList.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueList.java @@ -28,10 +28,10 @@ import java.util.List; * results. Subclass this to implement different selections of files from the * case. */ -final public class Md5MetadataList { +final public class CommonAttributeValueList { - private final List metadataList; - private final List delayedMetadataList; + private final List metadataList; + private final List delayedMetadataList; /** * Create a metadata object containing the list of metadata which can be @@ -39,17 +39,17 @@ final public class Md5MetadataList { * * @param metadata list of Md5Metadata indexed by size of Md5Metadata */ - Md5MetadataList(List metadata) { + CommonAttributeValueList(List metadata) { this.metadataList = new ArrayList<>(); this.delayedMetadataList = metadata; } - Md5MetadataList() { + CommonAttributeValueList() { this.metadataList = new ArrayList<>(); this.delayedMetadataList = new ArrayList<>(); } - public List getMetadataList() { + public List getMetadataList() { return Collections.unmodifiableList(this.metadataList); } @@ -59,7 +59,7 @@ final public class Md5MetadataList { } } - public void addMetadataToList(Md5Metadata metadata) { + public void addMetadataToList(CommonAttributeValue metadata) { delayedMetadataList.add(metadata); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java new file mode 100644 index 0000000000..e7160e8d11 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributeValueNode.java @@ -0,0 +1,155 @@ +/* + * + * 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.List; +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.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * 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 CommonAttributeValueNode extends DisplayableItemNode { + + private static final Logger LOGGER = Logger.getLogger(CommonAttributeValueNode.class.getName()); + + private final String value; + private final int commonFileCount; + private final String cases; + private final String dataSources; + + @NbBundle.Messages({ + "Md5Node.Md5Node.format=MD5: %s" + }) + /** + * Create a Match node whose children will all have this object in common. + * @param data the common feature, and the children + */ + public CommonAttributeValueNode(CommonAttributeValue data) { + super(Children.create( + new FileInstanceNodeFactory(data), true)); + + 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.value = data.getValue(); + + this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.value)); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS + } + + /** + * How many files are in common? This will be the number of children. + * @return int + */ + int getCommonFileCount() { + return this.commonFileCount; + } + + String getCases(){ + return this.cases; + } + + /** + * Datasources where these matches occur. + * @return string delimited list of sources + */ + String getDataSources() { + return this.dataSources; + } + + /** + * MD5 which is common to these matches + * @return string md5 hash + */ + public String getValue() { + return this.value; + } + + @NbBundle.Messages({"Md5Node.createSheet.noDescription= "}) + @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 String NO_DESCR = Bundle.Md5Node_createSheet_noDescription(); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSources())); + + return sheet; + } + + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /** + * Child generator for SleuthkitCaseFileInstanceNode of + * CommonAttributeValueNode. + */ + static class FileInstanceNodeFactory extends ChildFactory { + + private final CommonAttributeValue descendants; + + FileInstanceNodeFactory(CommonAttributeValue descendants) { + this.descendants = descendants; + } + + @Override + protected boolean createKeys(List list) { + list.addAll(this.descendants.getInstances()); + return true; + } + + @Override + protected Node[] createNodesForKey(AbstractCommonAttributeInstance searchResult) { + return searchResult.generateNodes(); + } + + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java new file mode 100644 index 0000000000..5630c56b89 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonAttributesSearchResultsViewerTable.java @@ -0,0 +1,100 @@ +/* + * + * 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.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; +import org.sleuthkit.autopsy.corecomponents.DelayedLoadChildNodesOnTreeExpansion; + +/** + * 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 CommonAttributesSearchResultsViewerTable extends DataResultViewerTable { + + private static final Map COLUMN_WIDTHS; + private static final long serialVersionUID = 1L; + + private static final Logger LOGGER = Logger.getLogger(CommonAttributesSearchResultsViewerTable.class.getName()); + + private static final int DEFAULT_WIDTH = 100; + + static { + Map map = new HashMap<>(); + 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); + map.put(Bundle.CommonFilesSearchResultsViewerTable_tagsColLbl1(), 300);; + + COLUMN_WIDTHS = Collections.unmodifiableMap(map); + } + + public CommonAttributesSearchResultsViewerTable() { + super(new DelayedLoadChildNodesOnTreeExpansion()); + } + + @NbBundle.Messages({ + "CommonFilesSearchResultsViewerTable.noDescText= ", + "CommonFilesSearchResultsViewerTable.filesColLbl=Files", + "CommonFilesSearchResultsViewerTable.instancesColLbl=Instances", + "CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path", + "CommonFilesSearchResultsViewerTable.hashsetHitsColLbl=Hash Set Hits", + "CommonFilesSearchResultsViewerTable.caseColLbl1=Case", + "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source", + "CommonFilesSearchResultsViewerTable.mimeTypeColLbl=MIME Type", + "CommonFilesSearchResultsViewerTable.tagsColLbl1=Tags" + }) + @Override + protected void setColumnWidths() { + TableColumnModel model = this.getColumnModel(); + + Enumeration columnsEnumerator = model.getColumns(); + while (columnsEnumerator.hasMoreElements()) { + + TableColumn column = columnsEnumerator.nextElement(); + + final String headerValue = column.getHeaderValue().toString(); + + final Integer defaultWidth = COLUMN_WIDTHS.get(headerValue); + + if(defaultWidth == null){ + column.setPreferredWidth(DEFAULT_WIDTH); + LOGGER.log(Level.SEVERE, String.format("Tried to set width on a column not supported by the CommonFilesSearchResultsViewerTable: %s", headerValue)); + } else { + column.setPreferredWidth(defaultWidth); + } + } + } +} 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 0341662ffa..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java +++ /dev/null @@ -1,74 +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.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 - */ - Md5MetadataList 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 (Md5MetadataList data : this.metadata.values()) { - for(Md5Metadata md5 : data.getMetadataList()){ - 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 3865c7a7e6..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 filterByMimeTypesWhereClause = " 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).addMetadataToList(md5Metadata); - } else { - Md5MetadataList metaList = new Md5MetadataList(); - metaList.addMetadataToList(md5Metadata); - instanceCollatedCommonFiles.put(size, metaList); - } - } - - 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(filterByMimeTypesWhereClause, 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 2bb9bbdd1e..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ /dev/null @@ -1,569 +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.openide.explorer.ExplorerManager; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; -import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.DelayedLoadChildNodesOnTreeExpansion; -import org.sleuthkit.autopsy.corecomponents.MultiLayerTableFilterNode; -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(multipleDataSources); - CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(multipleDataSources); - - if (!multipleDataSources) { - CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(true); - withinDataSourceSelected(true); - } - - 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() >= 2; - } - - @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.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 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 { - 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!!! Instead delay enumeration by dynamically loading nodes with DelayedLoadChildNodesOnTreeExpansion() - DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonFilesPanel.this)); - - MultiLayerTableFilterNode tableFilterWithDescendantsNode = new MultiLayerTableFilterNode(dataResultFilterNode, 3); - - DataResultViewerTable table = new DataResultViewerTable(new DelayedLoadChildNodesOnTreeExpansion()); - - Collection viewers = new ArrayList<>(1); - viewers.add(table); - - 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); - } - } - }.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 e470fd16bc..80e4f9754a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -19,28 +19,45 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.awt.event.ActionEvent; +import java.util.logging.Level; 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.core.Installer; +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; /** * Encapsulates a menu action which triggers the common files search dialog. */ 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; - + CommonFilesSearchAction() { super(); this.setEnabled(false); } - + @Override public boolean isEnabled(){ - return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); + boolean shouldBeEnabled = false; + try { + shouldBeEnabled = Case.isCaseOpen() + && Case.getCurrentCase().getDataSources().size() > 1 + || (EamDb.isEnabled() && EamDb.getInstance().getCases().size() > 1); + + } catch(TskCoreException 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; } public static synchronized CommonFilesSearchAction getDefault() { @@ -52,12 +69,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 new file mode 100644 index 0000000000..da418c6213 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -0,0 +1,153 @@ +/* + * + * 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.Iterator; +import java.util.List; +import java.util.Map; +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.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; + +/** + * Node used to indicate the number of matches found with the MD5 children + * of this Node. + */ +final public class InstanceCountNode extends DisplayableItemNode { + + final private int instanceCount; + final private CommonAttributeValueList attributeValues; + + /** + * Create a node with the given number of instances, and the given + * selection of metadata. + * @param instanceCount + * @param attributeValues + */ + @NbBundle.Messages({ + "InstanceCountNode.displayName=Files with %s instances (%s)" + }) + public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues) { + super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), true)); + + this.instanceCount = instanceCount; + this.attributeValues = attributeValues; + + this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), attributeValues.getMetadataList().size())); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS + } + + /** + * Number of matches found for each of the MD5 children. + * @return int match count + */ + int getInstanceCount() { + return this.instanceCount; + } + + public void refresh() { + attributeValues.displayDelayedMetadata(); + setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), false)); + } + + /** + * Get a list of metadata for the MD5s which are children of this object. + * @return List + */ + CommonAttributeValueList getAttributeValues() { + return this.attributeValues; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @NbBundle.Messages({"InstanceCountNode.createSheet.noDescription= "}) + @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 String NO_DESCR = Bundle.InstanceCountNode_createSheet_noDescription(); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, "")); + sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_instancesColLbl(), NO_DESCR, this.getInstanceCount())); + return sheet; + } + + + /** + * ChildFactory which builds CommonFileParentNodes from the + * CommonFilesMetaaData models. + */ + static class CommonAttributeValueNodeFactory extends ChildFactory { + + /** + * List of models, each of which is a parent node matching a single md5, + * containing children FileNodes. + */ + // maps sting version of value to value Object (??) + private final Map metadata; + + CommonAttributeValueNodeFactory(List attributeValues) { + this.metadata = new HashMap<>(); + + Iterator iterator = attributeValues.iterator(); + while (iterator.hasNext()) { + CommonAttributeValue attributeValue = iterator.next(); + this.metadata.put(attributeValue.getValue(), attributeValue); + } + } + + @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..2959c51078 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCasePanel.java @@ -0,0 +1,193 @@ +/* + * + * 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; + + /** + * Creates new form InterCasePanel + */ + public InterCasePanel() { + initComponents(); + this.errorMessage = ""; + this.caseMap = new HashMap<>(); + } + + 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); + } + }//GEN-LAST:event_specificCentralRepoCaseRadioActionPerformed + + private void anyCentralRepoCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_anyCentralRepoCaseRadioActionPerformed + this.caseComboBox.setEnabled(false); + }//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(){ + 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..942f84133a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InterCaseSearchResultsProcessor.java @@ -0,0 +1,230 @@ +/* + * 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.Collections; +import java.util.HashMap; +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"; + + InterCaseSearchResultsProcessor(Map dataSources){ + this.dataSources = dataSources; + } + + InterCaseSearchResultsProcessor(){} + + /** + * 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).addMetadataToList(commonAttributeValue); + } else { + CommonAttributeValueList value = new CommonAttributeValueList(); + value.addMetadataToList(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..195320917c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCaseCommonAttributeSearcher.java @@ -0,0 +1,166 @@ +/* + * + * 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("'").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; + } +} 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..e0938df3f8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/IntraCasePanel.java @@ -0,0 +1,218 @@ +/* + * + * 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; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * 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 static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName()); + + private boolean singleDataSource; + private String selectedDataSource; + 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<>(); + } + + public boolean isSingleDataSource(){ + return this.singleDataSource; + } + + public String getSelectedDataSource(){ + if(this.singleDataSource && this.selectedDataSource != null){ + return selectedDataSource; + } else { + return ""; + } + } + + public Map getDataSourceMap(){ + return Collections.unmodifiableMap(this.dataSourceMap); + } + + Long getSelectedDataSourceId(){ + 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.setEnabled(false); + selectDataSourceComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + selectDataSourceComboBoxActionPerformed(evt); + } + }); + + 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 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 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..3974e1684e --- /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 74% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java index f3f8ef689d..02bd480093 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleIntraCaseCommonAttributeSearcher.java @@ -20,13 +20,14 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.Map; +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 != 1 OR known IS NULL) and data_source_obj_id=%s%s) GROUP BY md5 HAVING COUNT(*) > 1) order by md5"; //NON-NLS + 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; private final String dataSourceName; @@ -40,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); @@ -49,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/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 14b8b283c4..339e842280 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -33,7 +33,7 @@ import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** @@ -142,7 +142,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov rootNode = selectedNode; } messagesResultPanel.setPath(rootNode.getDisplayName()); - messagesResultPanel.setNode(new SingleLayerTableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true)); + messagesResultPanel.setNode(new TableFilterNode(new DataResultFilterNode(rootNode, gacExplorerManager), true)); } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 4ebca21bff..8f08b248fd 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -38,7 +38,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.datamodel.NodeProperty; @@ -99,7 +99,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") public MessageContentViewer() { initComponents(); - drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new SingleLayerTableFilterNode(Node.EMPTY, false), 0, null); + drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null); attachmentsScrollPane.setViewportView(drp); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); @@ -596,7 +596,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, numberOfAttachments > 0); msgbodyTabbedPane.setTitleAt(ATTM_TAB_INDEX, "Attachments (" + numberOfAttachments + ")"); - drp.setNode(new SingleLayerTableFilterNode(new DataResultFilterNode(new AbstractNode( + drp.setNode(new TableFilterNode(new DataResultFilterNode(new AbstractNode( new AttachmentsChildren(attachments)), null), true)); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index b7a5498bf8..40a667e3d7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -56,7 +56,6 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.TimeStampUtils; /** * A file content viewer for SQLite database files. @@ -289,28 +288,22 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { fileChooser.setAcceptAllFileFilterUsed(true); fileChooser.setFileFilter(csvFilter); fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + String defaultFileName = (String) this.tablesDropdownList.getSelectedItem(); + fileChooser.setSelectedFile(new File(defaultFileName)); int choice = fileChooser.showSaveDialog((Component) evt.getSource()); //TODO if (JFileChooser.APPROVE_OPTION == choice) { - boolean overwrite = false; File file = fileChooser.getSelectedFile(); - if (file == null) { - JOptionPane.showMessageDialog(this, - Bundle.SQLiteViewer_csvExport_fileName_empty(), - Bundle.SQLiteViewer_csvExport_title(), - JOptionPane.WARNING_MESSAGE); - return; - } else if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) { + if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, Bundle.SQLiteViewer_csvExport_confirm_msg(), Bundle.SQLiteViewer_csvExport_title(), JOptionPane.YES_NO_OPTION)) { - overwrite = true; } else { return; } } - exportTableToCsv(file, overwrite); + exportTableToCsv(file); } }//GEN-LAST:event_exportCsvButtonActionPerformed @@ -567,9 +560,8 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { "SQLiteViewer.exportTableToCsv.FileName=File name: ", "SQLiteViewer.exportTableToCsv.TableName=Table name: " }) - private void exportTableToCsv(File file, boolean overwrite) { + private void exportTableToCsv(File file) { String tableName = (String) this.tablesDropdownList.getSelectedItem(); - String csvFileSuffix = "_" + tableName + "_" + TimeStampUtils.createTimeStamp() + ".csv"; try ( Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName)) { @@ -578,42 +570,42 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { if (Objects.isNull(currentTableRows) || currentTableRows.isEmpty()) { logger.log(Level.INFO, String.format("The table %s is empty. (objId=%d)", tableName, sqliteDbFile.getId())); //NON-NLS } else { - String fileName = file.getName(); File csvFile; - if (overwrite) { + String fileName = file.getName(); + if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) { csvFile = file; - } else if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) { - csvFile = new File(file.getParentFile(), FilenameUtils.removeExtension(fileName) + csvFileSuffix); } else { - csvFile = new File(file.toString() + csvFileSuffix); + csvFile = new File(file.toString() + ".csv"); } - FileOutputStream out = new FileOutputStream(csvFile, false); - out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes()); - out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes()); - // Set up the column names - Map row = currentTableRows.get(0); - StringBuffer header = new StringBuffer(); - for (Map.Entry col : row.entrySet()) { - String colName = col.getKey(); - if (header.length() > 0) { - header.append(',').append(colName); - } else { - header.append(colName); - } - } - out.write(header.append('\n').toString().getBytes()); + try (FileOutputStream out = new FileOutputStream(csvFile, false)) { - for (Map maps : currentTableRows) { - StringBuffer valueLine = new StringBuffer(); - maps.values().forEach((value) -> { - if (valueLine.length() > 0) { - valueLine.append(',').append(value.toString()); + out.write((Bundle.SQLiteViewer_exportTableToCsv_FileName() + csvFile.getName() + "\n").getBytes()); + out.write((Bundle.SQLiteViewer_exportTableToCsv_TableName() + tableName + "\n").getBytes()); + // Set up the column names + Map row = currentTableRows.get(0); + StringBuffer header = new StringBuffer(); + for (Map.Entry col : row.entrySet()) { + String colName = col.getKey(); + if (header.length() > 0) { + header.append(',').append(colName); } else { - valueLine.append(value.toString()); + header.append(colName); } - }); - out.write(valueLine.append('\n').toString().getBytes()); + } + out.write(header.append('\n').toString().getBytes()); + + for (Map maps : currentTableRows) { + StringBuffer valueLine = new StringBuffer(); + maps.values().forEach((value) -> { + if (valueLine.length() > 0) { + valueLine.append(',').append(value.toString()); + } else { + valueLine.append(value.toString()); + } + }); + out.write(valueLine.append('\n').toString().getBytes()); + } } } } catch (SQLException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 904ab285c8..22fa2b7135 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -69,6 +69,7 @@ public final class UserPreferences { private static final String MODE = "AutopsyMode"; // NON-NLS private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; private static final int LOG_FILE_NUM_INT = 10; + public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS // Prevent instantiation. private UserPreferences() { @@ -187,6 +188,14 @@ public final class UserPreferences { preferences.putInt(NUMBER_OF_FILE_INGEST_THREADS, value); } + public static boolean groupItemsInTreeByDatasource() { + return preferences.getBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, false); + } + + public static void setGroupItemsInTreeByDatasource(boolean value) { + preferences.putBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, value); + } + /** * Reads persisted case database connection info. * diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/CoreComponentControl.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/CoreComponentControl.java index bf26dc8295..06ad27962a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/CoreComponentControl.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/CoreComponentControl.java @@ -88,7 +88,21 @@ final public class CoreComponentControl { TopComponent directoryTree = null; TopComponent favorites = null; final WindowManager windowManager = WindowManager.getDefault(); + + // Set the UI selections to null before closing the top components. + // Otherwise it may experience errors trying to load data for the closed case. for (Mode mode : windowManager.getModes()) { + for (TopComponent tc : windowManager.getOpenedTopComponents(mode)) { + if(tc instanceof DataContent) { + ((DataContent) tc).setNode(null); + } else if(tc instanceof DataResult) { + ((DataResult) tc).setNode(null); + } + } + } + + for (Mode mode : windowManager.getModes()) { + for (TopComponent tc : windowManager.getOpenedTopComponents(mode)) { String tcName = tc.getName(); @@ -105,7 +119,7 @@ final public class CoreComponentControl { } } } - + if (directoryTree != null) { directoryTree.close(); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java index cccb6e1a46..4589c43755 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java @@ -1,12 +1,24 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2011-2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; @@ -16,6 +28,7 @@ import org.openide.windows.WindowManager; * the panel given to it. No additional buttons or features, except the default * close operation, which is set to dispose. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** @@ -27,6 +40,8 @@ public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationCleanDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java index 35a26344ea..a62d3a1459 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,26 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * AdvancedConfigurationDialog.java - * - * Created on Feb 28, 2012, 4:47:31 PM - */ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; /** - * - * @author dfickling + * Advanced configuration dialog. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** @@ -47,6 +39,8 @@ public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index bae8218148..bb98cc90f6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -26,7 +26,7 @@ LBL_Description=
Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools.
Copyright © 2003-2018.
URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.7.0/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.8.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html LBL_Close=Close diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index a5760a26aa..2cccd31428 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -486,7 +486,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()) - || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID())) { + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID()) + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())) { return 3; } else { return 6; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index 53491b407e..24f54fa7ac 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -28,7 +28,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; * but the initial method was needed only be viewers in * corecomponents and therefore can stay out of public API. */ -class DataContentViewerUtility { +public class DataContentViewerUtility { /** * Returns the first non-Blackboard Artifact from a Node. * Needed for (at least) Hex and Strings that want to view @@ -39,7 +39,7 @@ class DataContentViewerUtility { * @param node Node passed into content viewer * @return highest priority content or null if there is no content */ - static Content getDefaultContent(Node node) { + public static Content getDefaultContent(Node node) { Content bbContentSeen = null; for (Content content : (node).getLookup().lookupAll(Content.class)) { if (content instanceof BlackboardArtifact) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 535201de30..9d0a83c2ad 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -26,7 +26,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import javax.swing.JTabbedPane; -import javax.swing.SwingWorker; +import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.openide.explorer.ExplorerManager; @@ -443,7 +443,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * found. */ int tabToSelect = NO_TAB_SELECTED; - if (selectedNode instanceof SingleLayerTableFilterNode) { + if (selectedNode instanceof TableFilterNode) { NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { for (int i = 0; i < resultViewers.size(); ++i) { @@ -579,29 +579,6 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } } - /** - * Worker for RootNodeListener childrenAdded. - */ - class SetupTabsChildrenWorker extends SwingWorker { - - private final Node childNode; - - SetupTabsChildrenWorker(Node aChildNode) { - childNode = aChildNode; - } - - @Override - protected Void doInBackground() throws Exception { - setupTabs(childNode); - return null; - } - - @Override - protected void done() { - setupTabs(childNode); - } - } - /** * Responds to changes in the root node due to asynchronous child node * creation. @@ -628,8 +605,13 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C */ if (waitingForData && containsReal(delta)) { waitingForData = false; - Node childNode = nme.getNode(); - new SetupTabsChildrenWorker(childNode).execute(); + if (SwingUtilities.isEventDispatchThread()) { + setupTabs(nme.getNode()); + } else { + SwingUtilities.invokeLater(() -> { + setupTabs(nme.getNode()); + }); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index a9b7beaec2..1ef52568e1 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -79,7 +79,7 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; */ @ServiceProvider(service = DataResultViewer.class) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class DataResultViewerTable extends AbstractDataResultViewer { +public class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName()); @@ -156,10 +156,9 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * Configure the child OutlineView (explorer view) component. */ outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); - + outline = outlineView.getOutline(); outline.setRowSelectionAllowed(true); - outline.setColumnSelectionAllowed(true); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setRootVisible(false); outline.setDragEnabled(false); @@ -224,6 +223,11 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public void setNode(Node rootNode) { + if (! SwingUtilities.isEventDispatchThread()) { + LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread"); + return; + } + /* * The quick filter must be reset because when determining column width, * ETable.getRowCount is called, and the documentation states that quick @@ -297,7 +301,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * let the table resize itself. */ outline.setAutoResizeMode((props.isEmpty()) ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF); - + assignColumns(props); // assign columns to match the properties if (firstProp != null) { ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(firstProp.getDisplayName()); @@ -381,7 +385,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * Sets the column widths for the child OutlineView of this tabular results * viewer. */ - private void setColumnWidths() { + protected void setColumnWidths() { if (rootNode.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { @@ -417,6 +421,10 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); } } + + protected TableColumnModel getColumnModel(){ + return outline.getColumnModel(); + } /* * Sets up the columns for the child OutlineView of this tabular results @@ -547,7 +555,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (rootNode instanceof SingleLayerTableFilterNode) { + if (rootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final TableFilterNode tfn = ((TableFilterNode) rootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index b14dbf53c2..ea4f4d3136 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -77,7 +77,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); private final PageUpdater pageUpdater = new PageUpdater(); - private SingleLayerTableFilterNode rootNode; + private TableFilterNode rootNode; private ThumbnailViewChildren rootNodeChildren; private NodeSelectionListener selectionListener; private int currentPage; @@ -387,7 +387,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { } try { if (givenNode != null) { - rootNode = (SingleLayerTableFilterNode) givenNode; + rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will * produce ThumbnailPageNodes with ThumbnailViewNode children diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java index 8dd71af77d..dc47eba7cc 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DelayedLoadChildNodesOnTreeExpansion.java @@ -45,8 +45,8 @@ public final class DelayedLoadChildNodesOnTreeExpansion implements TreeExpansion @Override public synchronized void treeExpanded(final TreeExpansionEvent event) { Node eventNode = Visualizer.findNode(event.getPath().getLastPathComponent()); - if (eventNode instanceof MultiLayerTableFilterNode) { //!inRecursion && - final MultiLayerTableFilterNode node = (MultiLayerTableFilterNode) eventNode; + if (eventNode instanceof TableFilterNode) { //!inRecursion && + final TableFilterNode node = (TableFilterNode) eventNode; node.refresh(); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/NoAction.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java old mode 100644 new mode 100755 similarity index 59% rename from Core/src/org/sleuthkit/autopsy/commonfilesearch/NoAction.java rename to Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java index 308f7eb48e..4c49490373 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/NoAction.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java @@ -1,35 +1,31 @@ /* - * * 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.awt.event.ActionEvent; -import javax.swing.AbstractAction; +package org.sleuthkit.autopsy.corecomponents; /** - * Default action for nodes which do not represent files, such as - * InstanceCountNode and Md5Node. + * This class exists to support backwards compatibility of an erroneous call to + * Logger.getLogger(GSTVideoPanel.class.getName()) in OpenCVFrameCapture.java in + * an older version of the Video Triage Net Beans Module (NBM). It should be + * removed when we are ready to stop supporting older Video Triage NBMs. The + * current Video Triage code has already been updated. */ -public class NoAction extends AbstractAction { +@Deprecated +public class GSTVideoPanel { - @Override - public void actionPerformed(ActionEvent e) { - //intentionally does nothing - } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiLayerTableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiLayerTableFilterNode.java deleted file mode 100644 index 53f705b7b4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiLayerTableFilterNode.java +++ /dev/null @@ -1,106 +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.corecomponents; - -import org.openide.nodes.FilterNode; -import org.openide.nodes.Node; -import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; -import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; - -/** - * A filter node that creates multiple layers of child nodes for the node it - * wraps. It is designed to be used in the results view to ensure the individual - * viewers display only the first layer of child nodes. - */ -public class MultiLayerTableFilterNode extends FilterNode implements TableFilterNode { - - private final boolean createChildren; - private final boolean forceUseWrappedDisplayName; - private static final String columnOrderKey = "NONE"; - //private final Node innerNode; - - /** - * Constructs a filter node that generates children using - * TableFilterChildrenWithDescendants. This enables row to have descendants. - * - * Enables use of getDisplayName() for children of this node. - * - * @param node The node to wrap - */ - public MultiLayerTableFilterNode(Node node, int childLayerDepth) { - super(node, TableFilterChildrenWithDescendants.createInstance(node, true, childLayerDepth), Lookups.proxy(node)); - this.createChildren = true; - this.forceUseWrappedDisplayName = true; - //this.innerNode = node; - } - - void refresh() { - DataResultFilterNode innerNode = getLookup().lookup(DataResultFilterNode.class); - innerNode.refresh(); - } - - @Override - public String getDisplayName() { - if (this.forceUseWrappedDisplayName) { - return super.getDisplayName(); - } else if (createChildren) { - return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text"); - } else { - return super.getDisplayName(); - } - } - - @Override - public NodeSelectionInfo getChildNodeSelectionInfo() { - /* - * Currently, child selection is only supported for nodes selected in - * the tree view and decorated with a DataResultFilterNode. - */ - if (getOriginal() instanceof DataResultFilterNode) { - return ((DataResultFilterNode) getOriginal()).getChildNodeSelectionInfo(); - } else { - return null; - } - } - - @Override - public String getColumnOrderKey() { - return columnOrderKey; - } - - @Override - public String getParentDisplayName() { - return super.getDisplayName(); - } - - @Override - public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) { - /* - * TODO maybe we dont actually want to do anything here...? - * Currently, child selection is only supported for nodes selected in - * the tree view and decorated with a DataResultFilterNode. - */ - if (getOriginal() instanceof DataResultFilterNode) { - ((DataResultFilterNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java index 498857cdd5..7d03ec65eb 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ResultViewerPersistence.java @@ -152,7 +152,7 @@ final class ResultViewerPersistence { * @return A map from sort rank to sort criterion, where rank 1 means that * this is the most important sort criteria, 2 means second etc. */ - static List< SortCriterion> loadSortCriteria(SingleLayerTableFilterNode node) { + static List< SortCriterion> loadSortCriteria(TableFilterNode node) { List> availableProperties = ResultViewerPersistence.getAllChildProperties(node, 100); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); java.util.SortedMap criteriaMap = new TreeMap<>(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/SingleLayerTableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/SingleLayerTableFilterNode.java deleted file mode 100644 index cb27bf89d7..0000000000 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/SingleLayerTableFilterNode.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-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.corecomponents; - -import org.openide.nodes.FilterNode; -import org.openide.nodes.Node; -import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; -import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; - -/** - * A filter node that creates at most one layer of child nodes for the node it - * wraps. It is designed to be used in the results view to ensure the individual - * viewers display only the first layer of child nodes. - */ -public class SingleLayerTableFilterNode extends FilterNode implements TableFilterNode { - - private final boolean createChildren; - private final boolean forceUseWrappedDisplayName; - private String columnOrderKey = "NONE"; - - /** - * Constructs a filter node that creates at most one layer of child nodes - * for the node it wraps. It is designed to be used in the results view to - * ensure the individual viewers display only the first layer of child - * nodes. - * - * @param node The node to wrap in the filter node. - * @param createChildren True if a Children object should be created for the - * wrapped node. - */ - public SingleLayerTableFilterNode(Node node, boolean createChildren) { - super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); - this.forceUseWrappedDisplayName = false; - this.createChildren = createChildren; - } - - /** - * Constructs a filter node that creates at most one layer of child nodes - * for the node it wraps. It is designed to be used in the results view to - * ensure the individual viewers display only the first layer of child - * nodes. - * - * @param node The node to wrap in the filter node. - * @param createChildren True if a Children object should be created for the - * wrapped node. - * @param columnOrderKey A key that represents the type of the original - * wrapped node and what is being displayed under that node. - */ - public SingleLayerTableFilterNode(Node node, boolean createChildren, String columnOrderKey) { - super(node, TableFilterChildren.createInstance(node, createChildren)); - this.forceUseWrappedDisplayName = false; - this.createChildren = createChildren; - this.columnOrderKey = columnOrderKey; - } - - /** - * Gets the display name for the wrapped node, for use in the first column - * of an Autopsy table view. - * - * @return The display name. - */ - @Override - public String getDisplayName() { - if (this.forceUseWrappedDisplayName) { - return super.getDisplayName(); - } else if (createChildren) { - return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text"); - } else { - return super.getDisplayName(); - } - } - - @Override - public String getParentDisplayName() { - return super.getDisplayName(); - } - - /** - * Adds information about which child node of this node, if any, should be - * selected. Can be null. - * - * @param selectedChildNodeInfo The child node selection information. - */ - @Override - public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) { - /* - * Currently, child selection is only supported for nodes selected in - * the tree view and decorated with a DataResultFilterNode. - */ - if (getOriginal() instanceof DataResultFilterNode) { - ((DataResultFilterNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo); - } - } - - /** - * Gets information about which child node of this node, if any, should be - * selected. - * - * @return The child node selection information, or null if no child should - * be selected. - */ - @Override - public NodeSelectionInfo getChildNodeSelectionInfo() { - /* - * Currently, child selection is only supported for nodes selected in - * the tree view and decorated with a DataResultFilterNode. - */ - if (getOriginal() instanceof DataResultFilterNode) { - return ((DataResultFilterNode) getOriginal()).getChildNodeSelectionInfo(); - } else { - return null; - } - } - - /** - * @return the column order key, which allows custom column ordering to be - * written into a properties file and be reloaded for future use in a table - * with the same root node or for different cases. This is done by - * DataResultViewerTable. The key should represent what kinds of items the - * table is showing. - */ - @Override - public String getColumnOrderKey() { - return columnOrderKey; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java index cc42efebb7..d5715845a3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java @@ -23,26 +23,25 @@ import org.openide.nodes.FilterNode; import org.openide.nodes.Node; /** - * A Children implementation for a SingleLayerTableFilterNode. A SingleLayerTableFilterNode creates at - most one layer of child nodes for the node it wraps. It is designed to be - used in the results view to ensure the individual viewers display only the - first layer of child nodes. + * A Children implementation for a + * TableFilterNode. A + * TableFilterNode creates at most one layer of child + * nodes for the node it wraps. It is designed to be used in the results view + * to ensure the individual viewers display only the first layer of child nodes. */ class TableFilterChildren extends FilterNode.Children { /** - * Creates a Children object for a SingleLayerTableFilterNode. A SingleLayerTableFilterNode + * Creates a Children object for a TableFilterNode. A TableFilterNode creates at most one layer of child nodes for the node it wraps. It is designed to be used in the results view to ensure the individual viewers display only the first layer of child nodes. * - * - * @param wrappedNode The node wrapped by the SingleLayerTableFilterNode. + * @param wrappedNode The node wrapped by the TableFilterNode. * @param createChildren True if a children (child factory) object should be * created for the wrapped node. * - * @return A children (child factory) object for a node wrapped by a - SingleLayerTableFilterNode. + * @return A children (child factory) object for a node wrapped by a TableFilterNode. */ public static Children createInstance(Node wrappedNode, boolean createChildren) { @@ -54,28 +53,29 @@ class TableFilterChildren extends FilterNode.Children { } /** - * Constructs a children (child factory) implementation for a - SingleLayerTableFilterNode. A SingleLayerTableFilterNode creates at most one layer of child - nodes for the node it wraps. It is designed to be used for nodes - displayed in Autopsy table views. + * Constructs a children (child factory) implementation for a + * TableFilterNode. A + * TableFilterNode creates at most one layer of + * child nodes for the node it wraps. It is designed to be used for nodes + * displayed in Autopsy table views. * - * @param wrappedNode The node wrapped by the SingleLayerTableFilterNode. + * @param wrappedNode The node wrapped by the TableFilterNode. */ TableFilterChildren(Node wrappedNode) { super(wrappedNode); } /** - * Copies a SingleLayerTableFilterNode, with the create children (child factory) flag - set to false. + * Copies a TableFilterNode, with the create children + (child factory) flag set to false. * - * @param nodeToCopy The SingleLayerTableFilterNode to copy. + * @param nodeToCopy The TableFilterNode to copy. * - * @return A copy of a SingleLayerTableFilterNode. + * @return A copy of a TableFilterNode. */ @Override protected Node copyNode(Node nodeToCopy) { - return new SingleLayerTableFilterNode(nodeToCopy, false); + return new TableFilterNode(nodeToCopy, false); } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java index 7b728f84c4..6f5f32b247 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java @@ -28,14 +28,29 @@ import org.openide.nodes.Node; */ final class TableFilterChildrenWithDescendants extends TableFilterChildren { - private int childLayerDepth; + private final int childLayerDepth; + /** + * Used to create children of the given node, with the specified number of + * child generations. + * + * @param wrappedNode node with children + * @param childLayerDepth number of subsequent generations. + */ private TableFilterChildrenWithDescendants(Node wrappedNode, int childLayerDepth) { super(wrappedNode); this.childLayerDepth = childLayerDepth; } - public static Children createInstance(Node wrappedNode, boolean createChildren, int childLayerDepth){ + /** + * Factory method for getting an instance of the Children object based on the + * node with children, and the number of subsequent generations. + * + * @param wrappedNode node that has children + * @param childLayerDepth + * @return object capable of generating child node + */ + public static Children createInstance(Node wrappedNode, int childLayerDepth){ if(childLayerDepth == 0){ return Children.LEAF; } else { @@ -45,6 +60,6 @@ final class TableFilterChildrenWithDescendants extends TableFilterChildren { @Override protected Node copyNode(Node nodeToCopy){ - return new MultiLayerTableFilterNode(nodeToCopy, this.childLayerDepth); + return new TableFilterNode(nodeToCopy, this.childLayerDepth); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java index 0a798f971c..890fb4ace3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java @@ -1,16 +1,15 @@ /* - * * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. + * + * Copyright 2011-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. @@ -19,15 +18,97 @@ */ package org.sleuthkit.autopsy.corecomponents; +import org.openide.nodes.FilterNode; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; +import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; /** - * Specifies behavior of nodes which are displayed in the DataResultTopComponent. + * A filter node that creates at most one layer of child nodes for the node it + * wraps. It is designed to be used in the results view to ensure the individual + * viewers display only the first layer of child nodes. */ -public interface TableFilterNode { +public class TableFilterNode extends FilterNode { + + private final boolean createChildren; + private final boolean forceUseWrappedDisplayName; + private String columnOrderKey = "NONE"; + + /** + * Constructs a filter node that creates at most one layer of child nodes + * for the node it wraps. It is designed to be used in the results view to + * ensure the individual viewers display only the first layer of child + * nodes. + * + * @param node The node to wrap in the filter node. + * @param createChildren True if a Children object should be created for the + * wrapped node. + */ + public TableFilterNode(Node node, boolean createChildren) { + super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); + this.forceUseWrappedDisplayName = false; + this.createChildren = createChildren; + } + + /** + * Constructs a filter node that creates at most one layer of child nodes + * for the node it wraps. It is designed to be used in the results view to + * ensure the individual viewers display only the first layer of child + * nodes. + * + * @param node The node to wrap in the filter node. + * @param createChildren True if a Children object should be created for the + * wrapped node. + * @param columnOrderKey A key that represents the type of the original + * wrapped node and what is being displayed under that node. + */ + public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) { + super(node, TableFilterChildren.createInstance(node, createChildren)); + this.forceUseWrappedDisplayName = false; + this.createChildren = createChildren; + this.columnOrderKey = columnOrderKey; + } - //TODO DisplayableItemNode - //TODO would be nice if this could be cast to Node - probably not totally critical... + public TableFilterNode(Node node, int childLayerDepth){ + super(node, TableFilterChildrenWithDescendants.createInstance(node, childLayerDepth), Lookups.proxy(node)); + this.createChildren = true; + this.forceUseWrappedDisplayName = true; + } + + /** + * Gets the display name for the wrapped node, for use in the first column + * of an Autopsy table view. + * + * @return The display name. + */ + @Override + public String getDisplayName() { + if (this.forceUseWrappedDisplayName) { + return super.getDisplayName(); + } else if (createChildren) { + return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text"); + } else { + return super.getDisplayName(); + } + } + + /** + * Adds information about which child node of this node, if any, should be + * selected. Can be null. + * + * @param selectedChildNodeInfo The child node selection information. + */ + public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo) { + /* + * Currently, child selection is only supported for nodes selected in + * the tree view and decorated with a DataResultFilterNode. + */ + if (getOriginal() instanceof DataResultFilterNode) { + ((DataResultFilterNode) getOriginal()).setChildNodeSelectionInfo(selectedChildNodeInfo); + } + } /** * Gets information about which child node of this node, if any, should be @@ -36,7 +117,23 @@ public interface TableFilterNode { * @return The child node selection information, or null if no child should * be selected. */ - public NodeSelectionInfo getChildNodeSelectionInfo(); + public NodeSelectionInfo getChildNodeSelectionInfo() { + /* + * Currently, child selection is only supported for nodes selected in + * the tree view and decorated with a DataResultFilterNode. + */ + if (getOriginal() instanceof DataResultFilterNode) { + return ((DataResultFilterNode) getOriginal()).getChildNodeSelectionInfo(); + } else { + return null; + } + } + + void refresh() { + DataResultFilterNode innerNode = getLookup().lookup(DataResultFilterNode.class); + innerNode.refresh(); + + } /** * @return the column order key, which allows custom column ordering to be @@ -45,23 +142,7 @@ public interface TableFilterNode { * DataResultViewerTable. The key should represent what kinds of items the * table is showing. */ - public String getColumnOrderKey(); - - /** - * Gets the display name for the wrapped node, for use in the first column - * of an Autopsy table view. - * - * @return The display name. - */ - public String getDisplayName(); - - public String getParentDisplayName(); - - /** - * Adds information about which child node of this node, if any, should be - * selected. Can be null. - * - * @param selectedChildNodeInfo The child node selection information. - */ - public void setChildNodeSelectionInfo(NodeSelectionInfo selectedChildNodeInfo); + public String getColumnOrderKey() { + return columnOrderKey; + } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 2e2461a3ea..79c02fcc84 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -133,10 +133,10 @@ class ThumbnailViewChildren extends Children.Keys { private synchronized Comparator getComparator() { Comparator comp = (node1, node2) -> 0; //eveything is equal. - if (!(parent instanceof SingleLayerTableFilterNode)) { + if (!(parent instanceof TableFilterNode)) { return comp; } else { - List sortCriteria = loadSortCriteria((SingleLayerTableFilterNode) parent); + List sortCriteria = loadSortCriteria((TableFilterNode) parent); /** * Make a comparator that will sort the nodes. diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java index 4c742acfac..5b432124ba 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/FileUtil.java @@ -169,7 +169,7 @@ public class FileUtil { public static String escapeFileName(String fileName) { //for now escaping /:"*?<>| (not valid in file name, at least on Windows) //with underscores. We are only keeping \ as it could be part of the path. - return fileName.replaceAll("[/:\"*?<>|]+", "_"); + return fileName.replaceAll("[\\p{Cntrl}/:\"*?<>|]+", "_"); } /** diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 5089ed41e2..e856b25418 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -124,9 +124,9 @@ public class ImageUtils { if (OpenCvLoader.isOpenCvLoaded()) { try { if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS - System.loadLibrary("opencv_ffmpeg2413_64"); //NON-NLS + System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS } else { - System.loadLibrary("opencv_ffmpeg2413"); //NON-NLS + System.loadLibrary("opencv_ffmpeg248"); //NON-NLS } tempFfmpegLoaded = true; } catch (UnsatisfiedLinkError e) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index c9b271a68e..1a52a61bb2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -251,34 +251,19 @@ public abstract class AbstractAbstractFileNode extends A map.put(TYPE_DIR.toString(), content.getDirType().getLabel()); map.put(TYPE_META.toString(), content.getMetaType().toString()); map.put(KNOWN.toString(), content.getKnown().getName()); - map.put(HASHSETS.toString(), getHashSetHitsForFile(content)); + map.put(HASHSETS.toString(), getHashSetHitsCsvList(content)); map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash())); map.put(ObjectID.toString(), content.getId()); map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType())); map.put(EXTENSION.toString(), content.getNameExtension()); } - @Override - public Action[] getActions(boolean context) { - List actionsList = new ArrayList<>(); - - actionsList.addAll(Arrays.asList(super.getActions(true))); - - // Create the "Add/Edit Central Repository Comment" menu item if the enabled. - AbstractFile file = content; - if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { - actionsList.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); - } - - return actionsList.toArray(new Action[actionsList.size()]); - } - /** * Used by subclasses of AbstractAbstractFileNode to add the tags property * to their sheets. * * @param sheetSet the modifiable Sheet.Set returned by - * Sheet.get(Sheet.PROPERTIES) + * Sheet.get(Sheet.PROPERTIES) */ @NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags") protected void addTagProperty(Sheet.Set sheetSet) { @@ -316,7 +301,15 @@ public abstract class AbstractAbstractFileNode extends A } } - public static String getHashSetHitsForFile(AbstractFile file) { + /** + * Gets a comma-separated values list of the names of the hash sets + * currently identified as including a given file. + * + * @param file The file. + * + * @return The CSV list of hash set names. + */ + protected static String getHashSetHitsCsvList(AbstractFile file) { try { return StringUtils.join(file.getHashSetNames(), ", "); } catch (TskCoreException tskCoreException) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bf139f9a47..71705d2725 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -162,12 +162,12 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(DeletedContent dc) { - return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase()); + return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override public AbstractNode visit(FileSize dc) { - return new FileSize.FileSizeRootNode(dc.getSleuthkitCase()); + return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override @@ -192,22 +192,27 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { - return tagsNodeKey.new RootNode(); + return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); } @Override public AbstractNode visit(DataSources i) { - return new DataSourcesNode(); + return new DataSourcesNode(i.filteringDataSourceObjId()); } + @Override + public AbstractNode visit(DataSourceGrouping datasourceGrouping) { + return new DataSourceGroupingNode(datasourceGrouping.getDataSource()); + } + @Override public AbstractNode visit(Views v) { - return new ViewsNode(v.getSleuthkitCase()); + return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId()); } @Override - public AbstractNode visit(Results r) { - return new ResultsNode(r.getSleuthkitCase()); + public AbstractNode visit(Results results) { + return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() ); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index b4c34e5da7..3fc3fa9f4e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -110,6 +110,17 @@ public abstract class AbstractContentNode extends ContentNode */ public static boolean contentHasVisibleContentChildren(Content c){ if (c != null) { + + try { + if( ! c.hasChildren()) { + return false; + } + } catch (TskCoreException ex) { + + logger.log(Level.SEVERE, "Error checking if the node has children, for content: " + c, ex); //NON-NLS + return false; + } + String query = "SELECT COUNT(obj_id) AS count FROM " + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId() + " AND type = " + TskData.ObjectType.ARTIFACT.getObjectType() diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index 2dcc5942db..29843bccb7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.datamodel.accounts.Accounts; public interface AutopsyItemVisitor { T visit(DataSources i); + + T visit(DataSourceGrouping datasourceGrouping); T visit(Views v); @@ -173,6 +175,11 @@ public interface AutopsyItemVisitor { return defaultVisit(v); } + @Override + public T visit(DataSourceGrouping datasourceGrouping) { + return defaultVisit(datasourceGrouping); + } + @Override public T visit(Results r) { return defaultVisit(r); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java new file mode 100644 index 0000000000..70f0ca3ce6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildFactory.java @@ -0,0 +1,141 @@ +/* + * 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Child factory to create the top level children of the autopsy tree + * + */ +public final class AutopsyTreeChildFactory extends ChildFactory.Detachable { + + private static final Logger logger = Logger.getLogger(AutopsyTreeChildFactory.class.getName()); + + /** + * Listener for handling DATA_SOURCE_ADDED events. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString()) && + UserPreferences.groupItemsInTreeByDatasource()) { + refreshChildren(); + } + } + }; + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + protected void removeNotify() { + super.removeNotify(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + /** + * Creates keys for the top level children. + * + * @param list list of keys created + * @return true, indicating that the key list is complete + */ + @Override + protected boolean createKeys(List list) { + + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + + if (UserPreferences.groupItemsInTreeByDatasource()) { + List dataSources = tskCase.getDataSources(); + List keys = new ArrayList<>(); + dataSources.forEach((datasource) -> { + keys.add(new DataSourceGrouping(datasource)); + }); + list.addAll(keys); + + list.add(new Reports()); + } else { + List keys = new ArrayList<>(Arrays.asList( + new DataSources(), + new Views(tskCase), + new Results(tskCase), + new Tags(), + new Reports())); + + list.addAll(keys); + } + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error getting datas sources list from the database.", tskCoreException); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + } + return true; + } + + /** + * Creates nodes for the top level Key + * + * @param key + * + * @return Node for the key, null if key is unknown. + */ + @Override + protected Node createNodeForKey(Object key) { + + if (key instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); + } else if (key instanceof AutopsyVisitableItem) { + return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); + } + else { + logger.log(Level.SEVERE, "Unknown key type ", key.getClass().getName()); + return null; + } + } + + /** + * Refresh the children + */ + public void refreshChildren() { + refresh(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 9e003c5be4..d6fb9afc04 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -221,11 +221,6 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.displayName"), + NO_DESCR, + size)); + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), + NO_DESCR, + path)); + } addTagProperty(sheetSet); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index 6e7a8205c7..16cf40680c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2016 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,8 @@ import javax.swing.Action; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; @@ -60,6 +60,7 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { this.tag = tag; } + @Messages({"BlackboardArtifactTagNode.createSheet.userName.text=User Name"}) @Override protected Sheet createSheet() { Sheet propertySheet = super.createSheet(); @@ -81,6 +82,7 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); //NON-NLS contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); } + properties.put(new NodeProperty<>( NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), @@ -96,7 +98,11 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.comment.text"), "", tag.getComment())); - + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.userName.text"), + NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.userName.text"), + "", + tag.getUserName())); return propertySheet; } @@ -132,8 +138,8 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); } actions.add(new ViewTaggedArtifactAction(BlackboardArtifactTagNode_viewSourceArtifact_text(), artifact)); - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), true)); - actions.add(DeleteBlackboardArtifactTagAction.getInstance()); + actions.addAll(DataModelActionsFactory.getActions(tag, true)); + return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 733c49e8b7..f45a333ec1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -54,8 +54,8 @@ DataModelActionsFactory.srcFileInDir.text=View Source File in Directory DataModelActionsFactory.fileInDir.text=View File in Directory DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer -DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash DataSourcesNode.name=Data Sources +DataSourcesNode.group_by_datasource.name=Data Source Files DataSourcesNode.createSheet.name.name=Name DataSourcesNode.createSheet.name.displayName=Name DataSourcesNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index 3d20fded35..e2c331b390 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -55,8 +55,8 @@ DataModelActionsFactory.srcFileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u DataModelActionsFactory.fileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u8868\u793a DataModelActionsFactory.viewNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30a6\u306b\u8868\u793a DataModelActionsFactory.openExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u306b\u8868\u793a -DataModelActionsFactory.srfFileSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 DataSourcesNode.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 +DataSourcesNode.group_by_datasource.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb DataSourcesNode.createSheet.name.name=\u540d\u524d DataSourcesNode.createSheet.name.displayName=\u540d\u524d DataSourcesNode.createSheet.name.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java b/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java deleted file mode 100644 index 0c83a12f61..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/CommonFileChildNodeLoading.java +++ /dev/null @@ -1,95 +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.datamodel; - -import java.util.LinkedHashMap; -import java.util.Map; -import org.openide.nodes.Children; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; - -/** - * A dummy node used by the child factory to display while children are being - * generated - */ -public class CommonFileChildNodeLoading extends DisplayableItemNode { - - public CommonFileChildNodeLoading(Children children) { - super(children); - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public String getItemType() { - return getClass().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); - } - - Map map = new LinkedHashMap<>(); - map.put(CommonFileChildLoadingPropertyType.File.toString(), "Loading..."); - - final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); - for (CommonFileChildLoadingPropertyType propType : CommonFileChildLoadingPropertyType.values()) { - final String propString = propType.toString(); - sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); - } - - return sheet; - } - - /** - * Represents the sole column for the 'dummy' loading node. - */ - @NbBundle.Messages({ - "CommonFileChildLoadingPropertyType.fileColLbl=File" - }) - public enum CommonFileChildLoadingPropertyType { - - File(Bundle.CommonFileChildLoadingPropertyType_fileColLbl()); - - final private String displayString; - - private CommonFileChildLoadingPropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return displayString; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 59d7d21015..e213e460cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -29,7 +29,6 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -60,8 +59,8 @@ class ContentTagNode extends DisplayableItemNode { @Messages({ "ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash", - "ContentTagNode.createSheet.artifactMD5.name=MD5 Hash" - }) + "ContentTagNode.createSheet.artifactMD5.name=MD5 Hash", + "ContentTagNode.createSheet.userName.text=User Name"}) @Override protected Sheet createSheet() { Content content = tag.getContent(); @@ -116,6 +115,11 @@ class ContentTagNode extends DisplayableItemNode { Bundle.ContentTagNode_createSheet_artifactMD5_displayName(), "", file != null ? StringUtils.defaultString(file.getMd5Hash()) : "")); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.userName.text"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.userName.text"), + "", + tag.getUserName())); return propertySheet; } @@ -124,12 +128,14 @@ class ContentTagNode extends DisplayableItemNode { List actions = new ArrayList<>(); actions.addAll(Arrays.asList(super.getActions(context))); - AbstractFile file = getLookup().lookup(AbstractFile.class); + AbstractFile file = getLookup().lookup(AbstractFile.class + ); if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), false)); - actions.add(DeleteContentTagAction.getInstance()); + + actions.addAll(DataModelActionsFactory.getActions(tag, false)); + return actions.toArray(new Action[actions.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index b4b88463c4..88e90f054d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -28,18 +28,23 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.File; @@ -70,8 +75,6 @@ public class DataModelActionsFactory { .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.viewNewWin.text"); public static final String OPEN_IN_EXTERNAL_VIEWER = NbBundle .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.openExtViewer.text"); - public static final String SEARCH_FOR_FILES_SAME_MD5 = NbBundle - .getMessage(DataModelActionsFactory.class, "DataModelActionsFactory.srfFileSameMD5.text"); public static List getActions(File file, boolean isArtifactSource) { List actionsList = new ArrayList<>(); @@ -82,7 +85,6 @@ public class DataModelActionsFactory { actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, fileNode)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, fileNode)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); if (isArtifactSource) { @@ -351,6 +353,78 @@ public class DataModelActionsFactory { return actionsList; } + public static List getActions(ContentTag contentTag, boolean isArtifactSource) { + + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), contentTag.getContent())); + final ContentTagNode tagNode = new ContentTagNode(contentTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteContentTagAction.getInstance()); + actionsList.add(ReplaceContentTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + + public static List getActions(BlackboardArtifactTag artifactTag, boolean isArtifactSource) { + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), artifactTag.getContent())); + final BlackboardArtifactTagNode tagNode = new BlackboardArtifactTagNode(artifactTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteBlackboardArtifactTagAction.getInstance()); + actionsList.add(ReplaceBlackboardArtifactTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + public static List getActions(Content content, boolean isArtifactSource) { if (content instanceof File) { return getActions((File) content, isArtifactSource); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java new file mode 100644 index 0000000000..db291f3b50 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -0,0 +1,66 @@ +/* + * 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.datamodel; + +import java.util.Objects; +import org.sleuthkit.datamodel.DataSource; + +/** + * A top level UI grouping of Files, Views, Results, Tags + * for 'Group by Data Source' view of the tree. + * + */ +public class DataSourceGrouping implements AutopsyVisitableItem { + + private final DataSource dataSource; + + public DataSourceGrouping(DataSource dataSource) { + this.dataSource = dataSource; + } + + DataSource getDataSource() { + return this.dataSource; + } + + @Override + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); + } + + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DataSourceGrouping other = (DataSourceGrouping) obj; + return this.dataSource.getId() == other.getDataSource().getId(); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 17 * hash + Objects.hashCode(this.dataSource); + return hash; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java new file mode 100644 index 0000000000..4c8ee90e7c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -0,0 +1,99 @@ +/* + * 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.datamodel; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +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.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; + + +/** + * Data source grouping node - an optional grouping node in the data tree view + * + */ +class DataSourceGroupingNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName()); + + /** + * Creates a data source grouping node for the given data source. + * + * @param dataSource specifies the data source + */ + DataSourceGroupingNode(DataSource dataSource) { + + super (Optional.ofNullable(createDSGroupingNodeChildren(dataSource)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); + + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) { + + long dsObjId = dataSource.getId(); + try { + return new RootContentChildren(Arrays.asList( + new DataSources(dsObjId), + new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Tags(dsObjId) ) + + ); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index 5f8cbfff54..52ca52e89f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -23,9 +23,20 @@ package org.sleuthkit.autopsy.datamodel; */ public class DataSources implements AutopsyVisitableItem { + private final long datasourceObjId; + public DataSources() { + this(0); } + public DataSources(long datasourceObjId) { + this.datasourceObjId = datasourceObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 6e8374252d..d4e0ebf261 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -33,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; /** * Nodes for the images @@ -40,22 +42,27 @@ import org.sleuthkit.datamodel.TskCoreException; public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); + private final String displayName; // NOTE: The images passed in via argument will be ignored. @Deprecated public DataSourcesNode(List images) { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } public DataSourcesNode() { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } + public DataSourcesNode(long dsObjId) { + super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); + displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; + init(); + } + private void init() { setName(NAME); - setDisplayName(NAME); + setDisplayName(displayName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS } @@ -70,14 +77,20 @@ public class DataSourcesNode extends DisplayableItemNode { public static class DataSourcesNodeChildren extends AbstractContentChildren { private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName()); - + private final long datasourceObjId; + List currentKeys; public DataSourcesNodeChildren() { - super(); - this.currentKeys = new ArrayList<>(); + this(0); } + public DataSourcesNodeChildren(long dsObjId) { + super(); + this.currentKeys = new ArrayList<>(); + this.datasourceObjId = dsObjId; + } + private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -103,9 +116,15 @@ public class DataSourcesNode extends DisplayableItemNode { private void reloadKeys() { try { - currentKeys = Case.getCurrentCaseThrows().getDataSources(); + if (datasourceObjId == 0) { + currentKeys = Case.getCurrentCaseThrows().getDataSources(); + } + else { + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); + currentKeys = new ArrayList<>(Arrays.asList(content)); + } setKeys(currentKeys); - } catch (TskCoreException | NoCurrentCaseException ex) { + } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS setKeys(Collections.emptySet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 812eaa065b..370cc5cec7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskData; public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", "DeletedContent.allDelFilter.text=All"}) @@ -101,9 +102,18 @@ public class DeletedContent implements AutopsyVisitableItem { } public DeletedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public DeletedContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -118,8 +128,8 @@ public class DeletedContent implements AutopsyVisitableItem { @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files") private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name(); - DeletedContentsNode(SleuthkitCase skCase) { - super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME)); + DeletedContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new DeletedContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS @@ -164,11 +174,13 @@ public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; private Observable notifier; + private final long datasourceObjId; // true if we have already told user that not all files will be shown private static volatile boolean maxFilesDialogShown = false; - public DeletedContentsChildren(SleuthkitCase skCase) { + public DeletedContentsChildren(SleuthkitCase skCase, long dsObjId) { this.skCase = skCase; + this.datasourceObjId = dsObjId; this.notifier = new DeletedContentsChildrenObservable(); } @@ -257,24 +269,27 @@ public class DeletedContent implements AutopsyVisitableItem { @Override protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) { - return new DeletedContentNode(skCase, key, notifier); + return new DeletedContentNode(skCase, key, notifier, datasourceObjId); } public class DeletedContentNode extends DisplayableItemNode { private final DeletedContent.DeletedContentFilter filter; + private final long datasourceObjId; // Use version that has observer for updates @Deprecated - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter) { - super(Children.create(new DeletedContentChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId ), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); } - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o) { - super(Children.create(new DeletedContentChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); o.addObserver(new DeletedContentNodeObserver()); } @@ -299,7 +314,7 @@ public class DeletedContent implements AutopsyVisitableItem { private void updateDisplayName() { //get count of children without preloading all children nodes - final long count = DeletedContentChildren.calculateItems(skCase, filter); + final long count = DeletedContentChildren.calculateItems(skCase, filter, datasourceObjId); //final long count = getChildren().getNodesCount(true); super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); } @@ -351,11 +366,13 @@ public class DeletedContent implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); private static final int MAX_OBJECTS = 10001; private final Observable notifier; + private final long datasourceObjId; - DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o) { + DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = datasourceObjId; } private final Observer observer = new DeletedContentChildrenObserver(); @@ -366,7 +383,7 @@ public class DeletedContent implements AutopsyVisitableItem { @Override public void update(Observable o, Object arg) { refresh(true); - } + } } @Override @@ -405,7 +422,7 @@ public class DeletedContent implements AutopsyVisitableItem { return true; } - static private String makeQuery(DeletedContent.DeletedContentFilter filter) { + static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { String query = ""; switch (filter) { case FS_DELETED_FILTER: @@ -443,6 +460,10 @@ public class DeletedContent implements AutopsyVisitableItem { + " OR known IS NULL)"; //NON-NLS } + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + query += " LIMIT " + MAX_OBJECTS; //NON-NLS return query; } @@ -450,7 +471,7 @@ public class DeletedContent implements AutopsyVisitableItem { private List runFsQuery() { List ret = new ArrayList<>(); - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); try { ret = skCase.findAllFilesWhere(query); } catch (TskCoreException e) { @@ -469,9 +490,9 @@ public class DeletedContent implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index b406d2186e..cd14117c95 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -18,7 +18,11 @@ */ package org.sleuthkit.autopsy.datamodel; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesNode; +import org.sleuthkit.autopsy.commonfilesearch.CentralRepoCommonAttributeInstanceNode; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResultRootNode; +import org.sleuthkit.autopsy.commonfilesearch.InstanceCountNode; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValueNode; +import org.sleuthkit.autopsy.commonfilesearch.CaseDBCommonAttributeInstanceNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; @@ -61,6 +65,8 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); + T visit(DataSourceGroupingNode dataSourceGroupingNode); + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); T visit(DeletedContentNode dcn); @@ -110,13 +116,13 @@ public interface DisplayableItemNodeVisitor { T visit(InterestingHits.SetNameNode ihsn); - T visit(Md5Node mn); + T visit(CommonAttributeValueNode mn); - T visit(CommonFilesNode cfn); + T visit(CommonAttributeSearchResultRootNode cfn); - T visit(FileInstanceNode fin); - - T visit(CommonFileChildNodeLoading cfcnl); + T visit(CaseDBCommonAttributeInstanceNode fin); + + T visit(CentralRepoCommonAttributeInstanceNode crfin); T visit(InstanceCountNode icn); @@ -189,17 +195,17 @@ public interface DisplayableItemNodeVisitor { protected abstract T defaultVisit(DisplayableItemNode c); @Override - public T visit(FileInstanceNode fin) { + public T visit(CaseDBCommonAttributeInstanceNode fin) { return defaultVisit(fin); } @Override - public T visit(Md5Node mn) { + public T visit(CommonAttributeValueNode mn) { return defaultVisit(mn); } @Override - public T visit(CommonFilesNode cfn) { + public T visit(CommonAttributeSearchResultRootNode cfn) { return defaultVisit(cfn); } @@ -207,10 +213,10 @@ public interface DisplayableItemNodeVisitor { public T visit(InstanceCountNode icn){ return defaultVisit(icn); } - + @Override - public T visit(CommonFileChildNodeLoading cfcnl) { - return defaultVisit(cfcnl); + public T visit(CentralRepoCommonAttributeInstanceNode crfin){ + return defaultVisit(crfin); } @Override @@ -343,6 +349,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(vn); } + @Override + public T visit(DataSourceGroupingNode dataSourceGroupingNode) { + return defaultVisit(dataSourceGroupingNode); + } + @Override public T visit(ResultsNode rn) { return defaultVisit(rn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 409c3165e8..d9df849d86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -40,6 +40,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -86,10 +87,29 @@ public class EmailExtracted implements AutopsyVisitableItem { } private SleuthkitCase skCase; private final EmailResults emailResults; + private final long datasourceObjId; + + /** + * Constructor + * + * @param skCase Case DB + */ public EmailExtracted(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public EmailExtracted(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; emailResults = new EmailResults(); } @@ -141,6 +161,9 @@ public class EmailExtracted implements AutopsyVisitableItem { + "attribute_type_id=" + pathAttrId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 304073c9b6..366aa12979 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -35,9 +35,11 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; @@ -57,12 +59,31 @@ import org.sleuthkit.datamodel.TskException; public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed + private Blackboard blackboard; public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); + private final long datasourceObjId; + /** + * Constructs extracted content object + * + * @param skCase Case DB + */ public ExtractedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + /** + * Constructs extracted content object + * + * @param skCase Case DB + * @param objId Object id of the parent datasource + */ + public ExtractedContent(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.datasourceObjId = objId; + this.blackboard = skCase.getBlackboard(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -270,7 +291,10 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { - List types = skCase.getArtifactTypesInUse(); + List types = (UserPreferences.groupItemsInTreeByDatasource()) ? + blackboard.getArtifactTypesInUse(datasourceObjId) : + skCase.getArtifactTypesInUse() ; + types.removeAll(doNotShow); Collections.sort(types, new Comparator() { @@ -332,7 +356,9 @@ public class ExtractedContent implements AutopsyVisitableItem { // a performance increase might be had by adding a // "getBlackboardArtifactCount()" method to skCase try { - this.childCount = skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); + this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifactsCount(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) .log(Level.WARNING, "Error getting child count", ex); //NON-NLS @@ -454,7 +480,10 @@ public class ExtractedContent implements AutopsyVisitableItem { protected boolean createKeys(List list) { if (skCase != null) { try { - List arts = skCase.getBlackboardArtifacts(type.getTypeID()); + List arts = + UserPreferences.groupItemsInTreeByDatasource() ? + blackboard.getArtifacts(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java deleted file mode 100644 index e5c0f25964..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java +++ /dev/null @@ -1,134 +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.datamodel; - -import java.util.LinkedHashMap; -import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; -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. - */ -public class FileInstanceNode extends FileNode { - - private final String dataSource; - - public FileInstanceNode(AbstractFile fsContent, String dataSource) { - super(fsContent); - this.content = fsContent; - this.dataSource = dataSource; - } - - @Override - public boolean isLeafTypeNode(){ - //Not used atm - could maybe be leveraged for better use in Children objects - return true; - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public AbstractFile getContent() { - return this.content; - } - - String getDataSource() { - return this.dataSource; - } - - @Override - protected Sheet createSheet() { - Sheet sheet = new Sheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - - final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); - for (CommonFilePropertyType propType : CommonFilePropertyType.values()) { - final String propString = propType.toString(); - final Object property = map.get(propString); - final NodeProperty nodeProperty = new NodeProperty<>(propString, propString, NO_DESCR, property); - sheetSet.put(nodeProperty); - } - - this.addTagProperty(sheetSet); - - return sheet; - } - - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, FileInstanceNode node) { - - map.put(CommonFilePropertyType.ParentPath.toString(), node.getContent().getParentPath()); - map.put(CommonFilePropertyType.HashsetHits.toString(), getHashSetHitsForFile(node.getContent())); - //map.put(CommonFilePropertyType.Case.toString(), ""); - map.put(CommonFilePropertyType.DataSource.toString(), node.getDataSource()); - map.put(CommonFilePropertyType.MimeType.toString(), StringUtils.defaultString(node.content.getMIMEType())); - } - - /** - * Encapsulates the columns to be displayed for reach row represented by an - * instance of this object. - */ - @NbBundle.Messages({ - "CommonFilePropertyType.fileColLbl=File", - "CommonFilePropertyType.pathColLbl=Parent Path", - "CommonFilePropertyType.hashsetHitsColLbl=Hash Set Hits", - "CommonFilePropertyType.dataSourceColLbl=Data Source", - "CommonFilePropertyType.caseColLbl=Case", - "CommonFilePropertyType.mimeTypeColLbl=MIME Type" - }) - public enum CommonFilePropertyType { - - ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), - HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), - //Case(Bundle.CommonFilePropertyType_caseColLbl()), - DataSource(Bundle.CommonFilePropertyType_dataSourceColLbl()), - MimeType(Bundle.CommonFilePropertyType_mimeTypeColLbl()); - - final private String displayString; - - private CommonFilePropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return displayString; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 4bb8ced8bd..13e7ef34a5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -34,7 +34,6 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; @@ -166,7 +165,6 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(null); // Creates an item separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this)); actionsList.add(null); // Creates an item separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 4bf994fb0c..51ece423da 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.VirtualDirectory; public class FileSize implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public enum FileSizeFilter implements AutopsyVisitableItem { @@ -97,9 +98,14 @@ public class FileSize implements AutopsyVisitableItem { } public FileSize(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public FileSize(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -109,6 +115,9 @@ public class FileSize implements AutopsyVisitableItem { return this.skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /* * Root node. Children are nodes for specific sizes. */ @@ -116,8 +125,8 @@ public class FileSize implements AutopsyVisitableItem { private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name"); - FileSizeRootNode(SleuthkitCase skCase) { - super(Children.create(new FileSizeRootChildren(skCase), true), Lookups.singleton(NAME)); + FileSizeRootNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new FileSizeRootChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS @@ -161,10 +170,12 @@ public class FileSize implements AutopsyVisitableItem { public static class FileSizeRootChildren extends ChildFactory { private SleuthkitCase skCase; + private final long datasourceObjId; private Observable notifier; - public FileSizeRootChildren(SleuthkitCase skCase) { + public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { this.skCase = skCase; + this.datasourceObjId = datasourceObjId; notifier = new FileSizeRootChildrenObservable(); } @@ -248,7 +259,7 @@ public class FileSize implements AutopsyVisitableItem { @Override protected Node createNodeForKey(FileSizeFilter key) { - return new FileSizeNode(skCase, key, notifier); + return new FileSizeNode(skCase, key, notifier, datasourceObjId); } /* @@ -257,12 +268,14 @@ public class FileSize implements AutopsyVisitableItem { public class FileSizeNode extends DisplayableItemNode { private FileSizeFilter filter; + private final long datasourceObjId; // use version with observer instead so that it updates @Deprecated - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter) { - super(Children.create(new FileSizeChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, null, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); } @@ -272,10 +285,12 @@ public class FileSize implements AutopsyVisitableItem { * @param filter * @param o Observable that provides updates when events are * fired + * @param datasourceObjId filter by data source, if configured in user preferences */ - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o) { - super(Children.create(new FileSizeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, o, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); o.addObserver(new FileSizeNodeObserver()); } @@ -309,7 +324,7 @@ public class FileSize implements AutopsyVisitableItem { } private void updateDisplayName() { - final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter); + final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter, datasourceObjId); super.setDisplayName(filter.getDisplayName() + " (" + numVisibleChildren + ")"); } @@ -349,6 +364,7 @@ public class FileSize implements AutopsyVisitableItem { private final SleuthkitCase skCase; private final FileSizeFilter filter; private final Observable notifier; + private final long datasourceObjId; private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName()); /** @@ -358,10 +374,12 @@ public class FileSize implements AutopsyVisitableItem { * @param o Observable that provides updates when new files are * added to case */ - FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o) { + FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = dsObjId; + } @Override @@ -395,7 +413,7 @@ public class FileSize implements AutopsyVisitableItem { return true; } - private static String makeQuery(FileSizeFilter filter) { + private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { String query; switch (filter) { case SIZE_50_200: @@ -427,6 +445,11 @@ public class FileSize implements AutopsyVisitableItem { query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS } + // filter by datasource if indicated in user preferences + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + return query; } @@ -434,7 +457,7 @@ public class FileSize implements AutopsyVisitableItem { List ret = new ArrayList<>(); try { - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); ret = skCase.findAllFilesWhere(query); } catch (Exception e) { @@ -449,9 +472,9 @@ public class FileSize implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java index eafa8377b2..f09238e507 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java @@ -27,7 +27,7 @@ import java.util.List; */ public class FileTypeExtensions { - private final static List IMAGE_EXTENSIONS = Arrays.asList(".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec"); //NON-NLS + private final static List IMAGE_EXTENSIONS = Arrays.asList(".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec", ".tif"); //NON-NLS private final static List VIDEO_EXTENSIONS = Arrays.asList(".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf"); //NON-NLS private final static List AUDIO_EXTENSIONS = Arrays.asList(".aiff", ".aif", ".flac", ".wav", ".m4a", ".ape", //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index e4dca77d01..aa1d0a04ec 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -72,11 +72,18 @@ public final class FileTypes implements AutopsyVisitableItem { private final SleuthkitCase skCase; + private final long datasourceObjId; + FileTypes(SleuthkitCase skCase) { - this.skCase = skCase; - updateShowCounts(); + this(skCase, 0); } + FileTypes(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + updateShowCounts(); + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -86,6 +93,9 @@ public final class FileTypes implements AutopsyVisitableItem { return skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /** * Check the db to determine if the nodes should show child counts. */ @@ -239,7 +249,7 @@ public final class FileTypes implements AutopsyVisitableItem { if (typesRoot.showCounts) { //only show "(counting...)" the first time, otherwise it is distracting. setDisplayName(getDisplayNameBase() + ((childCount < 0) ? Bundle.FileTypes_bgCounting_placeholder() - : ("(" + childCount + ")"))); //NON-NLS + : (" (" + childCount + ")"))); //NON-NLS new SwingWorker() { @Override protected Long doInBackground() throws Exception { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 2d04fe9733..1d24f081ab 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -69,6 +69,10 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. @@ -359,6 +363,9 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { + (UserPreferences.hideKnownFilesInViewsTree() ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ") + + (UserPreferences.groupItemsInTreeByDatasource() + ? " AND data_source_obj_id = " + filteringDataSourceObjId() + : " ") + " AND (extension IN (" + filter.getFilter().stream() .map(String::toLowerCase) .map(s -> "'"+StringUtils.substringAfter(s, ".")+"'") diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e41477d96e..90cb90ae77 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; import org.sleuthkit.autopsy.coreutils.Logger; @@ -91,15 +92,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * @return The base expression to be used in the where clause of queries for * files by mime type. */ - static private String createBaseWhereExpr() { + private String createBaseWhereExpr() { return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + " AND (type IN (" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() - + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + "))" + + ( UserPreferences.groupItemsInTreeByDatasource() ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ") + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : ""); } @@ -188,6 +190,10 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi return visitor.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Method to check if the node in question is a ByMimeTypeNode which is * empty. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index a720409489..24bae3d368 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -63,9 +64,29 @@ public class HashsetHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(HashsetHits.class.getName()); private SleuthkitCase skCase; private final HashsetResults hashsetResults; - + private final long datasourceObjId; + + + /** + * Constructor + * + * @param skCase Case DB + * + */ public HashsetHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public HashsetHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; hashsetResults = new HashsetResults(); } @@ -120,7 +141,10 @@ public class HashsetHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS - + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); synchronized (hashSetHitsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/InstanceCountNode.java deleted file mode 100644 index 3389284d20..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InstanceCountNode.java +++ /dev/null @@ -1,157 +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.datamodel; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -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.commonfilesearch.Md5Metadata; -import org.sleuthkit.autopsy.commonfilesearch.Md5MetadataList; - -/** - * - */ -final public class InstanceCountNode extends DisplayableItemNode { - - final private int instanceCount; - final private Md5MetadataList metadataList; - - public InstanceCountNode(int instanceCount, Md5MetadataList md5Metadata) { - super(Children.create(new Md5NodeFactory(md5Metadata.getMetadataList()), true)); - - this.instanceCount = instanceCount; - this.metadataList = md5Metadata; - - this.setDisplayName(Integer.toString(instanceCount)); - } - - int getInstanceCount() { - return this.instanceCount; - } - - List getMetadata() { - return this.metadataList.getMetadataList(); - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - public void refresh() { - metadataList.displayDelayedMetadata(); - setChildren(Children.create(new Md5NodeFactory(metadataList.getMetadataList()), false)); - } - - @Override - public String getItemType() { - return getClass().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); - } - - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - - final String NO_DESCR = org.sleuthkit.autopsy.datamodel.Bundle.AbstractFsContentNode_noDesc_text(); - for (InstanceCountNode.InstanceCountNodePropertyType propType : InstanceCountNode.InstanceCountNodePropertyType.values()) { - final String propString = propType.toString(); - sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); - } - - return sheet; - } - - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, InstanceCountNode node) { - map.put(InstanceCountNodePropertyType.Match.toString(), node.getInstanceCount()); - } - - @NbBundle.Messages({ - "InstanceCountNodeProeprtyType.matchCountColLbl1=Match" - }) - public enum InstanceCountNodePropertyType { - - Match(Bundle.InstanceCountNodeProeprtyType_matchCountColLbl1()); - - final private String displayString; - - private InstanceCountNodePropertyType(String displayName) { - this.displayString = displayName; - } - - @Override - public String toString() { - return this.displayString; - } - } - - /** - * ChildFactory which builds CommonFileParentNodes from the - * CommonFilesMetaaData models. - */ - static class Md5NodeFactory extends ChildFactory { - - /** - * List of models, each of which is a parent node matching a single md5, - * containing children FileNodes. - */ - private final List metadata; - - Md5NodeFactory(List metadata) { - this.metadata = metadata; - } - - @Override - protected Node createNodeForKey(Md5Metadata metadataKey) { - return new Md5Node(metadataKey); - } - - @Override - protected boolean createKeys(List list) { - list.addAll(metadata); - return true; - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 7c3249f990..67622d180c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,9 +60,28 @@ public class InterestingHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(InterestingHits.class.getName()); private SleuthkitCase skCase; private final InterestingResults interestingResults = new InterestingResults(); + private final long datasourceObjId; + /** + * Constructor + * + * @param skCase Case DB + * + */ public InterestingHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public InterestingHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; interestingResults.update(); } @@ -112,6 +132,9 @@ public class InterestingHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { synchronized (interestingItemsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c35d9bdda0..51367297b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -44,6 +44,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -73,6 +74,7 @@ public class KeywordHits implements AutopsyVisitableItem { private SleuthkitCase skCase; private final KeywordResults keywordResults; + private final long datasourceObjId; /** * String used in the instance MAP so that exact matches and substring can @@ -81,6 +83,7 @@ public class KeywordHits implements AutopsyVisitableItem { */ private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; + /** * query attributes table for the ones that we need for the tree */ @@ -101,8 +104,25 @@ public class KeywordHits implements AutopsyVisitableItem { return (instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME)); } - public KeywordHits(SleuthkitCase skCase) { + /** + * Constructor + * + * @param skCase Case DB + */ + KeywordHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public KeywordHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; keywordResults = new KeywordResults(); } @@ -300,7 +320,12 @@ public class KeywordHits implements AutopsyVisitableItem { return; } - try (CaseDbQuery dbQuery = skCase.executeQuery(KEYWORD_HIT_ATTRIBUTES_QUERY)) { + String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; + if (UserPreferences.groupItemsInTreeByDatasource()) { + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { long artifactId = resultSet.getLong("artifact_id"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 133f90c291..d5064312b3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -36,7 +36,6 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; @@ -107,8 +106,6 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - actionsList.add(new HashSearchAction( - NbBundle.getMessage(this.getClass(), "LocalFileNode.getActions.searchFilesSameMd5.text"), this)); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java deleted file mode 100644 index b5092432a4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ /dev/null @@ -1,178 +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.datamodel; - -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -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.commonfilesearch.FileInstanceMetadata; -import org.sleuthkit.autopsy.commonfilesearch.Md5Metadata; -import org.sleuthkit.autopsy.coreutils.Logger; -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. - */ -public class Md5Node extends DisplayableItemNode { - - private static final Logger LOGGER = Logger.getLogger(Md5Node.class.getName()); - - private final String md5Hash; - private final int commonFileCount; - private final String dataSources; - - public Md5Node(Md5Metadata data) { - super(Children.create(new FileInstanceNodeFactory(data), true)); - - this.commonFileCount = data.size(); - this.dataSources = String.join(", ", data.getDataSources()); - this.md5Hash = data.getMd5(); - this.setDisplayName(this.md5Hash); - } - - int getCommonFileCount() { - return this.commonFileCount; - } - - String getDataSources() { - return this.dataSources; - } - - public String getMd5() { - return this.md5Hash; - } - - @Override - protected Sheet createSheet() { - Sheet sheet = new Sheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - Map map = new LinkedHashMap<>(); - fillPropertyMap(map, this); - - final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text(); - for (Md5Node.CommonFileParentPropertyType propType : Md5Node.CommonFileParentPropertyType.values()) { - final String propString = propType.toString(); - sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); - } - - return sheet; - } - - /** - * Fill map with AbstractFile properties - * - * @param map map with preserved ordering, where property names/values are - * put - * @param node The item to get properties for. - */ - static private void fillPropertyMap(Map map, Md5Node node) { - //map.put(CommonFileParentPropertyType.Case.toString(), ""); - map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - @Override - public String getItemType() { - return getClass().getName(); - } - - /** - * Child generator for FileInstanceNode of - * Md5Node. - */ - static class FileInstanceNodeFactory extends ChildFactory { - - private final Md5Metadata descendants; - - FileInstanceNodeFactory(Md5Metadata 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()); - return true; - } - } - - @NbBundle.Messages({ - "CommonFileParentPropertyType.fileColLbl=File", - "CommonFileParentPropertyType.instanceColLbl=Instance Count", - "CommonFileParentPropertyType.caseColLbl=Case", - "CommonFileParentPropertyType.dataSourceColLbl=Data Source"}) - public enum CommonFileParentPropertyType { - - //Case(Bundle.CommonFilePropertyType_caseColLbl()), - DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()); - - final private String displayString; - - private CommonFileParentPropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return this.displayString; - } - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 7f1cd1cea7..b2d9f4799b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -26,11 +26,17 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Results implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Results(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Results(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -39,4 +45,8 @@ public class Results implements AutopsyVisitableItem { public SleuthkitCase getSleuthkitCase() { return skCase; } + + long filteringDataSourceObjId() { + return datasourceObjId; + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 87a3d5f903..91d18bbcc6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,17 +33,22 @@ public class ResultsNode extends DisplayableItemNode { @NbBundle.Messages("ResultsNode.name.text=Results") public static final String NAME = Bundle.ResultsNode_name_text(); - - public ResultsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new ExtractedContent(sleuthkitCase), - new KeywordHits(sleuthkitCase), - new HashsetHits(sleuthkitCase), - new EmailExtracted(sleuthkitCase), - new InterestingHits(sleuthkitCase), - new Accounts(sleuthkitCase) - )), Lookups.singleton(NAME)); + this(sleuthkitCase, 0); + } + + public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + super( + + new RootContentChildren(Arrays.asList( + new ExtractedContent(sleuthkitCase, dsObjId ), + new KeywordHits(sleuthkitCase, dsObjId), + new HashsetHits(sleuthkitCase, dsObjId), + new EmailExtracted(sleuthkitCase, dsObjId), + new InterestingHits(sleuthkitCase, dsObjId ), + new Accounts(sleuthkitCase, dsObjId) ) + ), + Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 9a01fa608f..c41a750f7e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -36,6 +36,7 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -57,6 +58,20 @@ public class Tags implements AutopsyVisitableItem { private final String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final long datasourceObjId; + + Tags() { + this(0); + } + + Tags(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -83,11 +98,13 @@ public class Tags implements AutopsyVisitableItem { */ public class RootNode extends DisplayableItemNode { - public RootNode() { - super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME)); + + public RootNode(long objId) { + super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); + } @Override @@ -121,6 +138,8 @@ public class Tags implements AutopsyVisitableItem { private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { + private final long datasourceObjId; + private final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.CONTENT_TAG_ADDED, @@ -176,6 +195,15 @@ public class Tags implements AutopsyVisitableItem { } }; + /** + * Constructor + * @param objId data source object id + */ + TagNameNodeFactory(long objId) { + this.datasourceObjId = objId; + + } + @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); @@ -196,7 +224,11 @@ public class Tags implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - List tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + + List tagNamesInUse = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse() + ; Collections.sort(tagNamesInUse); keys.addAll(tagNamesInUse); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -244,8 +276,15 @@ public class Tags implements AutopsyVisitableItem { long tagsCount = 0; try { TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + if (UserPreferences.groupItemsInTreeByDatasource()) { + tagsCount = tm.getContentTagsCountByTagName(tagName, datasourceObjId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId); + } + else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -348,7 +387,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -403,7 +444,11 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { // Use the content tags bearing the specified tag name as the keys. try { - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName)); + List contentTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + + keys.addAll(contentTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -447,7 +492,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -502,7 +549,10 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { try { // Use the blackboard artifact tags bearing the specified tag name as the keys. - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + List artifactTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + keys.addAll(artifactTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index b31cfda543..d2a8671678 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -26,11 +26,21 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Views implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Views(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Views(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 08f2369a61..a1114761bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,19 +34,28 @@ public class ViewsNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); public ViewsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new FileTypes(sleuthkitCase), - // June '15: Recent Files was removed because it was not useful w/out filtering - // add it back in if we can filter the results to a more managable size. - // new RecentFiles(sleuthkitCase), - new DeletedContent(sleuthkitCase), - new FileSize(sleuthkitCase))), - Lookups.singleton(NAME)); + this(sleuthkitCase, 0); + } + + public ViewsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + + super( + new RootContentChildren(Arrays.asList( + new FileTypes(sleuthkitCase, dsObjId), + // June '15: Recent Files was removed because it was not useful w/out filtering + // add it back in if we can filter the results to a more managable size. + // new RecentFiles(sleuthkitCase), + new DeletedContent(sleuthkitCase, dsObjId), + new FileSize(sleuthkitCase, dsObjId)) + ), + Lookups.singleton(NAME) + ); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 8301928528..557e6582ca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -58,6 +58,7 @@ import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; @@ -94,6 +95,8 @@ final public class Accounts implements AutopsyVisitableItem { final public static String NAME = Bundle.AccountsRootNode_name(); private SleuthkitCase skCase; + private final long datasourceObjId; + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); /* Should rejected accounts be shown in the accounts section of the tree. */ @@ -108,12 +111,24 @@ final public class Accounts implements AutopsyVisitableItem { * @param skCase The SleuthkitCase object to use for db queries. */ public Accounts(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + * @param objId Object id of the data source + */ + public Accounts(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; this.rejectActionInstance = new RejectAccounts(); this.approveActionInstance = new ApproveAccounts(); } - + + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -130,6 +145,18 @@ final public class Accounts implements AutopsyVisitableItem { return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS } + /** + * Returns the clause to filter artifacts by data source. + * + * @return A clause that will or will not filter artifacts by datasource + * based on the UserPreferences groupItemsInTreeByDatasource setting + */ + private String getFilterByDataSourceClause() { + return (UserPreferences.groupItemsInTreeByDatasource()) ? + " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId + " " + : " "; + } + /** * Gets a new Action that when invoked toggles showing rejected artifacts on * or off. @@ -291,10 +318,14 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( - "SELECT DISTINCT blackboard_attributes.value_text as account_type " - + " FROM blackboard_attributes " - + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + String accountTypesInUseQuery = + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() + + getFilterByDataSourceClause(); + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery ); ResultSet resultSet = executeQuery.getResultSet()) { while (resultSet.next()) { String accountType = resultSet.getString("account_type"); @@ -429,6 +460,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet rs = results.getResultSet();) { @@ -739,6 +771,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + " ORDER BY hits DESC "; //NON-NLS @@ -807,6 +840,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -943,6 +977,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY BIN " //NON-NLS + " ORDER BY BIN "; //NON-NLS @@ -1009,6 +1044,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { @@ -1304,6 +1340,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " ORDER BY blackboard_attributes.value_text"; //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -1375,6 +1412,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java index 073f7b4eee..fad5d90b61 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog for adding or editing an external viewer rule */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddExternalViewerRuleDialog extends JDialog { private ExternalViewerRule rule; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 647c6f70d3..926c4b4626 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -119,3 +119,9 @@ AddExternalViewerRulePanel.browseButton.text=Browse AddExternalViewerRulePanel.exePathTextField.text= AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension AddExternalViewerRulePanel.extRadioButton.text=Extension +DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source +GroupDataSourcesDialog.dataSourceCountLabel.text=jLabel1 +GroupDataSourcesDialog.queryLabel.text=Would you like to group by data source for faster loading? +GroupDataSourcesDialog.yesButton.text=Yes +GroupDataSourcesDialog.noButton.text=No +GroupDataSourcesDialog.title=Group by Data Source? \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 00da91813c..c639a96672 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; -import org.sleuthkit.autopsy.commonfilesearch.NoAction; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; @@ -52,20 +51,21 @@ import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; -import org.sleuthkit.autopsy.datamodel.FileInstanceNode; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; -import org.sleuthkit.autopsy.datamodel.InstanceCountNode; +import org.sleuthkit.autopsy.commonfilesearch.InstanceCountNode; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValueNode; +import org.sleuthkit.autopsy.commonfilesearch.CentralRepoCommonAttributeInstanceNode; import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; -import org.sleuthkit.autopsy.datamodel.Md5Node; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.Reports; import org.sleuthkit.autopsy.datamodel.SlackFileNode; +import org.sleuthkit.autopsy.commonfilesearch.CaseDBCommonAttributeInstanceNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; -import static org.sleuthkit.autopsy.directorytree.Bundle.*; +import static org.sleuthkit.autopsy.directorytree.Bundle.DataResultFilterNode_viewSourceArtifact_text; import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -409,10 +409,8 @@ public class DataResultFilterNode extends FilterNode { } Content c = ban.getLookup().lookup(File.class); Node n = null; - boolean md5Action = false; if (c != null) { n = new FileNode((AbstractFile) c); - md5Action = true; } else if ((c = ban.getLookup().lookup(Directory.class)) != null) { n = new DirectoryNode((Directory) c); } else if ((c = ban.getLookup().lookup(VirtualDirectory.class)) != null) { @@ -446,10 +444,6 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.openInExtViewer.text"), n)); actionsList.add(null); // creates a menu separator actionsList.add(ExtractAction.getInstance()); - if (md5Action) { - actionsList.add(new HashSearchAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.searchFilesSameMd5.text"), n)); - } actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); actionsList.add(AddBlackboardArtifactTagAction.getInstance()); @@ -539,19 +533,24 @@ public class DataResultFilterNode extends FilterNode { @Override public AbstractAction visit(InstanceCountNode icn) { - return new NoAction(); + return null; } @Override - public AbstractAction visit(Md5Node md5n) { - return new NoAction(); + public AbstractAction visit(CommonAttributeValueNode md5n){ + return null; } @Override - public AbstractAction visit(FileInstanceNode fin) { - return new NoAction(); + public AbstractAction visit(CaseDBCommonAttributeInstanceNode fin){ + return null; } + @Override + public AbstractAction visit(CentralRepoCommonAttributeInstanceNode iccan){ + return null; + } + @Override public AbstractAction visit(BlackboardArtifactNode ban) { BlackboardArtifact artifact = ban.getArtifact(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 90c6ac00a6..732e34c20b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -16,14 +16,16 @@ - - - - - - - - + + + + + + + + + + @@ -31,14 +33,23 @@ - - - - - + + + + + + + + + + + + + + - - + + @@ -60,7 +71,7 @@ - + @@ -68,7 +79,7 @@ - + @@ -80,10 +91,10 @@ - + - + @@ -93,7 +104,7 @@ - + @@ -101,7 +112,7 @@ - + @@ -113,10 +124,10 @@ - + - + @@ -130,5 +141,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 822a195f8a..fb9ded33e2 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -24,16 +24,23 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; import java.util.prefs.PreferenceChangeListener; +import java.util.Properties; import javax.swing.Action; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; @@ -59,13 +66,12 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponentinterfaces.CoreComponentControl; import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.CreditCards; -import org.sleuthkit.autopsy.datamodel.DataSources; -import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmailExtracted; import org.sleuthkit.autopsy.datamodel.EmptyNode; @@ -73,12 +79,8 @@ import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; -import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.Results; import org.sleuthkit.autopsy.datamodel.ResultsNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildren; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Views; +import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildFactory; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; @@ -87,7 +89,6 @@ import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -107,7 +108,11 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); - private RootContentChildren contentChildren; + private AutopsyTreeChildFactory autopsyTreeChildFactory; + private Children autopsyTreeChildren; + private static final long DEFAULT_DATASOURCE_GROUPING_THRESHOLD = 5; // Threshold for prompting the user about grouping by data source + private static final String GROUPING_THRESHOLD_NAME = "GroupDataSourceThreshold"; + private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS /** * the constructor @@ -130,6 +135,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.forwardList = new LinkedList<>(); backButton.setEnabled(false); forwardButton.setEnabled(false); + + groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); } /** @@ -142,6 +149,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat switch (evt.getKey()) { case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: + case UserPreferences.GROUP_ITEMS_IN_TREE_BY_DATASOURCE: refreshContentTreeSafe(); break; case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE: @@ -151,7 +159,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } } }); - + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_ADDED), this); this.em.addPropertyChangeListener(this); IngestManager.getInstance().addIngestJobEventListener(this); @@ -182,35 +190,36 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); showRejectedCheckBox = new javax.swing.JCheckBox(); + groupByDatasourceCheckBox = new javax.swing.JCheckBox(); treeView.setBorder(null); - backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back.png"))); // NOI18N + backButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(backButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.backButton.text")); // NOI18N backButton.setBorderPainted(false); backButton.setContentAreaFilled(false); - backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled.png"))); // NOI18N + backButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png"))); // NOI18N backButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); backButton.setMaximumSize(new java.awt.Dimension(55, 100)); backButton.setMinimumSize(new java.awt.Dimension(5, 5)); - backButton.setPreferredSize(new java.awt.Dimension(23, 23)); - backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover.png"))); // NOI18N + backButton.setPreferredSize(new java.awt.Dimension(32, 32)); + backButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png"))); // NOI18N backButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { backButtonActionPerformed(evt); } }); - forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward.png"))); // NOI18N + forwardButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(forwardButton, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.forwardButton.text")); // NOI18N forwardButton.setBorderPainted(false); forwardButton.setContentAreaFilled(false); - forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled.png"))); // NOI18N + forwardButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png"))); // NOI18N forwardButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); forwardButton.setMaximumSize(new java.awt.Dimension(55, 100)); forwardButton.setMinimumSize(new java.awt.Dimension(5, 5)); - forwardButton.setPreferredSize(new java.awt.Dimension(23, 23)); - forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover.png"))); // NOI18N + forwardButton.setPreferredSize(new java.awt.Dimension(32, 32)); + forwardButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png"))); // NOI18N forwardButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { forwardButtonActionPerformed(evt); @@ -219,30 +228,44 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(groupByDatasourceCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.groupByDatasourceCheckBox.text")); // NOI18N + groupByDatasourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + groupByDatasourceCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) + .addComponent(treeView) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) - .addComponent(showRejectedCheckBox) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 51, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showRejectedCheckBox) + .addComponent(groupByDatasourceCheckBox)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(showRejectedCheckBox)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(groupByDatasourceCheckBox)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(forwardButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 854, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 843, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents @@ -296,9 +319,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed + private void groupByDatasourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_groupByDatasourceCheckBoxActionPerformed + UserPreferences.setGroupItemsInTreeByDatasource(this.groupByDatasourceCheckBox.isSelected()); + }//GEN-LAST:event_groupByDatasourceCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; + private javax.swing.JCheckBox groupByDatasourceCheckBox; private javax.swing.JCheckBox showRejectedCheckBox; private javax.swing.JScrollPane treeView; // End of variables declaration//GEN-END:variables @@ -352,6 +380,51 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat return TopComponent.PERSISTENCE_NEVER; } + /** + * Ask the user if they want to group by data source when opening a large + * case. + * + * @param currentCase + * @param dataSourceCount + */ + private void promptForDataSourceGrouping(Case currentCase, int dataSourceCount) { + Path settingsFile = Paths.get(currentCase.getConfigDirectory(), SETTINGS_FILE); //NON-NLS + if (settingsFile.toFile().exists()) { + // Read the setting + try (InputStream inputStream = Files.newInputStream(settingsFile)) { + Properties props = new Properties(); + props.load(inputStream); + if (props.getProperty("groupByDataSource", "false").equals("true")) { + UserPreferences.setGroupItemsInTreeByDatasource(true); + groupByDatasourceCheckBox.setSelected(true); + } + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Error reading settings file", ex); + } + } else { + GroupDataSourcesDialog dialog = new GroupDataSourcesDialog(dataSourceCount); + dialog.display(); + if (dialog.groupByDataSourceSelected()) { + UserPreferences.setGroupItemsInTreeByDatasource(true); + groupByDatasourceCheckBox.setSelected(true); + } + + // Save the response + Properties props = new Properties(); + if (dialog.groupByDataSourceSelected()) { + props.setProperty("groupByDataSource", "true"); + } else { + props.setProperty("groupByDataSource", "false"); + } + + try (OutputStream fos = Files.newOutputStream(settingsFile)) { + props.store(fos, ""); //NON-NLS + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Error writing settings file", ex); + } + } + } + /** * Called only when top component was closed on all workspaces before and * now is opened for the first time on some workspace. The intent is to @@ -359,6 +432,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * existing workspaces. Subclasses will usually perform initializing tasks * here. */ + @NbBundle.Messages({"# {0} - dataSourceCount", + "DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contains {0} data sources. Would you like to group by data source for faster loading?", + "DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source?"}) @Override public void componentOpened() { // change the cursor to "waiting cursor" for this operation @@ -374,15 +450,35 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat if (null == currentCase || currentCase.hasData() == false) { getTree().setRootVisible(false); // hide the root } else { - // if there's at least one image, load the image and open the top component - final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - contentChildren = new RootContentChildren(Arrays.asList( - new DataSources(), - new Views(tskCase), - new Results(tskCase), - new Tags(), - new Reports())); - Node root = new AbstractNode(contentChildren) { + // If the case contains a lot of data sources, and they aren't already grouping + // by data source, give the user the option to do so before loading the tree. + if (RuntimeProperties.runningWithGUI()) { + long threshold = DEFAULT_DATASOURCE_GROUPING_THRESHOLD; + if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME)) { + try { + threshold = Long.parseLong(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME)); + } catch (NumberFormatException ex) { + LOGGER.log(Level.SEVERE, "Group data sources threshold is not a number", ex); + } + } else { + ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, GROUPING_THRESHOLD_NAME, String.valueOf(threshold)); + } + + try { + int dataSourceCount = currentCase.getDataSources().size(); + if (!UserPreferences.groupItemsInTreeByDatasource() + && dataSourceCount > threshold) { + promptForDataSourceGrouping(currentCase, dataSourceCount); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error loading data sources", ex); + } + } + + // if there's at least one image, load the image and open the top componen + autopsyTreeChildFactory = new AutopsyTreeChildFactory(); + autopsyTreeChildren = Children.create(autopsyTreeChildFactory, true); + Node root = new AbstractNode(autopsyTreeChildren) { //JIRA-2807: What is the point of these overrides? /** * to override the right click action in the white blank space @@ -422,21 +518,27 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat TreeView tree = getTree(); Node results = rootChildren.findChild(ResultsNode.NAME); - tree.expandNode(results); - Children resultsChildren = results.getChildren(); - Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); + if (!Objects.isNull(results)) { + tree.expandNode(results); + Children resultsChildren = results.getChildren(); + Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); - Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); + Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + if (!Objects.isNull(accounts)) { + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + } + } Node views = rootChildren.findChild(ViewsNode.NAME); - Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); - tree.collapseNode(views); + if (!Objects.isNull(views)) { + Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); + tree.collapseNode(views); + } /* - * JIRA-2806: What is this supposed to do? Right now it selects - * the data sources node, but the comment seems to indicate - * it is supposed to select the first datasource. + * JIRA-2806: What is this supposed to do? Right now it + * selects the data sources node, but the comment seems to + * indicate it is supposed to select the first datasource. */ // select the first image node, if there is one // (this has to happen after dataResult is opened, because the event @@ -464,7 +566,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // of changing the selected node fires a handler that tries to make // dataResult active) try { - em.setSelectedNodes(get()); + Node[] selections = get(); + if (selections != null && selections.length > 0) { + em.setSelectedNodes(selections); + } } catch (PropertyVetoException ex) { LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS } catch (InterruptedException | ExecutionException ex) { @@ -486,7 +591,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public void componentClosed() { //@@@ push the selection node to null? - contentChildren = null; + autopsyTreeChildren = null; } void writeProperties(java.util.Properties p) { @@ -570,15 +675,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } /** - * The "listener" that listens to any changes made in the Case.java class. - * It will do something based on the changes in the Case.java class. + * The "listener" that monitors changes made in the Case class. This serves + * the purpose of keeping the UI in sync with the data as it changes. * - * @param evt the property change event + * @param event The property change event. */ @Override - public void propertyChange(PropertyChangeEvent evt) { + public void propertyChange(PropertyChangeEvent event) { if (RuntimeProperties.runningWithGUI()) { - String changed = evt.getPropertyName(); + String changed = event.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { // changed current case // When a case is closed, the old value of this property is the // closed Case object and the new value is null. When a case is @@ -588,15 +693,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // opened events instead of property change events would be a better // solution. Either way, more probably needs to be done to clean up // data model objects when a case is closed. - if (evt.getOldValue() != null && evt.getNewValue() == null) { + if (event.getOldValue() != null && event.getNewValue() == null) { // The current case has been closed. Reset the ExplorerManager. SwingUtilities.invokeLater(() -> { Node emptyNode = new AbstractNode(Children.LEAF); em.setRootContext(emptyNode); }); - } else if (evt.getNewValue() != null) { + } else if (event.getNewValue() != null) { // A new case has been opened. Reset the ExplorerManager. - Case newCase = (Case) evt.getNewValue(); + Case newCase = (Case) event.getNewValue(); final String newCaseName = newCase.getName(); SwingUtilities.invokeLater(() -> { em.getRootContext().setName(newCaseName); @@ -620,20 +725,29 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * already closed. */ try { - Case currentCase = Case.getCurrentCaseThrows(); - // We only need to trigger openCoreWindows() when the - // first data source is added. - if (currentCase.getDataSources().size() == 1) { - SwingUtilities.invokeLater(CoreComponentControl::openCoreWindows); - } - } catch (NoCurrentCaseException | TskCoreException notUsed) { + Case.getCurrentCaseThrows(); + /* + * In case the Case 'updateGUIForCaseOpened()' method hasn't + * already done so, open the tree and all other core + * windows. + * + * TODO: (JIRA-4053) DirectoryTreeTopComponent should not be + * responsible for opening core windows. Consider moving + * this elsewhere. + */ + SwingUtilities.invokeLater(() -> { + if (! DirectoryTreeTopComponent.this.isOpened()) { + CoreComponentControl.openCoreWindows(); + } + }); + } catch (NoCurrentCaseException notUsed) { /** * Case is closed, do nothing. */ } } // change in node selection else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) { - respondSelection((Node[]) evt.getOldValue(), (Node[]) evt.getNewValue()); + respondSelection((Node[]) event.getOldValue(), (Node[]) event.getNewValue()); } else if (changed.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { // nothing to do here. // all nodes should be listening for these events and update accordingly. @@ -669,16 +783,16 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat Node originNode = ((DirectoryTreeFilterNode) treeNode).getOriginal(); //set node, wrap in filter node first to filter out children Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); - // Create a SingleLayerTableFilterNode with knowledge of the node's type to allow for column order settings + // Create a TableFilterNode with knowledge of the node's type to allow for column order settings if (FileTypesByMimeType.isEmptyMimeTypeNode(originNode)) { //Special case for when File Type Identification has not yet been run and //there are no mime types to populate Files by Mime Type Tree EmptyNode emptyNode = new EmptyNode(Bundle.DirectoryTreeTopComponent_emptyMimeNode_text()); - dataResult.setNode(new SingleLayerTableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS + dataResult.setNode(new TableFilterNode(emptyNode, true, "This Node Is Empty")); //NON-NLS } else if (originNode instanceof DisplayableItemNode) { - dataResult.setNode(new SingleLayerTableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType())); + dataResult.setNode(new TableFilterNode(drfn, true, ((DisplayableItemNode) originNode).getItemType())); } else { - dataResult.setNode(new SingleLayerTableFilterNode(drfn, true)); + dataResult.setNode(new TableFilterNode(drfn, true)); } String displayName = ""; Content content = originNode.getLookup().lookup(Content.class); @@ -729,7 +843,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ String[] currentLast = backList.peekLast(); String lastNodeName = null; - if (currentLast != null) { + if (currentLast != null && currentLast.length > 0) { lastNodeName = currentLast[currentLast.length - 1]; } @@ -773,25 +887,68 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { - SwingUtilities.invokeLater(this::refreshDataSourceTree); + SwingUtilities.invokeLater(this::rebuildTree); } /** - * Refreshes changed content nodes + * Rebuilds the autopsy tree. + * + * Does nothing if there is no open case. */ - private void refreshDataSourceTree() { - Node selectedNode = getSelectedNode(); - final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); - Children rootChildren = em.getRootContext().getChildren(); - Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); - if (dataSourcesFilterNode == null) { - LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS + private void rebuildTree() { + + // if no open case or has no data then there is no tree to rebuild + Case currentCase; + try { + currentCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { return; } - Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal(); - DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren(); - contentRootChildren.refreshContentKeys(); - setSelectedNode(selectedPath, DataSourcesNode.NAME); + if (null == currentCase || currentCase.hasData() == false) { + return; + } + + // refresh all children of the root. + autopsyTreeChildFactory.refreshChildren(); + + // Select the first node and reset the selection history + // This should happen on the EDT once the tree has been rebuilt. + // hence the SwingWorker that does this in the done() method + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + selectFirstChildNode(); + resetHistory(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error selecting tree node.", ex); //NON-NLS + } //NON-NLS + } + }.execute(); + } + + /** + * Selects the first node in the tree. + * + */ + private void selectFirstChildNode() { + Children rootChildren = em.getRootContext().getChildren(); + + if (rootChildren.getNodesCount() > 0) { + Node firstNode = rootChildren.getNodeAt(0); + if (firstNode != null) { + final String[] selectedPath = NodeOp.createPath(firstNode, em.getRootContext()); + setSelectedNode(selectedPath, null); + } + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 2f45e44331..1f50859bb8 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -25,7 +25,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JFileChooser; @@ -33,7 +35,6 @@ import javax.swing.JOptionPane; import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.casemodule.Case; @@ -44,8 +45,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.TskCoreException; /** * Extracts AbstractFiles to a location selected by the user. @@ -66,6 +65,9 @@ public final class ExtractAction extends AbstractAction { return instance; } + /** + * Private constructor for the action. + */ private ExtractAction() { super(NbBundle.getMessage(ExtractAction.class, "ExtractAction.title.extractFiles.text")); } @@ -94,16 +96,16 @@ public final class ExtractAction extends AbstractAction { /** * Called when user has selected a single file to extract * - * @param e + * @param event * @param selectedFile Selected file */ - @NbBundle.Messages ({"ExtractAction.noOpenCase.errMsg=No open case available."}) - private void extractFile(ActionEvent e, AbstractFile selectedFile) { + @NbBundle.Messages({"ExtractAction.noOpenCase.errMsg=No open case available."}) + private void extractFile(ActionEvent event, AbstractFile selectedFile) { Case openCase; try { openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); + JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS return; } @@ -111,60 +113,70 @@ public final class ExtractAction extends AbstractAction { fileChooser.setCurrentDirectory(new File(openCase.getExportDirectory())); // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName()))); - if (fileChooser.showSaveDialog((Component) e.getSource()) == JFileChooser.APPROVE_OPTION) { + if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { ArrayList fileExtractionTasks = new ArrayList<>(); fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile())); - runExtractionTasks(e, fileExtractionTasks); + runExtractionTasks(event, fileExtractionTasks); } } /** * Called when a user has selected multiple files to extract * - * @param e + * @param event * @param selectedFiles Selected files */ - private void extractFiles(ActionEvent e, Collection selectedFiles) { + private void extractFiles(ActionEvent event, Collection selectedFiles) { Case openCase; try { openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) e.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); + JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS return; } JFileChooser folderChooser = new JFileChooser(); folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); folderChooser.setCurrentDirectory(new File(openCase.getExportDirectory())); - if (folderChooser.showSaveDialog((Component) e.getSource()) == JFileChooser.APPROVE_OPTION) { + if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { File destinationFolder = folderChooser.getSelectedFile(); if (!destinationFolder.exists()) { try { destinationFolder.mkdirs(); } catch (Exception ex) { - JOptionPane.showMessageDialog((Component) e.getSource(), NbBundle.getMessage(this.getClass(), + JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(), "ExtractAction.extractFiles.cantCreateFolderErr.msg")); logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS return; } } - /* get the unique set of files from the list. A user once reported extraction taking - * days because it was extracting the same PST file 20k times. They selected 20k - * email messages in the tree and chose to extract them. */ + /* + * get the unique set of files from the list. A user once reported + * extraction taking days because it was extracting the same PST + * file 20k times. They selected 20k email messages in the tree and + * chose to extract them. + */ Set uniqueFiles = new HashSet<>(selectedFiles); - + // make a task for each file ArrayList fileExtractionTasks = new ArrayList<>(); for (AbstractFile source : uniqueFiles) { // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName())))); } - runExtractionTasks(e, fileExtractionTasks); + runExtractionTasks(event, fileExtractionTasks); } } - private void runExtractionTasks(ActionEvent e, ArrayList fileExtractionTasks) { + /** + * Execute a series of file extraction tasks. + * + * @param event ActionEvent whose source will be used for + * centering popup dialogs. + * @param fileExtractionTasks List of file extraction tasks. + */ + private void runExtractionTasks(ActionEvent event, List fileExtractionTasks) { // verify all of the sources and destinations are OK for (Iterator it = fileExtractionTasks.iterator(); it.hasNext();) { @@ -177,16 +189,16 @@ public final class ExtractAction extends AbstractAction { } /* - * This code assumes that each destination is unique. We previously satisfied - * that by adding the unique ID. + * This code assumes that each destination is unique. We previously + * satisfied that by adding the unique ID. */ if (task.destination.exists()) { - if (JOptionPane.showConfirmDialog((Component) e.getSource(), + if (JOptionPane.showConfirmDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.msg", task.destination.getAbsolutePath()), NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.title"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { if (!FileUtil.deleteFileDir(task.destination)) { - JOptionPane.showMessageDialog((Component) e.getSource(), + JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(), "ExtractAction.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath())); it.remove(); } @@ -210,11 +222,20 @@ public final class ExtractAction extends AbstractAction { } } + /** + * Stores source and destination for file extraction. + */ private class FileExtractionTask { AbstractFile source; File destination; + /** + * Create an instance of the FileExtractionTask. + * + * @param source The file to be extracted. + * @param destination The destination for the extraction. + */ FileExtractionTask(AbstractFile source, File destination) { this.source = source; this.destination = destination; @@ -226,11 +247,16 @@ public final class ExtractAction extends AbstractAction { */ private class FileExtracter extends SwingWorker { - private Logger logger = Logger.getLogger(FileExtracter.class.getName()); + private final Logger logger = Logger.getLogger(FileExtracter.class.getName()); private ProgressHandle progress; - private ArrayList extractionTasks; + private final List extractionTasks; - FileExtracter(ArrayList extractionTasks) { + /** + * Create an instance of the FileExtracter. + * + * @param extractionTasks List of file extraction tasks. + */ + FileExtracter(List extractionTasks) { this.extractionTasks = extractionTasks; } @@ -275,7 +301,7 @@ public final class ExtractAction extends AbstractAction { boolean msgDisplayed = false; try { super.get(); - } catch (Exception ex) { + } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS MessageNotifyUtil.Message.info( NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.extractErr", ex.getMessage())); @@ -289,22 +315,23 @@ public final class ExtractAction extends AbstractAction { } } - private int calculateProgressBarWorkUnits(AbstractFile file) { - int workUnits = 0; - if (file.isFile()) { - workUnits += file.getSize(); - } else { - try { - for (Content child : file.getChildren()) { - if (child instanceof AbstractFile) { - workUnits += calculateProgressBarWorkUnits((AbstractFile) child); - } - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not get children of content", ex); //NON-NLS - } - } - return workUnits; + /** + * Calculate the number of work units for the progress bar. + * + * @param file File whose children will be reviewed to get the number of + * work units. + * + * @return The number of work units. + */ + /* + * private int calculateProgressBarWorkUnits(AbstractFile file) { int + * workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); } + * else { try { for (Content child : file.getChildren()) { if (child + * instanceof AbstractFile) { workUnits += + * calculateProgressBarWorkUnits((AbstractFile) child); } } } catch + * (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get + * children of content", ex); //NON-NLS } } return workUnits; } + */ } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index b0729d24c6..74c7ee1f64 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.VolumeSystem; * Extracts all the unallocated space as a single file */ final class ExtractUnallocAction extends AbstractAction { + private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); private final List filesToExtract = new ArrayList<>(); @@ -69,19 +70,32 @@ final class ExtractUnallocAction extends AbstractAction { private long currentImage = 0L; private final boolean isImage; - public ExtractUnallocAction(String title, Volume volume){ + /** + * Create an instance of ExtractUnallocAction with a volume. + * + * @param title The title + * @param volume The volume set for extraction. + */ + public ExtractUnallocAction(String title, Volume volume) { super(title); isImage = false; try { OutputFileData outputFileData = new OutputFileData(volume); filesToExtract.add(outputFileData); - } catch (NoCurrentCaseException ex) { + } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); setEnabled(false); } - + } + /** + * Create an instance of ExtractUnallocAction with an image. + * + * @param title The title. + * @param image The image set for extraction. + * @throws NoCurrentCaseException If no case is open. + */ public ExtractUnallocAction(String title, Image image) throws NoCurrentCaseException { super(title); isImage = true; @@ -101,17 +115,17 @@ final class ExtractUnallocAction extends AbstractAction { * Writes the unallocated files to * $CaseDir/Export/ImgName-Unalloc-ImgObjectID-VolumeID.dat * - * @param e + * @param event */ @NbBundle.Messages({"# {0} - fileName", - "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume", - "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume", - "ExtractUnallocAction.noFiles=No unallocated files found on volume", - "ExtractUnallocAction.imageError=Error extracting unallocated space from image", - "ExtractUnallocAction.noOpenCase.errMsg=No open case available."}) + "ExtractUnallocAction.volumeInProgress=Already extracting unallocated space into {0} - will skip this volume", + "ExtractUnallocAction.volumeError=Error extracting unallocated space from volume", + "ExtractUnallocAction.noFiles=No unallocated files found on volume", + "ExtractUnallocAction.imageError=Error extracting unallocated space from image", + "ExtractUnallocAction.noOpenCase.errMsg=No open case available."}) @Override - public void actionPerformed(ActionEvent e) { - if (filesToExtract != null && filesToExtract.size() > 0) { + public void actionPerformed(ActionEvent event) { + if (filesToExtract != null && filesToExtract.isEmpty() == false) { // This check doesn't absolutely guarantee that the image won't be in progress when we make the worker, // but in general it will suffice. if (isImage && isImageInProgress(currentImage)) { @@ -150,15 +164,15 @@ final class ExtractUnallocAction extends AbstractAction { NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg")); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); fileChooser.setAcceptAllFileFilterUsed(false); - int returnValue = fileChooser.showSaveDialog((Component) e.getSource()); + int returnValue = fileChooser.showSaveDialog((Component) event.getSource()); if (returnValue == JFileChooser.APPROVE_OPTION) { String destination = fileChooser.getSelectedFile().getPath(); for (OutputFileData outputFileData : filesToExtract) { outputFileData.setPath(destination); - - if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (! isVolumeInProgress(outputFileData.getFileName()))) { + + if (outputFileData.layoutFiles != null && outputFileData.layoutFiles.size() > 0 && (!isVolumeInProgress(outputFileData.getFileName()))) { //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat - + // Check if there is already a file with this name if (outputFileData.fileInstance.exists()) { int res = JOptionPane.showConfirmDialog(new Frame(), NbBundle.getMessage(this.getClass(), @@ -172,22 +186,22 @@ final class ExtractUnallocAction extends AbstractAction { copyList.remove(outputFileData); } } - + if (!isImage & !copyList.isEmpty()) { - try{ + try { ExtractUnallocWorker worker = new ExtractUnallocWorker(outputFileData); worker.execute(); - } catch (Exception ex){ - logger.log(Level.WARNING, "Already extracting unallocated space into " + outputFileData.getFileName()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Already extracting unallocated space into {0}", outputFileData.getFileName()); MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.volumeInProgress", outputFileData.getFileName())); } } } else { // The output file for this volume could not be created for one of the following reasons - if (outputFileData.layoutFiles == null){ + if (outputFileData.layoutFiles == null) { MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.volumeError")); logger.log(Level.SEVERE, "Tried to get unallocated content but the list of unallocated files was null"); //NON-NLS - } else if (outputFileData.layoutFiles.isEmpty()){ + } else if (outputFileData.layoutFiles.isEmpty()) { MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.noFiles")); logger.log(Level.WARNING, "No unallocated files found in volume"); //NON-NLS copyList.remove(outputFileData); @@ -198,16 +212,16 @@ final class ExtractUnallocAction extends AbstractAction { } } } - + // This needs refactoring. The idea seems to be that we'll take advantage of the loop above to // check whether each output file exists but wait until this point to make a worker // to extract everything (the worker in the above loop doesn't get created because isImage is true) // It's also unclear to me why we need the two separate worker types. if (isImage && !copyList.isEmpty()) { - try{ + try { ExtractUnallocWorker worker = new ExtractUnallocWorker(copyList); worker.execute(); - } catch (Exception ex){ + } catch (Exception ex) { logger.log(Level.WARNING, "Error creating ExtractUnallocWorker", ex); MessageNotifyUtil.Message.info(NbBundle.getMessage(this.getClass(), "ExtractUnallocAction.imageError")); } @@ -220,16 +234,16 @@ final class ExtractUnallocAction extends AbstractAction { /** * Gets all the unallocated files in a given Content. * - * @param c Content to get Unallocated Files from + * @param content Content to get Unallocated Files from * * @return A list if it didn't crash List may be empty. */ - private List getUnallocFiles(Content c) { - UnallocVisitor uv = new UnallocVisitor(); + private List getUnallocFiles(Content content) { + UnallocVisitor unallocVisitor = new UnallocVisitor(); try { - for (Content contentChild : c.getChildren()) { + for (Content contentChild : content.getChildren()) { if (contentChild instanceof AbstractContent) { - return contentChild.accept(uv); //call on first non-artifact child added to database + return contentChild.accept(unallocVisitor); //call on first non-artifact child added to database } } } catch (TskCoreException tce) { @@ -237,38 +251,37 @@ final class ExtractUnallocAction extends AbstractAction { } return Collections.emptyList(); } - + synchronized static private void addVolumeInProgress(String volumeOutputFileName) throws TskCoreException { - if(volumesInProgress.contains(volumeOutputFileName)){ + if (volumesInProgress.contains(volumeOutputFileName)) { throw new TskCoreException("Already writing unallocated space to " + volumeOutputFileName); - } + } volumesInProgress.add(volumeOutputFileName); } - - synchronized static private void removeVolumeInProgress(String volumeOutputFileName){ + + synchronized static private void removeVolumeInProgress(String volumeOutputFileName) { volumesInProgress.remove(volumeOutputFileName); } - - synchronized static private boolean isVolumeInProgress(String volumeOutputFileName){ + + synchronized static private boolean isVolumeInProgress(String volumeOutputFileName) { return volumesInProgress.contains(volumeOutputFileName); } - - synchronized static private void addImageInProgress(Long id) throws TskCoreException { - if(imagesInProgress.contains(id)){ - throw new TskCoreException("Image " + id + " is in use"); - } - imagesInProgress.add(id); + + synchronized static private void addImageInProgress(Long objId) throws TskCoreException { + if (imagesInProgress.contains(objId)) { + throw new TskCoreException("Image " + objId + " is in use"); + } + imagesInProgress.add(objId); } - - synchronized static private void removeImageInProgress(Long id){ - imagesInProgress.remove(id); - } - - synchronized static private boolean isImageInProgress(Long id){ - return imagesInProgress.contains(id); + + synchronized static private void removeImageInProgress(Long objId) { + imagesInProgress.remove(objId); } - - + + synchronized static private boolean isImageInProgress(Long objId) { + return imagesInProgress.contains(objId); + } + /** * Private class for dispatching the file IO in a background thread. */ @@ -276,9 +289,9 @@ final class ExtractUnallocAction extends AbstractAction { private ProgressHandle progress; private boolean canceled = false; - private List outputFileDataList = new ArrayList<>(); + private final List outputFileDataList = new ArrayList<>(); private File currentlyProcessing; - private int totalSizeinMegs; + private final int totalSizeinMegs; long totalBytes = 0; ExtractUnallocWorker(OutputFileData outputFileData) throws TskCoreException { @@ -291,32 +304,32 @@ final class ExtractUnallocAction extends AbstractAction { ExtractUnallocWorker(List outputFileDataList) throws TskCoreException { addImageInProgress(currentImage); - + //Getting the total megs this worker is going to be doing for (OutputFileData outputFileData : outputFileDataList) { - try{ + try { // If a volume is locked, skip it but continue trying to process any other requested volumes addVolumeInProgress(outputFileData.getFileName()); totalBytes += outputFileData.getSizeInBytes(); this.outputFileDataList.add(outputFileData); - } catch (TskCoreException ex){ - logger.log(Level.WARNING, "Already extracting data into " + outputFileData.getFileName()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Already extracting data into {0}", outputFileData.getFileName()); } } - + // If we don't have anything to output (because of locking), throw an exception - if(this.outputFileDataList.isEmpty()){ + if (this.outputFileDataList.isEmpty()) { throw new TskCoreException("No unallocated files can be extracted"); } - + totalSizeinMegs = toMb(totalBytes); } private int toMb(long bytes) { if (bytes > 1024 && (bytes / 1024.0) <= Double.MAX_VALUE) { - double Mb = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes - if (Mb <= Integer.MAX_VALUE) { - return (int) Math.ceil(Mb); + double megabytes = ((bytes / 1024.0) / 1024.0);//Bytes -> Megabytes + if (megabytes <= Integer.MAX_VALUE) { + return (int) Math.ceil(megabytes); } } return 0; @@ -347,7 +360,7 @@ final class ExtractUnallocAction extends AbstractAction { int mbs = 0; //Increments every 128th tick of kbs for (OutputFileData outputFileData : this.outputFileDataList) { currentlyProcessing = outputFileData.getFile(); - logger.log(Level.INFO, "Writing Unalloc file to " + currentlyProcessing.getPath()); //NON-NLS + logger.log(Level.INFO, "Writing Unalloc file to {0}", currentlyProcessing.getPath()); //NON-NLS OutputStream outputStream = new FileOutputStream(currentlyProcessing); long bytes = 0; int i = 0; @@ -374,9 +387,9 @@ final class ExtractUnallocAction extends AbstractAction { if (canceled) { outputFileData.getFile().delete(); - logger.log(Level.INFO, "Canceled extraction of " + outputFileData.getFileName() + " and deleted file"); //NON-NLS + logger.log(Level.INFO, "Canceled extraction of {0} and deleted file", outputFileData.getFileName()); //NON-NLS } else { - logger.log(Level.INFO, "Finished writing unalloc file " + outputFileData.getFile().getPath()); //NON-NLS + logger.log(Level.INFO, "Finished writing unalloc file {0}", outputFileData.getFile().getPath()); //NON-NLS } } progress.finish(); @@ -589,7 +602,7 @@ final class ExtractUnallocAction extends AbstractAction { */ private class OutputFileData { - private List layoutFiles; + private final List layoutFiles; private final long sizeInBytes; private long volumeId; private long imageId; @@ -601,7 +614,7 @@ final class ExtractUnallocAction extends AbstractAction { * Contingency constructor in event no VolumeSystem exists on an Image. * * @param img Image file to be analyzed - * + * * @throws NoCurrentCaseException if there is no open case. */ OutputFileData(Image img) throws NoCurrentCaseException { @@ -619,7 +632,7 @@ final class ExtractUnallocAction extends AbstractAction { * Default constructor for extracting info from Volumes. * * @param volume Volume file to be analyzed - * + * * @throws NoCurrentCaseException if there is no open case. */ OutputFileData(Volume volume) throws NoCurrentCaseException { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.form b/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.form new file mode 100644 index 0000000000..e31c97d4ba --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.form @@ -0,0 +1,101 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.java new file mode 100644 index 0000000000..7641655d57 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/GroupDataSourcesDialog.java @@ -0,0 +1,147 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-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.directorytree; + +import javax.swing.JFrame; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; + +/** + * + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +final class GroupDataSourcesDialog extends javax.swing.JDialog { + + boolean shouldGroupByDataSource = false; + + /** + * Creates new form GroupDataSourcesDialog + */ + @NbBundle.Messages({"# {0} - dataSourceCount", + "GroupDataSourcesDialog.groupDataSources.text=This case contains {0} data sources."}) + GroupDataSourcesDialog(int dataSourceCount) { + super((JFrame) WindowManager.getDefault().getMainWindow()); + initComponents(); + dataSourceCountLabel.setText(Bundle.GroupDataSourcesDialog_groupDataSources_text(dataSourceCount)); + } + + /** + * Display the dialog. + */ + void display() { + setModal(true); + setSize(getPreferredSize()); + setLocationRelativeTo(this.getParent()); + setAlwaysOnTop(false); + pack(); + setVisible(true); + } + + boolean groupByDataSourceSelected() { + return shouldGroupByDataSource; + } + + /** + * 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() { + + dataSourceCountLabel = new javax.swing.JLabel(); + queryLabel = new javax.swing.JLabel(); + yesButton = new javax.swing.JButton(); + noButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle(org.openide.util.NbBundle.getMessage(GroupDataSourcesDialog.class, "GroupDataSourcesDialog.title")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dataSourceCountLabel, org.openide.util.NbBundle.getMessage(GroupDataSourcesDialog.class, "GroupDataSourcesDialog.dataSourceCountLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(queryLabel, org.openide.util.NbBundle.getMessage(GroupDataSourcesDialog.class, "GroupDataSourcesDialog.queryLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(yesButton, org.openide.util.NbBundle.getMessage(GroupDataSourcesDialog.class, "GroupDataSourcesDialog.yesButton.text")); // NOI18N + yesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + yesButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(noButton, org.openide.util.NbBundle.getMessage(GroupDataSourcesDialog.class, "GroupDataSourcesDialog.noButton.text")); // NOI18N + noButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + noButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(queryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(dataSourceCountLabel) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(yesButton, javax.swing.GroupLayout.PREFERRED_SIZE, 76, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(noButton, javax.swing.GroupLayout.PREFERRED_SIZE, 76, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(dataSourceCountLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(queryLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(yesButton) + .addComponent(noButton)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void yesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_yesButtonActionPerformed + shouldGroupByDataSource = true; + dispose(); + }//GEN-LAST:event_yesButtonActionPerformed + + private void noButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noButtonActionPerformed + shouldGroupByDataSource = false; + dispose(); + }//GEN-LAST:event_noButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel dataSourceCountLabel; + private javax.swing.JButton noButton; + private javax.swing.JLabel queryLabel; + private javax.swing.JButton yesButton; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java deleted file mode 100644 index b7cd2eab12..0000000000 --- a/Core/src/org/sleuthkit/autopsy/directorytree/HashSearchAction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.directorytree; - -import java.awt.event.ActionEvent; -import javax.annotation.concurrent.Immutable; -import javax.swing.AbstractAction; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbSearchAction; - -/** - * Action to lookup the interface and call the real action in HashDatabase. The - * real action, HashDbSearchAction, implements HashSearchProvider, and should be - * the only instance of it. - * - * //TODO: HashDBSearchAction needs a public constructor and a service - * registration annotation for the lookup technique to work - */ -@Immutable -public class HashSearchAction extends AbstractAction { - - private final Node contentNode; - - public HashSearchAction(String title, Node contentNode) { - super(title); - this.contentNode = contentNode; - } - - @Override - public void actionPerformed(ActionEvent e) { - //HashSearchProvider searcher = Lookup.getDefault().lookup(HashSearchProvider.class); - //TODO: HashDBSearchAction needs a public constructor and a service registration annotation for the above technique to work - HashDbSearchAction searcher = HashDbSearchAction.getDefault(); - searcher.search(contentNode); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 792252605a..f4da3a3edf 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -24,6 +24,7 @@ import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; @@ -33,6 +34,8 @@ import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; @@ -44,9 +47,12 @@ import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.VolumeSystem; /** @@ -122,7 +128,9 @@ public class ViewContextAction extends AbstractAction { @Override @Messages({ "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.", - "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.",}) + "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.", + "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree." + }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { /* @@ -130,7 +138,40 @@ public class ViewContextAction extends AbstractAction { */ DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + + Node parentTreeViewNode; + if (UserPreferences.groupItemsInTreeByDatasource()) { // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = content.getDataSource().getId(); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node datasourceGroupingNode = rootChildren.findChild(dsname); + if (! Objects.isNull(datasourceGroupingNode) ) { + Children dsChildren = datasourceGroupingNode.getChildren(); + parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + } + else { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; + } + } catch (NoCurrentCaseException| TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + return; + } + } else { // Classic view + // Start the search at the DataSourcesNode + parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + } + /* * Get the parent content for the content to be selected in the * results view. If the parent content is null, then the specified diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png new file mode 100644 index 0000000000..53e6bb25d4 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_disabled_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png new file mode 100644 index 0000000000..f012692c6f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_hover_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png new file mode 100644 index 0000000000..56810faa2f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_back_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png new file mode 100644 index 0000000000..b1d97b6897 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_disabled_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png new file mode 100644 index 0000000000..04b4471bb9 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_hover_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png new file mode 100644 index 0000000000..4566b3dbba Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/directorytree/btn_step_forward_large.png differ diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java index 96d7973d8a..1ed54df58d 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.filesearch; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -29,7 +27,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; -import javax.swing.JList; import javax.swing.event.ListSelectionEvent; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -47,38 +44,28 @@ public class DataSourcePanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(DataSourcePanel.class.getName()); private static final long serialVersionUID = 1L; private final Map dataSourceMap = new HashMap<>(); - private final List toolTipList = new ArrayList<>(); /** * Creates new form DataSourcePanel */ public DataSourcePanel() { initComponents(); - this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { - firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); - }); - this.dataSourceList.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseDragged(MouseEvent evt) { - //Unused by now - } - - @Override - public void mouseMoved(MouseEvent evt) { - if (evt.getSource() instanceof JList) { - JList dsList = (JList) evt.getSource(); - int index = dsList.locationToIndex(evt.getPoint()); - if (index > -1) { - dsList.setToolTipText(toolTipList.get(index)); - } - } - } - }); + if (this.dataSourceList.getModel().getSize() > 1) { + this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { + firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null); + }); + } else { + /* + * Disable data source filtering since there aren't multiple data + * sources to choose from. + */ + this.dataSourceCheckBox.setEnabled(false); + this.dataSourceList.setEnabled(false); + } } /** - * Get dataSourceMap with object id and data source display name. Add the data source full name to toolTipList + * Get dataSourceMap with object id and data source display name. * * @return The list of data source name */ @@ -93,8 +80,7 @@ public class DataSourcePanel extends javax.swing.JPanel { String dsName = ds.getName(); File dataSourceFullName = new File(dsName); String displayName = dataSourceFullName.getName(); - dataSourceMap.put(ds.getId(), displayName); - toolTipList.add(dsName); + dataSourceMap.put(ds.getId(), displayName); dsList.add(displayName); } } catch (NoCurrentCaseException ex) { @@ -107,7 +93,8 @@ public class DataSourcePanel extends javax.swing.JPanel { /** * Get a set of data source object ids that are selected. - * @return A set of selected object ids. + * + * @return A set of selected object ids. */ Set getDataSourcesSelected() { Set dataSourceObjIdSet = new HashSet<>(); @@ -124,6 +111,7 @@ public class DataSourcePanel extends javax.swing.JPanel { /** * Is dataSourceCheckBox selected + * * @return true if the dataSoureCheckBox is selected */ boolean isSelected() { @@ -131,7 +119,8 @@ public class DataSourcePanel extends javax.swing.JPanel { } /** - * Enable the dsList and dataSourceNoteLable if the dataSourceCheckBox is checked. + * Enable the dsList and dataSourceNoteLable if the dataSourceCheckBox is + * checked. */ final void setComponentsEnabled() { boolean enabled = this.isSelected(); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java index 9171104238..845c39f06c 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java @@ -16,24 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * FileSearchDialog.java - * - * Created on Mar 5, 2012, 1:57:33 PM - */ package org.sleuthkit.autopsy.filesearch; -import org.openide.util.NbBundle; - import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; +import org.openide.util.NbBundle; import org.openide.windows.WindowManager; /** * File search dialog */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchDialog extends javax.swing.JDialog { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 2695154ec4..55a90487a5 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -35,14 +35,16 @@ import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; +import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.filesearch.FileSearchFilter.FilterValidationException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; @@ -166,6 +168,7 @@ class FileSearchPanel extends javax.swing.JPanel { * Action when the "Search" button is pressed. * */ + @NbBundle.Messages("FileSearchPanel.emptyNode.display.text=No results found.") private void search() { // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); @@ -192,9 +195,16 @@ class FileSearchPanel extends javax.swing.JPanel { } SearchNode sn = new SearchNode(contentList); - final TopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText, - new SingleLayerTableFilterNode(sn, true, sn.getName()), contentList.size()); - + TableFilterNode tableFilterNode = new TableFilterNode(sn, true, sn.getName()); + final TopComponent searchResultWin; + if (contentList.isEmpty()) { + Node emptyNode = new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()); + searchResultWin = DataResultTopComponent.createInstance(title, pathText, + emptyNode, 0); + } else { + searchResultWin = DataResultTopComponent.createInstance(title, pathText, + tableFilterNode, contentList.size()); + } searchResultWin.requestActive(); // make it the active top component /** diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java similarity index 78% rename from Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java rename to Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java index 57a5ae452d..251fe35572 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java @@ -52,44 +52,42 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; - /** * Class for recording data on the health of the system. - * - * For timing data: - * Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object - * Modules will call submitTimingMetric() with the obtained TimingMetric object to log it + * + * For timing data: Modules will call getTimingMetric() before the code to be + * timed to get a TimingMetric object Modules will call submitTimingMetric() + * with the obtained TimingMetric object to log it */ -public final class EnterpriseHealthMonitor implements PropertyChangeListener { - - private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); - private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; +public final class HealthMonitor implements PropertyChangeListener { + + private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName()); + private final static String DATABASE_NAME = "HealthMonitor"; private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes - public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION - = new CaseDbSchemaVersionNumber(1, 1); - - private static final AtomicBoolean isEnabled = new AtomicBoolean(false); - private static EnterpriseHealthMonitor instance; - + private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1); + + private final static AtomicBoolean isEnabled = new AtomicBoolean(false); + private static HealthMonitor instance; + private ScheduledThreadPoolExecutor healthMonitorOutputTimer; private final Map timingInfoMap; private final List userInfoList; - private static final int CONN_POOL_SIZE = 10; + private final static int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; private CaseDbConnectionInfo connectionSettingsInUse = null; private String hostName; - - private EnterpriseHealthMonitor() throws HealthMonitorException { - + + private HealthMonitor() throws HealthMonitorException { + // Create the map to collect timing metrics. The map will exist regardless // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); - + // Create the list to hold user information. The list will exist regardless // of whether the monitor is enabled. userInfoList = new ArrayList<>(); - + // Get the host name try { hostName = java.net.InetAddress.getLocalHost().getHostName(); @@ -98,84 +96,87 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { hostName = UUID.randomUUID().toString(); logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); } - + // Read from the database to determine if the module is enabled updateFromGlobalEnabledStatus(); - + // Start the timer for database checks and writes startTimer(); } - + /** - * Get the instance of the EnterpriseHealthMonitor + * Get the instance of the HealthMonitor + * * @return the instance - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException { + synchronized static HealthMonitor getInstance() throws HealthMonitorException { if (instance == null) { - instance = new EnterpriseHealthMonitor(); + instance = new HealthMonitor(); Case.addPropertyChangeListener(instance); } return instance; } - + /** - * Activate the health monitor. - * Creates/initialized the database (if needed), clears any existing metrics - * out of the maps, and sets up the timer for writing to the database. - * @throws HealthMonitorException + * Activate the health monitor. Creates/initialized the database (if + * needed), clears any existing metrics out of the maps, and sets up the + * timer for writing to the database. + * + * @throws HealthMonitorException */ private synchronized void activateMonitorLocally() throws HealthMonitorException { - + logger.log(Level.INFO, "Activating Servies Health Monitor"); - + // Make sure there are no left over connections to an old database shutdownConnections(); - + if (!UserPreferences.getIsMultiUserModeEnabled()) { throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor"); } - + // Set up database (if needed) try (CoordinationService.Lock lock = getExclusiveDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + // Check if the database exists - if (! databaseExists()) { - + if (!databaseExists()) { + // If not, create a new one createDatabase(); } - - if( ! databaseIsInitialized()) { + + if (!databaseIsInitialized()) { initializeDatabaseSchema(); } - - if( ! CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { + + if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { upgradeDatabaseSchema(); } - + } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error releasing database lock", ex); } - + // Clear out any old data timingInfoMap.clear(); userInfoList.clear(); } - + /** * Upgrade an older database */ private void upgradeDatabaseSchema() throws HealthMonitorException { - + logger.log(Level.INFO, "Upgrading Health Monitor database"); CaseDbSchemaVersionNumber currentSchema = getVersion(); - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } @@ -184,23 +185,23 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // Upgrade from 1.0 to 1.1 // Changes: user_data table added - if(currentSchema.compareTo(new CaseDbSchemaVersionNumber(1,1)) < 0) { - + if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) { + // Add the user_data table - statement.execute("CREATE TABLE IF NOT EXISTS user_data ("+ - "id SERIAL PRIMARY KEY," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "event_type int NOT NULL," + - "is_examiner boolean NOT NULL," + - "case_name text NOT NULL" + - ")"); + statement.execute("CREATE TABLE IF NOT EXISTS user_data (" + + "id SERIAL PRIMARY KEY," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "event_type int NOT NULL," + + "is_examiner boolean NOT NULL," + + "case_name text NOT NULL" + + ")"); } - + // Update the schema version statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "' WHERE name='SCHEMA_VERSION'"); statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "' WHERE name='SCHEMA_MINOR_VERSION'"); - + conn.commit(); logger.log(Level.INFO, "Health Monitor database upgraded to version {0}", CURRENT_DB_SCHEMA_VERSION.toString()); } catch (SQLException ex) { @@ -218,82 +219,89 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * Deactivate the health monitor. - * This should only be used when disabling the monitor, not when Autopsy is closing. - * Clears out any metrics that haven't been written, stops the database write timer, - * and shuts down the connection pool. - * @throws HealthMonitorException + * Deactivate the health monitor. This should only be used when disabling + * the monitor, not when Autopsy is closing. Clears out any metrics that + * haven't been written, stops the database write timer, and shuts down the + * connection pool. + * + * @throws HealthMonitorException */ private synchronized void deactivateMonitorLocally() throws HealthMonitorException { - + logger.log(Level.INFO, "Deactivating Servies Health Monitor"); - + // Clear out the collected data timingInfoMap.clear(); - + // Shut down the connection pool shutdownConnections(); } - + /** - * Start the ScheduledThreadPoolExecutor that will handle the database writes. + * Start the ScheduledThreadPoolExecutor that will handle the database + * writes. */ private synchronized void startTimer() { // Make sure the previous executor (if it exists) has been stopped stopTimer(); - + healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); healthMonitorOutputTimer.scheduleWithFixedDelay(new PeriodicHealthMonitorTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); } - + /** * Stop the ScheduledThreadPoolExecutor to prevent further database writes. */ private synchronized void stopTimer() { - if(healthMonitorOutputTimer != null) { + if (healthMonitorOutputTimer != null) { ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer); } } /** - * Called from the installer to set up the Health Monitor instance at startup. - * @throws HealthMonitorException + * Called from the installer to set up the Health Monitor instance at + * startup. + * + * @throws HealthMonitorException */ static synchronized void startUpIfEnabled() throws HealthMonitorException { getInstance().addUserEvent(UserEvent.LOG_ON); } - + /** - * Called when the application is closing. - * Create a log off event and write all existing metrics to the database - * @throws HealthMonitorException + * Called when the application is closing. Create a log off event and write + * all existing metrics to the database + * + * @throws HealthMonitorException */ static synchronized void shutdown() throws HealthMonitorException { getInstance().addUserEvent(UserEvent.LOG_OFF); recordMetrics(); } - + /** * Enabled/disable the health monitor. + * * @param enabled true to enable the monitor, false to disable it - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { - if(enabled == isEnabled.get()) { + if (enabled == isEnabled.get()) { // The setting has not changed, so do nothing return; } - - if(enabled) { + + if (enabled) { getInstance().activateMonitorLocally(); - + // If activateMonitor fails, we won't update this getInstance().setGlobalEnabledStatusInDB(true); isEnabled.set(true); } else { - if(isEnabled.get()) { + if (isEnabled.get()) { // If we were enabled before, set the global state to disabled getInstance().setGlobalEnabledStatusInDB(false); } @@ -301,33 +309,34 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { getInstance().deactivateMonitorLocally(); } } - + /** * Get a metric that will measure the time to execute a section of code. - * Call this before the section of code to be timed and then - * submit it afterward using submitTimingMetric(). - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. + * Call this before the section of code to be timed and then submit it + * afterward using submitTimingMetric(). This method is safe to call + * regardless of whether the health monitor is enabled. + * * @param name A short but descriptive name describing the code being timed. * This name will appear in the UI. + * * @return The TimingMetric object */ public static TimingMetric getTimingMetric(String name) { - if(isEnabled.get()) { + if (isEnabled.get()) { return new TimingMetric(name); } return null; } - + /** * Submit the metric that was previously obtained through getTimingMetric(). - * Call this immediately after the section of code being timed. - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. + * Call this immediately after the section of code being timed. This method + * is safe to call regardless of whether the health monitor is enabled. + * * @param metric The TimingMetric object obtained from getTimingMetric() */ public static void submitTimingMetric(TimingMetric metric) { - if(isEnabled.get() && (metric != null)) { + if (isEnabled.get() && (metric != null)) { metric.stopTiming(); try { getInstance().addTimingMetric(metric); @@ -337,18 +346,20 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Submit the metric that was previously obtained through getTimingMetric(), - * incorporating a count that the time should be divided by. - * Call this immediately after the section of code being timed. - * This method is safe to call regardless of whether the Enterprise Health - * Monitor is enabled. - * @param metric The TimingMetric object obtained from getTimingMetric() - * @param normalization The number to divide the time by (a zero here will be treated as a one) + * incorporating a count that the time should be divided by. Call this + * immediately after the section of code being timed. This method is safe to + * call regardless of whether the health monitor is enabled. + * + * @param metric The TimingMetric object obtained from + * getTimingMetric() + * @param normalization The number to divide the time by (a zero here will + * be treated as a one) */ public static void submitNormalizedTimingMetric(TimingMetric metric, long normalization) { - if(isEnabled.get() && (metric != null)) { + if (isEnabled.get() && (metric != null)) { metric.stopTiming(); try { metric.normalize(normalization); @@ -359,125 +370,130 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Add the timing metric data to the map. - * @param metric The metric to add. stopTiming() should already have been called. + * + * @param metric The metric to add. stopTiming() should already have been + * called. */ private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { - + // Do as little as possible within the synchronized block to minimize // blocking with multiple threads. - synchronized(this) { + synchronized (this) { // There's a small check-then-act situation here where isEnabled // may have changed before reaching this code. This is fine - // the map still exists and any extra data added after the monitor // is disabled will be deleted if the monitor is re-enabled. This // seems preferable to doing another check on isEnabled within // the synchronized block. - if(timingInfoMap.containsKey(metric.getName())) { + if (timingInfoMap.containsKey(metric.getName())) { timingInfoMap.get(metric.getName()).addMetric(metric); } else { timingInfoMap.put(metric.getName(), new TimingInfo(metric)); } } } - + /** * Add a user event to the list. - * @param eventType + * + * @param eventType */ private void addUserEvent(UserEvent eventType) { UserData userInfo = new UserData(eventType); - synchronized(this) { + synchronized (this) { userInfoList.add(userInfo); } } - + /** - * Time a database query. - * Database queries are hard to test in normal processing because the time - * is so dependent on the size of the tables being queried. We use getImages here - * because every table it queries is the same size (one entry for each image) so - * we a) know the size of the tables and b) can use that table size to do - * normalization. - * @throws HealthMonitorException + * Time a database query. Database queries are hard to test in normal + * processing because the time is so dependent on the size of the tables + * being queried. We use getImages here because every table it queries is + * the same size (one entry for each image) so we a) know the size of the + * tables and b) can use that table size to do normalization. + * + * @throws HealthMonitorException */ private void performDatabaseQuery() throws HealthMonitorException { try { SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query"); + TimingMetric metric = HealthMonitor.getTimingMetric("Database: getImages query"); List images = skCase.getImages(); - + // Through testing we found that this normalization gives us fairly // consistent results for different numbers of data sources. long normalization = images.size(); if (images.isEmpty()) { normalization += 2; - } else if (images.size() == 1){ + } else if (images.size() == 1) { normalization += 3; } else if (images.size() < 10) { normalization += 5; } else { normalization += 7; } - - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); + + HealthMonitor.submitNormalizedTimingMetric(metric, normalization); } catch (NoCurrentCaseException ex) { // If there's no case open, we just can't do the metrics. } catch (TskCoreException ex) { throw new HealthMonitorException("Error running getImages()", ex); } } - + /** * Collect metrics at a scheduled time. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void gatherTimerBasedMetrics() throws HealthMonitorException { performDatabaseQuery(); } - + /** * Write the collected metrics to the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void writeCurrentStateToDatabase() throws HealthMonitorException { - + Map timingMapCopy; List userDataCopy; - + // Do as little as possible within the synchronized block since it will // block threads attempting to record metrics. - synchronized(this) { - if(! isEnabled.get()) { + synchronized (this) { + if (!isEnabled.get()) { return; } - + // Make a shallow copy of the timing map. The map should be small - one entry // per metric name. timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); - + userDataCopy = new ArrayList<>(userInfoList); userInfoList.clear(); } - + // Check if there's anything to report - if(timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) { + if (timingMapCopy.keySet().isEmpty() && userDataCopy.isEmpty()) { return; } - + logger.log(Level.INFO, "Writing health monitor metrics to database"); - + // Write to the database try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } @@ -485,9 +501,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; String addUserInfoSql = "INSERT INTO user_data (host, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?)"; try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql); - PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { + PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { - for(String name:timingMapCopy.keySet()) { + for (String name : timingMapCopy.keySet()) { TimingInfo info = timingMapCopy.get(name); timingStatement.setString(1, name); @@ -500,8 +516,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { timingStatement.execute(); } - - for(UserData userInfo:userDataCopy) { + + for (UserData userInfo : userDataCopy) { userStatement.setString(1, hostName); userStatement.setLong(2, userInfo.getTimestamp()); userStatement.setInt(3, userInfo.getEventType().getEventValue()); @@ -523,14 +539,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error releasing database lock", ex); } } - + /** - * Check whether the health monitor database exists. - * Does not check the schema. + * Check whether the health monitor database exists. Does not check the + * schema. + * * @return true if the database exists, false otherwise - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private boolean databaseExists() throws HealthMonitorException { + private boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -538,13 +556,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { ResultSet rs = null; try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS Statement statement = connection.createStatement();) { - String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; + String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; rs = statement.executeQuery(createCommand); - if(rs.next()) { + if (rs.next()) { return true; } } finally { - if(rs != null) { + if (rs != null) { rs.close(); } } @@ -553,10 +571,11 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } return false; } - + /** * Create a new health monitor database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void createDatabase() throws HealthMonitorException { try { @@ -576,13 +595,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Setup a connection pool for db connections. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void setupConnectionPool() throws HealthMonitorException { try { CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); connectionSettingsInUse = db; - + connectionPool = new BasicDataSource(); connectionPool.setDriverClassName("org.postgresql.Driver"); @@ -606,15 +626,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error loading database configuration", ex); } } - + /** * Shut down the connection pool - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void shutdownConnections() throws HealthMonitorException { try { - synchronized(this) { - if(connectionPool != null){ + synchronized (this) { + if (connectionPool != null) { connectionPool.close(); connectionPool = null; // force it to be re-created on next connect() } @@ -623,12 +644,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS } } - + /** - * Get a database connection. - * Sets up the connection pool if needed. + * Get a database connection. Sets up the connection pool if needed. + * * @return The Connection object - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private Connection connect() throws HealthMonitorException { synchronized (this) { @@ -643,20 +665,22 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS } } - + /** - * Test whether the database schema has been initialized. - * We do this by looking for the version number. + * Test whether the database schema has been initialized. We do this by + * looking for the version number. + * * @return True if it has been initialized, false otherwise. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private boolean databaseIsInitialized() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } ResultSet resultSet = null; - + try (Statement statement = conn.createStatement()) { resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); return resultSet.next(); @@ -664,7 +688,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // This likely just means that the db_info table does not exist return false; } finally { - if(resultSet != null) { + if (resultSet != null) { try { resultSet.close(); } catch (SQLException ex) { @@ -676,67 +700,69 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (SQLException ex) { logger.log(Level.SEVERE, "Error closing Connection.", ex); } - } + } } - + /** - * Return whether the health monitor is locally enabled. - * This does not query the database. + * Return whether the health monitor is locally enabled. This does not query + * the database. + * * @return true if it is enabled, false otherwise */ static boolean monitorIsEnabled() { return isEnabled.get(); } - + /** - * Check whether monitoring should be enabled from the monitor database - * and enable/disable as needed. - * @throws HealthMonitorException + * Check whether monitoring should be enabled from the monitor database and + * enable/disable as needed. + * + * @throws HealthMonitorException */ synchronized void updateFromGlobalEnabledStatus() throws HealthMonitorException { - + boolean previouslyEnabled = monitorIsEnabled(); - + // We can't even check the database if multi user settings aren't enabled. if (!UserPreferences.getIsMultiUserModeEnabled()) { isEnabled.set(false); - if(previouslyEnabled) { + if (previouslyEnabled) { deactivateMonitorLocally(); } return; } - + // If the health monitor database doesn't exist or if it is not initialized, // then monitoring isn't enabled - if ((! databaseExists()) || (! databaseIsInitialized())) { + if ((!databaseExists()) || (!databaseIsInitialized())) { isEnabled.set(false); - - if(previouslyEnabled) { + + if (previouslyEnabled) { deactivateMonitorLocally(); } return; } - + // If we're currently enabled, check whether the multiuser settings have changed. // If they have, force a reset on the connection pool. - if(previouslyEnabled && (connectionSettingsInUse != null)) { + if (previouslyEnabled && (connectionSettingsInUse != null)) { try { CaseDbConnectionInfo currentSettings = UserPreferences.getDatabaseConnectionInfo(); - if(! (connectionSettingsInUse.getUserName().equals(currentSettings.getUserName()) + if (!(connectionSettingsInUse.getUserName().equals(currentSettings.getUserName()) && connectionSettingsInUse.getPassword().equals(currentSettings.getPassword()) && connectionSettingsInUse.getPort().equals(currentSettings.getPort()) - && connectionSettingsInUse.getHost().equals(currentSettings.getHost()) )) { + && connectionSettingsInUse.getHost().equals(currentSettings.getHost()))) { shutdownConnections(); } } catch (UserPreferencesException ex) { throw new HealthMonitorException("Error reading database connection info", ex); } } - + boolean currentlyEnabled = getGlobalEnabledStatusFromDB(); - if( currentlyEnabled != previouslyEnabled) { - if( ! currentlyEnabled ) { + if (currentlyEnabled != previouslyEnabled) { + if (!currentlyEnabled) { isEnabled.set(false); deactivateMonitorLocally(); } else { @@ -745,54 +771,59 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * Read the enabled status from the database. - * Check that the health monitor database exists before calling this. + * Read the enabled status from the database. Check that the health monitor + * database exists before calling this. + * * @return true if the database is enabled, false otherwise - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private boolean getGlobalEnabledStatusFromDB() throws HealthMonitorException { - + try (Connection conn = connect(); - Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) { + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='MONITOR_ENABLED'")) { if (resultSet.next()) { - return(resultSet.getBoolean("value")); + return (resultSet.getBoolean("value")); } throw new HealthMonitorException("No enabled status found in database"); } catch (SQLException ex) { throw new HealthMonitorException("Error initializing database", ex); } } - + /** * Set the global enabled status in the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void setGlobalEnabledStatusInDB(boolean status) throws HealthMonitorException { - + try (Connection conn = connect(); - Statement statement = conn.createStatement();) { - statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); + Statement statement = conn.createStatement();) { + statement.execute("UPDATE db_info SET value='" + status + "' WHERE name='MONITOR_ENABLED'"); } catch (SQLException ex) { throw new HealthMonitorException("Error setting enabled status", ex); } } - + /** * Get the current schema version + * * @return the current schema version - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } ResultSet resultSet = null; - + try (Statement statement = conn.createStatement()) { int minorVersion = 0; int majorVersion = 0; @@ -820,7 +851,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (SQLException ex) { throw new HealthMonitorException("Error initializing database", ex); } finally { - if(resultSet != null) { + if (resultSet != null) { try { resultSet.close(); } catch (SQLException ex) { @@ -834,51 +865,51 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Initialize the database. - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ private void initializeDatabaseSchema() throws HealthMonitorException { Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } try (Statement statement = conn.createStatement()) { conn.setAutoCommit(false); - statement.execute("CREATE TABLE IF NOT EXISTS timing_data (" + - "id SERIAL PRIMARY KEY," + - "name text NOT NULL," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "count bigint NOT NULL," + - "average double precision NOT NULL," + - "max double precision NOT NULL," + - "min double precision NOT NULL" + - ")"); - - statement.execute("CREATE TABLE IF NOT EXISTS db_info (" + - "id SERIAL PRIMARY KEY NOT NULL," + - "name text NOT NULL," + - "value text NOT NULL" + - ")"); - - statement.execute("CREATE TABLE IF NOT EXISTS user_data ("+ - "id SERIAL PRIMARY KEY," + - "host text NOT NULL," + - "timestamp bigint NOT NULL," + - "event_type int NOT NULL," + - "is_examiner BOOLEAN NOT NULL," + - "case_name text NOT NULL" + - ")"); - - + statement.execute("CREATE TABLE IF NOT EXISTS timing_data (" + + "id SERIAL PRIMARY KEY," + + "name text NOT NULL," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "count bigint NOT NULL," + + "average double precision NOT NULL," + + "max double precision NOT NULL," + + "min double precision NOT NULL" + + ")"); + + statement.execute("CREATE TABLE IF NOT EXISTS db_info (" + + "id SERIAL PRIMARY KEY NOT NULL," + + "name text NOT NULL," + + "value text NOT NULL" + + ")"); + + statement.execute("CREATE TABLE IF NOT EXISTS user_data (" + + "id SERIAL PRIMARY KEY," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "event_type int NOT NULL," + + "is_examiner BOOLEAN NOT NULL," + + "case_name text NOT NULL" + + ")"); + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('MONITOR_ENABLED', 'true')"); - + conn.commit(); } catch (SQLException ex) { try { @@ -895,10 +926,10 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * The task called by the ScheduledThreadPoolExecutor to handle - * the periodic database update + * The task called by the ScheduledThreadPoolExecutor to handle the periodic + * database update */ static final class PeriodicHealthMonitorTask implements Runnable { @@ -907,18 +938,17 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { recordMetrics(); } } - + /** - * Perform all periodic tasks: - * - Check if monitoring has been enabled / disabled in the database - * - Gather any additional metrics - * - Write current metric data to the database - * Do not run this from a new thread if the case/application is closing. + * Perform all periodic tasks: - Check if monitoring has been enabled / + * disabled in the database - Gather any additional metrics - Write current + * metric data to the database Do not run this from a new thread if the + * case/application is closing. */ private static void recordMetrics() { try { getInstance().updateFromGlobalEnabledStatus(); - if(monitorIsEnabled()) { + if (monitorIsEnabled()) { getInstance().gatherTimerBasedMetrics(); getInstance().writeCurrentStateToDatabase(); } @@ -926,7 +956,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS } } - + @Override public void propertyChange(PropertyChangeEvent evt) { @@ -936,48 +966,48 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // Case is closing addUserEvent(UserEvent.CASE_CLOSE); - - } else if((null == evt.getOldValue()) && (evt.getNewValue() instanceof Case)) { + + } else if ((null == evt.getOldValue()) && (evt.getNewValue() instanceof Case)) { // Case is opening addUserEvent(UserEvent.CASE_OPEN); } break; } } - + /** - * Debugging method to generate sample data for the database. - * It will delete all current timing data and replace it with randomly generated values. - * If there is more than one node, the second node's times will trend upwards. + * Debugging method to generate sample data for the database. It will delete + * all current timing data and replace it with randomly generated values. If + * there is more than one node, the second node's times will trend upwards. */ void populateDatabaseWithSampleData(int nDays, int nNodes, boolean createVerificationData) throws HealthMonitorException { - - if(! isEnabled.get()) { + + if (!isEnabled.get()) { throw new HealthMonitorException("Can't populate database - monitor not enabled"); } - + // Get the database lock CoordinationService.Lock lock = getSharedDbLock(); - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + String[] metricNames = {"Disk Reads: Hash calculation", "Database: getImages query", "Solr: Index chunk", "Solr: Connectivity check", "Correlation Engine: Notable artifact query", "Correlation Engine: Bulk insert"}; // NON-NLS - + Random rand = new Random(); - + long maxTimestamp = System.currentTimeMillis(); long millisPerHour = 1000 * 60 * 60; long minTimestamp = maxTimestamp - (nDays * (millisPerHour * 24)); - + Connection conn = null; try { conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); } - + try (Statement statement = conn.createStatement()) { statement.execute("DELETE FROM timing_data"); // NON-NLS @@ -985,19 +1015,17 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { logger.log(Level.SEVERE, "Error clearing timing data", ex); return; } - - // Add timing metrics to the database String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { - for(String metricName:metricNames) { + for (String metricName : metricNames) { long baseIndex = rand.nextInt(900) + 100; int multiplier = rand.nextInt(5); long minIndexTimeNanos; - switch(multiplier) { + switch (multiplier) { case 0: minIndexTimeNanos = baseIndex; break; @@ -1010,16 +1038,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } long maxIndexTimeOverMin = minIndexTimeNanos * 3; - - for(int node = 0;node < nNodes; node++) { - + + for (int node = 0; node < nNodes; node++) { + String host = "testHost" + node; // NON-NLS - + double count = 0; double maxCount = nDays * 24 + 1; - + // Record data every hour, with a small amount of randomness about when it starts - for(long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55);timestamp < maxTimestamp;timestamp += millisPerHour) { + for (long timestamp = minTimestamp + rand.nextInt(1000 * 60 * 55); timestamp < maxTimestamp; timestamp += millisPerHour) { double aveTime; @@ -1027,29 +1055,29 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { // collection count++; double slowNodeMultiplier = 1.0; - if((maxCount - count) <= 3 * 24) { + if ((maxCount - count) <= 3 * 24) { slowNodeMultiplier += (3 - (maxCount - count) / 24) * 0.33; } - if( ! createVerificationData ) { + if (!createVerificationData) { // Try to make a reasonable sample data set, with most points in a small range // but some higher and lower int outlierVal = rand.nextInt(30); long randVal = rand.nextLong(); - if(randVal < 0) { + if (randVal < 0) { randVal *= -1; } - if(outlierVal < 2){ + if (outlierVal < 2) { aveTime = minIndexTimeNanos + maxIndexTimeOverMin + randVal % maxIndexTimeOverMin; - } else if(outlierVal == 2){ + } else if (outlierVal == 2) { aveTime = (minIndexTimeNanos / 2) + randVal % (minIndexTimeNanos / 2); - } else if(outlierVal < 17) { + } else if (outlierVal < 17) { aveTime = minIndexTimeNanos + randVal % (maxIndexTimeOverMin / 2); } else { aveTime = minIndexTimeNanos + randVal % maxIndexTimeOverMin; } - - if(node == 1) { + + if (node == 1) { aveTime = aveTime * slowNodeMultiplier; } } else { @@ -1061,8 +1089,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { int day = thisDate.get(Calendar.DAY_OF_MONTH); aveTime = day * 1000000; } - - + statement.setString(1, metricName); statement.setString(2, host); statement.setLong(3, timestamp); @@ -1080,8 +1107,8 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } finally { try { - if(conn != null) { - conn.close(); + if (conn != null) { + conn.close(); } } catch (SQLException ex) { logger.log(Level.SEVERE, "Error closing Connection.", ex); @@ -1093,45 +1120,48 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** * Get timing metrics currently stored in the database. + * * @param timeRange Maximum age for returned metrics (in milliseconds) + * * @return A map with metric name mapped to a list of data - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ Map> getTimingMetricsFromDatabase(long timeRange) throws HealthMonitorException { - + // Make sure the monitor is enabled. It could theoretically get disabled after this // check but it doesn't seem worth holding a lock to ensure that it doesn't since that // may slow down ingest. - if(! isEnabled.get()) { + if (!isEnabled.get()) { throw new HealthMonitorException("Health Monitor is not enabled"); } - + // Calculate the smallest timestamp we should return long minimumTimestamp = System.currentTimeMillis() - timeRange; try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } - + Connection conn = connect(); - if(conn == null) { + if (conn == null) { throw new HealthMonitorException("Error getting database connection"); - } + } Map> resultMap = new HashMap<>(); try (Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) { - + ResultSet resultSet = statement.executeQuery("SELECT * FROM timing_data WHERE timestamp > " + minimumTimestamp)) { + while (resultSet.next()) { String name = resultSet.getString("name"); DatabaseTimingResult timingResult = new DatabaseTimingResult(resultSet); - if(resultMap.containsKey(name)) { + if (resultMap.containsKey(name)) { resultMap.get(name).add(timingResult); } else { List resultList = new ArrayList<>(); @@ -1153,36 +1183,39 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { throw new HealthMonitorException("Error getting database lock", ex); } } - + /** * Get user metrics currently stored in the database. + * * @param timeRange Maximum age for returned metrics (in milliseconds) + * * @return A list of user metrics - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ List getUserMetricsFromDatabase(long timeRange) throws HealthMonitorException { - + // Make sure the monitor is enabled. It could theoretically get disabled after this // check but it doesn't seem worth holding a lock to ensure that it doesn't since that // may slow down ingest. - if(! isEnabled.get()) { + if (!isEnabled.get()) { throw new HealthMonitorException("Health Monitor is not enabled"); } - + // Calculate the smallest timestamp we should return long minimumTimestamp = System.currentTimeMillis() - timeRange; try (CoordinationService.Lock lock = getSharedDbLock()) { - if(lock == null) { + if (lock == null) { throw new HealthMonitorException("Error getting database lock"); } List resultList = new ArrayList<>(); try (Connection conn = connect(); - Statement statement = conn.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) { - + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM user_data WHERE timestamp > " + minimumTimestamp)) { + while (resultSet.next()) { resultList.add(new UserData(resultSet)); } @@ -1193,47 +1226,51 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error getting database lock", ex); } - } - + } + /** - * Get an exclusive lock for the health monitor database. - * Acquire this before creating, initializing, or updating the database schema. + * Get an exclusive lock for the health monitor database. Acquire this + * before creating, initializing, or updating the database schema. + * * @return The lock - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ + private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException { try { CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES); - if(lock != null){ + if (lock != null) { return lock; } throw new HealthMonitorException("Error acquiring database lock"); - } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error acquiring database lock", ex); } - } - + } + /** - * Get an shared lock for the health monitor database. - * Acquire this before database reads or writes. + * Get an shared lock for the health monitor database. Acquire this before + * database reads or writes. + * * @return The lock - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ - private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{ + private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException { try { String databaseNodeName = DATABASE_NAME; CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); - if(lock != null){ + if (lock != null) { return lock; } throw new HealthMonitorException("Error acquiring database lock"); - } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error acquiring database lock"); } - } - + } + /** * Types of user events being logged */ @@ -1242,72 +1279,79 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { LOG_OFF(1), CASE_OPEN(2), CASE_CLOSE(3); - + int value; - + UserEvent(int value) { this.value = value; } - + /** * Get the integer value of the event to store in the database. + * * @return value corresponding to the event */ int getEventValue() { return value; } - + /** * Get the UserEvent from the value stored in the database + * * @param value + * * @return the corresponding UserEvent object - * @throws HealthMonitorException + * + * @throws HealthMonitorException */ static UserEvent valueOf(int value) throws HealthMonitorException { for (UserEvent v : UserEvent.values()) { - if (v.value == value) { - return v; - } + if (v.value == value) { + return v; + } } throw new HealthMonitorException("Can not create UserEvent from unknown value " + value); } - + /** - * Return whether a case is considered to be open given this event - * as the last recorded event. + * Return whether a case is considered to be open given this event as + * the last recorded event. + * * @return true if a case is open, false otherwise */ boolean caseIsOpen() { - return(this.equals(CASE_OPEN)); + return (this.equals(CASE_OPEN)); } - + /** * Return whether a user is considered to be logged in given this event * as the last recorded event. + * * @return true if a the user is logged in, false otherwise */ boolean userIsLoggedIn() { // LOG_ON, CASE_OPEN, and CASE_CLOSED events all imply that the user // is logged in - return( ! this.equals(LOG_OFF)); + return (!this.equals(LOG_OFF)); } } - + /** - * Class holding user metric data. - * Can be used for storing new events or retrieving - * events out of the database. + * Class holding user metric data. Can be used for storing new events or + * retrieving events out of the database. */ static class UserData { + private final UserEvent eventType; private long timestamp; private final boolean isExaminer; private final String hostname; private String caseName; - + /** - * Create a new UserData object using the given event type - * and the current settings. + * Create a new UserData object using the given event type and the + * current settings. + * * @param eventType The type of event being recorded */ private UserData(UserEvent eventType) { @@ -1315,7 +1359,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.timestamp = System.currentTimeMillis(); this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode()); this.hostname = ""; - + // If there's a case open, record the name try { this.caseName = Case.getCurrentCaseThrows().getDisplayName(); @@ -1324,12 +1368,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.caseName = ""; } } - + /** * Create a UserData object from a database result set. + * * @param resultSet The result set containing the data + * * @throws SQLException - * @throws HealthMonitorException + * @throws HealthMonitorException */ UserData(ResultSet resultSet) throws SQLException, HealthMonitorException { this.timestamp = resultSet.getLong("timestamp"); @@ -1338,11 +1384,13 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.isExaminer = resultSet.getBoolean("is_examiner"); this.caseName = resultSet.getString("case_name"); } - + /** * This should only be used to make a dummy object to use for timestamp * comparisons. + * * @param timestamp + * * @return A UserData object with the given timestamp */ static UserData createDummyUserData(long timestamp) { @@ -1350,131 +1398,144 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { userData.timestamp = timestamp; return userData; } - + /** * Get the timestamp for the event + * * @return Timestamp in milliseconds */ long getTimestamp() { return timestamp; } - + /** * Get the host that created the metric + * * @return the host name */ String getHostname() { return hostname; } - + /** * Get the type of event + * * @return the event type */ UserEvent getEventType() { return eventType; } - + /** * Check whether this node is an examiner node or an auto ingest node + * * @return true if it is an examiner node */ boolean isExaminerNode() { return isExaminer; } - + /** * Get the name of the case for this metric + * * @return the case name. Will be the empty string if no case was open. */ String getCaseName() { return caseName; } } - + /** - * Internal class for collecting timing metrics. - * Instead of storing each TimingMetric, we only store the min and max - * seen and the number of metrics and total duration to compute the average - * later. - * One TimingInfo instance should be created per metric name, and - * additional timing metrics will be added to it. + * Internal class for collecting timing metrics. Instead of storing each + * TimingMetric, we only store the min and max seen and the number of + * metrics and total duration to compute the average later. One TimingInfo + * instance should be created per metric name, and additional timing metrics + * will be added to it. */ private class TimingInfo { + private long count; // Number of metrics collected private double sum; // Sum of the durations collected (nanoseconds) private double max; // Maximum value found (nanoseconds) private double min; // Minimum value found (nanoseconds) - + TimingInfo(TimingMetric metric) throws HealthMonitorException { count = 1; sum = metric.getDuration(); max = metric.getDuration(); min = metric.getDuration(); } - + /** - * Add a new TimingMetric to an existing TimingInfo object. - * This is called in a synchronized block for almost all new - * TimingMetric objects, so do as little processing here as possible. + * Add a new TimingMetric to an existing TimingInfo object. This is + * called in a synchronized block for almost all new TimingMetric + * objects, so do as little processing here as possible. + * * @param metric The new metric - * @throws HealthMonitorException Will be thrown if the metric hasn't been stopped + * + * @throws HealthMonitorException Will be thrown if the metric hasn't + * been stopped */ void addMetric(TimingMetric metric) throws HealthMonitorException { - + // Keep track of needed info to calculate the average count++; sum += metric.getDuration(); - + // Check if this is the longest duration seen - if(max < metric.getDuration()) { + if (max < metric.getDuration()) { max = metric.getDuration(); } - + // Check if this is the lowest duration seen - if(min > metric.getDuration()) { + if (min > metric.getDuration()) { min = metric.getDuration(); } } - + /** * Get the average duration + * * @return average duration (milliseconds) */ double getAverage() { return sum / count; } - + /** * Get the maximum duration + * * @return maximum duration (milliseconds) */ double getMax() { return max; } - + /** * Get the minimum duration + * * @return minimum duration (milliseconds) */ double getMin() { return min; } - + /** * Get the total number of metrics collected + * * @return number of metrics collected */ long getCount() { return count; } } - + /** - * Class for retrieving timing metrics from the database to display to the user. - * All times will be in milliseconds. + * Class for retrieving timing metrics from the database to display to the + * user. All times will be in milliseconds. */ static class DatabaseTimingResult { + private final long timestamp; // Time the metric was recorded private final String hostname; // Host that recorded the metric private final long count; // Number of metrics collected @@ -1490,49 +1551,55 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { this.max = resultSet.getDouble("max"); this.min = resultSet.getDouble("min"); } - + /** * Get the timestamp for when the metric was recorded - * @return + * + * @return */ long getTimestamp() { return timestamp; } - + /** * Get the average duration + * * @return average duration (milliseconds) */ double getAverage() { return average; } - + /** * Get the maximum duration + * * @return maximum duration (milliseconds) */ double getMax() { return max; } - + /** * Get the minimum duration + * * @return minimum duration (milliseconds) */ double getMin() { return min; } - + /** * Get the total number of metrics collected + * * @return number of metrics collected */ long getCount() { return count; - } - + } + /** * Get the name of the host that recorded this metric + * * @return the host */ String getHostName() { diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java index 07f8fcced7..265cab388f 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -61,8 +61,8 @@ public class HealthMonitorDashboard { private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; // NON-NLS private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(Places.getUserDirectory().getAbsolutePath(), ADMIN_ACCESS_FILE_NAME).toString(); - Map> timingData; - List userData; + Map> timingData; + List userData; private JComboBox timingDateComboBox = null; private JComboBox timingHostComboBox = null; @@ -90,7 +90,7 @@ public class HealthMonitorDashboard { * Display the dashboard. */ @NbBundle.Messages({"HealthMonitorDashboard.display.errorCreatingDashboard=Error creating health monitor dashboard", - "HealthMonitorDashboard.display.dashboardTitle=Enterprise Health Monitor"}) + "HealthMonitorDashboard.display.dashboardTitle=Health Monitor"}) public void display() { // Update the enabled status and get the timing data, then create all @@ -153,14 +153,14 @@ public class HealthMonitorDashboard { private void updateData() throws HealthMonitorException { // Update the monitor status - EnterpriseHealthMonitor.getInstance().updateFromGlobalEnabledStatus(); + HealthMonitor.getInstance().updateFromGlobalEnabledStatus(); - if(EnterpriseHealthMonitor.monitorIsEnabled()) { + if(HealthMonitor.monitorIsEnabled()) { // Get a copy of the timing data from the database - timingData = EnterpriseHealthMonitor.getInstance().getTimingMetricsFromDatabase(DateRange.getMaximumTimestampRange()); + timingData = HealthMonitor.getInstance().getTimingMetricsFromDatabase(DateRange.getMaximumTimestampRange()); // Get a copy of the user data from the database - userData = EnterpriseHealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.getMaximumTimestampRange()); + userData = HealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.getMaximumTimestampRange()); } } @@ -174,7 +174,7 @@ public class HealthMonitorDashboard { private JPanel createTimingPanel() throws HealthMonitorException { // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { //timingMetricPanel.setPreferredSize(new Dimension(400,100)); JPanel emptyTimingMetricPanel = new JPanel(); emptyTimingMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingPanel_timingMetricsTitle())); @@ -223,7 +223,7 @@ public class HealthMonitorDashboard { JPanel timingControlPanel = new JPanel(); // If the monitor is not enabled, don't add any components - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { return timingControlPanel; } @@ -247,7 +247,7 @@ public class HealthMonitorDashboard { // Create an array of host names Set hostNameSet = new HashSet<>(); for(String metricType:timingData.keySet()) { - for(EnterpriseHealthMonitor.DatabaseTimingResult result: timingData.get(metricType)) { + for(HealthMonitor.DatabaseTimingResult result: timingData.get(metricType)) { hostNameSet.add(result.getHostName()); } } @@ -364,7 +364,7 @@ public class HealthMonitorDashboard { for(String metricName:timingData.keySet()) { // If necessary, trim down the list of results to fit the selected time range - List intermediateTimingDataForDisplay; + List intermediateTimingDataForDisplay; if(timingDateComboBox.getSelectedItem() != null) { DateRange selectedDateRange = DateRange.fromLabel(timingDateComboBox.getSelectedItem().toString()); long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); @@ -403,7 +403,7 @@ public class HealthMonitorDashboard { "HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics"}) private JPanel createUserPanel() throws HealthMonitorException { // If the monitor isn't enabled, just add a message - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { JPanel emptyUserMetricPanel = new JPanel(); emptyUserMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle())); emptyUserMetricPanel.add(new JLabel(" ")); @@ -448,7 +448,7 @@ public class HealthMonitorDashboard { JPanel userControlPanel = new JPanel(); // If the monitor is not enabled, don't add any components - if(! EnterpriseHealthMonitor.monitorIsEnabled()) { + if(! HealthMonitor.monitorIsEnabled()) { return userControlPanel; } @@ -535,7 +535,7 @@ public class HealthMonitorDashboard { JButton enableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_enableButton()); JButton disableButton = new JButton(Bundle.HealthMonitorDashboard_createAdminPanel_disableButton()); - boolean isEnabled = EnterpriseHealthMonitor.monitorIsEnabled(); + boolean isEnabled = HealthMonitor.monitorIsEnabled(); enableButton.setEnabled(! isEnabled); disableButton.setEnabled(isEnabled); @@ -545,7 +545,7 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(true); + HealthMonitor.setEnabled(true); redisplay(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error enabling monitoring", ex); @@ -561,7 +561,7 @@ public class HealthMonitorDashboard { public void actionPerformed(ActionEvent arg0) { try { dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EnterpriseHealthMonitor.setEnabled(false); + HealthMonitor.setEnabled(false); redisplay(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error disabling monitoring", ex); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 187513a84a..4a709e79fb 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -45,7 +45,7 @@ public class Installer extends ModuleInstall { public void restored() { try { - EnterpriseHealthMonitor.startUpIfEnabled(); + HealthMonitor.startUpIfEnabled(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } @@ -54,7 +54,7 @@ public class Installer extends ModuleInstall { @Override public void close() { try { - EnterpriseHealthMonitor.shutdown(); + HealthMonitor.shutdown(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error stopping health services monitor", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index a540ea8c6b..c88ce1e627 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -39,7 +39,7 @@ import java.util.logging.Level; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.DatabaseTimingResult; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor.DatabaseTimingResult; /** * Creates a graph of the given timing metric data diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java index d8da7afe97..b5b76b6993 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java @@ -38,7 +38,7 @@ import javax.swing.JPanel; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor.UserData; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor.UserData; /** * Creates graphs using the given user metric data diff --git a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java index 71404c76e7..4200620749 100644 --- a/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java +++ b/Core/src/org/sleuthkit/autopsy/imagewriter/ImageWriter.java @@ -134,7 +134,7 @@ class ImageWriter implements PropertyChangeListener{ @Messages({ "# {0} - data source name", - "ImageWriter.progressBar.message=Finishing acquisition of {0}" + "ImageWriter.progressBar.message=Finishing acquisition of {0} (unplug device to cancel)" }) private void startFinishImage(String dataSourceName){ diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobConfigurator.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobConfigurator.java deleted file mode 100644 index a9b4145068..0000000000 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobConfigurator.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014 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.ingest; - -import java.util.List; -import javax.swing.JPanel; -import org.sleuthkit.datamodel.Content; - -/** - * Provides a mechanism for creating and persisting an ingest job configuration - * for a particular context and for launching ingest jobs that process one or - * more data sources using the ingest job configuration. - * - * @deprecated Use the IngestModuleSettings and IngestJobConfigurationPanel - * classes and IngestManager.beginIngestJob() instead. - */ -@Deprecated -public final class IngestJobConfigurator { - - private final IngestJobSettings settings; - private final IngestJobSettingsPanel settingsPanel; - - /** - * Constructs an ingest job launcher that creates and persists ingest job - * settings for a particular context and launches ingest jobs that process - * one or more data sources using the settings. - * - * @param context The context identifier. - */ - @Deprecated - public IngestJobConfigurator(String context) { - this.settings = new IngestJobSettings(context); - this.settingsPanel = new IngestJobSettingsPanel(settings); - } - - /** - * Gets any warnings generated when the persisted ingest job settings for - * the specified context are loaded or saved. - * - * @return A collection of warning messages, possibly empty. - */ - @Deprecated - public List getIngestJobConfigWarnings() { - return this.settings.getWarnings(); - } - - /** - * Gets the user interface panel the launcher uses to obtain the user's - * ingest job settings for the specified context. - * - * @return The panel. - */ - @Deprecated - public JPanel getIngestJobConfigPanel() { - return settingsPanel; - } - - /** - * Persists the ingest job settings for the specified context. - */ - @Deprecated - public void saveIngestJobConfig() { - this.settings.save(); - } - - /** - * Launches ingest job for one or more data sources using the ingest job - * settings for the specified context. - * - * @param dataSources The data sources to ingest. - */ - @Deprecated - public void startIngestJobs(List dataSources) { - IngestManager.getInstance().queueIngestJob(dataSources, this.settings); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java index eddebd59c4..c86f90ad4b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog that displays ingest task progress snapshots. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestProgressSnapshotDialog extends JDialog { private static final String TITLE = NbBundle.getMessage(IngestProgressSnapshotDialog.class, "IngestProgressSnapshotDialog.title.text"); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties index dfda2e6061..89cf1636b7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties @@ -13,7 +13,7 @@ EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=Content-o EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull=Full Encryption (Archive File) EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir\: {0}\: {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive\: {0}, item\: {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping item in {1}. +EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping items in {1}. EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected\: {0} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive\: {0}, will use\: {1} diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 8c443b28ee..66c7f7030d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.io.File; import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.AbstractFile; @@ -30,6 +31,8 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Archive; /** * A file level ingest module that extracts embedded files from supported @@ -44,10 +47,13 @@ import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; }) public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAdapter { - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // "iso"}; NON-NLS + //Outer concurrent hashmap with keys of JobID, inner concurrentHashmap with keys of objectID + private static final ConcurrentHashMap> mapOfDepthTrees = new ConcurrentHashMap<>(); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private MSOfficeEmbeddedContentExtractor officeExtractor; private SevenZipExtractor archiveExtractor; private FileTypeDetector fileTypeDetector; + private long jobId; /** * Constructs a file level ingest module that extracts embedded files from @@ -64,9 +70,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * case database for extracted (derived) file paths. The absolute path * is used to write the extracted (derived) files to local storage. */ + jobId = context.getJobId(); String moduleDirRelative = null; String moduleDirAbsolute = null; - + try { final Case currentCase = Case.getCurrentCaseThrows(); moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); @@ -94,16 +101,18 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } - - /* - * Construct a 7Zip file extractor for processing archive files. - */ try { this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); } catch (SevenZipNativeInitializationException ex) { throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); } - + if (refCounter.incrementAndGet(jobId) == 1) { + /* + * Construct a concurrentHashmap to keep track of depth in archives + * while processing archive files. + */ + mapOfDepthTrees.put(jobId, new ConcurrentHashMap<>()); + } /* * Construct an embedded content extractor for processing Microsoft * Office documents. @@ -113,6 +122,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex); } + } @Override @@ -144,13 +154,20 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * type/format. */ if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) { - archiveExtractor.unpack(abstractFile); + archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId)); } else if (officeExtractor.isContentExtractionSupported(abstractFile)) { officeExtractor.extractEmbeddedContent(abstractFile); } return ProcessResult.OK; } + @Override + public void shutDown() { + if (refCounter.decrementAndGet(jobId) == 0) { + mapOfDepthTrees.remove(jobId); + } + } + /** * Creates a unique name for a file by concatentating the file name and the * file object id. diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java index 722f211951..b122dbd4fb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.awt.event.ActionEvent; import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -125,7 +126,7 @@ public class ExtractArchiveWithPasswordAction extends AbstractAction { } try { SevenZipExtractor extractor = new SevenZipExtractor(null, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); - done = extractor.unpack(archive, password); + done = extractor.unpack(archive, new ConcurrentHashMap<>(), password); } catch (SevenZipNativeInitializationException ex) { IngestServices.getInstance().postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Unable to extract file with password", password)); logger.log(Level.INFO, "Unable to extract file with password", ex); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 9e78e0d58e..ee9415ddca 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import net.sf.sevenzipjbinding.ArchiveFormat; import static net.sf.sevenzipjbinding.ArchiveFormat.RAR; @@ -73,7 +74,6 @@ class SevenZipExtractor { private IngestServices services = IngestServices.getInstance(); private final IngestJobContext context; private final FileTypeDetector fileTypeDetector; - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS //encryption type strings private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel"); @@ -84,8 +84,6 @@ class SevenZipExtractor { private static final int MAX_COMPRESSION_RATIO = 600; private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L; private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB - //counts archive depth - private ArchiveDepthCountTree archiveDepthCountTree; private String moduleDirRelative; private String moduleDirAbsolute; @@ -131,7 +129,6 @@ class SevenZipExtractor { this.fileTypeDetector = fileTypeDetector; this.moduleDirRelative = moduleDirRelative; this.moduleDirAbsolute = moduleDirAbsolute; - this.archiveDepthCountTree = new ArchiveDepthCountTree(); } /** @@ -150,8 +147,8 @@ class SevenZipExtractor { } } return false; - } - + } + /** * Check if the item inside archive is a potential zipbomb * @@ -159,12 +156,18 @@ class SevenZipExtractor { * * More heuristics to be added here * - * @param archiveName the parent archive - * @param archiveFileItem the archive item + * @param archiveFile the AbstractFile for the parent archive which + * which we are checking + * @param archiveFileItem the current item being extracted from the parent + * archive + * @param depthMap a concurrent hashmap which keeps track of the + * depth of all nested archives, key of objectID + * @param escapedFilePath the path to the archiveFileItem which has been + * escaped * * @return true if potential zip bomb, false otherwise */ - private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) { + private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem, ConcurrentHashMap depthMap, String escapedFilePath) { try { final Long archiveItemSize = archiveFileItem.getSize(); @@ -183,20 +186,12 @@ class SevenZipExtractor { int cRatio = (int) (archiveItemSize / archiveItemPackedSize); if (cRatio >= MAX_COMPRESSION_RATIO) { - String itemName = archiveFileItem.getPath(); - logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName); - String path; - try { - path = archiveFile.getUniquePath(); - } catch (TskCoreException ex) { - path = archiveFile.getParentPath() + archiveFile.getName(); - } + Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId()); String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", + cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath); return true; } else { return false; @@ -208,6 +203,47 @@ class SevenZipExtractor { } } + /** + * Flag the root archive archive as a zipbomb by creating an interesting + * file artifact and posting a message to the inbox for the user. + * + * @param rootArchive - the Archive which the artifact is to be for + * @param archiveFile - the AbstractFile which for the file which + * triggered the potential zip bomb to be detected + * @param details - the String which contains the details about how + * the potential zip bomb was detected + * @param escapedFilePath - the escaped file path for the archiveFile + */ + private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) { + rootArchive.flagAsZipBomb(); + logger.log(Level.INFO, details); //NON-NLS + String msg = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath); + try { + BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), + "Possible Zip Bomb")); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, + EmbeddedFileExtractorModuleFactory.getModuleName(), + Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName()))); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + EmbeddedFileExtractorModuleFactory.getModuleName(), + details)); + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error( + Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName()); + } + services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS + } + services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + } + /** * Check file extension and return appropriate input options for * SevenZip.openInArchive() @@ -459,11 +495,13 @@ class SevenZipExtractor { * Unpack the file to local folder and return a list of derived files * * @param archiveFile file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives, key of objectID * - * @return list of unpacked derived files + * @return true if unpacking is complete */ - void unpack(AbstractFile archiveFile) { - unpack(archiveFile, null); + void unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap) { + unpack(archiveFile, depthMap, null); } /** @@ -471,12 +509,16 @@ class SevenZipExtractor { * the password if specified. * * @param archiveFile - file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives, key of objectID * @param password - the password to use, null for no password * - * @return list of unpacked derived files + * @return true if unpacking is complete */ - @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) - boolean unpack(AbstractFile archiveFile, String password) { + @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.", + "# {0} - rootArchive", + "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"}) + boolean unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap, String password) { boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and boolean hasEncrypted = false; boolean fullEncryption = true; @@ -491,8 +533,7 @@ class SevenZipExtractor { SevenZipContentReadStream stream = null; final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName()); //recursion depth check for zip bomb - final long archiveId = archiveFile.getId(); - SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr; + Archive parentAr; try { blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { @@ -515,20 +556,24 @@ class SevenZipExtractor { unpackSuccessful = false; return unpackSuccessful; } - parentAr = archiveDepthCountTree.findArchive(archiveId); + parentAr = depthMap.get(archiveFile.getId()); if (parentAr == null) { - parentAr = archiveDepthCountTree.addArchive(null, archiveId); - - } else if (parentAr.getDepth() == MAX_DEPTH) { - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); - String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", - parentAr.getDepth(), escapedArchiveFilePath); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); - unpackSuccessful = false; - return unpackSuccessful; + parentAr = new Archive(0, archiveFile.getId(), archiveFile); + depthMap.put(archiveFile.getId(), parentAr); + } else { + Archive rootArchive = depthMap.get(parentAr.getRootArchiveId()); + if (rootArchive.isFlaggedAsZipBomb()) { + //skip this archive as the root archive has already been determined to contain a zip bomb + unpackSuccessful = false; + return unpackSuccessful; + } else if (parentAr.getDepth() == MAX_DEPTH) { + String details = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", + parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath); + unpackSuccessful = false; + return unpackSuccessful; + } } try { stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile)); @@ -579,8 +624,9 @@ class SevenZipExtractor { ++itemNumber; //check if possible zip bomb - if (isZipBombArchiveItemCheck(archiveFile, item)) { - continue; //skip the item + if (isZipBombArchiveItemCheck(archiveFile, item, depthMap, escapedArchiveFilePath)) { + unpackSuccessful = false; + return unpackSuccessful; } SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive); //update progress bar @@ -608,7 +654,6 @@ class SevenZipExtractor { escapedArchiveFilePath, item.getPath()); String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details"); - //MessageNotifyUtil.Notify.error(msg, details); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, item.getPath()}); //NON-NLS logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS @@ -666,7 +711,9 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId()); + Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); + parentAr.addChild(child); + depthMap.put(unpackedFile.getId(), child); } } @@ -908,7 +955,7 @@ class SevenZipExtractor { this.rootNode = new UnpackedNode(); this.rootNode.setFile(archiveFile); this.rootNode.setFileName(archiveFile.getName()); - this.rootNode.localRelPath = localPathRoot; + this.rootNode.setLocalRelPath(localPathRoot); } /** @@ -951,6 +998,7 @@ class SevenZipExtractor { // create new node if (child == null) { child = new UnpackedNode(childName, parent); + parent.addChild(child); } // go down one more level @@ -965,7 +1013,7 @@ class SevenZipExtractor { */ List getRootFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { ret.add(child.getFile()); } return ret; @@ -979,7 +1027,7 @@ class SevenZipExtractor { */ List getAllFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { getAllFileObjectsRec(ret, child); } return ret; @@ -987,7 +1035,7 @@ class SevenZipExtractor { private void getAllFileObjectsRec(List list, UnpackedNode parent) { list.add(parent.getFile()); - for (UnpackedNode child : parent.children) { + for (UnpackedNode child : parent.getChildren()) { getAllFileObjectsRec(list, child); } } @@ -998,7 +1046,7 @@ class SevenZipExtractor { */ void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException { final FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath); } } @@ -1055,7 +1103,7 @@ class SevenZipExtractor { node.getFileName()), ex); } //recurse adding the children if this file was incomplete the children presumably need to be added - for (UnpackedNode child : node.children) { + for (UnpackedNode child : node.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath)); } } @@ -1067,7 +1115,7 @@ class SevenZipExtractor { private String fileName; private AbstractFile file; - private List children = new ArrayList<>(); + private final List children = new ArrayList<>(); private String localRelPath = ""; private long size; private long ctime, crtime, atime, mtime; @@ -1082,31 +1130,53 @@ class SevenZipExtractor { UnpackedNode(String fileName, UnpackedNode parent) { this.fileName = fileName; this.parent = parent; - this.localRelPath = parent.localRelPath + File.separator + fileName; - //new child derived file will be set by unpack() method - parent.children.add(this); + this.localRelPath = parent.getLocalRelPath() + File.separator + fileName; } - public long getCtime() { + long getCtime() { return ctime; } - public long getCrtime() { + long getCrtime() { return crtime; } - public long getAtime() { + long getAtime() { return atime; } - public long getMtime() { + long getMtime() { return mtime; } - public void setFileName(String fileName) { + void setFileName(String fileName) { this.fileName = fileName; } + /** + * Add a child to the list of child nodes associated with this node. + * + * @param child - the node which is a child node of this node + */ + void addChild(UnpackedNode child) { + children.add(child); + } + + /** + * Get this nodes list of child UnpackedNode + * + * @return children - the UnpackedNodes which are children of this + * node. + */ + List getChildren() { + return children; + } + + /** + * Gets the parent node of this node. + * + * @return - the parent UnpackedNode + */ UnpackedNode getParent() { return parent; } @@ -1137,7 +1207,7 @@ class SevenZipExtractor { UnpackedNode getChild(String childFileName) { UnpackedNode ret = null; for (UnpackedNode child : children) { - if (child.fileName.equals(childFileName)) { + if (child.getFileName().equals(childFileName)) { ret = child; break; } @@ -1145,94 +1215,132 @@ class SevenZipExtractor { return ret; } - public String getFileName() { + String getFileName() { return fileName; } - public AbstractFile getFile() { + AbstractFile getFile() { return file; } - public String getLocalRelPath() { + String getLocalRelPath() { return localRelPath; } - public long getSize() { + /** + * Set the local relative path associated with this UnpackedNode + * + * @param localRelativePath - the local relative path to be + * associated with this node. + */ + void setLocalRelPath(String localRelativePath) { + localRelPath = localRelativePath; + } + + long getSize() { return size; } - public boolean isIsFile() { + boolean isIsFile() { return isFile; } } } /** - * Tracks archive hierarchy and archive depth + * Class to keep track of an objects id and its depth in the archive + * structure. */ - private static class ArchiveDepthCountTree { + static class Archive { - //keeps all nodes refs for easy search - private final List archives = new ArrayList<>(); + //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within + private final int depth; + private final List children; + private final long rootArchiveId; + private boolean flaggedAsZipBomb = false; + private final AbstractFile archiveFile; /** - * Search for previously added parent archive by id + * Create a new Archive object. * - * @param objectId parent archive object id - * - * @return the archive node or null if not found + * @param depth the depth in the archive structure - 0 will be + * the root archive unpack was called on, and it + * will increase as unpack recurses down through + * archives contained within + * @param rootArchiveId the unique object id of the root parent archive + * of this archive + * @param archiveFile the AbstractFile which this Archive object + * represents */ - Archive findArchive(long objectId) { - for (Archive ar : archives) { - if (ar.objectId == objectId) { - return ar; - } - } - - return null; + Archive(int depth, long rootArchiveId, AbstractFile archiveFile) { + this.children = new ArrayList<>(); + this.depth = depth; + this.rootArchiveId = rootArchiveId; + this.archiveFile = archiveFile; } /** - * Add a new archive to track of depth + * Add a child to the list of child archives associated with this + * archive. * - * @param parent parent archive or null - * @param objectId object id of the new archive - * - * @return the archive added + * @param child - the archive which is a child archive of this archive */ - Archive addArchive(Archive parent, long objectId) { - Archive child = new Archive(parent, objectId); - archives.add(child); - return child; + void addChild(Archive child) { + children.add(child); } - private static class Archive { + /** + * Set the flag which identifies whether this file has been determined to be a zip bomb to true. + */ + synchronized void flagAsZipBomb() { + flaggedAsZipBomb = true; + } - int depth; - long objectId; - Archive parent; - List children; + /** + * Gets whether or not this archive has been flagged as a zip bomb. + * + * @return True when flagged as a zip bomb, false if it is not flagged + */ + synchronized boolean isFlaggedAsZipBomb() { + return flaggedAsZipBomb; + } - Archive(Archive parent, long objectId) { - this.parent = parent; - this.objectId = objectId; - children = new ArrayList<>(); - if (parent != null) { - parent.children.add(this); - this.depth = parent.depth + 1; - } else { - this.depth = 0; - } - } + /** + * Get the AbstractFile which this Archive object represents. + * + * @return archiveFile - the AbstractFile which this Archive represents. + */ + AbstractFile getArchiveFile() { + return archiveFile; + } - /** - * get archive depth of this archive - * - * @return - */ - int getDepth() { - return depth; - } + /** + * Get the object id of the root archive which contained this archive. + * + * @return rootArchiveId - the objectID of the root archive + */ + long getRootArchiveId() { + return rootArchiveId; + } + + /** + * Get the object id of this archive. + * + * @return the unique objectId of this archive from its AbstractFile + */ + long getObjectId() { + return archiveFile.getId(); + } + + /** + * Get archive depth of this archive + * + * @return depth - an integer representing that represents how many + * times the upack method has been recursed from the root + * archive unpack was called on + */ + int getDepth() { + return depth; } } @@ -1260,7 +1368,7 @@ class SevenZipExtractor { /** * Get the AbstractFile contained in this object * - * @return abstractFile - The abstractFile this object wraps + * @return archiveFile - The archiveFile this object wraps */ private AbstractFile getFile() { return abstractFile; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java index 372c62769a..e5373bc555 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.awt.BorderLayout; import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; @@ -40,6 +39,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; * A dialog box that allows a user to create a file type signature, to be added * to a selected file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddFileTypeSignatureDialog extends JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java index c22ecd94a7..bd047b86e0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * Panel for creating a file type signature to be added to a file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddFileTypeSignaturePanel extends javax.swing.JPanel { private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem"); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 795afd0838..8c225381df 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -95,7 +95,7 @@ public class FileTypeDetector { private static SortedSet getTikaDetectedTypes() { if (null == tikaDetectedTypes) { tikaDetectedTypes = org.apache.tika.mime.MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes() - .stream().filter(t -> !t.hasParameters()).map(s -> s.toString()).collect(Collectors.toCollection(TreeSet::new)); + .stream().filter(t -> !t.hasParameters()).map(s -> s.toString().replace("tika-", "")).collect(Collectors.toCollection(TreeSet::new)); } return Collections.unmodifiableSortedSet(tikaDetectedTypes); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.form index da0b07d99d..bf4e6ef5a8 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.form @@ -26,20 +26,21 @@ - + - + + - - - - - - - + + + + + + + @@ -47,21 +48,18 @@ - - + + - - - - - - - - - - + + + + + + + - + @@ -102,14 +100,14 @@
- + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java index 68535928c7..bcae027d61 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java @@ -73,7 +73,7 @@ public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { } else { setDefaultCloseOperation(0); } - AddValuesToHashDatabaseButton.setEnabled(enable); + okButton.setEnabled(enable); cancelButton.setEnabled(enable); pasteFromClipboardButton.setEnabled(enable); } @@ -91,7 +91,7 @@ public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { jScrollPane1 = new javax.swing.JScrollPane(); hashValuesTextArea = new javax.swing.JTextArea(); pasteFromClipboardButton = new javax.swing.JButton(); - AddValuesToHashDatabaseButton = new javax.swing.JButton(); + okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -115,10 +115,10 @@ public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { } }); - org.openide.awt.Mnemonics.setLocalizedText(AddValuesToHashDatabaseButton, org.openide.util.NbBundle.getMessage(AddHashValuesToDatabaseDialog.class, "AddHashValuesToDatabaseDialog.AddValuesToHashDatabaseButton.text_2")); // NOI18N - AddValuesToHashDatabaseButton.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(AddHashValuesToDatabaseDialog.class, "AddHashValuesToDatabaseDialog.okButton.text_2")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - AddValuesToHashDatabaseButtonActionPerformed(evt); + okButtonActionPerformed(evt); } }); @@ -136,31 +136,30 @@ public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1) .addGroup(layout.createSequentialGroup() .addComponent(instructionLabel) .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(jScrollPane1)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(AddValuesToHashDatabaseButton, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(cancelButton, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pasteFromClipboardButton, javax.swing.GroupLayout.Alignment.TRAILING)) + .addGroup(layout.createSequentialGroup() + .addComponent(pasteFromClipboardButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 121, Short.MAX_VALUE) + .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, 84, javax.swing.GroupLayout.PREFERRED_SIZE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addComponent(instructionLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(AddValuesToHashDatabaseButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(cancelButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pasteFromClipboardButton)) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 274, Short.MAX_VALUE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 279, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pasteFromClipboardButton) + .addComponent(okButton) + .addComponent(cancelButton)) .addContainerGap()) ); @@ -213,17 +212,17 @@ public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { } }//GEN-LAST:event_hashValuesTextAreaMouseClicked - private void AddValuesToHashDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AddValuesToHashDatabaseButtonActionPerformed + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed AddHashValuesToDatabaseProgressDialog progressDialog = new AddHashValuesToDatabaseProgressDialog(this, hashDb, hashValuesTextArea.getText()); progressDialog.addHashValuesToDatabase(); - }//GEN-LAST:event_AddValuesToHashDatabaseButtonActionPerformed + }//GEN-LAST:event_okButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton AddValuesToHashDatabaseButton; private javax.swing.JButton cancelButton; private javax.swing.JTextArea hashValuesTextArea; private javax.swing.JLabel instructionLabel; private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JButton okButton; private javax.swing.JButton pasteFromClipboardButton; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties index c5f6d82425..5b8d772f9d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties @@ -87,8 +87,8 @@ HashDbImportDatabaseDialog.importHashDbErr=Import Hash Set Error HashDbImportDatabaseDialog.mustSelectHashDbFilePathMsg=A hash set file path must be selected. HashDbImportDatabaseDialog.hashDbDoesNotExistMsg=The selected hash set does not exist. HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg=Failed to open hash set at {0}. -HashDbIngestModule.moduleName=Hash Lookup -HashDbIngestModule.moduleDescription=Identifies known and notable files using supplied hash sets, such as a standard NSRL hash set. +HashLookupModuleFactory.moduleName.text=Hash Lookup +HashLookupModuleFactory.moduleDescription.text=Identifies known and notable files using supplied hash sets, such as a standard NSRL hash set. HashDbIngestModule.fileReadErrorMsg=Read Error\: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0}. HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error\: {0} @@ -172,7 +172,6 @@ HashDbSearchPanel.hashTable.defaultModel.title.text=MD5 Hashes AddHashValuesToDatabaseDialog.JDialog.Title=Add Hashes to Hash Set AddHashValuesToDatabaseDialog.instructionLabel.text_1=Paste MD5 hash values (one per line) below: AddHashValuesToDatabaseDialog.cancelButton.text_2=Cancel -AddHashValuesToDatabaseDialog.AddValuesToHashDatabaseButton.text_2=Add Hashes to Hash Set AddHashValuesToDatabaseDialog.pasteFromClipboardButton.text_2=Paste From Clipboard AddHashValuesToDatabaseProgressDialog.okButton.text=OK AddHashValuesToDatabaseProgressDialog.statusLabel.text=status @@ -237,3 +236,4 @@ HashDbCreateDatabaseDialog.centralRepoRadioButton.text=Remote (Central Repositor HashDbCreateDatabaseDialog.lbOrg.text=Source Organization: HashDbCreateDatabaseDialog.orgButton.text=Manage Organizations HashDbCreateDatabaseDialog.databasePathLabel.text=Hash Set Path: +AddHashValuesToDatabaseDialog.okButton.text_2=OK diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties index 828b027bdb..feb99fc532 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties @@ -76,8 +76,8 @@ HashDbImportDatabaseDialog.importHashDbErr=\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\ HashDbImportDatabaseDialog.mustSelectHashDbFilePathMsg=\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u30d5\u30a1\u30a4\u30eb\u30d1\u30b9\u306e\u9078\u629e\u304c\u5fc5\u8981\u3067\u3059\u3002 HashDbImportDatabaseDialog.hashDbDoesNotExistMsg=\u9078\u629e\u3055\u308c\u305f\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306f\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg={0}\u3067\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u958b\u304f\u306e\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 -HashDbIngestModule.moduleName=\u30cf\u30c3\u30b7\u30e5\u30eb\u30c3\u30af\u30a2\u30c3\u30d7 -HashDbIngestModule.moduleDescription=\u6a19\u6e96\u306eNSRL\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306a\u3069\u3001\u63d0\u4f9b\u3055\u308c\u305f\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u5229\u7528\u3057\u3066\u3001\u65e2\u77e5\u307e\u305f\u306f\u7591\u308f\u3057\u3044\u3082\u306e\u3092\u691c\u77e5\u3057\u307e\u3059\u3002 +HashLookupModuleFactory.moduleName.text=\u30cf\u30c3\u30b7\u30e5\u30eb\u30c3\u30af\u30a2\u30c3\u30d7 +HashLookupModuleFactory.moduleDescription.text=\u6a19\u6e96\u306eNSRL\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306a\u3069\u3001\u63d0\u4f9b\u3055\u308c\u305f\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u5229\u7528\u3057\u3066\u3001\u65e2\u77e5\u307e\u305f\u306f\u7591\u308f\u3057\u3044\u3082\u306e\u3092\u691c\u77e5\u3057\u307e\u3059\u3002 HashDbIngestModule.noKnownHashDbSetMsg=\u65e2\u77e5\u306e\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u5b58\u5728\u3057\u307e\u305b\u3093\u3002 HashDbIngestModule.knownFileSearchWillNotExecuteWarn=\u65e2\u77e5\u306e\u30d5\u30a1\u30a4\u30eb\u691c\u7d22\u304c\u5b9f\u884c\u3055\u308c\u307e\u305b\u3093\u3002 HashDbIngestModule.noKnownBadHashDbSetMsg=\u65e2\u77e5\u306e\u60aa\u8cea\u306a\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30bb\u30c3\u30c8\u306f\u3042\u308a\u307e\u305b\u3093\u3002 @@ -86,7 +86,6 @@ HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=\u65e2\u77e5\u306e\u60aa HashDbIngestModule.fileReadErrorMsg=\u8aad\u307f\u8fbc\u307f\u30a8\u30e9\u30fc\uff1a {0} HashDbIngestModule.calcHashValueErr={0}\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u8a08\u7b97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 HashDbIngestModule.hashLookupErrorMsg=\u30cf\u30c3\u30b7\u30e5\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u30a8\u30e9\u30fc\uff1a {0} -HashDbIngestModule.settingKnownBadStateErr={0}\u306e\u65e2\u77e5\u306e\u60aa\u8cea\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u8a2d\u5b9a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 HashDbIngestModule.lookingUpKnownBadHashValueErr={0}\u306e\u65e2\u77e5\u306e\u60aa\u8cea\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 HashDbIngestModule.lookingUpKnownHashValueErr={0}\u306e\u65e2\u77e5\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 HashDbIngestModule.postToBB.fileName=\u30d5\u30a1\u30a4\u30eb\u540d @@ -172,7 +171,6 @@ AddHashValuesToDatabaseDialog.JDialog.Title=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9 HashLookupSettingsPanel.addHashesToDatabaseButton.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u30cf\u30c3\u30b7\u30e5\u3092\u8ffd\u52a0 AddHashValuesToDatabaseDialog.instructionLabel.text_1=\u4e0b\u8a18\u306bMD5\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u8cbc\u308a\u4ed8\u3051\u308b\uff08\u30e9\u30a4\u30f3\u3054\u3068\u306b\u4e00\u3064\u305a\u3064\uff09\uff1a AddHashValuesToDatabaseDialog.cancelButton.text_2=\u30ad\u30e3\u30f3\u30bb\u30eb -AddHashValuesToDatabaseDialog.AddValuesToHashDatabaseButton.text_2=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u30cf\u30c3\u30b7\u30e5\u3092\u8ffd\u52a0 AddHashValuesToDatabaseDialog.pasteFromClipboardButton.text_2=\u30af\u30ea\u30c3\u30d7\u30dc\u30fc\u30c9\u304b\u3089\u8cbc\u308a\u4ed8\u3051\u308b AddHashValuesToDatabaseProgressDialog.okButton.text=OK AddHashValuesToDatabaseProgressDialog.statusLabel.text=\u30b9\u30c6\u30fc\u30bf\u30b9 @@ -207,3 +205,4 @@ HashLookupSettingsPanel.nameLabel.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c HashLookupSettingsPanel.hashDatabasesLabel.text=\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\uff1a HashLookupSettingsPanel.importDatabaseButton.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u30a4\u30f3\u30dd\u30fc\u30c8 HashDbCreateDatabaseDialog.databasePathLabel.text=\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d1\u30b9\uff1a +AddHashValuesToDatabaseDialog.okButton.text_2=OK diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index d907a131e9..ee3ba1c089 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 - 2013 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestMessage; @@ -53,7 +53,10 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; -@NbBundle.Messages({ +/** + * File ingest module to mark files based on hash values. + */ +@Messages({ "HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.", "HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.", "HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.", @@ -67,18 +70,21 @@ public class HashDbIngestModule implements FileIngestModule { private final SleuthkitCase skCase; private final HashDbManager hashDbManager = HashDbManager.getInstance(); private final HashLookupModuleSettings settings; - private List knownBadHashSets = new ArrayList<>(); - private List knownHashSets = new ArrayList<>(); + private final List knownBadHashSets = new ArrayList<>(); + private final List knownHashSets = new ArrayList<>(); private long jobId; private static final HashMap totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private Blackboard blackboard; + /** + * A container of values for storing ingest metrics for the job. + */ private static class IngestJobTotals { - private AtomicLong totalKnownBadCount = new AtomicLong(0); - private AtomicLong totalCalctime = new AtomicLong(0); - private AtomicLong totalLookuptime = new AtomicLong(0); + private final AtomicLong totalKnownBadCount = new AtomicLong(0); + private final AtomicLong totalCalctime = new AtomicLong(0); + private final AtomicLong totalLookuptime = new AtomicLong(0); } private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) { @@ -90,6 +96,15 @@ public class HashDbIngestModule implements FileIngestModule { return totals; } + /** + * Create a HashDbIngestModule object that will mark files based on a + * supplied list of hash values. The supplied HashLookupModuleSettings + * object is used to configure the module. + * + * @param settings The module settings. + * + * @throws NoCurrentCaseException If there is no open case. + */ HashDbIngestModule(HashLookupModuleSettings settings) throws NoCurrentCaseException { this.settings = settings; skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); @@ -140,12 +155,18 @@ public class HashDbIngestModule implements FileIngestModule { enabledHashSets.add(db); } } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName()+ " hash set", ex); //NON-NLS + logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName() + " hash set", ex); //NON-NLS } } } } + @Messages({ + "# {0} - File name", + "HashDbIngestModule.dialogTitle.errorFindingArtifacts=Error Finding Artifacts: {0}", + "# {0} - File name", + "HashDbIngestModule.errorMessage.lookingForFileArtifacts=Error encountered while looking for existing artifacts for {0}." + }) @Override public ProcessResult process(AbstractFile file) { try { @@ -156,8 +177,8 @@ public class HashDbIngestModule implements FileIngestModule { } // Skip unallocated space files. - if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || - file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -181,10 +202,11 @@ public class HashDbIngestModule implements FileIngestModule { // calc hash value String name = file.getName(); + long fileId = file.getId(); String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); + TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); if (file.getSize() > 0) { @@ -192,10 +214,10 @@ public class HashDbIngestModule implements FileIngestModule { // strongly with file size until the files get large. // Only normalize if the file size is greater than ~1MB. if (file.getSize() < 1000000) { - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } else { // In testing, this normalization gave reasonable resuls - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); } } file.setMd5Hash(md5Hash); @@ -203,15 +225,11 @@ public class HashDbIngestModule implements FileIngestModule { totals.totalCalctime.addAndGet(delta); } catch (IOException ex) { - logger.log(Level.WARNING, "Error calculating hash of file " + name, ex); //NON-NLS + logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", name, fileId), ex); //NON-NLS services.postMessage(IngestMessage.createErrorMessage( HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.fileReadErrorMsg", - name), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.calcHashValueErr", - name))); + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", name), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr", name))); return ProcessResult.ERROR; } } @@ -245,21 +263,37 @@ public class HashDbIngestModule implements FileIngestModule { } } - postHashSetHitToBlackboard(file, md5Hash, hashSetName, comment, db.getSendIngestMessages()); + /* + * We have a match. Now create an artifact if it is + * determined that one hasn't been created yet. + */ + List attributesList = new ArrayList<>(); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, HashLookupModuleFactory.getModuleName(), hashSetName)); + try { + org.sleuthkit.datamodel.Blackboard tskBlackboard = skCase.getBlackboard(); + if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) == false) { + postHashSetHitToBlackboard(file, md5Hash, hashSetName, comment, db.getSendIngestMessages()); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format( + "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", name, fileId), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(name), + Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(name))); + ret = ProcessResult.ERROR; + } } long delta = (System.currentTimeMillis() - lookupstart); totals.totalLookuptime.addAndGet(delta); } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't lookup notable hash for file " + name + " - see sleuthkit log for details", ex); //NON-NLS + logger.log(Level.WARNING, String.format( + "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS services.postMessage(IngestMessage.createErrorMessage( HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.hashLookupErrorMsg", - name), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.lookingUpKnownBadHashValueErr", - name))); + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownBadHashValueErr", name))); ret = ProcessResult.ERROR; } } @@ -279,15 +313,12 @@ public class HashDbIngestModule implements FileIngestModule { totals.totalLookuptime.addAndGet(delta); } catch (TskException ex) { - logger.log(Level.WARNING, "Couldn't lookup known hash for file " + name + " - see sleuthkit log for details", ex); //NON-NLS + logger.log(Level.WARNING, String.format( + "Couldn't lookup known hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS services.postMessage(IngestMessage.createErrorMessage( HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.hashLookupErrorMsg", - name), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.lookingUpKnownHashValueErr", - name))); + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownHashValueErr", name))); ret = ProcessResult.ERROR; } } @@ -296,18 +327,29 @@ public class HashDbIngestModule implements FileIngestModule { return ret; } - @Messages({"HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search."}) + /** + * Post a hash set hit to the blackboard. + * + * @param abstractFile The file to be processed. + * @param md5Hash The MD5 hash value of the file. + * @param hashSetName The name of the hash set with which to associate + * the hit. + * @param comment A comment to be attached to the artifact. + * @param showInboxMessage Show a message in the inbox? + */ + @Messages({ + "HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search." + }) private void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, String comment, boolean showInboxMessage) { try { - String MODULE_NAME = NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.moduleName"); - + String moduleName = HashLookupModuleFactory.getModuleName(); BlackboardArtifact badFile = abstractFile.newArtifact(ARTIFACT_TYPE.TSK_HASHSET_HIT); Collection attributes = new ArrayList<>(); //TODO Revisit usage of deprecated constructor as per TSK-583 //BlackboardAttribute att2 = new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), MODULE_NAME, "Known Bad", hashSetName); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, hashSetName)); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HASH_MD5, MODULE_NAME, md5Hash)); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, comment)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, moduleName, hashSetName)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_HASH_MD5, moduleName, md5Hash)); + attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, moduleName, comment)); badFile.addAttributes(attributes); @@ -351,19 +393,24 @@ public class HashDbIngestModule implements FileIngestModule { detailsSb.append(""); //NON-NLS services.postMessage(IngestMessage.createDataMessage(HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "HashDbIngestModule.postToBB.knownBadMsg", - abstractFile.getName()), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.postToBB.knownBadMsg", abstractFile.getName()), detailsSb.toString(), abstractFile.getName() + md5Hash, badFile)); } - services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, ARTIFACT_TYPE.TSK_HASHSET_HIT, Collections.singletonList(badFile))); + services.fireModuleDataEvent(new ModuleDataEvent(moduleName, ARTIFACT_TYPE.TSK_HASHSET_HIT, Collections.singletonList(badFile))); } catch (TskException ex) { logger.log(Level.WARNING, "Error creating blackboard artifact", ex); //NON-NLS } } + /** + * Post a message summarizing the results of the ingest. + * + * @param jobId The ID of the job. + * @param knownBadHashSets The list of hash sets for "known bad" files. + * @param knownHashSets The list of hash sets for "known" files. + */ private static synchronized void postSummary(long jobId, List knownBadHashSets, List knownHashSets) { IngestJobTotals jobTotals = getTotalsForIngestJobs(jobId); @@ -399,8 +446,7 @@ public class HashDbIngestModule implements FileIngestModule { IngestServices.getInstance().postMessage(IngestMessage.createMessage( IngestMessage.MessageType.INFO, HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(HashDbIngestModule.class, - "HashDbIngestModule.complete.hashLookupResults"), + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.hashLookupResults"), detailsSb.toString())); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java index 068c52cf5e..af27157fd8 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java @@ -42,13 +42,18 @@ public class HashLookupModuleFactory extends IngestModuleFactoryAdapter { return getModuleName(); } + /** + * Get the name of the module. + * + * @return The module name. + */ static String getModuleName() { - return NbBundle.getMessage(HashLookupModuleFactory.class, "HashDbIngestModule.moduleName"); + return NbBundle.getMessage(HashLookupModuleFactory.class, "HashLookupModuleFactory.moduleName.text"); } @Override public String getModuleDescription() { - return NbBundle.getMessage(HashLookupModuleFactory.class, "HashDbIngestModule.moduleDescription"); + return NbBundle.getMessage(HashLookupModuleFactory.class, "HashLookupModuleFactory.moduleDescription.text"); } @Override @@ -101,8 +106,8 @@ public class HashLookupModuleFactory extends IngestModuleFactoryAdapter { @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { if (!(settings instanceof HashLookupModuleSettings)) { - throw new IllegalArgumentException( - NbBundle.getMessage(this.getClass(), "HashLookupModuleFactory.createFileIngestModule.exception.msg")); + throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(), + "HashLookupModuleFactory.createFileIngestModule.exception.msg")); } try { return new HashDbIngestModule((HashLookupModuleSettings) settings); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties index 830901a6d9..35378ab1a3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties @@ -21,7 +21,7 @@ FilesSetRulePanel.extensionRadioButton.text=Extension Only FilesSetRulePanel.pathRegexCheckBox.text=Regex FilesSetRulePanel.pathTextField.text= FilesSetRulePanel.fullNameRadioButton.text=Full Name -FilesSetRulePanel.nameRegexCheckbox.text=Regex +FilesSetRulePanel.nameRegexCheckbox.text=Substring / Regex FilesSetRulePanel.ruleNameTextField.text= FilesSetRulePanel.nameTextField.text= FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional): @@ -36,8 +36,8 @@ FilesIdentifierIngestJobSettingsPanel.border.title=Select interesting files sets FilesSetRulePanel.jLabel1.text=Type: FilesSetRulePanel.interesting.jLabel5.text=Enter information about files that you want to find. FilesSetRulePanel.ingest.jLabel5.text=Enter information about files that you want to run ingest on. -FilesSetRulePanel.nameCheck.text=Name Pattern: -FilesSetRulePanel.pathCheck.text=Path Pattern: +FilesSetRulePanel.nameCheck.text=Name: +FilesSetRulePanel.pathCheck.text=Path Substring: FilesSetRulePanel.filesRadioButton.text=Files FilesSetRulePanel.dirsRadioButton.text=Directories FilesSetDefsPanel.interesting.setsListLabel.text=Rule Sets: @@ -56,18 +56,18 @@ FilesSetDefsPanel.newRuleButton.text=New Rule FilesSetDefsPanel.jLabel8.text=File Size: FilesSetDefsPanel.jLabel7.text=MIME Type: FilesSetDefsPanel.rulePathConditionRegexCheckBox.text=Regex -FilesSetDefsPanel.jLabel4.text=Path Pattern: +FilesSetDefsPanel.jLabel4.text=Path Substring: FilesSetDefsPanel.jLabel1.text=Rule Details FilesSetDefsPanel.dirsRadioButton.text=Directories FilesSetDefsPanel.jLabel2.text=File Type: FilesSetDefsPanel.deleteRuleButton.text=Delete Rule -FilesSetDefsPanel.fileNameRegexCheckbox.text=Regex +FilesSetDefsPanel.fileNameRegexCheckbox.text=Substring / Regex FilesSetDefsPanel.ignoreKnownFilesCheckbox.text=Ignore Known Files FilesSetDefsPanel.rulePathConditionTextField.text= -FilesSetDefsPanel.fileNameRadioButton.text=File Name +FilesSetDefsPanel.fileNameRadioButton.text=Full Name FilesSetDefsPanel.jLabel5.text=Description: FilesSetDefsPanel.fileNameTextField.text= -FilesSetDefsPanel.jLabel3.text=Name Pattern: +FilesSetDefsPanel.jLabel3.text=Name: FilesSetDefsPanel.fileNameExtensionRadioButton.text=Extension Only FilesSetDefsPanel.rulesListLabel.text=Rules: FilesSetDefsPanel.editRuleButton.text=Edit Rule diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java index 7347d32c8f..0019800ee1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCond */ public final class FilesSetsManager extends Observable { - @NbBundle.Messages({"FilesSetsManager.allFilesAndDirectories=All Files and Directories", + @NbBundle.Messages({"FilesSetsManager.allFilesAndDirectories=All Files and Directories (Not Unallocated Space)", "FilesSetsManager.allFilesDirectoriesAndUnallocated=All Files, Directories, and Unallocated Space"}) private static final List ILLEGAL_FILE_NAME_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", "/", ":", "*", "?", "\"", "<", ">"))); private static final List ILLEGAL_FILE_PATH_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", ":", "*", "?", "\"", "<", ">"))); diff --git a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java index b50742f197..700aa0cc5c 100644 --- a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java +++ b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java @@ -33,8 +33,11 @@ import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.python.util.PythonInterpreter; +import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.report.GeneralReportModule; @@ -66,12 +69,22 @@ public final class JythonModuleLoader { public static List getGeneralReportModules() { return getInterfaceImplementations(new GeneralReportModuleDefFilter(), GeneralReportModule.class); } - + @Messages({"JythonModuleLoader.pythonInterpreterError.title=Python Modules", + "JythonModuleLoader.pythonInterpreterError.msg=Failed to load python modules, See log for more details"}) private static List getInterfaceImplementations(LineFilter filter, Class interfaceClass) { List objects = new ArrayList<>(); Set pythonModuleDirs = new HashSet<>(); - PythonInterpreter interpreter = new PythonInterpreter(); - + PythonInterpreter interpreter = null; + // This method has previously thrown unchecked exceptions when it could not load because of non-latin characters. + try { + interpreter = new PythonInterpreter(); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to load python Intepreter. Cannot load python modules", ex); + if(RuntimeProperties.runningWithGUI()){ + MessageNotifyUtil.Notify.show(Bundle.JythonModuleLoader_pythonInterpreterError_title(),Bundle.JythonModuleLoader_pythonInterpreterError_msg(), MessageNotifyUtil.MessageType.ERROR); + } + return objects; + } // add python modules from 'autospy/build/cluster/InternalPythonModules' folder // which are copied from 'autopsy/*/release/InternalPythonModules' folders. for (File f : InstalledFileLocator.getDefault().locateAll("InternalPythonModules", "org.sleuthkit.autopsy.core", false)) { //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java index 50e9715a16..b0655dfa2b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java @@ -41,6 +41,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Allow examiner to select artifacts on which to report. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ArtifactSelectionDialog extends javax.swing.JDialog { private ArtifactModel model; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index b69d84b629..b2d388bedc 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -80,7 +80,6 @@ class ReportHTML implements TableReportModule { private static final int MAX_THUMBS_PER_PAGE = 1000; private static final String HTML_SUBDIR = "content"; private Case currentCase; - private SleuthkitCase skCase; static Integer THUMBNAIL_COLUMNS = 5; private Map dataTypes; @@ -109,7 +108,6 @@ class ReportHTML implements TableReportModule { // Refesh the member variables private void refresh() throws NoCurrentCaseException { currentCase = Case.getCurrentCaseThrows(); - skCase = currentCase.getSleuthkitCase(); dataTypes = new TreeMap<>(); @@ -274,7 +272,7 @@ class ReportHTML implements TableReportModule { in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS break; default: - logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = " + dataType); //NON-NLS + logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS iconFileName = "star.png"; //NON-NLS iconFilePath = subPath + File.separator + iconFileName; @@ -556,17 +554,29 @@ class ReportHTML implements TableReportModule { } /** - * Add a row to the current table. + * Add a row to the current table, escaping the text to be contained in the + * row. * * @param row values for each cell in the row */ @Override public void addRow(List row) { + addRow(row, true); + } + + /** + * Add a row to the current table. + * + * @param row values for each cell in the row + * @param escapeText whether or not the text of the row should be escaped, + * true for escaped, false for not escaped + */ + private void addRow(List row, boolean escapeText) { StringBuilder builder = new StringBuilder(); builder.append("\t\n"); //NON-NLS for (String cell : row) { - String escapeHTMLCell = EscapeUtil.escapeHtml(cell); - builder.append("\t\t").append(escapeHTMLCell).append("\n"); //NON-NLS + String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell; + builder.append("\t\t").append(cellText).append("\n"); //NON-NLS } builder.append("\t\n"); //NON-NLS rowCount++; @@ -593,7 +603,7 @@ class ReportHTML implements TableReportModule { public void addRowWithTaggedContentHyperlink(List row, ContentTag contentTag) { Content content = contentTag.getContent(); if (content instanceof AbstractFile == false) { - addRow(row); + addRow(row, true); return; } AbstractFile file = (AbstractFile) content; @@ -615,12 +625,19 @@ class ReportHTML implements TableReportModule { int positionCounter = 0; for (String cell : row) { // position-dependent code used to format this report. Not great, but understandable for formatting. - if (positionCounter == 1) { // Convert the file name to a hyperlink and left-align it - builder.append("\t\t").append(localFileLink.toString()).append(cell).append("\n"); //NON-NLS - } else if (positionCounter == 7) { // Right-align the bytes column. - builder.append("\t\t").append(cell).append("\n"); //NON-NLS - } else { // Regular case, not a file name nor a byte count - builder.append("\t\t").append(cell).append("\n"); //NON-NLS + switch (positionCounter) { + case 1: + // Convert the file name to a hyperlink and left-align it + builder.append("\t\t").append(localFileLink.toString()).append(cell).append("\n"); //NON-NLS + break; + case 7: + // Right-align the bytes column. + builder.append("\t\t").append(cell).append("\n"); //NON-NLS + break; + default: + // Regular case, not a file name nor a byte count + builder.append("\t\t").append(cell).append("\n"); //NON-NLS + break; } ++positionCounter; } @@ -647,7 +664,7 @@ class ReportHTML implements TableReportModule { int pages = 1; for (Content content : images) { if (currentRow.size() == THUMBNAIL_COLUMNS) { - addRow(currentRow); + addRow(currentRow, false); currentRow.clear(); } @@ -707,7 +724,7 @@ class ReportHTML implements TableReportModule { for (int i = 0; i < tags.size(); i++) { ContentTag tag = tags.get(i); String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; - linkToThumbnail.append(tag.getName().getDisplayName() + notableString); + linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString); if (i != tags.size() - 1) { linkToThumbnail.append(", "); } @@ -727,7 +744,7 @@ class ReportHTML implements TableReportModule { // Finish out the row. currentRow.add(""); } - addRow(currentRow); + addRow(currentRow, false); } // manually set rowCount to be the total number of images. @@ -739,12 +756,9 @@ class ReportHTML implements TableReportModule { return true; } AbstractFile file = (AbstractFile) c; - if (file.isDir() + return file.isDir() || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS - || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { - return true; - } - return false; + || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS; } /** @@ -1040,9 +1054,9 @@ class ReportHTML implements TableReportModule { * Write the summary of the current case for this report. */ private void writeSummary() { - Writer out = null; + Writer output = null; try { - out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS + output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS StringBuilder head = new StringBuilder(); head.append("\n\n").append( //NON-NLS NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("\n"); //NON-NLS @@ -1068,7 +1082,7 @@ class ReportHTML implements TableReportModule { head.append("li {padding-bottom: 5px;}"); head.append("\n"); //NON-NLS head.append("\n\n"); //NON-NLS - out.write(head.toString()); + output.write(head.toString()); DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); Date date = new Date(); @@ -1094,8 +1108,8 @@ class ReportHTML implements TableReportModule { summary.append("
\n"); //NON-NLS summary.append(writeSummaryCaseDetails()); summary.append(writeSummaryImageInfo()); - summary.append(writeSummarySoftwareInfo(skCase,ingestJobs)); - summary.append(writeSummaryIngestHistoryInfo(skCase,ingestJobs)); + summary.append(writeSummarySoftwareInfo(skCase, ingestJobs)); + summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs)); if (generatorLogoSet) { summary.append("
\n"); //NON-NLS summary.append("\n"); //NON-NLS @@ -1107,7 +1121,7 @@ class ReportHTML implements TableReportModule { } summary.append("
\n"); //NON-NLS summary.append(""); //NON-NLS - out.write(summary.toString()); + output.write(summary.toString()); } catch (FileNotFoundException ex) { logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS } catch (UnsupportedEncodingException ex) { @@ -1118,22 +1132,21 @@ class ReportHTML implements TableReportModule { logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report."); } finally { try { - if (out != null) { - out.flush(); - out.close(); + if (output != null) { + output.flush(); + output.close(); } } catch (IOException ex) { } } } - + /** * Write the case details section of the summary for this report. - * + * * @return StringBuilder updated html report with case details */ - - private StringBuilder writeSummaryCaseDetails(){ + private StringBuilder writeSummaryCaseDetails() { StringBuilder summary = new StringBuilder(); String caseName = currentCase.getDisplayName(); String caseNumber = currentCase.getNumber(); @@ -1146,40 +1159,39 @@ class ReportHTML implements TableReportModule { imagecount = 0; } summary.append("
\n"); //NON-NLS - if (agencyLogoSet) { - summary.append("
\n"); //NON-NLS - summary.append("\n"); //NON-NLS - summary.append("
\n"); //NON-NLS - } - final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS - summary.append("
\n"); //NON-NLS - summary.append("\n"); //NON-NLS - summary.append("\n"); //NON-NLS NON-NLS - summary.append("\n"); //NON-NLS - summary.append("\n"); //NON-NLS - summary.append("\n"); //NON-NLS - summary.append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS - .append("").append(caseName).append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS - .append("").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS - .getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("") //NON-NLS - .append(!examiner.isEmpty() ? examiner : NbBundle - .getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer")) - .append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS - .append("").append(imagecount).append("
\n"); //NON-NLS + if (agencyLogoSet) { + summary.append("
\n"); //NON-NLS + summary.append("\n"); //NON-NLS summary.append("
\n"); //NON-NLS - summary.append("
\n"); //NON-NLS - summary.append("
\n"); //NON-NLS - return summary; + } + final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS + summary.append("
\n"); //NON-NLS + summary.append("\n"); //NON-NLS + summary.append("\n"); //NON-NLS NON-NLS + summary.append("\n"); //NON-NLS + summary.append("\n"); //NON-NLS + summary.append("\n"); //NON-NLS + summary.append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS + .append("").append(caseName).append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS + .append("").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS + .getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("") //NON-NLS + .append(!examiner.isEmpty() ? examiner : NbBundle + .getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer")) + .append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS + .append("").append(imagecount).append("
\n"); //NON-NLS + summary.append("
\n"); //NON-NLS + summary.append("
\n"); //NON-NLS + summary.append("
\n"); //NON-NLS + return summary; } - + /** * Write the Image Information section of the summary for this report. - * + * * @return StringBuilder updated html report with Image Information */ - private StringBuilder writeSummaryImageInfo() { StringBuilder summary = new StringBuilder(); summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading")); @@ -1208,13 +1220,12 @@ class ReportHTML implements TableReportModule { summary.append("
\n"); //NON-NLS return summary; } - + /** * Write the software information section of the summary for this report. - * + * * @return StringBuilder updated html report with software information */ - private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List ingestJobs) { StringBuilder summary = new StringBuilder(); summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading")); @@ -1244,13 +1255,12 @@ class ReportHTML implements TableReportModule { summary.append("
\n"); //NON-NLS return summary; } - + /** * Write the Ingest History section of the summary for this report. - * + * * @return StringBuilder updated html report with ingest history */ - private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List ingestJobs) { StringBuilder summary = new StringBuilder(); try { @@ -1304,4 +1314,4 @@ class ReportHTML implements TableReportModule { + thumbFile.getName(); } -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form index e1f23ebffc..a67060d6f3 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.form @@ -97,6 +97,9 @@ + + +
diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java index 82de40aac9..03f3bda809 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java @@ -109,7 +109,7 @@ final class ReportVisualPanel2 extends JPanel { } for (TagName tagName : tagNamesInUse) { - String notableString = tagName.getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + String notableString = tagName.getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; tagStates.put(tagName.getDisplayName() + notableString, Boolean.FALSE); } tags.addAll(tagStates.keySet()); @@ -124,7 +124,9 @@ final class ReportVisualPanel2 extends JPanel { tagsList.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { - + if (!taggedResultsRadioButton.isSelected()) { + return; + } int index = tagsList.locationToIndex(evt.getPoint()); if (index < tagsModel.getSize() && index >= 0) { String value = tagsModel.getElementAt(index); @@ -184,16 +186,26 @@ final class ReportVisualPanel2 extends JPanel { return tagStates; } + /** + * Are any tags selected? + * + * @return True if any tags are selected; otherwise false. + */ private boolean areTagsSelected() { boolean result = false; for (Entry entry : tagStates.entrySet()) { if (entry.getValue()) { result = true; + break; } } return result; } + /** + * Set the Finish button as either enabled or disabled depending on the UI + * component selections. + */ private void updateFinishButton() { if (taggedResultsRadioButton.isSelected()) { wizPanel.setFinish(areTagsSelected()); @@ -209,6 +221,19 @@ final class ReportVisualPanel2 extends JPanel { return taggedResultsRadioButton.isSelected(); } + /** + * Set all tagged results as either selected or unselected. + * + * @param selected Should all tagged results be selected? + */ + void setAllTaggedResultsSelected(boolean selected) { + for (String tag : tags) { + tagStates.put(tag, (selected ? Boolean.TRUE : Boolean.FALSE)); + } + tagsList.repaint(); + wizPanel.setFinish(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 @@ -234,6 +259,11 @@ final class ReportVisualPanel2 extends JPanel { optionsButtonGroup.add(allResultsRadioButton); org.openide.awt.Mnemonics.setLocalizedText(allResultsRadioButton, org.openide.util.NbBundle.getMessage(ReportVisualPanel2.class, "ReportVisualPanel2.allResultsRadioButton.text")); // NOI18N + allResultsRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + allResultsRadioButtonActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(dataLabel, org.openide.util.NbBundle.getMessage(ReportVisualPanel2.class, "ReportVisualPanel2.dataLabel.text")); // NOI18N @@ -312,25 +342,21 @@ final class ReportVisualPanel2 extends JPanel { }// //GEN-END:initComponents private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed - for (String tag : tags) { - tagStates.put(tag, Boolean.TRUE); - } - tagsList.repaint(); - wizPanel.setFinish(true); + setAllTaggedResultsSelected(true); }//GEN-LAST:event_selectAllButtonActionPerformed private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed - for (String tag : tags) { - tagStates.put(tag, Boolean.FALSE); - } - tagsList.repaint(); - wizPanel.setFinish(false); + setAllTaggedResultsSelected(false); }//GEN-LAST:event_deselectAllButtonActionPerformed private void advancedButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_advancedButtonActionPerformed artifactStates = dialog.display(); }//GEN-LAST:event_advancedButtonActionPerformed + private void allResultsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allResultsRadioButtonActionPerformed + setAllTaggedResultsSelected(false); + }//GEN-LAST:event_allResultsRadioButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton advancedButton; private javax.swing.JRadioButton allResultsRadioButton; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 9d5d41c836..69384bfe0c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -38,12 +38,14 @@ import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import static org.sleuthkit.autopsy.casemodule.services.TagsManager.getNotableTagLabel; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -53,10 +55,11 @@ import org.sleuthkit.datamodel.BlackboardAttribute.Type; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -class TableReportGenerator { +class TableReportGenerator { private final List artifactTypes = new ArrayList<>(); private final HashSet tagNamesFilter = new HashSet<>(); @@ -262,6 +265,7 @@ class TableReportGenerator { /** * Make table for tagged files */ + @Messages({"ReportGenerator.tagTable.header.userName=User Name"}) @SuppressWarnings("deprecation") private void makeContentTagsTables() { @@ -284,7 +288,8 @@ class TableReportGenerator { ArrayList columnHeaders = new ArrayList<>(Arrays.asList( NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.tag"), NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.file"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.comment"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.userName"), NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeModified"), NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeChanged"), NbBundle.getMessage(this.getClass(), "ReportGenerator.htmlOutput.header.timeAccessed"), @@ -322,7 +327,7 @@ class TableReportGenerator { fileName = tag.getContent().getName(); } - ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getName().getDisplayName() + notableString, fileName, tag.getComment())); + ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getName().getDisplayName() + notableString, fileName, tag.getComment(), tag.getUserName())); Content content = tag.getContent(); if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; @@ -384,7 +389,8 @@ class TableReportGenerator { NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.resultType"), NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.tag"), NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.comment"), - NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile")))); + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.srcFile"), + NbBundle.getMessage(this.getClass(), "ReportGenerator.tagTable.header.userName")))); // Give the modules the rows for the content tags. for (BlackboardArtifactTag tag : tags) { @@ -394,7 +400,8 @@ class TableReportGenerator { } List row; - row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName() + notableString, tag.getComment(), tag.getContent().getName())); + row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName() + notableString, + tag.getComment(), tag.getContent().getName(), tag.getUserName())); tableReport.addRow(row); // check if the tag is an image that we should later make a thumbnail for @@ -538,24 +545,93 @@ class TableReportGenerator { logger.log(Level.SEVERE, "Exception while getting open case: ", ex); //NON-NLS return; } + + // Get a list of all selected tag IDs + String tagIDList = ""; + if( ! tagNamesFilter.isEmpty()) { + try { + Map tagNamesMap = Case.getCurrentCaseThrows().getServices().getTagsManager().getDisplayNamesToTagNamesMap(); + for(String tagDisplayName : tagNamesFilter) { + if(tagNamesMap.containsKey(tagDisplayName)) { + if (! tagIDList.isEmpty()) { + tagIDList += ","; + } + tagIDList += tagNamesMap.get(tagDisplayName).getId(); + } else { + // If the tag name ends with "(Notable)", try stripping that off + if(tagDisplayName.endsWith(getNotableTagLabel())) { + String editedDisplayName = tagDisplayName.substring(0, tagDisplayName.length() - getNotableTagLabel().length()); + if(tagNamesMap.containsKey(editedDisplayName)) { + if (! tagIDList.isEmpty()) { + tagIDList += ","; + } + tagIDList += tagNamesMap.get(editedDisplayName).getId(); + } + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Exception while getting tag info - proceeding without tag filter: ", ex); //NON-NLS + tagIDList = ""; + } + } + + // Check if there are any ad-hoc results + String adHocCountQuery = "SELECT COUNT(*) FROM " + //NON-NLS + "(SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 ";//NON-NLS + if (!tagIDList.isEmpty()) { + adHocCountQuery += ", blackboard_artifact_tags as tag "; //NON-NLS + } + adHocCountQuery += "WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; // NON-NLS + if (!tagIDList.isEmpty()) { + adHocCountQuery += " AND (art.artifact_id = tag.artifact_id) AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS + } + adHocCountQuery += "EXCEPT " + // NON-NLS + "SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ")) AS adHocHits"; //NON-NLS + + int adHocCount = 0; + try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(adHocCountQuery)) { + ResultSet adHocCountResultSet = dbQuery.getResultSet(); + if (adHocCountResultSet.next()) { + adHocCount = adHocCountResultSet.getInt(1); //NON-NLS + } else { + throw new TskCoreException("Error counting ad hoc keywords"); + } + } catch (TskCoreException | SQLException ex) { + errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWLists")); + logger.log(Level.SEVERE, "Failed to count ad hoc searches with query " + adHocCountQuery, ex); //NON-NLS + return; + } + + // Create the query to get the keyword list names if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS + orderByClause = "ORDER BY convert_to(list, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS } else { orderByClause = "ORDER BY list ASC"; //NON-NLS } String keywordListQuery = "SELECT att.value_text AS list " + //NON-NLS - "FROM blackboard_attributes AS att, blackboard_artifacts AS art " - + //NON-NLS - "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + "FROM blackboard_attributes AS att, blackboard_artifacts AS art "; // NON-NLS + if(! tagIDList.isEmpty()) { + keywordListQuery += ", blackboard_artifact_tags as tag "; //NON-NLS + } + keywordListQuery += "WHERE att.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + " " + //NON-NLS "AND art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + " " + //NON-NLS - "AND att.artifact_id = art.artifact_id " - + //NON-NLS - "GROUP BY list " + orderByClause; //NON-NLS + "AND att.artifact_id = art.artifact_id "; + if (! tagIDList.isEmpty()) { + keywordListQuery += "AND (art.artifact_id = tag.artifact_id) " + //NON-NLS + "AND (tag.tag_name_id IN (" + tagIDList + ")) "; //NON-NLS + } + if (adHocCount > 0) { + keywordListQuery += " UNION SELECT \'\' AS list "; + } + keywordListQuery = "SELECT * FROM ( " + keywordListQuery + " ) kwListNames "; + keywordListQuery += "GROUP BY list " + orderByClause; //NON-NLS + // Make the table of contents links for each list type try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordListQuery)) { ResultSet listsRs = dbQuery.getResultSet(); List lists = new ArrayList<>(); @@ -575,22 +651,24 @@ class TableReportGenerator { BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getDisplayName())); } catch (TskCoreException | SQLException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWLists")); - logger.log(Level.SEVERE, "Failed to query keyword lists: ", ex); //NON-NLS + logger.log(Level.SEVERE, "Failed to query keyword lists with query " + keywordListQuery, ex); //NON-NLS return; } + // Query for keywords, grouped by list if (openCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) { - orderByClause = "ORDER BY convert_to(att3.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(att1.value_text, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(f.name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS - + "convert_to(att2.value_text, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS + orderByClause = "ORDER BY convert_to(list, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(keyword, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(parent_path, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(name, 'SQL_ASCII') ASC NULLS FIRST, " //NON-NLS + + "convert_to(preview, 'SQL_ASCII') ASC NULLS FIRST"; //NON-NLS } else { orderByClause = "ORDER BY list ASC, keyword ASC, parent_path ASC, name ASC, preview ASC"; //NON-NLS } - // Query for keywords, grouped by list - String keywordsQuery - = "SELECT art.artifact_id, art.obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path " + + // Query for keywords that are part of a list + String keywordListsQuery + = "SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, att3.value_text AS list, f.name AS name, f.parent_path AS parent_path " + //NON-NLS "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, blackboard_attributes AS att3, tsk_files AS f " + //NON-NLS @@ -608,9 +686,24 @@ class TableReportGenerator { + //NON-NLS "AND (att3.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + ") " + //NON-NLS - "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " - + //NON-NLS - orderByClause; //NON-NLS + "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; + + // Query for keywords that are not part of a list + String keywordAdHocQuery = + "SELECT art.artifact_id AS artifact_id, art.obj_id AS obj_id, att1.value_text AS keyword, att2.value_text AS preview, \'\' AS list, f.name AS name, f.parent_path AS parent_path " + // NON-NLS + "FROM blackboard_artifacts AS art, blackboard_attributes AS att1, blackboard_attributes AS att2, tsk_files AS f " + // NON-NLS + "WHERE " + // NON-NLS + " (art.artifact_id IN (SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") " + // NON-NLS + "EXCEPT " + // NON-NLS + "SELECT art.artifact_id FROM blackboard_artifacts AS art, blackboard_attributes AS att1 WHERE (att1.artifact_id = art.artifact_id) AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "))) " + //NON-NLS + "AND (att1.artifact_id = art.artifact_id) " + //NON-NLS + "AND (att2.artifact_id = art.artifact_id) " + //NON-NLS + "AND (f.obj_id = art.obj_id) " + //NON-NLS + "AND (att1.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID() + ") " + // NON-NLS + "AND (att2.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW.getTypeID() + ") " + // NON-NLS + "AND (art.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() + ") "; // NON-NLS + + String keywordsQuery = "SELECT * FROM ( " + keywordListsQuery + " UNION " + keywordAdHocQuery + " ) kwHits " + orderByClause; try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordsQuery)) { ResultSet resultSet = dbQuery.getResultSet(); @@ -683,7 +776,7 @@ class TableReportGenerator { tableModule.endDataType(); } catch (TskCoreException | SQLException ex) { errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedQueryKWs")); - logger.log(Level.SEVERE, "Failed to query keywords: ", ex); //NON-NLS + logger.log(Level.SEVERE, "Failed to query keywords with query " + keywordsQuery, ex); //NON-NLS } } @@ -1623,14 +1716,15 @@ class TableReportGenerator { private HashSet getUniqueTagNames(long artifactId) throws TskCoreException { HashSet uniqueTagNames = new HashSet<>(); - String query = "SELECT display_name, artifact_id FROM tag_names AS tn, blackboard_artifact_tags AS bat " + String query = "SELECT display_name, artifact_id, knownStatus FROM tag_names AS tn, blackboard_artifact_tags AS bat " + //NON-NLS "WHERE tn.tag_name_id = bat.tag_name_id AND bat.artifact_id = " + artifactId; //NON-NLS try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { ResultSet tagNameRows = dbQuery.getResultSet(); while (tagNameRows.next()) { - uniqueTagNames.add(tagNameRows.getString("display_name")); //NON-NLS + String notableString = tagNameRows.getInt("knownStatus") == TskData.FileKnown.BAD.ordinal() ? getNotableTagLabel() : ""; + uniqueTagNames.add(tagNameRows.getString("display_name") + notableString); //NON-NLS } } catch (TskCoreException | SQLException | NoCurrentCaseException ex) { throw new TskCoreException("Error getting tag names for artifact: ", ex); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 82e479c4ab..dede3d537a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,6 +72,7 @@ import org.sleuthkit.datamodel.TskCoreException; * to choose a specific event and a time range around it to show in the Timeline * List View. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ShowInTimelineDialog extends Dialog { private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index a7be338d6f..2f6c6ed559 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -62,7 +62,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponents.DataContentPanel; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.timeline.actions.Back; @@ -209,7 +209,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer case DETAIL: //make a root node with nodes for the selected events as children and push it to the result viewer. EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel()); - SingleLayerTableFilterNode tableFilterNode = new SingleLayerTableFilterNode(rootNode, true, "Event"); + TableFilterNode tableFilterNode = new TableFilterNode(rootNode, true, "Event"); SwingUtilities.invokeLater(() -> { dataResultPanel.setPath(getResultViewerSummaryString()); dataResultPanel.setNode(tableFilterNode); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java index 7515891356..6bc26f8d7a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/listvew/ListTimeline.java @@ -567,8 +567,11 @@ class ListTimeline extends BorderPane { super.updateItem(item, empty); if (empty || item == null) { setText(null); + setTooltip(null); } else { - setText(textSupplier.apply(getEvent())); + String text = textSupplier.apply(getEvent()); + setText(text); + setTooltip(new Tooltip(text)); } } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index d47efa3995..05c454a962 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -46,7 +46,7 @@ import static junit.framework.Assert.assertTrue; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /** - * + * Functional tests for the Central Repository data model. */ public class CentralRepoDatamodelTest extends TestCase { @@ -115,7 +115,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDbUtil.setUseCentralRepo(true); EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); EamDbPlatformEnum.saveSelectedPlatform(); - } catch (Exception ex) { + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -838,7 +838,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This is the expected behavior } - // Test getting instances with expected resuls + // Test getting instances with expected results try { List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(fileType, inAllDataSourcesHash); assertTrue("getArtifactInstancesByTypeValue returned " + instances.size() + " results - expected 3", instances.size() == 3); @@ -1123,6 +1123,34 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected } + + // Test running processinstance which queries all rows from instances table + try { + // Add two instances to the central repository and use the callback query to verify we can see them + CorrelationAttribute attr = new CorrelationAttribute(fileType, callbackTestFileHash); + CorrelationAttributeInstance inst1 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, callbackTestFilePath1); + CorrelationAttributeInstance inst2 = new CorrelationAttributeInstance(case1, dataSource1fromCase1, callbackTestFilePath2); + attr.addInstance(inst1); + attr.addInstance(inst2); + EamDb DbManager = EamDb.getInstance(); + DbManager.addArtifact(attr); + AttributeInstanceTableCallback instancetableCallback = new AttributeInstanceTableCallback(); + DbManager.processInstanceTableWhere(fileType, String.format("id = %s", attr.getID()), instancetableCallback); + int count1 = instancetableCallback.getCounter(); + int count2 = instancetableCallback.getCounterNamingConvention(); + assertTrue("Process Instance count with filepath naming convention: " + count2 + "-expected 2", count2 == 2); + assertTrue("Process Instance count with filepath without naming convention: " + count1 + "-expected greater than 0", count1 > 0); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + } + + try { + //test null inputs + EamDb.getInstance().processInstanceTableWhere(null, null, null); + Assert.fail("processinstance method failed to throw exception for null type value"); + } catch (EamDbException ex) { + // This is the expected + } } /** @@ -2554,7 +2582,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This seems to help in allowing the Autopsy case to be deleted try { Thread.sleep(2000); - } catch (Exception ex) { + } catch (InterruptedException ex) { } } catch (CaseActionException ex) { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileType.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileType.java deleted file mode 100644 index a599763346..0000000000 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileType.java +++ /dev/null @@ -1,465 +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.commonfilessearch; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import junit.framework.Test; -import org.netbeans.junit.NbModuleSuite; -import org.netbeans.junit.NbTestCase; -import org.openide.util.Exceptions; -import org.python.icu.impl.Assert; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonfilesearch.AllDataSourcesCommonFilesAlgorithm; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadata; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadataBuilder; -import org.sleuthkit.autopsy.commonfilesearch.SingleDataSource; -import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseUtils.*; -import org.sleuthkit.autopsy.ingest.IngestJobSettings; -import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; -import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; -import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; -import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; -import org.sleuthkit.autopsy.testutils.IngestUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Add set 1, set 2, set 3, and set 4 to case and ingest with hash algorithm. - */ -public class IngestedWithHashAndFileType extends NbTestCase { - - public static Test suite() { - NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithHashAndFileType.class). - clusters(".*"). - enableModules(".*"); - return conf.suite(); - } - - private final IntraCaseUtils utils; - - public IngestedWithHashAndFileType(String name) { - super(name); - - this.utils = new IntraCaseUtils(this, "IngestedWithHashAndFileTypeTests"); - } - - @Override - public void setUp() { - this.utils.setUp(); - - IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); - IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()); - - ArrayList templates = new ArrayList<>(); - templates.add(hashLookupTemplate); - templates.add(mimeTypeLookupTemplate); - - IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithHashAndFileType.class.getCanonicalName(), IngestType.FILES_ONLY, templates); - - try { - IngestUtils.runIngestJob(Case.getCurrentCaseThrows().getDataSources(), ingestJobSettings); - } catch (NoCurrentCaseException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - @Override - public void tearDown() { - this.utils.tearDown(); - } - - /** - * Find all matches & all file types. Confirm file.jpg is found on all three - * and file.docx is found on two. - */ - public void testOneA() { - try { - Map dataSources = this.utils.getDataSourceMap(); - - CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, false, false); - CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); - - Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); - - List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find all matches & only image types. Confirm file.jpg is found on all - * three. - */ - public void testOneB() { - try { - Map dataSources = this.utils.getDataSourceMap(); - - CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, true, false); - CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find all matches & only image types. Confirm file.jpg is found on all - * three. - */ - public void testOneC() { - try { - Map dataSources = this.utils.getDataSourceMap(); - - CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, false, true); - CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 1 & all file types. Confirm same results. - * - */ - public void testTwoA() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long first = getDataSourceIdByName(SET1, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(first, dataSources, false, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 1 & only media types. Confirm same results. - * - */ - public void testTwoB() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long first = getDataSourceIdByName(SET1, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(first, dataSources, true, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 1 & all file types. Confirm same results. - * - */ - public void testTwoC() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long first = getDataSourceIdByName(SET1, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(first, dataSources, false, true); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 2 & all file types: Confirm file.jpg. - * - */ - public void testThree() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long second = getDataSourceIdByName(SET2, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(second, dataSources, false, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 4 & all file types: Confirm nothing is found. - */ - public void testFour() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long last = getDataSourceIdByName(SET4, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(last, dataSources, false, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - /** - * Find matches on set 3 & all file types: Confirm file.jpg and file.docx. - */ - public void testFive() { - try { - Map dataSources = this.utils.getDataSourceMap(); - Long third = getDataSourceIdByName(SET3, dataSources); - - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(third, dataSources, false, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); - - Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); - - List files = getFiles(objectIdToDataSource.keySet()); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); - - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); - assertTrue(verifyFileExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } -} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java new file mode 100644 index 0000000000..d758e8c935 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeInterCaseTests.java @@ -0,0 +1,181 @@ +/* + * + * 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.commonfilessearch; + +import java.util.Map; +import junit.framework.Test; +import org.netbeans.junit.NbModuleSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.AllInterCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import org.sleuthkit.autopsy.commonfilesearch.SingleInterCaseCommonAttributeSearcher; +import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.*; + +/** + * Tests with case 3 as the current case. + * + * If I use the search all cases option: One node for Hash A (1_1_A.jpg, + * 1_2_A.jpg, 3_1_A.jpg) If I search for matches only in Case 1: One node for + * Hash A (1_1_A.jpg, 1_2_A.jpg, 3_1_A.jpg) If I search for matches only in Case + * 2: No matches If I only search in the current case (existing mode), allowing + * all data sources: One node for Hash C (3_1_C.jpg, 3_2_C.jpg) + */ +public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase { + + private final InterCaseTestUtils utils; + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithHashAndFileTypeInterCaseTests.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + public IngestedWithHashAndFileTypeInterCaseTests(String name) { + super(name); + this.utils = new InterCaseTestUtils(this); + } + + @Override + public void setUp(){ + this.utils.clearTestDir(); + try { + this.utils.enableCentralRepo(); + this.utils.createCases(this.utils.getIngestSettingsForHashAndFileType(), InterCaseTestUtils.CASE3); + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + @Override + public void tearDown(){ + this.utils.clearTestDir(); + this.utils.tearDown(); + } + + /** + * Search All cases with no file type filtering. + */ + public void testOne() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + //note that the params false and false are presently meaningless because that feature is not supported yet + AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false); + + CommonAttributeSearchResults metadata = builder.findFiles(); + + assertTrue("Results should not be empty", metadata.size() != 0); + + //case 1 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_1, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_1, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_1, CASE1, 1)); + + //case 1 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_2, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_2, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_2, CASE1, 1)); + + //case 2 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_PDF, CASE2_DATASET_1, CASE2, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_JPG, CASE2_DATASET_1, CASE2, 0)); + + //case 2 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE2_DATASET_2, CASE2, 1)); + + //case 3 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_JPG, CASE3_DATASET_1, CASE3, 0)); + + //case 3 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 1)); + + + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Search All cases with no file type filtering. + */ + public void testTwo() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + int matchesMustAlsoBeFoundInThisCase = this.utils.getCaseMap().get(CASE2); + + AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false); + + CommonAttributeSearchResults metadata = builder.findFiles(); + + assertTrue("Results should not be empty", metadata.size() != 0); + + //case 1 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_1, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_1, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_1, CASE1, 1)); + + //case 1 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_2, CASE1, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_2, CASE1, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_2, CASE1, 1)); + + //case 2 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_PDF, CASE2_DATASET_1, CASE2, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_JPG, CASE2_DATASET_1, CASE2, 0)); + + //case 2 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE2_DATASET_2, CASE2, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE2_DATASET_2, CASE2, 1)); + + //case 3 data set 1 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE3_DATASET_1, CASE3, 1)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_1, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_JPG, CASE3_DATASET_1, CASE3, 0)); + + //case 3 data set 2 + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0)); + assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 1)); + + + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java new file mode 100644 index 0000000000..d1a004d281 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileTypeIntraCaseTests.java @@ -0,0 +1,466 @@ +/* + * + * 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.commonfilessearch; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import junit.framework.Test; +import org.netbeans.junit.NbModuleSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.AllIntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import org.sleuthkit.autopsy.commonfilesearch.SingleIntraCaseCommonAttributeSearcher; +import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseTestUtils.*; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Add set 1, set 2, set 3, and set 4 to case and ingest with hash algorithm. + */ +public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase { + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithHashAndFileTypeIntraCaseTests.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + private final IntraCaseTestUtils utils; + + public IngestedWithHashAndFileTypeIntraCaseTests(String name) { + super(name); + + this.utils = new IntraCaseTestUtils(this, "IngestedWithHashAndFileTypeTests"); + } + + @Override + public void setUp() { + this.utils.setUp(); + + IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); + IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()); + + ArrayList templates = new ArrayList<>(); + templates.add(hashLookupTemplate); + templates.add(mimeTypeLookupTemplate); + + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithHashAndFileTypeIntraCaseTests.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + + try { + IngestUtils.runIngestJob(Case.getCurrentCaseThrows().getDataSources(), ingestJobSettings); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + @Override + public void tearDown() { + this.utils.tearDown(); + } + + /** + * Find all matches & all file types. Confirm file.jpg is found on all three + * and file.docx is found on two. + */ + public void testOneA() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); + + Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); + + List files = IntraCaseTestUtils.getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find all matches & only image types. Confirm file.jpg is found on all + * three. + */ + public void testOneB() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find all matches & only image types. Confirm file.jpg is found on all + * three. + */ + public void testOneC() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, true); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 1 & all file types. Confirm same results. + * + */ + public void testTwoA() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long first = getDataSourceIdByName(SET1, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 1 & only media types. Confirm same results. + * + */ + public void testTwoB() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long first = getDataSourceIdByName(SET1, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, true, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 1 & all file types. Confirm same results. + * + */ + public void testTwoC() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long first = getDataSourceIdByName(SET1, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, true); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 2 & all file types: Confirm file.jpg. + * + */ + public void testThree() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long second = getDataSourceIdByName(SET2, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(second, dataSources, false, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 4 & all file types: Confirm nothing is found. + */ + public void testFour() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long last = getDataSourceIdByName(SET4, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(last, dataSources, false, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find matches on set 3 & all file types: Confirm file.jpg and file.docx. + */ + public void testFive() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long third = getDataSourceIdByName(SET3, dataSources); + + AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, false, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); + + Map objectIdToDataSource = mapFileInstancesToDataSources(metadata); + + List files = getFiles(objectIdToDataSource.keySet()); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET1, 2)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET2, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, IMG, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET1, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET3, 1)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, DOC, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, PDF, SET4, 0)); + + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET1, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET2, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); + assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypes.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java similarity index 65% rename from Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypes.java rename to Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java index 0090d0f699..ce56385c61 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypes.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypesIntraCaseTests.java @@ -19,7 +19,6 @@ */ package org.sleuthkit.autopsy.commonfilessearch; -import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -31,10 +30,10 @@ import org.openide.util.Exceptions; import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonfilesearch.AllDataSourcesCommonFilesAlgorithm; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadata; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadataBuilder; -import org.sleuthkit.autopsy.commonfilesearch.SingleDataSource; +import org.sleuthkit.autopsy.commonfilesearch.AllIntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import org.sleuthkit.autopsy.commonfilesearch.IntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.SingleIntraCaseCommonAttributeSearcher; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; @@ -51,21 +50,21 @@ import org.sleuthkit.datamodel.TskCoreException; * Add images set 1, set 2, set 3, and set 4 to case. Do not run mime type * module. */ -public class IngestedWithNoFileTypes extends NbTestCase { +public class IngestedWithNoFileTypesIntraCaseTests extends NbTestCase { public static Test suite() { - NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithNoFileTypes.class). + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithNoFileTypesIntraCaseTests.class). clusters(".*"). enableModules(".*"); return conf.suite(); } - private final IntraCaseUtils utils; + private final IntraCaseTestUtils utils; - public IngestedWithNoFileTypes(String name) { + public IngestedWithNoFileTypesIntraCaseTests(String name) { super(name); - this.utils = new IntraCaseUtils(this, "IngestedWithNoFileTypes"); + this.utils = new IntraCaseTestUtils(this, "IngestedWithNoFileTypes"); } @Override @@ -77,7 +76,7 @@ public class IngestedWithNoFileTypes extends NbTestCase { ArrayList templates = new ArrayList<>(); templates.add(hashLookupTemplate); - IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithHashAndFileType.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithNoFileTypesIntraCaseTests.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates); try { IngestUtils.runIngestJob(Case.getCurrentCaseThrows().getDataSources(), ingestJobSettings); @@ -100,17 +99,18 @@ public class IngestedWithNoFileTypes extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, true, false); - CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); + IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); - Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); + Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); - List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); + List files = IntraCaseTestUtils.getFiles(objectIdToDataSource.keySet()); assertTrue(files.isEmpty()); - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + } catch (Exception ex) { Exceptions.printStackTrace(ex); + Assert.fail(ex); } } @@ -121,18 +121,18 @@ public class IngestedWithNoFileTypes extends NbTestCase { public void testTwo() { try { Map dataSources = this.utils.getDataSourceMap(); - Long third = IntraCaseUtils.getDataSourceIdByName(IntraCaseUtils.SET3, dataSources); + Long third = IntraCaseTestUtils.getDataSourceIdByName(IntraCaseTestUtils.SET3, dataSources); - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(third, dataSources, true, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); + IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, true, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); - Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); + Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); - List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); + List files = IntraCaseTestUtils.getFiles(objectIdToDataSource.keySet()); assertTrue(files.isEmpty()); - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + } catch (Exception ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java new file mode 100644 index 0000000000..cf6bd36fbc --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/InterCaseTestUtils.java @@ -0,0 +1,392 @@ +/* + * + * 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 testFile 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.commonfilessearch; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbPlatformEnum; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteEamDbSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.CaseUtils; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.TskCoreException; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeInstance; +import org.sleuthkit.autopsy.commonfilesearch.CaseDBCommonAttributeInstanceNode; +import org.sleuthkit.autopsy.commonfilesearch.CentralRepoCommonAttributeInstance; +import org.sleuthkit.autopsy.commonfilesearch.CentralRepoCommonAttributeInstanceNode; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValue; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Utilities for testing intercase correlation feature. + * + * This will be more useful when we add more flush out the intercase + * correlation features and need to add more tests. In particular, + * testing scenarios where we need different cases to be the current case + * will suggest that we create additional test classes, and we will want to + * import this utility in each new intercase test file. + * + * Description of Test Data: + (Note: files of the same name and extension are identical; + files of the same name and differing extension are not identical.) + + Case 1 + +Data Set 1 + - Hash-0.dat [testFile of size 0] + - Hash-A.jpg + - Hash-A.pdf + +Data Set2 + - Hash-0.dat [testFile of size 0] + - Hash-A.jpg + - Hash-A.pdf + Case 2 + +Data Set 1 + - Hash-B.jpg + - Hash-B.pdf + +Data Set 2 + - Hash-A.jpg + - Hash-A.pdf + - Hash_D.doc + Case 3 + +Data Set 1 + - Hash-A.jpg + - Hash-A.pdf + - Hash-C.jpg + - Hash-C.pdf + - Hash-D.jpg + +Data Set 2 + - Hash-C.jpg + - Hash-C.pdf + - Hash-D.doc + */ +class InterCaseTestUtils { + + private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "InterCaseCommonFilesSearchTest"); + private static final String CR_DB_NAME = "testcentralrepo.db"; + + static final String CASE1 = "Case1"; + static final String CASE2 = "Case2"; + static final String CASE3 = "Case3"; + static final String CASE4 = "Case4"; + + final Path case1DataSet1Path; + final Path case1DataSet2Path; + final Path case2DataSet1Path; + final Path case2DataSet2Path; + final Path case3DataSet1Path; + final Path case3DataSet2Path; + + static final String HASH_0_DAT = "Hash-0.dat"; + static final String HASH_A_JPG = "Hash-A.jpg"; + static final String HASH_A_PDF = "Hash-A.pdf"; + static final String HASH_B_JPG = "Hash-B.jpg"; + static final String HASH_B_PDF = "Hash-B.pdf"; + static final String HASH_C_JPG = "Hash-C.jpg"; + static final String HASH_C_PDF = "Hash-C.pdf"; + static final String HASH_D_JPG = "Hash-D.jpg"; + static final String HASH_D_DOC = "Hash-D.doc"; + + static final String CASE1_DATASET_1 = "c1ds1_v1.vhd"; + static final String CASE1_DATASET_2 = "c1ds2_v1.vhd"; + static final String CASE2_DATASET_1 = "c2ds1_v1.vhd"; + static final String CASE2_DATASET_2 = "c2ds2_v1.vhd"; + static final String CASE3_DATASET_1 = "c3ds1_v1.vhd"; + static final String CASE3_DATASET_2 = "c3ds2_v1.vhd"; + + private final ImageDSProcessor imageDSProcessor; + + private final IngestJobSettings hashAndFileType; + private final IngestJobSettings hashAndNoFileType; + private final DataSourceLoader dataSourceLoader; + + InterCaseTestUtils(NbTestCase testCase) { + + this.case1DataSet1Path = Paths.get(testCase.getDataDir().toString(), CASE1_DATASET_1); + this.case1DataSet2Path = Paths.get(testCase.getDataDir().toString(), CASE1_DATASET_2); + this.case2DataSet1Path = Paths.get(testCase.getDataDir().toString(), CASE1_DATASET_1); + this.case2DataSet2Path = Paths.get(testCase.getDataDir().toString(), CASE2_DATASET_2); + this.case3DataSet1Path = Paths.get(testCase.getDataDir().toString(), CASE3_DATASET_1); + this.case3DataSet2Path = Paths.get(testCase.getDataDir().toString(), CASE3_DATASET_2); + + this.imageDSProcessor = new ImageDSProcessor(); + + final IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); + final IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()); + final IngestModuleTemplate eamDbTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.centralrepository.ingestmodule.IngestModuleFactory()); + + ArrayList hashAndMimeTemplate = new ArrayList<>(2); + hashAndMimeTemplate.add(hashLookupTemplate); + hashAndMimeTemplate.add(mimeTypeLookupTemplate); + hashAndMimeTemplate.add(eamDbTemplate); + + this.hashAndFileType = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.FILES_ONLY, hashAndMimeTemplate); + + ArrayList hashAndNoMimeTemplate = new ArrayList<>(1); + hashAndNoMimeTemplate.add(hashLookupTemplate); + hashAndMimeTemplate.add(eamDbTemplate); + + this.hashAndNoFileType = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.FILES_ONLY, hashAndNoMimeTemplate); + + this.dataSourceLoader = new DataSourceLoader(); + } + + void clearTestDir(){ + if(CASE_DIRECTORY_PATH.toFile().exists()){ + try{ + if(EamDb.isEnabled()) { + EamDb.getInstance().shutdownConnections(); + } + FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile()); + } catch(IOException | EamDbException ex){ + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + CASE_DIRECTORY_PATH.toFile().exists(); + } + + Map getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException { + return this.dataSourceLoader.getDataSourceMap(); + } + + Map getCaseMap() throws EamDbException { + + if (EamDb.isEnabled()) { + Map mapOfCaseIdsToCase = new HashMap<>(); + + for (CorrelationCase caze : EamDb.getInstance().getCases()) { + mapOfCaseIdsToCase.put(caze.getDisplayName(), caze.getID()); + } + return mapOfCaseIdsToCase; + } else { + //it is reasonable that this might happen... + // for example when we test the feature in the absence of an enabled eamdb + return new HashMap<>(0); + } + } + + IngestJobSettings getIngestSettingsForHashAndFileType() { + return this.hashAndFileType; + } + + IngestJobSettings getIngestSettingsForHashAndNoFileType() { + return this.hashAndNoFileType; + } + + void enableCentralRepo() throws EamDbException { + + SqliteEamDbSettings crSettings = new SqliteEamDbSettings(); + crSettings.setDbName(CR_DB_NAME); + crSettings.setDbDirectory(CASE_DIRECTORY_PATH.toString()); + if (!crSettings.dbDirectoryExists()) { + crSettings.createDbDirectory(); + } + + crSettings.initializeDatabaseSchema(); + crSettings.insertDefaultDatabaseContent(); + + crSettings.saveSettings(); + + EamDbUtil.setUseCentralRepo(true); + EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); + EamDbPlatformEnum.saveSelectedPlatform(); + } + + /** + * Create 3 cases and ingest each with the given settings. Null settings are + * permitted but IngestUtils will not be run. + * + * @param ingestJobSettings HashLookup FileType etc... + * @param caseReferenceToStore + */ + Case createCases(IngestJobSettings ingestJobSettings, String caseReferenceToStore) throws TskCoreException { + + Case currentCase = null; + + String[] cases = new String[]{ + CASE1, + CASE2, + CASE3}; + + Path[][] paths = { + {this.case1DataSet1Path, this.case1DataSet2Path}, + {this.case2DataSet1Path, this.case2DataSet2Path}, + {this.case3DataSet1Path, this.case3DataSet2Path}}; + + String lastCaseName = null; + Path[] lastPathsForCase = null; + //iterate over the collections above, creating cases, and storing + // just one of them for future reference + for (int i = 0; i < cases.length; i++) { + String caseName = cases[i]; + Path[] pathsForCase = paths[i]; + + if (caseName.equals(caseReferenceToStore)) { + //put aside and do this one last so we can hang onto the case + lastCaseName = caseName; + lastPathsForCase = pathsForCase; + } else { + //dont hang onto this case; close it + this.createCase(caseName, ingestJobSettings, false, pathsForCase); + } + } + + if (lastCaseName != null && lastPathsForCase != null) { + //hang onto this caes and dont close it + currentCase = this.createCase(lastCaseName, ingestJobSettings, true, lastPathsForCase); + } + + if (currentCase == null) { + Assert.fail(new IllegalArgumentException("caseReferenceToStore should be one of: CASE1, CASE2, CASE3")); + return null; + } else { + return currentCase; + } + } + + private Case createCase(String caseName, IngestJobSettings ingestJobSettings, boolean keepAlive, Path... dataSetPaths) throws TskCoreException { + + Case caze = CaseUtils.createAsCurrentCase(caseName); + for (Path dataSetPath : dataSetPaths) { + IngestUtils.addDataSource(this.imageDSProcessor, dataSetPath); + } + if (ingestJobSettings != null) { + IngestUtils.runIngestJob(caze.getDataSources(), ingestJobSettings); + } + if (keepAlive) { + return caze; + } else { + CaseUtils.closeCurrentCase(false); + return null; + } + } + + //TODO refactor + static boolean verifyInstanceExistanceAndCount(CommonAttributeSearchResults searchDomain, String fileName, String dataSource, String crCase, int instanceCount){ + + int tally = 0; + + for(Map.Entry> entry : searchDomain.getMetadata().entrySet()){ + + for(CommonAttributeValue value : entry.getValue()){ + + for(AbstractCommonAttributeInstance commonAttribute : value.getInstances()){ + + if(commonAttribute instanceof CentralRepoCommonAttributeInstance){ + CentralRepoCommonAttributeInstance results = (CentralRepoCommonAttributeInstance) commonAttribute; + for (DisplayableItemNode din : results.generateNodes()){ + + if(din instanceof CentralRepoCommonAttributeInstanceNode){ + + CentralRepoCommonAttributeInstanceNode node = (CentralRepoCommonAttributeInstanceNode) din; + CorrelationAttributeInstance instance = node.getCorrelationAttributeInstance(); + + final String fullPath = instance.getFilePath(); + final File testFile = new File(fullPath); + + final String testCaseName = instance.getCorrelationCase().getDisplayName(); + + final String testFileName = testFile.getName(); + + final String testDataSource = instance.getCorrelationDataSource().getName(); + + boolean sameFileName = testFileName.equalsIgnoreCase(fileName); + boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); + boolean sameCrCase = testCaseName.equalsIgnoreCase(crCase); + + if( sameFileName && sameDataSource && sameCrCase){ + tally++; + } + } + + if(din instanceof CaseDBCommonAttributeInstanceNode){ + + CaseDBCommonAttributeInstanceNode node = (CaseDBCommonAttributeInstanceNode) din; + AbstractFile file = node.getContent(); + + final String testFileName = file.getName(); + final String testCaseName = node.getCase(); + final String testDataSource = node.getDataSource(); + + boolean sameFileName = testFileName.equalsIgnoreCase(fileName); + boolean sameCaseName = testCaseName.equalsIgnoreCase(crCase); + boolean sameDataSource = testDataSource.equalsIgnoreCase(dataSource); + + if(sameFileName && sameDataSource && sameCaseName){ + tally++; + } + } + } + } else { + Assert.fail("Unable to cast AbstractCommonAttributeInstanceNode to InterCaseCommonAttributeSearchResults."); + } + } + } + } + + return tally == instanceCount; + } + + /** + * Close the currently open case, delete the case directory, delete the + * central repo db. + */ + void tearDown() { + + CaseUtils.closeCurrentCase(false); + + String[] cases = new String[]{CASE1,CASE2,CASE3}; + + try { + for(String caze : cases){ + CaseUtils.deleteCaseDir(new File(caze)); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java similarity index 60% rename from Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java rename to Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java index 66a68f3a38..82c38cdf56 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseTestUtils.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2018 Basis Technology Corp. @@ -34,10 +33,10 @@ import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadata; +import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeInstance; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader; -import org.sleuthkit.autopsy.commonfilesearch.FileInstanceMetadata; -import org.sleuthkit.autopsy.commonfilesearch.Md5Metadata; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValue; import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -69,8 +68,8 @@ import org.sleuthkit.datamodel.TskCoreException; * set 4 * - file.dat (empty file) */ -class IntraCaseUtils { - +class IntraCaseTestUtils { + private static final String CASE_NAME = "IntraCaseCommonFilesSearchTest"; static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); @@ -78,48 +77,68 @@ class IntraCaseUtils { private final Path imagePath2; private final Path imagePath3; private final Path imagePath4; - + static final String IMG = "IMG_6175.jpg"; static final String DOC = "BasicStyleGuide.doc"; static final String PDF = "adsf.pdf"; //not a typo - it appears this way in the test image static final String EMPTY = "file.dat"; - - static final String SET1 = "commonfiles_image1_v1.vhd"; - static final String SET2 = "commonfiles_image2_v1.vhd"; - static final String SET3 = "commonfiles_image3_v1.vhd"; - static final String SET4 = "commonfiles_image4_v1.vhd"; - + + static final String SET1 = "CommonFiles_img1_v1.vhd"; + static final String SET2 = "CommonFiles_img2_v1.vhd"; + static final String SET3 = "CommonFiles_img3_v1.vhd"; + static final String SET4 = "CommonFiles_img4_v1.vhd"; + private final DataSourceLoader dataSourceLoader; - + private final String caseName; - IntraCaseUtils(NbTestCase nbTestCase, String caseName){ - imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image1_v1.vhd"); - imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image2_v1.vhd"); - imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image3_v1.vhd"); - imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image4_v1.vhd"); + IntraCaseTestUtils(NbTestCase nbTestCase, String caseName){ + this.imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), SET1); + this.imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); + this.imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); + this.imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); this.dataSourceLoader = new DataSourceLoader(); - + this.caseName = caseName; } - - void setUp(){ - CaseUtils.createAsCurrentCase(this.caseName); - + + void setUp() { + this.createAsCurrentCase(); + final ImageDSProcessor imageDSProcessor = new ImageDSProcessor(); - IngestUtils.addDataSource(imageDSProcessor, imagePath1); - IngestUtils.addDataSource(imageDSProcessor, imagePath2); - IngestUtils.addDataSource(imageDSProcessor, imagePath3); + this.addImageOne(imageDSProcessor); + this.addImageTwo(imageDSProcessor); + this.addImageThree(imageDSProcessor); + this.addImageFour(imageDSProcessor); + } + + void addImageFour(final ImageDSProcessor imageDSProcessor) { IngestUtils.addDataSource(imageDSProcessor, imagePath4); } - - Map getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException{ + + void addImageThree(final ImageDSProcessor imageDSProcessor) { + IngestUtils.addDataSource(imageDSProcessor, imagePath3); + } + + void addImageTwo(final ImageDSProcessor imageDSProcessor) { + IngestUtils.addDataSource(imageDSProcessor, imagePath2); + } + + void addImageOne(final ImageDSProcessor imageDSProcessor) { + IngestUtils.addDataSource(imageDSProcessor, imagePath1); + } + + void createAsCurrentCase() { + CaseUtils.createAsCurrentCase(this.caseName); + } + + Map getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException { return this.dataSourceLoader.getDataSourceMap(); } - - void tearDown(){ + + void tearDown() { CaseUtils.closeCurrentCase(false); try { CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH.toFile()); @@ -128,66 +147,76 @@ class IntraCaseUtils { //does not represent a failure in the common files search feature } } - + /** - * Verify that the given file appears a precise number times in the given + * Verify that the given file appears a precise number times in the given * data source. - * - * @param files search domain - * @param objectIdToDataSource mapping of file ids to data source names - * @param name name of file to search for + * + * @param searchDomain search domain + * @param objectIdToDataSourceMap mapping of file ids to data source names + * @param fileName name of file to search for * @param dataSource name of data source where file should appear - * @param count number of appearances of the given file - * @return true if a file with the given name exists the specified number - * of times in the given data source + * @param instanceCount number of appearances of the given file + * @return true if a file with the given name exists the specified number of + * times in the given data source */ - static boolean verifyFileExistanceAndCount(List files, Map objectIdToDataSource, String name, String dataSource, int count) { + static boolean verifyInstanceExistanceAndCount(List searchDomain, Map objectIdToDataSourceMap, String fileName, String dataSource, int instanceCount) { int tally = 0; - for (AbstractFile file : files) { + for (AbstractFile file : searchDomain) { Long objectId = file.getId(); - String fileName = file.getName(); + String name = file.getName(); - String dataSourceName = objectIdToDataSource.get(objectId); + String dataSourceName = objectIdToDataSourceMap.get(objectId); - if (fileName.equals(name) && dataSourceName.equals(dataSource)) { + if (name.equals(name) && dataSourceName.equals(dataSource)) { tally++; } } - return tally == count; + return tally == instanceCount; } /** - * Convenience method which verifies that a file exists within a given data + * Convenience method which verifies that a file exists within a given data * source exactly once. - * + * * @param files search domain * @param objectIdToDataSource mapping of file ids to data source names * @param name name of file to search for * @param dataSource name of data source where file should appear - * @return true if a file with the given name exists once in the given data - * source + * @return true if a file with the given name exists once in the given data + * source */ - static boolean verifySingularFileExistance(List files, Map objectIdToDataSource, String name, String dataSource) { - return verifyFileExistanceAndCount(files, objectIdToDataSource, name, dataSource, 1); + static boolean verifySingularInstanceExistance(List files, Map objectIdToDataSource, String name, String dataSource) { + return verifyInstanceExistanceAndCount(files, objectIdToDataSource, name, dataSource, 1); } - - static Map mapFileInstancesToDataSources(CommonFilesMetadata metadata) { + + /** + * Create a convenience lookup table mapping file instance object ids to + * the data source they appear in. + * + * @param metadata object returned by the code under test + * @return mapping of objectId to data source name + */ + static Map mapFileInstancesToDataSources(CommonAttributeSearchResults metadata) { Map instanceIdToDataSource = new HashMap<>(); - for (Map.Entry entry : metadata.getMetadata().entrySet()) { - for (FileInstanceMetadata md : entry.getValue().getMetadata()) { - instanceIdToDataSource.put(md.getObjectId(), md.getDataSourceName()); + for (Map.Entry> entry : metadata.getMetadata().entrySet()) { + for (CommonAttributeValue md : entry.getValue()) { + for (AbstractCommonAttributeInstance fim : md.getInstances()) { + instanceIdToDataSource.put(fim.getAbstractFileObjectId(), fim.getDataSource()); + } } } return instanceIdToDataSource; } + static List getFiles(Set objectIds) { List files = new ArrayList<>(objectIds.size()); @@ -203,18 +232,18 @@ class IntraCaseUtils { return files; } - - static Long getDataSourceIdByName(String name, Map dataSources){ - - if(dataSources.containsValue(name)){ - for(Map.Entry dataSource : dataSources.entrySet()){ - if(dataSource.getValue().equals(name)){ + + static Long getDataSourceIdByName(String name, Map dataSources) { + + if (dataSources.containsValue(name)) { + for (Map.Entry dataSource : dataSources.entrySet()) { + if (dataSource.getValue().equals(name)) { return dataSource.getKey(); } } } else { throw new IndexOutOfBoundsException(String.format("Name should be one of: {0}", String.join(",", dataSources.values()))); - } + } return null; } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java new file mode 100644 index 0000000000..f4c397af8d --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSourcesIntraCaseTests.java @@ -0,0 +1,129 @@ +/* + * + * 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.commonfilessearch; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import junit.framework.Test; +import org.netbeans.junit.NbModuleSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.AllIntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseTestUtils.*; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Ensures that matches only are found for files which appear in at least two + * data sources. + * + * The two datasources used here have no common files. One of the data sources + * has two identical files within it. This should not count as a match. + * + * None of the test files should be found in the results of this test. + */ +public class MatchesInAtLeastTwoSourcesIntraCaseTests extends NbTestCase { + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(MatchesInAtLeastTwoSourcesIntraCaseTests.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + private final IntraCaseTestUtils utils; + + public MatchesInAtLeastTwoSourcesIntraCaseTests(String name) { + super(name); + + this.utils = new IntraCaseTestUtils(this, "MatchesInAtLeastTwoSources"); + } + + @Override + public void setUp() { + this.utils.createAsCurrentCase(); + + final ImageDSProcessor imageDSProcessor = new ImageDSProcessor(); + + this.utils.addImageOne(imageDSProcessor); + this.utils.addImageFour(imageDSProcessor); + + IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); + IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()); + + ArrayList templates = new ArrayList<>(); + templates.add(hashLookupTemplate); + templates.add(mimeTypeLookupTemplate); + + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithHashAndFileTypeIntraCaseTests.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates); + + try { + IngestUtils.runIngestJob(Case.getCurrentCaseThrows().getDataSources(), ingestJobSettings); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + @Override + public void tearDown() { + this.utils.tearDown(); + } + + public void testOne() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); + + Map objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata); + + List files = IntraCaseTestUtils.getFiles(objectIdToDataSource.keySet()); + + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, IMG, SET1, 0)); + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, IMG, SET4, 0)); + + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, DOC, SET1, 0)); + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, DOC, SET4, 0)); + + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, EMPTY, SET1, 0)); + assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, EMPTY, SET4, 0)); + + } catch (NoCurrentCaseException | TskCoreException | SQLException | EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCases.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java similarity index 63% rename from Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCases.java rename to Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java index 9bd7a02bae..26c0d66296 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCases.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCasesIntraCaseTests.java @@ -19,7 +19,6 @@ */ package org.sleuthkit.autopsy.commonfilessearch; -import java.sql.SQLException; import java.util.Map; import static junit.framework.Assert.assertEquals; import junit.framework.Test; @@ -27,14 +26,12 @@ import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbTestCase; import org.openide.util.Exceptions; import org.python.icu.impl.Assert; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.commonfilesearch.AllDataSourcesCommonFilesAlgorithm; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadata; -import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadataBuilder; -import org.sleuthkit.autopsy.commonfilesearch.SingleDataSource; -import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseUtils.SET1; -import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseUtils.getDataSourceIdByName; -import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.commonfilesearch.AllIntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults; +import org.sleuthkit.autopsy.commonfilesearch.IntraCaseCommonAttributeSearcher; +import org.sleuthkit.autopsy.commonfilesearch.SingleIntraCaseCommonAttributeSearcher; +import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseTestUtils.SET1; +import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseTestUtils.getDataSourceIdByName; /** * Test that cases which are created but have not run any ingest modules turn up @@ -45,21 +42,21 @@ import org.sleuthkit.datamodel.TskCoreException; * Add images set 1, set 2, set 3, and set 4 to case. Do not ingest. * */ -public class UningestedCases extends NbTestCase { +public class UningestedCasesIntraCaseTests extends NbTestCase { public static Test suite() { - NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(UningestedCases.class). + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(UningestedCasesIntraCaseTests.class). clusters(".*"). enableModules(".*"); return conf.suite(); } - private final IntraCaseUtils utils; + private final IntraCaseTestUtils utils; - public UningestedCases(String name) { + public UningestedCasesIntraCaseTests(String name) { super(name); - this.utils = new IntraCaseUtils(this, "UningestedCasesTests"); + this.utils = new IntraCaseTestUtils(this, "UningestedCasesTests"); } @Override @@ -80,13 +77,13 @@ public class UningestedCases extends NbTestCase { try { Map dataSources = this.utils.getDataSourceMap(); - CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, false, false); - CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); + IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false); + CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles(); int resultCount = metadata.size(); assertEquals(resultCount, 0); - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + } catch (Exception ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -100,13 +97,13 @@ public class UningestedCases extends NbTestCase { Map dataSources = this.utils.getDataSourceMap(); Long first = getDataSourceIdByName(SET1, dataSources); - CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(first, dataSources, false, false); - CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); + IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false); + CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles(); int resultCount = metadata.size(); assertEquals(resultCount, 0); - } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + } catch (Exception ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 813c09334a..0bb8144527 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -23,6 +23,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import junit.framework.Test; import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbTestCase; @@ -37,13 +38,16 @@ import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; +/** + * Functional tests for embedded files. + */ public class EmbeddedFileTest extends NbTestCase { private static final String CASE_NAME = "EmbeddedFileTest"; - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "EmbeddedIM_img2_v1.vhd"); public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; private static final int DEEP_FOLDER_COUNT = 25; private Case openCase; @@ -90,13 +94,16 @@ public class EmbeddedFileTest extends NbTestCase { CaseUtils.closeCurrentCase(testSucceeded); } - public void testEncryption() { + public void testEncryptionAndZipBomb() { try { List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'"); String protectedName1 = "password_protected.zip"; String protectedName2 = "level1_protected.zip"; String protectedName3 = "42.zip"; - assertEquals(2207, results.size()); + String depthZipBomb = "DepthTriggerZipBomb.zip"; + String ratioZipBomb = "RatioTriggerZipBomb.zip"; + int zipBombs = 0; + assertEquals(2221, results.size()); int passwdProtectedZips = 0; for (AbstractFile file : results) { //.zip file has artifact TSK_ENCRYPTION_DETECTED @@ -107,6 +114,15 @@ public class EmbeddedFileTest extends NbTestCase { assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()); passwdProtectedZips++; } + } else if (file.getName().equalsIgnoreCase(depthZipBomb) || file.getName().equalsIgnoreCase(ratioZipBomb)){ + ArrayList artifacts = file.getAllArtifacts(); + assertEquals(1, artifacts.size()); + for (BlackboardArtifact artifact : artifacts) { + assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()); + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); + assertNotNull("Possible Zip Bomb", attribute); + zipBombs++; + } } else {//No other files have artifact defined assertEquals(0, file.getAllArtifacts().size()); } @@ -115,6 +131,8 @@ public class EmbeddedFileTest extends NbTestCase { } //Make sure 3 password protected zip files have been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing. assertEquals(3, passwdProtectedZips); + //Make sure 2 zip bomb files have been tested: DepthTriggerZipBomb.zip and RatioTriggerZipBomb.zip. + assertEquals(2, zipBombs); } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index fcb2b8a4ca..16b0f2f4a4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -50,10 +50,13 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +/** + * Functional tests for ingest file filters. + */ public class IngestFileFiltersTest extends NbTestCase { - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); - private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "local_files_test.zip"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"IngestFilters_img1_v1.img"); + private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "IngestFilters_local1_v1.zip"); private boolean testSucceeded; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index ef639bec5f..dc1f85afcd 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -47,6 +47,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; +/** + * Functional tests for Encryption Detection. + */ public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_DETECTION_CASE_NAME = "testBitlockerEncryption"; @@ -54,10 +57,10 @@ public class EncryptionDetectionTest extends NbTestCase { private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; private static final String SQLCIPHER_DETECTION_CASE_NAME = "SQLCipherDetectionTest"; - private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); - private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); - private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_sqlcipher_test.vhd"); + private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "BitlockerDetection_img1_v1.vhd"); + private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "PasswordDetection_img1_v1.img"); + private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "VeracryptDetection_img1_v1.vhd"); + private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "SqlCipherDetection_img1_v1.vhd"); private boolean testSucceeded; diff --git a/CoreLibs/manifest.mf b/CoreLibs/manifest.mf index e2386f67ce..24af339d11 100644 --- a/CoreLibs/manifest.mf +++ b/CoreLibs/manifest.mf @@ -1,8 +1,8 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.corelibs/3 -OpenIDE-Module-Implementation-Version: 4 +OpenIDE-Module-Implementation-Version: 5 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/corelibs/Bundle.properties -OpenIDE-Module-Specification-Version: 1.1 +OpenIDE-Module-Specification-Version: 1.2 AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 77a432d10c..08ccc6fea1 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -56,7 +56,7 @@ file.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10 file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar -file.reference.opencv-2413.jar=release/modules/ext/opencv-2413.jar +file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.3.jar file.reference.platform-3.4.0.jar=release/modules/ext/platform-3.4.0.jar file.reference.poi-3.17.jar=release/modules/ext/poi-3.17.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 8ed00c1826..b2aa57c54e 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -746,8 +746,8 @@ release/modules/ext/jfxtras-common-8.0-r4.jar - ext/opencv-2413.jar - release/modules/ext/opencv-2413.jar + ext/opencv-248.jar + release/modules/ext/opencv-248.jar ext/jsr305-1.3.9.jar diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index 6d3d3f6730..c0a18a9922 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -135,7 +135,7 @@ 10 - 10.11 + 10.12 @@ -144,7 +144,7 @@ 3 - 1.1 + 1.2 diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java index 9830cecbe9..e62c58343c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AddArchiveTask.java @@ -33,7 +33,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.LocalDiskDSProcessor; import org.sleuthkit.autopsy.casemodule.LocalFilesDSProcessor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java index 1cc10fab76..d744abe8b4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -125,18 +125,6 @@ final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents - /** - * Get the AutoIngestJob for the currently selected node of this panel. - * - * @return AutoIngestJob which is currently selected in this panel - */ - AutoIngestJob getSelectedAutoIngestJob() { - Node[] selectedRows = explorerManager.getSelectedNodes(); - if (selectedRows.length == 1) { - return ((JobNode) selectedRows[0]).getAutoIngestJob(); - } - return null; - } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 87c805fa12..1e1b82d8e7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -338,7 +338,7 @@ final class AutoIngestAdminActions { dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); AutoIngestManager.CaseDeletionResult result = dashboard.getMonitor().deleteCase(job); - dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor().getJobsSnapshot())); + dashboard.getCompletedJobsPanel().refresh(new AutoIngestNodeRefreshEvents.RefreshChildrenEvent(dashboard.getMonitor())); dashboard.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); if (AutoIngestManager.CaseDeletionResult.FAILED == result) { JOptionPane.showMessageDialog(dashboard, diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java index 3d3ff8951f..1a7f6314a4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java @@ -31,6 +31,7 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa private static final long serialVersionUID = 1L; private final String caseName; private final String nodeName; + private final String userName; /** * Constructs an event that is published when a case is deleted by the @@ -38,11 +39,13 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa * * @param caseName The case name. * @param nodeName The host name of the node that deleted the case. + * @param userName The user that deleted the case */ - AutoIngestCaseDeletedEvent(String caseName, String nodeName) { + AutoIngestCaseDeletedEvent(String caseName, String nodeName, String userName) { super(AutoIngestManager.Event.CASE_DELETED.toString(), null, null); this.caseName = caseName; this.nodeName = nodeName; + this.userName = userName; } String getCaseName() { @@ -52,5 +55,9 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa String getNodeName() { return nodeName; } + + String getUserName() { + return userName; + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 5fbf380601..884846db71 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -27,9 +27,22 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; */ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { + /** + * Possible event types + */ + enum EventType { + CASE_PRIORITIZED, + CASE_DEPRIORITIZED, + JOB_PRIORITIZED, + JOB_DEPRIORITIZED + } + private static final long serialVersionUID = 1L; private final String caseName; private final String nodeName; + private final String userName; + private final EventType eventType; + private final String dataSource; /** * Constructs an event published when an automated ingest manager @@ -37,11 +50,17 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement * * @param caseName The name of the case. * @param nodeName The host name of the node that prioritized the case. + * @param userName The logged in user + * @param eventType The type of prioritization event + * @pamam dataSource List of data sources that were prioritized/deprioritized (if it wasn't the whole case) */ - public AutoIngestCasePrioritizedEvent(String nodeName, String caseName) { + public AutoIngestCasePrioritizedEvent(String nodeName, String caseName, String userName, EventType eventType, String dataSource) { super(AutoIngestManager.Event.CASE_PRIORITIZED.toString(), null, null); this.caseName = caseName; this.nodeName = nodeName; + this.userName = userName; + this.eventType = eventType; + this.dataSource = dataSource; } /** @@ -61,5 +80,31 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement public String getNodeName() { return nodeName; } + + /** + * Gets the user logged in to the node that prioritized the case. + * + * @return The user name + */ + String getUserName() { + return userName; + } + /** + * Gets the type of prioritization + * + * @return The type + */ + EventType getEventType() { + return eventType; + } + + /** + * Gets the list of data sources (if applicable) + * + * @return The data sources + */ + String getDataSources() { + return dataSource; + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 402bae91a9..db8e25a592 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -142,6 +142,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final String UPDATE_TASKS_THREAD_NAME = "AID-update-tasks-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); + private static final String RUNNING_AS_SERVICE_PROPERTY = "autoingest.runningasservice"; private static final Logger sysLogger = AutoIngestSystemLogger.getLogger(); private static AutoIngestControlPanel instance; private final DefaultTableModel pendingTableModel; @@ -269,6 +270,12 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { initCompletedJobsTable(); initButtons(); completedTable.getRowSorter().toggleSortOrder(JobsTableModelColumns.COMPLETED_TIME.ordinal()); + + // Start auto ingest immediately if we are running as a service. + if (System.getProperty(RUNNING_AS_SERVICE_PROPERTY, "false").equalsIgnoreCase("true")) { + startUp(); + } + /* * Must set this flag, otherwise pop up menus don't close properly. */ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 49e9cdbd17..031aa64daf 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -38,7 +38,6 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.RefreshChildrenEvent; /** @@ -257,7 +256,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - if (arg instanceof JobsSnapshot) { + if (arg == null ) { EventQueue.invokeLater(() -> { refreshTables(); }); @@ -271,9 +270,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param nodeStateSnapshot The jobs snapshot. */ void refreshTables() { - pendingJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); - runningJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); - completedJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor.getJobsSnapshot())); + pendingJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor)); + runningJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor)); + completedJobsPanel.refresh(new RefreshChildrenEvent(autoIngestMonitor)); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java index a264a202e3..e466bf0354 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java @@ -25,8 +25,20 @@ import java.io.Serializable; */ public final class AutoIngestJobCancelEvent extends AutoIngestJobEvent implements Serializable{ private static final long serialVersionUID = 1L; + private final String nodeName; + private final String userName; - public AutoIngestJobCancelEvent(AutoIngestJob job) { + public AutoIngestJobCancelEvent(AutoIngestJob job, String nodeName, String userName) { super(AutoIngestManager.Event.CANCEL_JOB, job); + this.nodeName = nodeName; + this.userName = userName; + } + + String getNodeName() { + return nodeName; + } + + String getUserName() { + return userName; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java index 817e153c7e..a72df908a2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java @@ -24,9 +24,21 @@ import java.io.Serializable; * Event published to reprocess an AutoIngestJob. */ public final class AutoIngestJobReprocessEvent extends AutoIngestJobEvent implements Serializable{ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; + private final String nodeName; + private final String userName; - public AutoIngestJobReprocessEvent(AutoIngestJob job) { + public AutoIngestJobReprocessEvent(AutoIngestJob job, String nodeName, String userName) { super(AutoIngestManager.Event.REPROCESS_JOB, job); + this.nodeName = nodeName; + this.userName = userName; + } + + String getNodeName() { + return nodeName; + } + + String getUserName() { + return userName; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index a7b2ad6116..d5bfbd13d8 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Objects; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -33,9 +34,10 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.Stage; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; +import org.sleuthkit.autopsy.ingest.DataSourceIngestJob; /** * A node which represents all AutoIngestJobs of a given AutoIngestJobStatus. @@ -61,13 +63,13 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. * - * @param snapshot the snapshot which contains the AutoIngestJobs + * @param monitor the monitor which gives access to the AutoIngestJobs * @param status the status of the jobs being displayed * @param eventBus the event bus which will be used to send and receive * refresh events */ - AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status, EventBus eventBus) { - super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status, eventBus), false)); + AutoIngestJobsNode(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { + super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), false)); refreshChildrenEventBus = eventBus; } @@ -78,13 +80,106 @@ final class AutoIngestJobsNode extends AbstractNode { refreshChildrenEventBus.post(refreshEvent); } + /** + * The AutoIngestJob class considers auto ingest jobs to be equal if they + * have the same manifest path. This is not sufficient for the purposes of + * determining when the state of a job has changed. This class is used to + * distinguish between different auto ingest jobs based on the manifest + * path, the processing stage, the job snapshot and priority. + */ + private static final class AutoIngestJobWrapper implements Comparable { + + private final AutoIngestJob autoIngestJob; + + /** + * We keep our own references to the following job attributes because + * they can be changed by events in other threads which + */ + private final Stage jobStage; + private final List jobSnapshot; + private final Integer jobPriority; + + AutoIngestJobWrapper(AutoIngestJob job) { + autoIngestJob = job; + jobStage = job.getProcessingStage(); + jobSnapshot = job.getIngestJobSnapshots(); + jobPriority = job.getPriority(); + } + + AutoIngestJob getJob() { + return autoIngestJob; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof AutoIngestJobWrapper)) { + return false; + } + + if (this == other) { + return true; + } + + AutoIngestJob thisJob = this.autoIngestJob; + AutoIngestJob otherJob = ((AutoIngestJobWrapper) other).autoIngestJob; + + // Only equal if the manifest paths and processing stage details are the same. + return thisJob.getManifest().getFilePath().equals(otherJob.getManifest().getFilePath()) + && jobStage.equals(((AutoIngestJobWrapper) other).jobStage) + && jobSnapshot.equals(((AutoIngestJobWrapper) other).jobSnapshot) + && jobPriority.equals(((AutoIngestJobWrapper) other).jobPriority); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Objects.hashCode(this.autoIngestJob.getManifest().getFilePath()); + hash = 23 * hash + Objects.hashCode(this.jobStage); + hash = 23 * hash + Objects.hashCode(this.jobSnapshot); + hash = 23 * hash + Objects.hashCode(this.jobPriority); + return hash; + } + + @Override + public int compareTo(AutoIngestJobWrapper o) { + return autoIngestJob.compareTo(o.autoIngestJob); + } + + /** + * The remaining methods simply delegate to the wrapped job. + */ + Manifest getManifest() { + return autoIngestJob.getManifest(); + } + + boolean getErrorsOccurred() { + return autoIngestJob.getErrorsOccurred(); + } + + Date getCompletedDate() { + return autoIngestJob.getCompletedDate(); + } + + AutoIngestJob.StageDetails getProcessingStageDetails() { + return autoIngestJob.getProcessingStageDetails(); + } + + String getProcessingHostName() { + return autoIngestJob.getProcessingHostName(); + } + + Integer getPriority() { + return autoIngestJob.getPriority(); + } + } + /** * A ChildFactory for generating JobNodes. */ - static final class AutoIngestNodeChildren extends ChildFactory { + static final class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobStatus autoIngestJobStatus; - private JobsSnapshot jobsSnapshot; + private AutoIngestMonitor monitor; private final RefreshChildrenSubscriber refreshChildrenSubscriber = new RefreshChildrenSubscriber(); private final EventBus refreshEventBus; @@ -92,43 +187,45 @@ final class AutoIngestJobsNode extends AbstractNode { * Create children nodes for the AutoIngestJobsNode which will each * represent a single AutoIngestJob * - * @param snapshot the snapshot which contains the AutoIngestJobs + * @param monitor the monitor which gives access to the AutoIngestJobs * @param status the status of the jobs being displayed * @param eventBus the event bus which the class registers to for * refresh events */ - AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status, EventBus eventBus) { - jobsSnapshot = snapshot; + AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { + this.monitor = monitor; autoIngestJobStatus = status; refreshEventBus = eventBus; refreshChildrenSubscriber.register(refreshEventBus); } @Override - protected boolean createKeys(List list) { + protected boolean createKeys(List list) { List jobs; switch (autoIngestJobStatus) { case PENDING_JOB: - jobs = jobsSnapshot.getPendingJobs(); + jobs = monitor.getPendingJobs(); Collections.sort(jobs); break; case RUNNING_JOB: - jobs = jobsSnapshot.getRunningJobs(); + jobs = monitor.getRunningJobs(); break; case COMPLETED_JOB: - jobs = jobsSnapshot.getCompletedJobs(); + jobs = monitor.getCompletedJobs(); break; default: jobs = new ArrayList<>(); } if (jobs != null && jobs.size() > 0) { - list.addAll(jobs); + jobs.forEach(j -> { + list.add(new AutoIngestJobWrapper(j)); + }); } return true; } @Override - protected Node createNodeForKey(AutoIngestJob key) { + protected Node createNodeForKey(AutoIngestJobWrapper key) { return new JobNode(key, autoIngestJobStatus, refreshEventBus); } @@ -167,7 +264,7 @@ final class AutoIngestJobsNode extends AbstractNode { //Ignore netbeans suggesting this isn't being used, it is used behind the scenes by the EventBus //RefreshChildrenEvents can change which children are present however //RefreshJobEvents and RefreshCaseEvents can still change the order we want to display them in - jobsSnapshot = refreshEvent.getJobsSnapshot(); + monitor = refreshEvent.getMonitor(); refresh(true); } @@ -180,7 +277,7 @@ final class AutoIngestJobsNode extends AbstractNode { */ static final class JobNode extends AbstractNode { - private final AutoIngestJob autoIngestJob; + private final AutoIngestJobWrapper jobWrapper; private final AutoIngestJobStatus jobStatus; private final RefreshNodeSubscriber refreshNodeSubscriber = new RefreshNodeSubscriber(); @@ -191,12 +288,12 @@ final class AutoIngestJobsNode extends AbstractNode { * @param status - the current status of the AutoIngestJob being * represented */ - JobNode(AutoIngestJob job, AutoIngestJobStatus status, EventBus eventBus) { + JobNode(AutoIngestJobWrapper job, AutoIngestJobStatus status, EventBus eventBus) { super(Children.LEAF); jobStatus = status; - autoIngestJob = job; - setName(autoIngestJob.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob - setDisplayName(autoIngestJob.getManifest().getCaseName()); //displays user friendly case name as name + jobWrapper = job; + setName(jobWrapper.toString()); //alows job to be uniquely found by name since it will involve a hash of the AutoIngestJob + setDisplayName(jobWrapper.getManifest().getCaseName()); //displays user friendly case name as name refreshNodeSubscriber.register(eventBus); } @@ -206,7 +303,7 @@ final class AutoIngestJobsNode extends AbstractNode { * @return autoIngestJob */ AutoIngestJob getAutoIngestJob() { - return autoIngestJob; + return jobWrapper.getJob(); } @Override @@ -221,20 +318,20 @@ final class AutoIngestJobsNode extends AbstractNode { s.put(ss); } ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), - autoIngestJob.getManifest().getCaseName())); + jobWrapper.getManifest().getCaseName())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), - autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); + jobWrapper.getManifest().getDataSourcePath().getFileName().toString())); switch (jobStatus) { case PENDING_JOB: ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), - autoIngestJob.getManifest().getDateFileCreated())); + jobWrapper.getManifest().getDateFileCreated())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), - autoIngestJob.getPriority() > 0 ? Bundle.AutoIngestJobsNode_prioritized_true() : Bundle.AutoIngestJobsNode_prioritized_false())); + jobWrapper.getPriority() > 0 ? Bundle.AutoIngestJobsNode_prioritized_true() : Bundle.AutoIngestJobsNode_prioritized_false())); break; case RUNNING_JOB: - AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); + AutoIngestJob.StageDetails status = jobWrapper.getProcessingStageDetails(); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), - autoIngestJob.getProcessingHostName())); + jobWrapper.getProcessingHostName())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), status.getDescription())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), @@ -242,11 +339,11 @@ final class AutoIngestJobsNode extends AbstractNode { break; case COMPLETED_JOB: ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), - autoIngestJob.getManifest().getDateFileCreated())); + jobWrapper.getManifest().getDateFileCreated())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), - autoIngestJob.getCompletedDate())); + jobWrapper.getCompletedDate())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), - autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); + jobWrapper.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); break; default: } @@ -259,24 +356,24 @@ final class AutoIngestJobsNode extends AbstractNode { if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { switch (jobStatus) { case PENDING_JOB: - actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob)); - actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob)); - PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob); - deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(new PrioritizationAction.PrioritizeJobAction(jobWrapper.getJob())); + actions.add(new PrioritizationAction.PrioritizeCaseAction(jobWrapper.getJob())); + PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(jobWrapper.getJob()); + deprioritizeJobAction.setEnabled(jobWrapper.getPriority() > 0); actions.add(deprioritizeJobAction); - PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob); - deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0); + PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(jobWrapper.getJob()); + deprioritizeCaseAction.setEnabled(jobWrapper.getPriority() > 0); actions.add(deprioritizeCaseAction); break; case RUNNING_JOB: - actions.add(new AutoIngestAdminActions.ProgressDialogAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.CancelJobAction(autoIngestJob)); + actions.add(new AutoIngestAdminActions.ProgressDialogAction(jobWrapper.getJob())); + actions.add(new AutoIngestAdminActions.CancelJobAction(jobWrapper.getJob())); // actions.add(new AutoIngestAdminActions.CancelModuleAction()); break; case COMPLETED_JOB: - actions.add(new AutoIngestAdminActions.ReprocessJobAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.DeleteCaseAction(autoIngestJob)); - actions.add(new AutoIngestAdminActions.ShowCaseLogAction(autoIngestJob)); + actions.add(new AutoIngestAdminActions.ReprocessJobAction(jobWrapper.getJob())); + actions.add(new AutoIngestAdminActions.DeleteCaseAction(jobWrapper.getJob())); + actions.add(new AutoIngestAdminActions.ShowCaseLogAction(jobWrapper.getJob())); break; default: } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index a3cdc1c51d..fb6f79ab0c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -171,7 +171,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa ((AutoIngestJobsNode) explorerManager.getRootContext()).refresh(refreshEvent); } else { //Make a new AutoIngestJobsNode with it's own EventBus and set it as the root context - explorerManager.setRootContext(new AutoIngestJobsNode(refreshEvent.getJobsSnapshot(), status, new EventBus("AutoIngestJobsNodeEventBus"))); + explorerManager.setRootContext(new AutoIngestJobsNode(refreshEvent.getMonitor(), status, new EventBus("AutoIngestJobsNodeEventBus"))); } outline.setRowSelectionAllowed(true); outline.setFocusable(true); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e30c795013..8dddd2a3e6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -389,6 +389,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteJobCancelledEvent(AutoIngestJobCancelEvent event) { AutoIngestJob job = event.getJob(); if (job != null && job.getProcessingHostName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { + sysLogger.log(Level.INFO, "Received cancel job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{job.getManifest().getDataSourceFileName(), job.getManifest().getCaseName(), event.getUserName(), event.getNodeName()}); if (event.getJob().equals(currentJob)) { cancelCurrentJob(); } @@ -403,6 +405,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteJobReprocessEvent(AutoIngestJobReprocessEvent event) { synchronized (jobsLock) { AutoIngestJob job = event.getJob(); + sysLogger.log(Level.INFO, "Received reprocess job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{job.getManifest().getDataSourceFileName(), job.getManifest().getCaseName(), event.getUserName(), event.getNodeName()}); if (completedJobs.contains(job)) { // Remove from completed jobs table. completedJobs.remove(job); @@ -423,6 +427,29 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param event A prioritization event from another auto ingest node. */ private void handleRemoteCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { + switch (event.getEventType()) { + case CASE_PRIORITIZED: + sysLogger.log(Level.INFO, "Received prioritize case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case CASE_DEPRIORITIZED: + sysLogger.log(Level.INFO, "Received deprioritize case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case JOB_PRIORITIZED: + sysLogger.log(Level.INFO, "Received prioritize job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{event.getDataSources(), event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case JOB_DEPRIORITIZED: + sysLogger.log(Level.INFO, "Received deprioritize job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{event.getDataSources(), event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + default: + sysLogger.log(Level.WARNING, "Received invalid prioritization event from user {0} on machine {1}", + new Object[]{event.getUserName(), event.getNodeName()}); + break; + } + String hostName = event.getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); scanInputDirsNow(); @@ -437,6 +464,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param event A case deleted event from another auto ingest node. */ private void handleRemoteCaseDeletedEvent(AutoIngestCaseDeletedEvent event) { + sysLogger.log(Level.INFO, "Received delete case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); String hostName = event.getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); scanInputDirsNow(); @@ -454,13 +483,21 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteNodeControlEvent(AutoIngestNodeControlEvent event) { if (event.getTargetNodeName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { - sysLogger.log(Level.INFO, "Received {0} event from user {1} on machine {2}", new Object[]{event.getControlEventType().toString(), event.getUserName(), event.getOriginatingNodeName()}); + sysLogger.log(Level.INFO, "Received {0} event from user {1} on machine {2}", new Object[] {event.getControlEventType().toString(), event.getUserName(), event.getOriginatingNodeName()}); switch (event.getControlEventType()) { case PAUSE: pause(); break; case RESUME: resume(); + + /** + * Kick off an immediate scan so that the next pending job + * will be picked up sooner than having to wait for the + * InputDirScannerTask to run again. + */ + scanInputDirsNow(); + break; case SHUTDOWN: shutDown(); @@ -621,6 +658,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } jobProcessingTask.requestResume(); } + + /** + * Get the name of the currently logged in user + * @return + */ + static String getSystemUserNameProperty() { + return System.getProperty("user.name"); + } + /** * Removes the priority (set to zero) of all pending ingest jobs for a @@ -661,7 +707,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToDeprioritize.isEmpty()) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } } @@ -710,7 +757,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToPrioritize.isEmpty()) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } } @@ -760,8 +808,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (null != jobToDeprioritize) { final String caseName = jobToDeprioritize.getManifest().getCaseName(); + final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } } @@ -816,8 +866,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (null != jobToPrioritize) { final String caseName = jobToPrioritize.getManifest().getCaseName(); + final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } } @@ -970,7 +1022,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen casesToManifests.remove(caseName); } - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, getSystemUserNameProperty())); setChanged(); notifyObservers(Event.CASE_DELETED); return result; @@ -1796,13 +1848,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen */ setChanged(); notifyObservers(Event.RESUMED); - - /** - * Publish an event to let remote listeners know that the - * node has been resumed. - */ - eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME)); } + /** + * Publish an event to let remote listeners know that the node + * has been resumed. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.RESUMED, AutoIngestManager.LOCAL_HOST_NAME)); + pauseLock.notifyAll(); } } @@ -2028,14 +2080,21 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * while reading the lock data */ private Lock dequeueAndLockNextJob() throws CoordinationServiceException, InterruptedException { - sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job"); + sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job, enforcing max jobs per case"); Lock manifestLock; synchronized (jobsLock) { - manifestLock = dequeueAndLockNextJobHelper(); + manifestLock = dequeueAndLockNextJob(true); if (null != manifestLock) { sysLogger.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); } else { sysLogger.log(Level.INFO, "No ready job"); + sysLogger.log(Level.INFO, "Checking pending jobs queue for ready job, not enforcing max jobs per case"); + manifestLock = dequeueAndLockNextJob(false); + if (null != manifestLock) { + sysLogger.log(Level.INFO, "Dequeued job for {0}", currentJob.getManifest().getFilePath()); + } else { + sysLogger.log(Level.INFO, "No ready job"); + } } } return manifestLock; @@ -2047,6 +2106,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * queue, made the current job, and a coordination service lock on the * manifest for the job is returned. * + * @param enforceMaxJobsPerCase Whether or not to enforce the maximum + * concurrent jobs per case setting. * * @return A manifest file lock if a ready job was found, null * otherwise. @@ -2057,7 +2118,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @throws InterruptedException if the thread is interrupted * while reading the lock data */ - private Lock dequeueAndLockNextJobHelper() throws CoordinationServiceException, InterruptedException { + private Lock dequeueAndLockNextJob(boolean enforceMaxJobsPerCase) throws CoordinationServiceException, InterruptedException { Lock manifestLock = null; synchronized (jobsLock) { Iterator iterator = pendingJobs.iterator(); @@ -2087,6 +2148,19 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen continue; } + if (enforceMaxJobsPerCase) { + int currentJobsForCase = 0; + for (AutoIngestJob runningJob : hostNamesToRunningJobs.values()) { + if (0 == job.getManifest().getCaseName().compareTo(runningJob.getManifest().getCaseName())) { + ++currentJobsForCase; + } + } + if (currentJobsForCase >= AutoIngestUserPreferences.getMaxConcurrentJobsForOneCase()) { + manifestLock.release(); + manifestLock = null; + continue; + } + } iterator.remove(); currentJob = job; break; @@ -2390,7 +2464,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen throw new CaseManagementException(String.format("Error creating solr settings file for case %s for %s", caseName, manifest.getFilePath()), ex); } catch (CaseActionException ex) { throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); - } + } } else { throw new CaseManagementException(String.format("Timed out acquiring case name lock for %s for %s", caseName, manifest.getFilePath())); } @@ -2724,6 +2798,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen sysLogger.log(Level.INFO, "Finished ingest modules analysis for {0} ", manifestPath); IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { + AutoIngestJobLogger nestedJobLogger = new AutoIngestJobLogger(manifestPath, snapshot.getDataSource(), caseDirectoryPath); if (!snapshot.isCancelled()) { List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); if (!cancelledModules.isEmpty()) { @@ -2732,15 +2807,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen setCaseNodeDataErrorsOccurred(caseDirectoryPath); for (String module : snapshot.getCancelledDataSourceIngestModules()) { sysLogger.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, manifestPath)); - jobLogger.logIngestModuleCancelled(module); + nestedJobLogger.logIngestModuleCancelled(module); } } - jobLogger.logAnalysisCompleted(); + nestedJobLogger.logAnalysisCompleted(); } else { currentJob.setProcessingStage(AutoIngestJob.Stage.CANCELLING, Date.from(Instant.now())); currentJob.setErrorsOccurred(true); setCaseNodeDataErrorsOccurred(caseDirectoryPath); - jobLogger.logAnalysisCancelled(); + nestedJobLogger.logAnalysisCancelled(); CancellationReason cancellationReason = snapshot.getCancellationReason(); if (CancellationReason.NOT_CANCELLED != cancellationReason && CancellationReason.USER_CANCELLED != cancellationReason) { throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), manifestPath)); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 4e262c738a..cfdf6cf973 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.nio.file.Path; +import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -116,11 +117,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } catch (AutopsyEventException ex) { throw new AutoIngestMonitorException("Failed to open auto ingest event channel", ex); //NON-NLS } - coordSvcQueryExecutor.scheduleWithFixedDelay(new CoordinationServiceQueryTask(), 0, CORRD_SVC_QUERY_INERVAL_MINS, TimeUnit.MINUTES); + coordSvcQueryExecutor.scheduleWithFixedDelay(new StateRefreshTask(), 0, CORRD_SVC_QUERY_INERVAL_MINS, TimeUnit.MINUTES); eventPublisher.addSubscriber(EVENT_LIST, this); - // Publish an event that asks running nodes to send their state. - eventPublisher.publishRemotely(new AutoIngestRequestNodeStateEvent(AutoIngestManager.Event.REPORT_STATE)); + refreshNodeState(); } /** @@ -172,7 +172,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen jobsSnapshot.removePendingJob(event.getJob()); jobsSnapshot.addOrReplaceRunningJob(event.getJob()); setChanged(); - notifyObservers(jobsSnapshot); + notifyObservers(); } } @@ -190,7 +190,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen jobsSnapshot.removePendingJob(job); // Update the state of the existing job in the running jobs table - for (AutoIngestJob runningJob : jobsSnapshot.getRunningJobs()) { + for (AutoIngestJob runningJob : getRunningJobs()) { if (runningJob.equals(job)) { runningJob.setIngestJobsSnapshot(job.getIngestJobSnapshots()); runningJob.setIngestThreadSnapshot(job.getIngestThreadActivitySnapshots()); @@ -200,7 +200,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } } setChanged(); - notifyObservers(jobsSnapshot); + notifyObservers(); } } @@ -216,7 +216,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen jobsSnapshot.removeRunningJob(job); jobsSnapshot.addOrReplaceCompletedJob(job); setChanged(); - notifyObservers(jobsSnapshot); + notifyObservers(); } } @@ -226,7 +226,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @param event A job/case prioritization event. */ private void handleCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { - coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); + coordSvcQueryExecutor.submit(new StateRefreshTask()); } /** @@ -235,7 +235,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @param event A job/case deletion event. */ private void handleCaseDeletedEvent(AutoIngestCaseDeletedEvent event) { - coordSvcQueryExecutor.submit(new CoordinationServiceQueryTask()); + coordSvcQueryExecutor.submit(new StateRefreshTask()); } /** @@ -259,15 +259,35 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /** - * Gets the auto ingest monitor's current snapshot of the pending jobs - * queue, running jobs list, and completed jobs list for an auto ingest - * cluster. + * Gets the snapshot of the pending jobs queue for an auto ingest cluster. * - * @return The snapshot. + * @return The pending jobs queue. */ - JobsSnapshot getJobsSnapshot() { + List getPendingJobs() { synchronized (jobsLock) { - return jobsSnapshot; + return new ArrayList<>(jobsSnapshot.pendingJobs); + } + } + + /** + * Gets the snapshot of the running jobs list for an auto ingest cluster. + * + * @return The running jobs list. + */ + List getRunningJobs() { + synchronized (jobsLock) { + return new ArrayList<>(jobsSnapshot.runningJobs); + } + } + + /** + * Gets the snapshot of the completed jobs list for an auto ingest cluster. + * + * @return The completed jobs list. + */ + List getCompletedJobs() { + synchronized (jobsLock) { + return new ArrayList<>(jobsSnapshot.completedJobs); } } @@ -277,7 +297,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @return */ List getNodeStates() { - return nodeStates.values().stream().collect(Collectors.toList()); + // We only report the state for nodes for which we have received + // a 'state' event in the last 15 minutes. + return nodeStates.values() + .stream() + .filter(s -> s.getLastSeenTime().isAfter(Instant.now().minus(Duration.ofMinutes(15)))) + .collect(Collectors.toList()); } /** @@ -287,13 +312,20 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * * @return The refreshed snapshot. */ - JobsSnapshot refreshJobsSnapshot() { + void refreshJobsSnapshot() { synchronized (jobsLock) { jobsSnapshot = queryCoordinationService(); - return jobsSnapshot; } } + /** + * Ask running auto ingest nodes to report their state. + */ + private void refreshNodeState() { + // Publish an event that asks running nodes to send their state. + eventPublisher.publishRemotely(new AutoIngestRequestNodeStateEvent(AutoIngestManager.Event.REPORT_STATE)); + } + /** * Gets a new snapshot of the pending jobs queue, running jobs list, and * completed jobs list for an auto ingest cluster. @@ -358,13 +390,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @throws AutoIngestMonitorException If there is an error removing the * priority of the jobs for the case. * - * @return The latest jobs snapshot. */ - JobsSnapshot deprioritizeCase(final String caseName) throws AutoIngestMonitorException { + void deprioritizeCase(final String caseName) throws AutoIngestMonitorException { List jobsToDeprioritize = new ArrayList<>(); synchronized (jobsLock) { - for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { + for (AutoIngestJob pendingJob : getPendingJobs()) { if (pendingJob.getManifest().getCaseName().equals(caseName)) { jobsToDeprioritize.add(pendingJob); } @@ -380,16 +411,21 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen throw new AutoIngestMonitorException("Error removing priority for job " + job.toString(), ex); } job.setPriority(DEFAULT_PRIORITY); + + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(job); } /* * Publish a deprioritization event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } - return jobsSnapshot; } } @@ -401,13 +437,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @throws AutoIngestMonitorException If there is an error bumping the * priority of the jobs for the case. * - * @return The latest jobs snapshot. */ - JobsSnapshot prioritizeCase(final String caseName) throws AutoIngestMonitorException { + void prioritizeCase(final String caseName) throws AutoIngestMonitorException { List jobsToPrioritize = new ArrayList<>(); int highestPriority = 0; synchronized (jobsLock) { - for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { + for (AutoIngestJob pendingJob : getPendingJobs()) { if (pendingJob.getPriority() > highestPriority) { highestPriority = pendingJob.getPriority(); } @@ -427,16 +462,21 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen throw new AutoIngestMonitorException("Error bumping priority for job " + job.toString(), ex); } job.setPriority(highestPriority); + + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(job); } /* * Publish a prioritization event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } - return jobsSnapshot; } } @@ -448,15 +488,14 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @throws AutoIngestMonitorException If there is an error removing the * priority of the job. * - * @return The latest jobs snapshot. */ - JobsSnapshot deprioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { + void deprioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { synchronized (jobsLock) { AutoIngestJob jobToDeprioritize = null; /* * Make sure the job is still in the pending jobs queue. */ - for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { + for (AutoIngestJob pendingJob : getPendingJobs()) { if (pendingJob.equals(job)) { jobToDeprioritize = job; break; @@ -464,7 +503,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /* - * If the job was still in the pending jobs queue, bump its + * If the job was still in the pending jobs queue, reset its * priority. */ if (null != jobToDeprioritize) { @@ -478,16 +517,22 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } jobToDeprioritize.setPriority(DEFAULT_PRIORITY); + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(jobToDeprioritize); + /* * Publish a deprioritization event. */ final String caseName = job.getManifest().getCaseName(); + final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } - return jobsSnapshot; } } @@ -499,9 +544,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * @throws AutoIngestMonitorException If there is an error bumping the * priority of the job. * - * @return The latest jobs snapshot. */ - JobsSnapshot prioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { + void prioritizeJob(AutoIngestJob job) throws AutoIngestMonitorException { synchronized (jobsLock) { int highestPriority = 0; AutoIngestJob jobToPrioritize = null; @@ -509,7 +553,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Get the highest known priority and make sure the job is still in * the pending jobs queue. */ - for (AutoIngestJob pendingJob : jobsSnapshot.getPendingJobs()) { + for (AutoIngestJob pendingJob : getPendingJobs()) { if (pendingJob.getPriority() > highestPriority) { highestPriority = pendingJob.getPriority(); } @@ -534,16 +578,22 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } jobToPrioritize.setPriority(highestPriority); + /** + * Update job object in pending jobs queue + */ + jobsSnapshot.addOrReplacePendingJob(jobToPrioritize); + /* * Publish a prioritization event. */ final String caseName = job.getManifest().getCaseName(); + final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } - return jobsSnapshot; } } @@ -554,7 +604,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ void cancelJob(AutoIngestJob job) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job)); + eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } @@ -565,7 +615,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ void reprocessJob(AutoIngestJob job) throws AutoIngestMonitorException { synchronized (jobsLock) { - if (!jobsSnapshot.getCompletedJobs().contains(job)) { + if (!getCompletedJobs().contains(job)) { return; } @@ -599,7 +649,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a reprocess event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job)); + eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } @@ -634,7 +684,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen // Update the state of completed jobs associated with this case to indicate // that the case has been deleted - for (AutoIngestJob completedJob : jobsSnapshot.getCompletedJobs()) { + for (AutoIngestJob completedJob : getCompletedJobs()) { if (caseName.equals(completedJob.getManifest().getCaseName())) { try { completedJob.setProcessingStatus(DELETED); @@ -648,11 +698,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } // Remove jobs associated with this case from the completed jobs collection. - jobsSnapshot.completedJobs.removeIf((AutoIngestJob completedJob) -> - completedJob.getManifest().getCaseName().equals(caseName)); + jobsSnapshot.completedJobs.removeIf((AutoIngestJob completedJob) + -> completedJob.getManifest().getCaseName().equals(caseName)); // Publish a message to update auto ingest nodes. - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); } return CaseDeletionResult.FULLY_DELETED; @@ -666,7 +716,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ private void sendControlEventToNode(ControlEventType eventType, String nodeName) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } @@ -698,25 +748,25 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen } /** - * A task that queries the coordination service for auto ingest manifest - * node data and converts it to auto ingest jobs for publication top its - * observers. + * A task that updates the state maintained by the monitor. At present this + * includes auto ingest job and auto ingest node data. The job data is + * refreshed by querying the coordination service for auto ingest manifest + * nodes. The auto ingest node data is refreshed by publishing a message + * asking all nodes to report their state. */ - private final class CoordinationServiceQueryTask implements Runnable { + private final class StateRefreshTask implements Runnable { - /** - * Queries the coordination service for auto ingest manifest node data - * and converts it to auto ingest jobs for publication top its - * observers. - */ @Override public void run() { if (!Thread.currentThread().isInterrupted()) { - synchronized (jobsLock) { - jobsSnapshot = queryCoordinationService(); - setChanged(); - notifyObservers(jobsSnapshot); - } + // Query coordination service for jobs data. + refreshJobsSnapshot(); + + // Ask running auto ingest nodes to report their status. + refreshNodeState(); + + setChanged(); + notifyObservers(); } } @@ -726,42 +776,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * A snapshot of the pending jobs queue, running jobs list and completed * jobs list for an auto ingest cluster. */ - static final class JobsSnapshot { + private static final class JobsSnapshot { private final Set pendingJobs = new HashSet<>(); private final Set runningJobs = new HashSet<>(); private final Set completedJobs = new HashSet<>(); - /** - * Gets the snapshot of the pending jobs queue for an auto ingest - * cluster. - * - * @return The pending jobs queue. - */ - List getPendingJobs() { - return new ArrayList<>(this.pendingJobs); - } - - /** - * Gets the snapshot of the running jobs list for an auto ingest - * cluster. - * - * @return The running jobs list. - */ - List getRunningJobs() { - return new ArrayList<>(this.runningJobs); - } - - /** - * Gets the snapshot of the completed jobs list for an auto ingest - * cluster. - * - * @return The completed jobs list. - */ - List getCompletedJobs() { - return new ArrayList<>(this.completedJobs); - } - /** * Adds an auto job to the snapshot of the pending jobs queue for an * auto ingest cluster. If an equivalent job already exists, it is @@ -860,6 +880,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen private final String nodeName; private final State nodeState; + private final Instant lastSeenTime; AutoIngestNodeState(String name, Event event) { nodeName = name; @@ -889,6 +910,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen nodeState = State.UNKNOWN; break; } + lastSeenTime = Instant.now(); } String getName() { @@ -898,6 +920,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen State getState() { return nodeState; } + + Instant getLastSeenTime() { + return lastSeenTime; + } } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java index e9f63cc434..fbe7c9671d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java @@ -24,12 +24,12 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; /** * Event published to pause, resume or shutdown an AIN. */ -final class AutoIngestNodeControlEvent extends AutopsyEvent implements Serializable { +public final class AutoIngestNodeControlEvent extends AutopsyEvent implements Serializable { /** * The set of available controls. */ - enum ControlEventType { + public enum ControlEventType { PAUSE, RESUME, SHUTDOWN @@ -41,7 +41,7 @@ final class AutoIngestNodeControlEvent extends AutopsyEvent implements Serializa private final String userName; private final ControlEventType eventType; - AutoIngestNodeControlEvent(ControlEventType eventType, String targetNode, String originatingNode, String userName) { + public AutoIngestNodeControlEvent(ControlEventType eventType, String targetNode, String originatingNode, String userName) { super(eventType.toString(), null, null); this.eventType = eventType; this.targetNodeName = targetNode; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java index 2c554bf00f..338bce31c7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeRefreshEvents.java @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; - /** * Class which contains events to identify what should be refreshed in the * AutoIngestJobsNode @@ -31,19 +29,20 @@ class AutoIngestNodeRefreshEvents { */ static class AutoIngestRefreshEvent { - private final JobsSnapshot jobsSnapshot; + private final AutoIngestMonitor monitor; - AutoIngestRefreshEvent(JobsSnapshot jobs) { - this.jobsSnapshot = jobs; + AutoIngestRefreshEvent(AutoIngestMonitor monitor) { + this.monitor = monitor; } /** - * Get the state of the jobs lists when the event was fired. + * Get the monitor which will provide access to the state of + * the jobs. * * @return */ - JobsSnapshot getJobsSnapshot() { - return this.jobsSnapshot; + AutoIngestMonitor getMonitor() { + return this.monitor; } } @@ -56,8 +55,8 @@ class AutoIngestNodeRefreshEvents { /** * Constructs a RefreshChildrenEvent. */ - RefreshChildrenEvent(JobsSnapshot jobs) { - super(jobs); + RefreshChildrenEvent(AutoIngestMonitor monitor) { + super(monitor); } } @@ -72,11 +71,11 @@ class AutoIngestNodeRefreshEvents { /** * Contructs a RefreshCaseEvent * - * @param jobs The current state of the jobs lists. + * @param monitor The monitor that will provide access to the current state of the jobs lists. * @param name The name of the case whose nodes should be refreshed. */ - RefreshCaseEvent(JobsSnapshot jobs, String name) { - super(jobs); + RefreshCaseEvent(AutoIngestMonitor monitor, String name) { + super(monitor); caseName = name; } @@ -103,11 +102,11 @@ class AutoIngestNodeRefreshEvents { /** * Constructs a RefreshJobEvent. * - * @param jobs The curent state of the jobs lists. + * @param monitor The monitor which will provide access to the current state of the jobs lists. * @param job The job which should be refreshed. */ - RefreshJobEvent(JobsSnapshot jobs, AutoIngestJob job) { - super(jobs); + RefreshJobEvent(AutoIngestMonitor monitor, AutoIngestJob job) { + super(monitor); autoIngestJob = job; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index 3d57057bff..006dc3e580 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -145,7 +145,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { - return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor.getJobsSnapshot(), getJob()); + return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor, getJob()); } } @@ -184,7 +184,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { - return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor.getJobsSnapshot(), getJob()); + return new AutoIngestNodeRefreshEvents.RefreshJobEvent(monitor, getJob()); } } @@ -225,7 +225,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { - return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor.getJobsSnapshot(), getJob().getManifest().getCaseName()); + return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor, getJob().getManifest().getCaseName()); } } @@ -266,7 +266,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent getRefreshEvent(AutoIngestMonitor monitor) { - return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor.getJobsSnapshot(), getJob().getManifest().getCaseName()); + return new AutoIngestNodeRefreshEvents.RefreshCaseEvent(monitor, getJob().getManifest().getCaseName()); } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form index 60674ab755..5e1c5c0a41 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.form @@ -37,8 +37,8 @@ - - + +
@@ -82,95 +82,103 @@ - + - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + - - - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -229,6 +237,16 @@ + + + + + + + + + + @@ -269,6 +287,16 @@ + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java index 042ad04f41..96d5009df2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java @@ -63,6 +63,8 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { initThreadCount(); spSecondsBetweenJobs.setValue(AutoIngestUserPreferences.getSecondsToSleepBetweenCases()); spMaximumRetryAttempts.setValue(AutoIngestUserPreferences.getMaxNumTimesToProcessImage()); + int maxJobsPerCase = AutoIngestUserPreferences.getMaxConcurrentJobsForOneCase(); + spConcurrentJobsPerCase.setValue(maxJobsPerCase); spInputScanInterval.setValue(AutoIngestUserPreferences.getMinutesOfInputScanInterval()); spInputScanInterval.setEnabled(mode == AutoIngestSettingsPanel.OptionsUiMode.AIM); spSecondsBetweenJobs.setEnabled(mode == AutoIngestSettingsPanel.OptionsUiMode.AIM); @@ -81,6 +83,7 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { void store() { AutoIngestUserPreferences.setSecondsToSleepBetweenCases((int) spSecondsBetweenJobs.getValue()); AutoIngestUserPreferences.setMaxNumTimesToProcessImage((int) spMaximumRetryAttempts.getValue()); + AutoIngestUserPreferences.setMaxConcurrentIngestNodesForOneCase((int) spConcurrentJobsPerCase.getValue()); AutoIngestUserPreferences.setMinutesOfInputScanInterval((int) spInputScanInterval.getValue()); UserPreferences.setNumberOfFileIngestThreads((Integer) numberOfFileIngestThreadsComboBox.getSelectedItem()); boolean isChecked = cbTimeoutEnabled.isSelected(); @@ -120,9 +123,11 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { lbInputScanInterval = new javax.swing.JLabel(); lbRetriesAllowed = new javax.swing.JLabel(); lbNumberOfThreads = new javax.swing.JLabel(); + lbConcurrentJobsPerCase = new javax.swing.JLabel(); cbTimeoutEnabled = new javax.swing.JCheckBox(); numberOfFileIngestThreadsComboBox = new javax.swing.JComboBox<>(); lbRestartRequired = new javax.swing.JLabel(); + spConcurrentJobsPerCase = new javax.swing.JSpinner(); spMaximumRetryAttempts = new javax.swing.JSpinner(); spInputScanInterval = new javax.swing.JSpinner(); spTimeoutHours = new javax.swing.JSpinner(); @@ -157,6 +162,9 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { org.openide.awt.Mnemonics.setLocalizedText(lbNumberOfThreads, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.text")); // NOI18N lbNumberOfThreads.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.toolTipText_1")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(lbConcurrentJobsPerCase, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.text")); // NOI18N + lbConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText_1")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(cbTimeoutEnabled, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.text")); // NOI18N cbTimeoutEnabled.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.toolTipText")); // NOI18N cbTimeoutEnabled.addItemListener(new java.awt.event.ItemListener() { @@ -180,6 +188,9 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { lbRestartRequired.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbRestartRequired, org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbRestartRequired.text")); // NOI18N + spConcurrentJobsPerCase.setModel(new javax.swing.SpinnerNumberModel(3, 1, 100, 1)); + spConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText")); // NOI18N + spMaximumRetryAttempts.setModel(new javax.swing.SpinnerNumberModel(2, 0, 9999999, 1)); spMaximumRetryAttempts.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedAutoIngestSettingsPanel.class, "AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_2")); // NOI18N @@ -210,70 +221,76 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { .addGap(5, 5, 5) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbNumberOfThreads) - .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lbRestartRequired) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbInputScanInterval) + .addGap(49, 49, 49)) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbRetriesAllowed) + .addGap(54, 54, 54)) + .addComponent(lbConcurrentJobsPerCase, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbNumberOfThreads, javax.swing.GroupLayout.Alignment.LEADING)) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbInputScanInterval) - .addGap(49, 49, 49)) - .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbRetriesAllowed) - .addGap(54, 54, 54))) - .addGap(0, 21, Short.MAX_VALUE) + .addGap(0, 41, Short.MAX_VALUE) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbSecondsBetweenJobs) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelAutoIngestJobSettingsLayout.createSequentialGroup() - .addComponent(lbTimeoutText) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbSecondsBetweenJobsSeconds) - .addComponent(lbTimeoutHours) - .addComponent(lbInputScanIntervalMinutes)) - .addContainerGap(255, Short.MAX_VALUE)))) + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbSecondsBetweenJobs) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addComponent(lbTimeoutText) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbRestartRequired) + .addComponent(lbSecondsBetweenJobsSeconds) + .addComponent(lbTimeoutHours) + .addComponent(lbInputScanIntervalMinutes)) + .addContainerGap(50, Short.MAX_VALUE)) ); jPanelAutoIngestJobSettingsLayout.setVerticalGroup( jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() .addContainerGap() - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbSecondsBetweenJobs) - .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbSecondsBetweenJobsSeconds)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbTimeoutText) - .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbTimeoutHours)) - .addComponent(cbTimeoutEnabled)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbInputScanInterval) - .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbInputScanIntervalMinutes)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbRetriesAllowed) - .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(lbRestartRequired) - .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbNumberOfThreads) - .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(jPanelAutoIngestJobSettingsLayout.createSequentialGroup() + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbSecondsBetweenJobs) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbSecondsBetweenJobsSeconds)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbTimeoutText) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbTimeoutHours)) + .addComponent(cbTimeoutEnabled)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbInputScanInterval) + .addComponent(spInputScanInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbInputScanIntervalMinutes)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbRetriesAllowed) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbConcurrentJobsPerCase) + .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelAutoIngestJobSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbNumberOfThreads) + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); @@ -299,8 +316,8 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { .addGap(20, 20, 20) .addComponent(spMainScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanelAutoIngestJobSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(jPanelAutoIngestJobSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(26, 26, 26)) ); }// //GEN-END:initComponents @@ -319,6 +336,7 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox cbTimeoutEnabled; private javax.swing.JPanel jPanelAutoIngestJobSettings; + private javax.swing.JLabel lbConcurrentJobsPerCase; private javax.swing.JLabel lbInputScanInterval; private javax.swing.JLabel lbInputScanIntervalMinutes; private javax.swing.JLabel lbNumberOfThreads; @@ -329,6 +347,7 @@ class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { private javax.swing.JLabel lbTimeoutHours; private javax.swing.JLabel lbTimeoutText; private javax.swing.JComboBox numberOfFileIngestThreadsComboBox; + private javax.swing.JSpinner spConcurrentJobsPerCase; private javax.swing.JSpinner spInputScanInterval; private javax.swing.JScrollPane spMainScrollPane; private javax.swing.JSpinner spMaximumRetryAttempts; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java index e532b875cc..9696947535 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,15 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.configuration; -import java.util.Base64; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.PBEParameterSpec; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.TextConverter; +import org.sleuthkit.autopsy.coreutils.TextConverterException; /** * Provides convenient access to a Preferences node for auto ingest user @@ -58,16 +54,32 @@ public final class AutoIngestUserPreferences { private AutoIngestUserPreferences() { } - /** + /** + * Get the value for the given preference name. + * + * @param preferenceName + * + * @return The preference value if it exists, otherwise an empty string. + */ + private static String getPreferenceValue(String preferenceName) { + // User preferences can be overridden through system properties + // so we check those first. Defaults to empty string if the + // property doesn't exist. + String preferenceValue = System.getProperty(UserPreferences.SETTINGS_PROPERTIES + "." + preferenceName, ""); + + if (preferenceValue.isEmpty() && ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, preferenceName)) { + preferenceValue = ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, preferenceName); + } + return preferenceValue; + } + + /** * Get "Join auto ingest cluster" setting from persistent storage. * * @return SelectedMode Selected setting. */ public static boolean getJoinAutoModeCluster() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, JOIN_AUTO_MODE_CLUSTER)) { - return Boolean.parseBoolean(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, JOIN_AUTO_MODE_CLUSTER)); - } - return false; + return Boolean.parseBoolean(getPreferenceValue(JOIN_AUTO_MODE_CLUSTER)); } /** @@ -85,10 +97,7 @@ public final class AutoIngestUserPreferences { * @return String Selected input folder. */ public static String getAutoModeImageFolder() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, AUTO_MODE_IMAGES_FOLDER)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, AUTO_MODE_IMAGES_FOLDER); - } - return ""; + return getPreferenceValue(AUTO_MODE_IMAGES_FOLDER); } /** @@ -106,10 +115,7 @@ public final class AutoIngestUserPreferences { * @return String Selected output folder. */ public static String getAutoModeResultsFolder() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, AUTO_MODE_RESULTS_FOLDER)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, AUTO_MODE_RESULTS_FOLDER); - } - return ""; + return getPreferenceValue(AUTO_MODE_RESULTS_FOLDER); } /** @@ -127,10 +133,7 @@ public final class AutoIngestUserPreferences { * @return String Selected settings folder. */ public static String getSharedConfigFolder() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_FOLDER)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_FOLDER); - } - return ""; + return getPreferenceValue(SHARED_CONFIG_FOLDER); } /** @@ -149,10 +152,7 @@ public final class AutoIngestUserPreferences { * @return Boolean true if shared settings are enabled. */ public static Boolean getSharedConfigEnabled() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_ENABLED)) { - return Boolean.parseBoolean(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_ENABLED)); - } - return false; + return Boolean.parseBoolean(getPreferenceValue(SHARED_CONFIG_ENABLED)); } /** @@ -173,10 +173,7 @@ public final class AutoIngestUserPreferences { * @return true if this node is set as a shared configuration master */ public static Boolean getSharedConfigMaster() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_MASTER)) { - return Boolean.parseBoolean(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, SHARED_CONFIG_MASTER)); - } - return false; + return Boolean.parseBoolean(getPreferenceValue(SHARED_CONFIG_MASTER)); } /** @@ -212,10 +209,8 @@ public final class AutoIngestUserPreferences { * @return */ public static boolean getShowToolsWarning() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, SHOW_TOOLS_WARNING)) { - return Boolean.parseBoolean(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, SHOW_TOOLS_WARNING)); - } - return true; + String value = getPreferenceValue(SHOW_TOOLS_WARNING); + return value.isEmpty() || Boolean.parseBoolean(value); } /** @@ -224,10 +219,8 @@ public final class AutoIngestUserPreferences { * @return int the value in seconds, default is 30 seconds. */ public static int getSecondsToSleepBetweenCases() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, SLEEP_BETWEEN_CASES_TIME)) { - return Integer.parseInt(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, SLEEP_BETWEEN_CASES_TIME)); - } - return 30; + String value = getPreferenceValue(SLEEP_BETWEEN_CASES_TIME); + return value.isEmpty() ? 30 : Integer.parseInt(value); } /** @@ -250,10 +243,8 @@ public final class AutoIngestUserPreferences { * @return int maximum number of attempts, default is 0. */ public static int getMaxNumTimesToProcessImage() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, MAX_NUM_TIMES_TO_PROCESS_IMAGE)) { - return Integer.parseInt(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, MAX_NUM_TIMES_TO_PROCESS_IMAGE)); - } - return DEFAULT_MAX_TIMES_TO_PROCESS_IMAGE; + String value = getPreferenceValue(MAX_NUM_TIMES_TO_PROCESS_IMAGE); + return value.isEmpty() ? DEFAULT_MAX_TIMES_TO_PROCESS_IMAGE : Integer.parseInt(value); } /** @@ -267,17 +258,37 @@ public final class AutoIngestUserPreferences { ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, MAX_NUM_TIMES_TO_PROCESS_IMAGE, Integer.toString(retries)); } - /** + /** + * Get maximum number of concurrent ingest nodes allowable for one case at a + * time. + * + * @return maximum number of concurrent nodes for one case. Default is 3. + */ + public static int getMaxConcurrentJobsForOneCase() { + if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, MAX_CONCURRENT_NODES_FOR_ONE_CASE)) { + return Integer.parseInt(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, MAX_CONCURRENT_NODES_FOR_ONE_CASE)); + } + return 3; + } + + /** + * Get maximum number of concurrent ingest nodes allowable for one case at a + * time. + * + * @param numberOfNodes the number of concurrent nodes to allow for one case + */ + public static void setMaxConcurrentIngestNodesForOneCase(int numberOfNodes) { + ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, MAX_CONCURRENT_NODES_FOR_ONE_CASE, Integer.toString(numberOfNodes)); + } + + /** * Get status database logging checkbox state for automated ingest mode from * persistent storage. * * @return Boolean true if database logging is enabled. */ public static Boolean getStatusDatabaseLoggingEnabled() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, STATUS_DATABASE_LOGGING_ENABLED)) { - return Boolean.parseBoolean(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, STATUS_DATABASE_LOGGING_ENABLED)); - } - return false; + return Boolean.parseBoolean(getPreferenceValue(STATUS_DATABASE_LOGGING_ENABLED)); } /** @@ -297,10 +308,7 @@ public final class AutoIngestUserPreferences { * @return Logging database hostname or IP */ public static String getLoggingDatabaseHostnameOrIP() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, LOGGING_DB_HOSTNAME_OR_IP)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_DB_HOSTNAME_OR_IP); - } - return ""; + return getPreferenceValue(LOGGING_DB_HOSTNAME_OR_IP); } /** @@ -318,10 +326,7 @@ public final class AutoIngestUserPreferences { * @return logging database port */ public static String getLoggingPort() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PORT)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PORT); - } - return ""; + return getPreferenceValue(LOGGING_PORT); } /** @@ -339,10 +344,7 @@ public final class AutoIngestUserPreferences { * @return logging database username */ public static String getLoggingUsername() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, LOGGING_USERNAME)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_USERNAME); - } - return ""; + return getPreferenceValue(LOGGING_USERNAME); } /** @@ -362,10 +364,7 @@ public final class AutoIngestUserPreferences { * @throws org.sleuthkit.autopsy.core.UserPreferencesException */ public static String getLoggingPassword() throws UserPreferencesException { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PASSWORD)) { - return TextConverter.convertHexTextToText(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PASSWORD)); - } - return ""; + return getPreferenceValue(LOGGING_PASSWORD); } /** @@ -376,7 +375,11 @@ public final class AutoIngestUserPreferences { * @throws org.sleuthkit.autopsy.core.UserPreferencesException */ public static void setLoggingPassword(String password) throws UserPreferencesException { - ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PASSWORD, TextConverter.convertTextToHexText(password)); + try { + ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_PASSWORD, TextConverter.convertTextToHexText(password)); + } catch (TextConverterException ex) { + throw new UserPreferencesException("Error encrypting password", ex); + } } /** @@ -385,10 +388,7 @@ public final class AutoIngestUserPreferences { * @return logging database name */ public static String getLoggingDatabaseName() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, LOGGING_DATABASE_NAME)) { - return ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, LOGGING_DATABASE_NAME); - } - return ""; + return getPreferenceValue(LOGGING_DATABASE_NAME); } /** @@ -406,10 +406,8 @@ public final class AutoIngestUserPreferences { * @return int the value in minutes, default is 60 minutes. */ public static int getMinutesOfInputScanInterval() { - if (ModuleSettings.settingExists(UserPreferences.SETTINGS_PROPERTIES, INPUT_SCAN_INTERVAL_TIME)) { - return Integer.parseInt(ModuleSettings.getConfigSetting(UserPreferences.SETTINGS_PROPERTIES, INPUT_SCAN_INTERVAL_TIME)); - } - return 60; + String value = getPreferenceValue(INPUT_SCAN_INTERVAL_TIME); + return value.isEmpty() ? 60 : Integer.parseInt(value); } /** @@ -420,66 +418,4 @@ public final class AutoIngestUserPreferences { public static void setMinutesOfInputScanInterval(int value) { ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, INPUT_SCAN_INTERVAL_TIME, Integer.toString(value)); } - - /** - * Copied from Autopsy UserPreferences - can be removed once everything is - * merged together. Provides ability to convert text to hex text. - */ - static final class TextConverter { - - private static final char[] TMP = "hgleri21auty84fwe".toCharArray(); //NON-NLS - private static final byte[] SALT = { - (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12, - (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,}; - - /** - * Convert text to hex text. - * - * @param property Input text string. - * - * @return Converted hex string. - * - * @throws org.sleuthkit.autopsy.core.UserPreferencesException - */ - static String convertTextToHexText(String property) throws UserPreferencesException { - try { - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); //NON-NLS - SecretKey key = keyFactory.generateSecret(new PBEKeySpec(TMP)); - Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); //NON-NLS - pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); - return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8"))); - } catch (Exception ex) { - throw new UserPreferencesException("Error encrypting text"); - } - } - - private static String base64Encode(byte[] bytes) { - return Base64.getEncoder().encodeToString(bytes); - } - - /** - * Convert hex text back to text. - * - * @param property Input hex text string. - * - * @return Converted text string. - * - * @throws org.sleuthkit.autopsy.core.UserPreferencesException - */ - static String convertHexTextToText(String property) throws UserPreferencesException { - try { - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); //NON-NLS - SecretKey key = keyFactory.generateSecret(new PBEKeySpec(TMP)); - Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); //NON-NLS - pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); - return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8"); - } catch (Exception ex) { - throw new UserPreferencesException("Error decrypting text"); - } - } - - private static byte[] base64Decode(String property) { - return Base64.getDecoder().decode(property); - } - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties index eaae72eb66..97a82988f2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/Bundle.properties @@ -89,6 +89,7 @@ AdvancedAutoIngestSettingsPanel.spMaximumRetryAttempts.toolTipText=The maximum n AdvancedAutoIngestSettingsPanel.lbRestartRequired.text=Application restart required to take effect. AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.toolTipText=Components that spawn potentially long-running processes optionally terminate those processes if the specified time out period has elapsed. AdvancedAutoIngestSettingsPanel.cbTimeoutEnabled.text= +AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.text=Target concurrent jobs per case: AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.text=Number of threads to use for file ingest: AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.text=Maximum job retries allowed: AdvancedAutoIngestSettingsPanel.lbInputScanInterval.text=Interval between input scans: @@ -102,6 +103,7 @@ AdvancedAutoIngestSettingsPanel.lbInputScanIntervalMinutes.toolTipText= AdvancedAutoIngestSettingsPanel.lbTimeoutHours.toolTipText= AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_1=The maximum number of retries for crashed jobs. AdvancedAutoIngestSettingsPanel.lbRetriesAllowed.toolTipText_2=The maximum number of retries for crashed jobs. +AdvancedAutoIngestSettingsPanel.lbConcurrentJobsPerCase.toolTipText_1=A soft limit on the number of concurrent jobs per case when multiple cases are processed simultaneously. AdvancedAutoIngestSettingsPanel.lbNumberOfThreads.toolTipText_1=The number of threads running file level ingest modules. AdvancedAutoIngestSettingsPanel.numberOfFileIngestThreadsComboBox.toolTipText=The number of threads running file level ingest modules. NodeStatusLogPanel.tbDbName.toolTipText_1=Database name diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java index 860019238b..106e521142 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/SharedConfiguration.java @@ -47,7 +47,6 @@ import org.sleuthkit.autopsy.keywordsearch.KeywordListsManager; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.core.ServicesMonitor; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.experimental.configuration.AutoIngestSettingsPanel.UpdateConfigSwingWorker; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode; @@ -103,6 +102,7 @@ public class SharedConfiguration { private boolean hideKnownFilesInViews; private boolean hideSlackFilesInDataSource; private boolean hideSlackFilesInViews; + private boolean groupDatasources; private boolean keepPreferredViewer; /** @@ -207,6 +207,8 @@ public class SharedConfiguration { uploadHashDbSettings(remoteFolder); uploadFileExporterSettings(remoteFolder); uploadCentralRepositorySettings(remoteFolder); + uploadObjectDetectionClassifiers(remoteFolder); + uploadPythonModules(remoteFolder); try { Files.deleteIfExists(uploadInProgress.toPath()); @@ -272,6 +274,8 @@ public class SharedConfiguration { downloadAndroidTriageSettings(remoteFolder); downloadFileExporterSettings(remoteFolder); downloadCentralRepositorySettings(remoteFolder); + downloadObjectDetectionClassifiers(remoteFolder); + downloadPythonModules(remoteFolder); // Download general settings, then restore the current // values for the unshared fields @@ -349,6 +353,7 @@ public class SharedConfiguration { fileIngestThreads = UserPreferences.numberOfFileIngestThreads(); hideSlackFilesInDataSource = UserPreferences.hideSlackFilesInDataSourcesTree(); hideSlackFilesInViews = UserPreferences.hideSlackFilesInViewsTree(); + groupDatasources = UserPreferences.groupItemsInTreeByDatasource(); } /** @@ -365,6 +370,7 @@ public class SharedConfiguration { UserPreferences.setNumberOfFileIngestThreads(fileIngestThreads); UserPreferences.setHideSlackFilesInDataSourcesTree(hideSlackFilesInDataSource); UserPreferences.setHideSlackFilesInViewsTree(hideSlackFilesInViews); + UserPreferences.setGroupItemsInTreeByDatasource(groupDatasources); } /** @@ -512,6 +518,71 @@ public class SharedConfiguration { throw new SharedConfigurationException(String.format("Failed to copy %s to %s", remoteFile.getAbsolutePath(), localSettingsFolder.getAbsolutePath()), ex); } } + + /** + * Copy an entire local settings folder to the remote folder, deleting any existing files. + * + * @param localFolder The local folder to copy + * @param remoteBaseFolder The remote folder that will hold a copy of the original folder + * + * @throws SharedConfigurationException + */ + private void copyLocalFolderToRemoteFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Uploading {0} to {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); + + File newRemoteFolder = new File(remoteBaseFolder, localFolder.getName()); + + if(newRemoteFolder.exists()) { + try { + FileUtils.deleteDirectory(newRemoteFolder); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to delete remote folder {0}", newRemoteFolder.getAbsolutePath()); + throw new SharedConfigurationException(String.format("Failed to delete remote folder {0}", newRemoteFolder.getAbsolutePath()), ex); + } + } + + try { + FileUtils.copyDirectoryToDirectory(localFolder, remoteBaseFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); + } + } + + /** + * Copy an entire remote settings folder to the local folder, deleting any existing files. + * No error if the remote folder does not exist. + * + * @param localFolder The local folder that will be overwritten. + * @param remoteBaseFolder The remote folder holding the folder that will be copied + * + * @throws SharedConfigurationException + */ + private void copyRemoteFolderToLocalFolder(File localFolder, File remoteBaseFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Downloading {0} from {1}", new Object[]{localFolder.getAbsolutePath(), remoteBaseFolder.getAbsolutePath()}); + + // Clean out the local folder regardless of whether the remote version exists. leave the + // folder in place since Autopsy expects it to exist. + if(localFolder.exists()) { + try { + FileUtils.cleanDirectory(localFolder); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to delete files from local folder {0}", localFolder.getAbsolutePath()); + throw new SharedConfigurationException(String.format("Failed to delete files from local folder {0}", localFolder.getAbsolutePath()), ex); + } + } + + File remoteSubFolder = new File(remoteBaseFolder, localFolder.getName()); + if(! remoteSubFolder.exists()) { + logger.log(Level.INFO, "{0} does not exist", remoteSubFolder.getAbsolutePath()); + return; + } + + try { + FileUtils.copyDirectory(remoteSubFolder, localFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s from %s", localFolder, remoteBaseFolder.getAbsolutePath()), ex); + } + } /** * Upload the basic set of auto-ingest settings to the shared folder. @@ -829,6 +900,58 @@ public class SharedConfiguration { copyToLocalFolder(AUTO_INGEST_PROPERTIES, moduleDirPath, remoteFolder, false); } + /** + * Upload the object detection classifiers. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading object detection classfiers"); + File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); + copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); + } + + /** + * Download the object detection classifiers. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadObjectDetectionClassifiers(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading object detection classfiers"); + File classifiersFolder = new File(PlatformUtil.getObjectDetectionClassifierPath()); + copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); + } + + /** + * Upload the Python modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadPythonModules(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading python modules"); + File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); + copyLocalFolderToRemoteFolder(classifiersFolder, remoteFolder); + } + + /** + * Download the Python modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadPythonModules(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading python modules"); + File classifiersFolder = new File(PlatformUtil.getUserPythonModulesPath()); + copyRemoteFolderToLocalFolder(classifiersFolder, remoteFolder); + } + /** * Upload settings and hash databases to the shared folder. The general * algorithm is: - Copy the general settings in hashsets.xml - For each hash diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java index fe86fb9f0b..003a35a46d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java @@ -19,14 +19,11 @@ package org.sleuthkit.autopsy.experimental.objectdetection; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; import org.opencv.core.CvException; import org.opencv.core.Mat; import org.opencv.core.MatOfByte; @@ -52,7 +49,6 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; /** @@ -61,6 +57,7 @@ import org.sleuthkit.datamodel.TskCoreException; public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter { private final static Logger logger = Logger.getLogger(ObjectDetectectionFileIngestModule.class.getName()); + private final static int MAX_FILE_SIZE = 100000000; //Max size of pictures to perform object detection on private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private long jobId; private Map classifiers; @@ -100,16 +97,36 @@ public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter @Override public ProcessResult process(AbstractFile file) { if (!classifiers.isEmpty() && ImageUtils.isImageThumbnailSupported(file)) { - //Any image we can create a thumbnail for is one we should apply the classifiers to - InputStream inputStream = new ReadContentInputStream(file); - byte[] imageInMemory; + //Any image we can create a thumbnail for is one we should apply the classifiers to + + if (file.getSize() > MAX_FILE_SIZE) { + //prevent it from allocating gigabytes of memory for extremely large files + logger.log(Level.INFO, "Encountered file " + file.getParentPath() + file.getName() + " with object id of " + + file.getId() + " which exceeds max file size of " + MAX_FILE_SIZE + " bytes, with a size of " + file.getSize()); + return IngestModule.ProcessResult.OK; + } + + byte[] imageInMemory = new byte[(int) file.getSize()]; + try { - imageInMemory = IOUtils.toByteArray(inputStream); - } catch (IOException ex) { - logger.log(Level.WARNING, "Unable to perform object detection on " + file.getName(), ex); + file.read(imageInMemory, 0, file.getSize()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to read image to byte array for performing object detection on " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), ex); + return IngestModule.ProcessResult.ERROR; + } + + Mat originalImage; + try { + originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE); + } catch (CvException ex) { + //The image was something which could not be decoded by OpenCv, our isImageThumbnailSupported(file) check above failed us + logger.log(Level.WARNING, "Unable to decode image from byte array to perform object detection on " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } catch (Exception unexpectedException) { + //hopefully an unnecessary generic exception catch but currently present to catch any exceptions OpenCv throws which may not be documented + logger.log(Level.SEVERE, "Unexpected Exception encountered attempting to use OpenCV to decode picture: " + file.getParentPath() + file.getName() + " with object id of " + file.getId(), unexpectedException); return IngestModule.ProcessResult.ERROR; } - Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_GRAYSCALE); MatOfRect detectionRectangles = new MatOfRect(); //the rectangles which reprent the coordinates on the image for where objects were detected for (String classifierKey : classifiers.keySet()) { //apply each classifier to the file @@ -117,7 +134,10 @@ public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter classifiers.get(classifierKey).detectMultiScale(originalImage, detectionRectangles); } catch (CvException ignored) { //The image was likely an image which we are unable to generate a thumbnail for, and the classifier was likely one where that is not acceptable - logger.log(Level.INFO, String.format("Classifier '%s' could not be applied to file '%s'.", classifierKey, file.getParentPath() + file.getName())); //NON-NLS + continue; + } catch (Exception unexpectedException) { + //hopefully an unnecessary generic exception catch but currently present to catch any exceptions OpenCv throws which may not be documented + logger.log(Level.SEVERE, "Unexpected Exception encountered for image " + file.getParentPath() + file.getName() + " with object id of " + file.getId() + " while trying to apply classifier " + classifierKey, unexpectedException); continue; } @@ -148,10 +168,14 @@ public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS + detectionRectangles.release(); + originalImage.release(); return IngestModule.ProcessResult.ERROR; } } } + detectionRectangles.release(); + originalImage.release(); } return IngestModule.ProcessResult.OK; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java index dc05cb8376..5dbdca94ce 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java @@ -56,7 +56,7 @@ public class MemoryDSProcessor implements DataSourceProcessor { * * @return A data source type display string for this data source processor. */ - @Messages({"MemoryDSProcessor.dataSourceType=Memory Image File"}) + @Messages({"MemoryDSProcessor.dataSourceType=Memory Image File (Volatility)"}) public static String getType() { return Bundle.MemoryDSProcessor_dataSourceType(); } diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index 0fdad950f2..dcaa641e75 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -127,7 +127,7 @@ 10 - 10.11 + 10.12 @@ -136,7 +136,7 @@ 3 - 1.1 + 1.2 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java index 2b3fc1692f..7fbcaac1f0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java @@ -203,17 +203,20 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel { } void store() { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - Logger.getLogger(ImageGalleryOptionsPanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected()); ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected()); - new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected())); ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected()); + + // If a case is open, save the per case setting + try { + Case openCase = Case.getCurrentCaseThrows(); + new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected())); + } catch (NoCurrentCaseException ex) { + // It's not an error if there's no case open + } + + + } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index fa008fe3e9..7688ccd146 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -840,43 +840,6 @@ public final class DrawableDB { } } - /** - * Count the total number of files in the database.. - * - * @return Total number of files in the database - * - * @throws TskCoreException - */ - @Deprecated - public long countFiles() throws TskCoreException { - Statement statement = null; - ResultSet rs = null; - dbReadLock(); - try { - statement = con.createStatement(); - rs = statement.executeQuery("SELECT COUNT (*) FROM drawable_files"); //NON-NLS - return rs.getLong(1); - } catch (SQLException e) { - throw new TskCoreException("SQLException thrown when calling 'DrawableDB.countFiles(): ", e); - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing result set after executing countFiles", ex); //NON-NLS - } - } - if (statement != null) { - try { - statement.close(); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing statement after executing countFiles", ex); //NON-NLS - } - } - dbReadUnlock(); - } - } - /** * * @@ -952,7 +915,12 @@ public final class DrawableDB { return vals; } - public void insertGroup(final String value, DrawableAttribute groupBy) { + /** + * Insert new group into DB + * @param value Value of the group (unique to the type) + * @param groupBy Type of the grouping (CATEGORY, MAKE, etc.) + */ + private void insertGroup(final String value, DrawableAttribute groupBy) { dbWriteLock(); try { @@ -971,25 +939,6 @@ public final class DrawableDB { } } - /** - * @param id the obj_id of the file to return - * @param analyzed the analyzed state of the file - * - * @return a DrawableFile for the given obj_id and analyzed state - * - * @throws TskCoreException if unable to get a file from the currently open - * {@link SleuthkitCase} - */ - private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException { - try { - AbstractFile f = tskCase.getAbstractFileById(id); - return DrawableFile.create(f, analyzed, isVideoFile(f)); - } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex); //NON-NLS - return null; - } - } - /** * @param id the obj_id of the file to return * @@ -1230,6 +1179,12 @@ public final class DrawableDB { * @return the number of files with Cat-0 */ public long getUncategorizedCount(Collection fileIDs) { + + // if the fileset is empty, return count as 0 + if (fileIDs.isEmpty()) { + return 0; + } + DrawableTagsManager tagsManager = controller.getTagsManager(); // get a comma seperated list of TagName ids for non zero categories diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 7dddfa5ca8..fb381160f7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -82,6 +82,7 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.DbType; /** * Provides an abstraction layer on top of {@link DrawableDB} ( and to some @@ -351,11 +352,22 @@ public class GroupManager { case MIME_TYPE: if (nonNull(db)) { HashSet types = new HashSet<>(); - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery("select group_concat(obj_id), mime_type from tsk_files group by mime_type "); //NON-NLS + + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } + else { + groupConcatClause = " group_concat(obj_id) as object_ids"; + } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("group_concat(obj_id)"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS Pattern.compile(",").splitAsStream(objIds) .map(Long::valueOf) diff --git a/InternalPythonModules/android/browserlocation.py b/InternalPythonModules/android/browserlocation.py index 6db230c2e6..a0d73fdd28 100644 --- a/InternalPythonModules/android/browserlocation.py +++ b/InternalPythonModules/android/browserlocation.py @@ -105,7 +105,7 @@ class BrowserLocationAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactTypeName(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactTypeName()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/cachelocation.py b/InternalPythonModules/android/cachelocation.py index 019c03c654..53e60b51ee 100644 --- a/InternalPythonModules/android/cachelocation.py +++ b/InternalPythonModules/android/cachelocation.py @@ -138,7 +138,7 @@ class CacheLocationAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/calllog.py b/InternalPythonModules/android/calllog.py index 79d0e82479..b20c0d0512 100644 --- a/InternalPythonModules/android/calllog.py +++ b/InternalPythonModules/android/calllog.py @@ -157,7 +157,7 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index call log artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/contact.py b/InternalPythonModules/android/contact.py index 1d556607e0..22a1f797ec 100644 --- a/InternalPythonModules/android/contact.py +++ b/InternalPythonModules/android/contact.py @@ -164,7 +164,7 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index contact artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/googlemaplocation.py b/InternalPythonModules/android/googlemaplocation.py index 444c0477ee..712aacc2be 100644 --- a/InternalPythonModules/android/googlemaplocation.py +++ b/InternalPythonModules/android/googlemaplocation.py @@ -115,7 +115,7 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index GPS route artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/tangomessage.py b/InternalPythonModules/android/tangomessage.py index bd65738a6e..514bd48ecb 100644 --- a/InternalPythonModules/android/tangomessage.py +++ b/InternalPythonModules/android/tangomessage.py @@ -116,7 +116,7 @@ class TangoMessageAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index Tango message artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/textmessage.py b/InternalPythonModules/android/textmessage.py index a6b36ad549..5854977e8b 100644 --- a/InternalPythonModules/android/textmessage.py +++ b/InternalPythonModules/android/textmessage.py @@ -130,7 +130,7 @@ class TextMessageAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index text message artifact for keyword search.", artifact.getDisplayName()) diff --git a/InternalPythonModules/android/wwfmessage.py b/InternalPythonModules/android/wwfmessage.py index 446ab78b67..6b41ab2677 100644 --- a/InternalPythonModules/android/wwfmessage.py +++ b/InternalPythonModules/android/wwfmessage.py @@ -125,7 +125,7 @@ class WWFMessageAnalyzer(general.AndroidComponentAnalyzer): blackboard = Case.getCurrentCase().getServices().getBlackboard() blackboard.indexArtifact(artifact) except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex) + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index WWF message artifact for keyword search.", artifact.getDisplayName()) diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf index 9f3126687e..5b53ffe61d 100644 --- a/KeywordSearch/manifest.mf +++ b/KeywordSearch/manifest.mf @@ -1,7 +1,7 @@ Manifest-Version: 1.0 AutoUpdate-Show-In-Client: true OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/6 -OpenIDE-Module-Implementation-Version: 19 +OpenIDE-Module-Implementation-Version: 20 OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index 25142d8119..343dc691e7 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -119,7 +119,7 @@ 10 - 10.11 + 10.12 @@ -128,7 +128,7 @@ 3 - 1.1 + 1.2 diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index ced485d9c6..2b279bfa88 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -83,9 +83,17 @@ class AdHocSearchChildFactory extends ChildFactory { .collect(Collectors.toList()); private final Collection queryRequests; + private final boolean saveResults; - AdHocSearchChildFactory(Collection queryRequests) { + /** + * Constructor + * + * @param queryRequests Query results + * @param saveResults Flag whether to save search results as KWS artifacts. + */ + AdHocSearchChildFactory(Collection queryRequests, boolean saveResults) { this.queryRequests = queryRequests; + this.saveResults = saveResults; } /** @@ -121,6 +129,12 @@ class AdHocSearchChildFactory extends ChildFactory { createFlatKeys(queryRequest.getQuery(), toPopulate); } + + // If there were no hits, make a single Node that will display that + // no results were found. + if (toPopulate.isEmpty()) { + toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); + } return true; } @@ -207,9 +221,7 @@ class AdHocSearchChildFactory extends ChildFactory { } - if (hitNumber == 0) { - toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); - } else { + if (hitNumber != 0) { // Add all the nodes to toPopulate at once. Minimizes node creation // EDT threads, which can slow and/or hang the UI on large queries. toPopulate.addAll(tempList); @@ -219,7 +231,7 @@ class AdHocSearchChildFactory extends ChildFactory { //cannot reuse snippet in BlackboardResultWriter //because for regex searches in UI we compress results by showing a content per regex once (even if multiple term hits) //whereas in bb we write every hit per content separately - new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName()).execute(); + new BlackboardResultWriter(queryResults, queryRequest.getKeywordList().getName(), saveResults).execute(); return true; } @@ -392,10 +404,12 @@ class AdHocSearchChildFactory extends ChildFactory { private final KeywordSearchQuery query; private final QueryResults hits; private static final int QUERY_DISPLAY_LEN = 40; + private final boolean saveResults; - BlackboardResultWriter(QueryResults hits, String listName) { + BlackboardResultWriter(QueryResults hits, String listName, boolean saveResults) { this.hits = hits; this.query = hits.getQuery(); + this.saveResults = saveResults; } protected void finalizeWorker() { @@ -410,7 +424,7 @@ class AdHocSearchChildFactory extends ChildFactory { final String queryDisp = queryStr.length() > QUERY_DISPLAY_LEN ? queryStr.substring(0, QUERY_DISPLAY_LEN - 1) + " ..." : queryStr; try { progress = ProgressHandle.createHandle(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.progress.saving", queryDisp), () -> BlackboardResultWriter.this.cancel(true)); - hits.process(progress, null, this, false); + hits.process(progress, null, this, false, saveResults); } finally { finalizeWorker(); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java index 6eab8f3ee1..a79f050ee2 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java @@ -30,7 +30,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; -import org.sleuthkit.autopsy.corecomponents.SingleLayerTableFilterNode; +import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; /** @@ -74,8 +74,10 @@ class AdHocSearchDelegator { /** * Execute the keyword search based on keywords passed into constructor. * Post results into a new DataResultViewer. + * + * @param saveResults Flag whether to save search results as KWS artifacts. */ - public void execute() { + public void execute(boolean saveResults) { Collection queryRequests = new ArrayList<>(); int queryID = 0; StringBuilder queryConcat = new StringBuilder(); // concatenation of all query strings @@ -95,7 +97,7 @@ class AdHocSearchDelegator { Node rootNode; if (queryRequests.size() > 0) { Children childNodes = - Children.create(new AdHocSearchChildFactory(queryRequests), true); + Children.create(new AdHocSearchChildFactory(queryRequests, saveResults), true); rootNode = new AbstractNode(childNodes); } else { @@ -104,7 +106,7 @@ class AdHocSearchDelegator { final String pathText = NbBundle.getMessage(this.getClass(), "KeywordSearchQueryManager.pathText.text"); - DataResultTopComponent.initInstance(pathText, new SingleLayerTableFilterNode(rootNode, true, KeywordSearch.class.getName()), + DataResultTopComponent.initInstance(pathText, new TableFilterNode(rootNode, true, KeywordSearch.class.getName()), queryRequests.size(), searchResultWin); searchResultWin.requestActive(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 3177bbbbb4..57b84b11d6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -36,7 +36,6 @@ import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; -import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -48,7 +47,6 @@ import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.VirtualDirectory; /** @@ -126,50 +124,45 @@ class AdHocSearchFilterNode extends FilterNode { @Override public List visit(File f) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(DerivedFile f) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(Directory d) { - return getFileActions(false); + return getFileActions(); } @Override public List visit(LayoutFile lf) { - //we want hashsearch enabled on carved files but not unallocated blocks - boolean enableHashSearch = (lf.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.CARVED); - return getFileActions(enableHashSearch); + return getFileActions(); } @Override public List visit(LocalFile lf) { - return getFileActions(true); + return getFileActions(); } @Override public List visit(SlackFile f) { - return getFileActions(false); + return getFileActions(); } @Override public List visit(VirtualDirectory dir) { - return getFileActions(false); + return getFileActions(); } - private List getFileActions(boolean enableHashSearch) { + private List getFileActions() { List actionsList = new ArrayList<>(); actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); - Action hashSearchAction = new HashSearchAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.searchSameMd5"), getOriginal()); - hashSearchAction.setEnabled(enableHashSearch); - actionsList.add(hashSearchAction); actionsList.add(null); // creates a menu separator actionsList.add(AddContentTagAction.getInstance()); @@ -185,7 +178,7 @@ class AdHocSearchFilterNode extends FilterNode { @Override protected List defaultVisit(Content c) { - return getFileActions(false); + return getFileActions(); } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 89bfbab816..6190f392e8 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -37,12 +37,12 @@ import javax.swing.DefaultListModel; * class and extended classes model the user's intentions, not necessarily how * the search manager and 3rd party tools actually perform the search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); protected int filesIndexed; private final Map dataSourceMap = new HashMap<>(); - private final List toolTipList = new ArrayList<>(); private List dataSources = new ArrayList<>(); private final DefaultListModel dataSourceListModel = new DefaultListModel<>(); @@ -53,18 +53,18 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { private void initListeners() { KeywordSearch.addNumIndexedFilesChangeListener( new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String changed = evt.getPropertyName(); - Object newValue = evt.getNewValue(); + @Override + public void propertyChange(PropertyChangeEvent evt) { + String changed = evt.getPropertyName(); + Object newValue = evt.getNewValue(); - if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { - int newFilesIndexed = ((Integer) newValue).intValue(); - filesIndexed = newFilesIndexed; - postFilesIndexedChange(); - } - } - }); + if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { + int newFilesIndexed = ((Integer) newValue); + filesIndexed = newFilesIndexed; + postFilesIndexedChange(); + } + } + }); } /** @@ -98,8 +98,10 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { /** * Performs the search using the selected keywords. Creates a * DataResultTopComponent with the results. + * + * @param saveResults Flag whether to save search results as KWS artifacts. */ - public void search() { + public void search(boolean saveResults) { boolean isIngestRunning = IngestManager.getInstance().isIngestRunning(); if (filesIndexed == 0) { @@ -128,8 +130,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { } } - AdHocSearchDelegator man = null; - final List keywordLists = getKeywordLists(); if (keywordLists.isEmpty()) { KeywordSearchUtil.displayDialog(keywordSearchErrorDialogHeader, NbBundle.getMessage(this.getClass(), @@ -137,10 +137,10 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new AdHocSearchDelegator(keywordLists, getDataSourcesSelected()); - + + AdHocSearchDelegator man = new AdHocSearchDelegator(keywordLists, getDataSourcesSelected()); if (man.validate()) { - man.execute(); + man.execute(saveResults); } else { KeywordSearchUtil.displayDialog(keywordSearchErrorDialogHeader, NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.invalidSyntaxHeader"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); @@ -154,14 +154,12 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { */ synchronized List getDataSourceArray() { List dsList = new ArrayList<>(); - toolTipList.clear(); Collections.sort(this.dataSources, (DataSource ds1, DataSource ds2) -> ds1.getName().compareTo(ds2.getName())); for (DataSource ds : dataSources) { String dsName = ds.getName(); File dataSourceFullName = new File(dsName); String displayName = dataSourceFullName.getName(); dataSourceMap.put(ds.getId(), displayName); - toolTipList.add(dsName); dsList.add(displayName); } return dsList; @@ -176,22 +174,13 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { } /** - * Get dataSourceMap with object id and data source display name. Add the - * data source full name to toolTipList + * Get dataSourceMap with object id and data source display name. * * @return The list of data source name */ Map getDataSourceMap() { return dataSourceMap; } - - /** - * Get a list of tooltip text for data source - * @return A list of tool tips - */ - List getDataSourceToolTipList() { - return toolTipList; - } /** * Get a list of DataSourceListModel diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index ee49ae2c2b..eedf0cb8ec 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -313,3 +313,7 @@ ExtractedContentPanel.pagesLabel.text=Page: DropdownSingleTermSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownListSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownSingleTermSearchPanel.ingestIndexLabel.text=Files Indexed: +DropdownSingleTermSearchPanel.jSaveSearchResults.toolTipText=Perform keyword search without saving the results in the form of keyword hit artifacts +DropdownSingleTermSearchPanel.jSaveSearchResults.text=Save search results +DropdownListSearchPanel.jSaveSearchResults.toolTipText=Perform keyword search without saving the results in the form of keyword hit artifacts +DropdownListSearchPanel.jSaveSearchResults.text=Save search results diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index 06df441bf3..82494f2f0d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import java.io.PushbackReader; import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.text.Normalizer; import java.util.Iterator; import java.util.NoSuchElementException; import javax.annotation.concurrent.NotThreadSafe; @@ -148,7 +149,9 @@ class Chunker implements Iterator, Iterable { } private static StringBuilder sanitize(String s) { - return sanitizeToUTF8(replaceInvalidUTF16(s)); + String normStr = Normalizer.normalize(s, Normalizer.Form.NFKC); + return sanitizeToUTF8(replaceInvalidUTF16(normStr)); + } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form index 873824840b..70cd940085 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form @@ -23,38 +23,41 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + + + - + @@ -226,5 +229,15 @@ + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 72a1cbab5a..a8a9a93fc6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -18,11 +18,11 @@ */ package org.sleuthkit.autopsy.keywordsearch; -import java.awt.*; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -32,7 +32,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import javax.swing.JCheckBox; -import javax.swing.JList; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; @@ -73,22 +72,6 @@ class DropdownListSearchPanel extends AdHocSearchPanel { dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); - dataSourceList.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseDragged(MouseEvent evt) { - //Unused by now - } - - @Override - public void mouseMoved(MouseEvent evt) { - JList dsList = (JList) evt.getSource(); - int index = dsList.locationToIndex(evt.getPoint()); - if (index > -1) { - dsList.setToolTipText(getDataSourceToolTipList().get(index)); - } - } - }); } static synchronized DropdownListSearchPanel getDefault() { @@ -181,6 +164,8 @@ class DropdownListSearchPanel extends AdHocSearchPanel { } listsTableModel.resync(); updateIngestIndexLabel(); + + jSaveSearchResults.setSelected(true); } private void updateIngestIndexLabel() { @@ -223,6 +208,7 @@ class DropdownListSearchPanel extends AdHocSearchPanel { dataSourceCheckBox = new javax.swing.JCheckBox(); jScrollPane1 = new javax.swing.JScrollPane(); dataSourceList = new javax.swing.JList<>(); + jSaveSearchResults = new javax.swing.JCheckBox(); setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11)); @@ -282,23 +268,26 @@ class DropdownListSearchPanel extends AdHocSearchPanel { dataSourceList.setMinimumSize(new java.awt.Dimension(0, 200)); jScrollPane1.setViewportView(dataSourceList); + jSaveSearchResults.setText(org.openide.util.NbBundle.getMessage(DropdownListSearchPanel.class, "DropdownListSearchPanel.jSaveSearchResults.text")); // NOI18N + jSaveSearchResults.setToolTipText(org.openide.util.NbBundle.getMessage(DropdownListSearchPanel.class, "DropdownListSearchPanel.jSaveSearchResults.toolTipText")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGap(4, 4, 4) - .addComponent(searchAddButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(manageListsButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(ingestIndexLabel) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addComponent(dataSourceCheckBox) - .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(jSplitPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(jScrollPane1) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataSourceCheckBox) + .addComponent(jSaveSearchResults) + .addGroup(layout.createSequentialGroup() + .addComponent(searchAddButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(manageListsButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ingestIndexLabel))) + .addGap(0, 120, Short.MAX_VALUE)) ); layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {manageListsButton, searchAddButton}); @@ -307,16 +296,18 @@ class DropdownListSearchPanel extends AdHocSearchPanel { layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jSplitPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(dataSourceCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSaveSearchResults) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(manageListsButton) .addComponent(searchAddButton) .addComponent(ingestIndexLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap()) + .addGap(23, 23, 23)) ); }// //GEN-END:initComponents @@ -336,6 +327,7 @@ class DropdownListSearchPanel extends AdHocSearchPanel { private javax.swing.JCheckBox dataSourceCheckBox; private javax.swing.JList dataSourceList; private javax.swing.JLabel ingestIndexLabel; + private javax.swing.JCheckBox jSaveSearchResults; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JTable keywordsTable; @@ -350,7 +342,7 @@ class DropdownListSearchPanel extends AdHocSearchPanel { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - search(); + search(jSaveSearchResults.isSelected()); } finally { setCursor(null); } @@ -683,11 +675,21 @@ class DropdownListSearchPanel extends AdHocSearchPanel { * Set the dataSourceList enabled if the dataSourceCheckBox is selected */ private void setComponentsEnabled() { - boolean enabled = this.dataSourceCheckBox.isSelected(); - this.dataSourceList.setEnabled(enabled); - if (enabled) { - this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1); + + if (getDataSourceListModel().size() > 1) { + this.dataSourceCheckBox.setEnabled(true); + + boolean enabled = this.dataSourceCheckBox.isSelected(); + this.dataSourceList.setEnabled(enabled); + if (enabled) { + this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1); + } else { + this.dataSourceList.setSelectedIndices(new int[0]); + } } else { + this.dataSourceCheckBox.setEnabled(false); + this.dataSourceCheckBox.setSelected(false); + this.dataSourceList.setEnabled(false); this.dataSourceList.setSelectedIndices(new int[0]); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form index 19fb132f0d..95baf22255 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form @@ -59,21 +59,22 @@ - + - + - + + @@ -83,23 +84,25 @@ - + - + + + - + @@ -217,5 +220,15 @@ + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index b859df85ef..fc81c092c9 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -23,8 +23,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -32,7 +30,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; -import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle; @@ -88,22 +85,6 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); }); - this.dataSourceList.addMouseMotionListener(new MouseMotionListener() { - - @Override - public void mouseDragged(MouseEvent evt) { - //Unused by now - } - - @Override - public void mouseMoved(MouseEvent evt) { - JList DsList = (JList) evt.getSource(); - int index = DsList.locationToIndex(evt.getPoint()); - if (index > -1) { - DsList.setToolTipText(getDataSourceToolTipList().get(index)); - } - } - }); } /** @@ -139,6 +120,8 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { }; ingestRunning = IngestManager.getInstance().isIngestRunning(); updateIngestIndexLabel(); + + jSaveSearchResults.setSelected(true); IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { @Override @@ -240,6 +223,7 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { jScrollPane1 = new javax.swing.JScrollPane(); dataSourceList = new javax.swing.JList<>(); ingestIndexLabel = new javax.swing.JLabel(); + jSaveSearchResults = new javax.swing.JCheckBox(); org.openide.awt.Mnemonics.setLocalizedText(cutMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.cutMenuItem.text")); // NOI18N rightClickMenu.add(cutMenuItem); @@ -299,6 +283,9 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { ingestIndexLabel.setFont(ingestIndexLabel.getFont().deriveFont(ingestIndexLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 10)); org.openide.awt.Mnemonics.setLocalizedText(ingestIndexLabel, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSingleTermSearchPanel.ingestIndexLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jSaveSearchResults, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSingleTermSearchPanel.jSaveSearchResults.text")); // NOI18N + jSaveSearchResults.setToolTipText(org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSingleTermSearchPanel.jSaveSearchResults.toolTipText")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -312,34 +299,37 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { .addComponent(substringRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(regexRadioButton)) - .addComponent(dataSourceCheckBox) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 297, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jSaveSearchResults) .addGroup(layout.createSequentialGroup() .addComponent(searchButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGap(18, 18, 18) .addComponent(ingestIndexLabel)) - .addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(dataSourceCheckBox)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(exactRadioButton) .addComponent(substringRadioButton) .addComponent(regexRadioButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(dataSourceCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 61, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSaveSearchResults) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(searchButton) .addComponent(ingestIndexLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap()) + .addContainerGap(22, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -359,7 +349,7 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { */ private void keywordTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_keywordTextFieldActionPerformed try { - search(); + search(jSaveSearchResults.isSelected()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Error performing ad hoc single keyword search", e); //NON-NLS } @@ -390,18 +380,26 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { } setComponentsEnabled(); firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); - } /** * Set the dataSourceList enabled if the dataSourceCheckBox is selected */ private void setComponentsEnabled() { - boolean enabled = this.dataSourceCheckBox.isSelected(); - this.dataSourceList.setEnabled(enabled); - if (enabled) { - this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1); + if (getDataSourceListModel().size() > 1) { + this.dataSourceCheckBox.setEnabled(true); + + boolean enabled = this.dataSourceCheckBox.isSelected(); + this.dataSourceList.setEnabled(enabled); + if (enabled) { + this.dataSourceList.setSelectionInterval(0, this.dataSourceList.getModel().getSize()-1); + } else { + this.dataSourceList.setSelectedIndices(new int[0]); + } } else { + this.dataSourceCheckBox.setEnabled(false); + this.dataSourceCheckBox.setSelected(false); + this.dataSourceList.setEnabled(false); this.dataSourceList.setSelectedIndices(new int[0]); } } @@ -442,6 +440,7 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { private javax.swing.JList dataSourceList; private javax.swing.JRadioButton exactRadioButton; private javax.swing.JLabel ingestIndexLabel; + private javax.swing.JCheckBox jSaveSearchResults; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField keywordTextField; private javax.swing.JMenuItem pasteMenuItem; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index f7463bbccb..5061a2e2f0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JTextPane; import javax.swing.SizeRequirements; import javax.swing.SwingWorker; import javax.swing.text.Element; @@ -48,6 +47,7 @@ import org.sleuthkit.autopsy.coreutils.TextUtil; * Panel displays HTML content sent to ExtractedContentViewer, and provides a * combo-box to select between multiple sources. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ExtractedContentPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java index 70fd3ba370..cea9fd1a82 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java @@ -563,7 +563,7 @@ final class IngestSearchRunner { subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress); // Create blackboard artifacts - newResults.process(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages()); + newResults.process(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages(), true); } //if has results diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index b9c4541c7b..fb792808ab 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,7 +27,7 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; @@ -237,9 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index 2033dfbdb7..b56415094e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import java.text.Normalizer; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -57,11 +58,7 @@ class Keyword { * than a substring. */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord) { - this.searchTerm = searchTerm; - this.isLiteral = isLiteral; - this.isWholeWord = isWholeWord; - this.listName = ""; - this.originalTerm = searchTerm; + this(searchTerm, isLiteral, isWholeWord, "", searchTerm); } /** @@ -93,11 +90,12 @@ class Keyword { * used (e.g. for highlighting purposes). */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord, String listName, String originalTerm) { - this.searchTerm = searchTerm; + this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKC); this.isLiteral = isLiteral; this.isWholeWord = isWholeWord; this.listName = listName; this.originalTerm = originalTerm; + } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java index c34e672a1c..156bbd0220 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; /** * Global options panel for keyword searching. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class KeywordSearchGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 83b770bc58..a8abc03b83 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -28,7 +28,6 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.FileIngestModule; @@ -92,6 +91,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { private boolean startedSearching = false; private List textExtractors; private StringsTextExtractor stringExtractor; + private TextFileExtractor txtFileExtractor; private final KeywordSearchJobSettings settings; private boolean initialized = false; private long jobId; @@ -244,6 +244,8 @@ public final class KeywordSearchIngestModule implements FileIngestModule { stringExtractor.setScripts(KeywordSearchSettings.getStringExtractScripts()); stringExtractor.setOptions(KeywordSearchSettings.getStringExtractOptions()); + txtFileExtractor = new TextFileExtractor(); + textExtractors = new ArrayList<>(); //order matters, more specific extractors first textExtractors.add(new HtmlTextExtractor()); @@ -343,7 +345,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { textExtractors.clear(); textExtractors = null; stringExtractor = null; - + txtFileExtractor = null; initialized = false; } @@ -568,6 +570,17 @@ public final class KeywordSearchIngestModule implements FileIngestModule { putIngestStatus(jobId, aFile.getId(), IngestStatus.SKIPPED_ERROR_TEXTEXTRACT); } + if ((wasTextAdded == false) && (aFile.getNameExtension().equalsIgnoreCase("txt"))) { + try { + if (Ingester.getDefault().indexText(txtFileExtractor, aFile, context)) { + putIngestStatus(jobId, aFile.getId(), IngestStatus.TEXT_INGESTED); + wasTextAdded = true; + } + } catch (IngesterException ex) { + logger.log(Level.WARNING, "Unable to index as unicode", ex); + } + } + // if it wasn't supported or had an error, default to strings if (wasTextAdded == false) { extractStringsAndIndex(aFile); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index e13531da26..b3065dcad6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,14 +32,18 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.CursorMarkParams; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; @@ -203,15 +207,44 @@ class LuceneQuery implements KeywordSearchQuery { * @param listName The name of the keyword list that contained the * keyword for which the hit was found. * - * - * @return The newly created artifact or null if there was a problem - * creating it. + * @return The newly created artifact, or null if one wasn't created due to + * either the artifact already existing or an error while trying to + * create it. */ @Override public BlackboardArtifact postKeywordHitToBlackboard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); - Collection attributes = new ArrayList<>(); + List attributesList = new ArrayList<>(); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); + if (originalKeyword != null) { + BlackboardAttribute.ATTRIBUTE_TYPE selType = originalKeyword.getArtifactAttributeType(); + if (selType != null) { + attributesList.add(new BlackboardAttribute(selType, MODULE_NAME, foundKeyword.getSearchTerm())); + } + + if (originalKeyword.searchTermIsWholeWord()) { + attributesList.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.LITERAL.ordinal())); + } else { + attributesList.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); + } + } + if (StringUtils.isNotBlank(listName)) { + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } + + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + Blackboard blackboard = tskCase.getBlackboard(); + if (blackboard.artifactExists(content, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT, attributesList)) { + return null; + } + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format( + "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", + content.getName(), content.getId()), ex); //NON-NLS + } + BlackboardArtifact bba; try { bba = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); @@ -221,32 +254,15 @@ class LuceneQuery implements KeywordSearchQuery { } if (snippet != null) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); - } - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); - if (StringUtils.isNotBlank(listName)) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); - } - - if (originalKeyword != null) { - BlackboardAttribute.ATTRIBUTE_TYPE selType = originalKeyword.getArtifactAttributeType(); - if (selType != null) { - attributes.add(new BlackboardAttribute(selType, MODULE_NAME, foundKeyword.getSearchTerm())); - } - - if (originalKeyword.searchTermIsWholeWord()) { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.LITERAL.ordinal())); - } else { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal())); - } + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } hit.getArtifactID().ifPresent(artifactID - -> attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + -> attributesList.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) ); try { - bba.addAttributes(attributes); //write out to bb + bba.addAttributes(attributesList); //write out to bb return bba; } catch (TskCoreException e) { logger.log(Level.WARNING, "Error adding bb attributes to artifact", e); //NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java index e27a01b063..66d0803812 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryResults.java @@ -141,9 +141,10 @@ class QueryResults { * @param notifyInbox Whether or not to write a message to the ingest * messages inbox if there is a keyword hit in the text * exrtacted from the text source object. + * @param saveResults Flag whether to save search results as KWS artifacts. * */ - void process(ProgressHandle progress, ProgressContributor subProgress, SwingWorker worker, boolean notifyInbox) { + void process(ProgressHandle progress, ProgressContributor subProgress, SwingWorker worker, boolean notifyInbox, boolean saveResults) { /* * Initialize the progress indicator to the number of keywords that will * be processed. @@ -218,22 +219,24 @@ class QueryResults { } catch (TskCoreException | NoCurrentCaseException tskCoreException) { logger.log(Level.SEVERE, "Failed to get text source object for ", tskCoreException); //NON-NLS } + + if (saveResults) { + /* + * Post an artifact for the hit to the blackboard. + */ + BlackboardArtifact artifact = query.postKeywordHitToBlackboard(content, keyword, hit, snippet, query.getKeywordList().getName()); - /* - * Post an artifact for the hit to the blackboard. - */ - BlackboardArtifact artifact = query.postKeywordHitToBlackboard(content, keyword, hit, snippet, query.getKeywordList().getName()); - - /* - * Send an ingest inbox message for the hit. - */ - if (null != artifact) { - hitArtifacts.add(artifact); - if (notifyInbox) { - try { - writeSingleFileInboxMessage(artifact, content); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error sending message to ingest messages inbox", ex); //NON-NLS + /* + * Send an ingest inbox message for the hit. + */ + if (null != artifact) { + hitArtifacts.add(artifact); + if (notifyInbox) { + try { + writeSingleFileInboxMessage(artifact, content); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error sending message to ingest messages inbox", ex); //NON-NLS + } } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index cb640e20bd..102682c4fe 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -36,7 +36,6 @@ import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.CursorMarkParams; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -50,11 +49,13 @@ import static org.sleuthkit.autopsy.keywordsearch.TermsComponentQuery.KEYWORD_SE import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountFileInstance; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -74,7 +75,7 @@ import org.sleuthkit.datamodel.TskData; */ final class RegexQuery implements KeywordSearchQuery { - public static final Logger LOGGER = Logger.getLogger(RegexQuery.class.getName()); + public static final Logger logger = Logger.getLogger(RegexQuery.class.getName()); /** * Lucene regular expressions do not support the following Java predefined @@ -213,7 +214,7 @@ final class RegexQuery implements KeywordSearchQuery { hitsForKeyword.add(hit); } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error creating keyword hits", ex); //NON-NLS + logger.log(Level.SEVERE, "Error creating keyword hits", ex); //NON-NLS } } @@ -223,7 +224,7 @@ final class RegexQuery implements KeywordSearchQuery { } cursorMark = nextCursorMark; } catch (KeywordSearchModuleException ex) { - LOGGER.log(Level.SEVERE, "Error executing Regex Solr Query: " + keywordString, ex); //NON-NLS + logger.log(Level.SEVERE, "Error executing Regex Solr Query: " + keywordString, ex); //NON-NLS MessageNotifyUtil.Notify.error(NbBundle.getMessage(Server.class, "Server.query.exception.msg", keywordString), ex.getCause().getMessage()); } } @@ -437,16 +438,16 @@ final class RegexQuery implements KeywordSearchQuery { * @param listName The name of the keyword list that contained the * keyword for which the hit was found. * - * - * @return The newly created artifact or null if there was a problem - * creating it. + * @return The newly created artifact, or null if one wasn't created due to + * either the artifact already existing or an error while trying to + * create it. */ @Override public BlackboardArtifact postKeywordHitToBlackboard(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); if (content == null) { - LOGGER.log(Level.WARNING, "Error adding artifact for keyword hit to blackboard"); //NON-NLS + logger.log(Level.WARNING, "Error adding artifact for keyword hit to blackboard"); //NON-NLS return null; } @@ -458,41 +459,52 @@ final class RegexQuery implements KeywordSearchQuery { return null; } + List attributesList = new ArrayList<>(); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString())); + if (StringUtils.isNotBlank(listName)) { + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); + } + + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + Blackboard blackboard = tskCase.getBlackboard(); + if (blackboard.artifactExists(content, BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT, attributesList)) { + return null; + } + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format( + "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", + content.getName(), content.getId()), ex); //NON-NLS + } + /* * Create a "plain vanilla" keyword hit artifact with keyword and * regex attributes */ BlackboardArtifact newArtifact; - Collection attributes = new ArrayList<>(); - - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString())); try { newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS + logger.log(Level.SEVERE, "Error adding artifact for keyword hit to blackboard", ex); //NON-NLS return null; } - if (StringUtils.isNotBlank(listName)) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName)); - } if (snippet != null) { - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet)); } hit.getArtifactID().ifPresent(artifactID - -> attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) + -> attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID)) ); - attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal())); - try { - newArtifact.addAttributes(attributes); + newArtifact.addAttributes(attributesList); return newArtifact; } catch (TskCoreException e) { - LOGGER.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS + logger.log(Level.SEVERE, "Error adding bb attributes for terms search artifact", e); //NON-NLS return null; } } @@ -502,7 +514,7 @@ final class RegexQuery implements KeywordSearchQuery { final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { - LOGGER.log(Level.SEVERE, "Keyword hit is not a credit card number"); //NON-NLS + logger.log(Level.SEVERE, "Keyword hit is not a credit card number"); //NON-NLS return; } /* @@ -525,13 +537,13 @@ final class RegexQuery implements KeywordSearchQuery { if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) { if (hit.isArtifactHit()) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); //NON-NLS } else { try { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); //NON-NLS } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s' ", foundKeyword.getSearchTerm(), hit.getSnippet())); //NON-NLS - LOGGER.log(Level.SEVERE, "There was a error getting contentID for keyword hit.", ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s' ", foundKeyword.getSearchTerm(), hit.getSnippet())); //NON-NLS + logger.log(Level.SEVERE, "There was a error getting contentID for keyword hit.", ex); //NON-NLS } } return; @@ -599,7 +611,7 @@ final class RegexQuery implements KeywordSearchQuery { ccAccountInstance.addAttributes(attributes); } catch (TskCoreException | NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Error creating CCN account instance", ex); //NON-NLS + logger.log(Level.SEVERE, "Error creating CCN account instance", ex); //NON-NLS } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 4d36cd1164..1253d40d95 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,7 +70,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -710,9 +710,9 @@ public class Server { if (null == currentCore) { throw new NoOpenCoreException(); } - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Index chunk"); currentCore.addDocument(doc); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } finally { currentCoreLock.readLock().unlock(); } @@ -781,9 +781,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); @@ -1325,13 +1325,13 @@ public class Server { * @throws IOException */ void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check"); CoreAdminRequest statusRequest = new CoreAdminRequest(); statusRequest.setCoreName( null ); statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setIndexInfoNeeded(false); statusRequest.process(solrServer); - EnterpriseHealthMonitor.submitTimingMetric(metric); + HealthMonitor.submitTimingMetric(metric); } /** diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextFileExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextFileExtractor.java new file mode 100644 index 0000000000..bc11515e96 --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextFileExtractor.java @@ -0,0 +1,80 @@ +/* + * 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.keywordsearch; + +import java.io.IOException; +import java.io.Reader; +import java.util.logging.Level; +import org.apache.tika.parser.txt.CharsetDetector; +import org.apache.tika.parser.txt.CharsetMatch; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ReadContentInputStream; + +/** + * Extract text from .txt files + */ +final class TextFileExtractor extends ContentTextExtractor { + + //Set a Minimum confidence value to reject matches that may not have a valid text encoding + //Values of valid text encodings were generally 100, xml code sometimes had a value around 50, + //and pictures and other files with a .txt extention were showing up with a value of 5 or less in limited testing. + //This limited information was used to select the current value as one that would filter out clearly non-text + //files while hopefully working on all files with a valid text encoding + static final private int MIN_MATCH_CONFIDENCE = 20; + static final private Logger logger = Logger.getLogger(TextFileExtractor.class.getName()); + + @Override + boolean isContentTypeSpecific() { + return true; + } + + @Override + boolean isSupported(Content file, String detectedFormat) { + return true; + } + + @Override + public Reader getReader(Content source) throws TextExtractorException { + CharsetDetector detector = new CharsetDetector(); + ReadContentInputStream stream = new ReadContentInputStream(source); + try { + detector.setText(stream); + } catch (IOException ex) { + throw new TextExtractorException("Unable to get string from detected text in UnicodeTextExtractor", ex); + } + CharsetMatch match = detector.detect(); + if (match.getConfidence() < MIN_MATCH_CONFIDENCE) { + throw new TextExtractorException("Text does not match any character set with a high enough confidence for UnicodeTextExtractor"); + } + + return match.getReader(); + } + + @Override + public boolean isDisabled() { + return false; + } + + @Override + public void logWarning(String msg, Exception ex) { + logger.log(Level.WARNING, msg, ex); + } + +} diff --git a/NEWS.txt b/NEWS.txt index 73b199ef4f..cbdb02f349 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,3 +1,35 @@ +---------------- VERSION 4.8.0 -------------- +New Features: +- The case tree view can now be grouped by data source. +- Added a common files search tool that finds all instances of a file in a case. +- Text extraction optionally includes optical character recognition (OCR). +- Data source(s) filter added to ad hoc keyword search and file search by +attributes. +- SQLite tables can be now be exported to CSV files. +- User defined tags now appear first in tagging menus. +- Eliminated one tagging sub menu layer for faster tagging. +- Added Replace Tag item to tagging menus (shortcut for delete tag, add tag). +- The Other Occurrences content viewer now shows matches in the current case. +- A listing of cases in the central repository is displayed by the +central repository options panel. +- An interesting file artifact is now created when a "zip bomb" is detected. +- Text and queries sent to Solr are now normalized to handle diacritics, +ligatures, narrow and wide width Japanese characters, etc. +- An object detection ingest module that uses OpenCV and user-supplied +classifiers has been added to the "experimental" Net Beans Module (NBM). +- A data source processor that runs Volatility on a memory image has been +added to the "experimental" NBM. +- Comments can be added to all files (file correlation properties) recorded +in the central repository using a results view context menu item. +- Comments can be added to all correlation properties recorded +in the central repository using an Other Occurrences results content viewer +context menu item. + +Bug Fixes: +- Expanding the case tree is more efficient. +- Improved "zip bomb" detection. +- Assorted small bug fixes are included. + ---------------- VERSION 4.7.0 -------------- New Features: - A graph visualization was added to the Communications tool to make it easier to find messages and relationships. diff --git a/RecentActivity/manifest.mf b/RecentActivity/manifest.mf index 3121c6b8ca..c115996776 100644 --- a/RecentActivity/manifest.mf +++ b/RecentActivity/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/6 -OpenIDE-Module-Implementation-Version: 15 +OpenIDE-Module-Implementation-Version: 16 OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties OpenIDE-Module-Requires: diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index fd72fa7dfe..4d85f94e9b 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -60,7 +60,7 @@ 10 - 10.11 + 10.12 diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java index e5d93a91d4..4c8999ec47 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2012-2014 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. @@ -237,8 +237,19 @@ class SearchEngineURLQueryAnalyzer extends Extract { try { //try to decode the url String decoded = URLDecoder.decode(x, "UTF-8"); //NON-NLS return decoded; - } catch (UnsupportedEncodingException uee) { //if it fails, return the encoded string - logger.log(Level.FINE, "Error during URL decoding ", uee); //NON-NLS + } catch (UnsupportedEncodingException exception) { //if it fails, return the encoded string + logger.log(Level.FINE, "Error during URL decoding, returning undecoded value:" + + "\n\tURL: " + url + + "\n\tUndecoded value: " + x + + "\n\tEngine name: " + eng.getEngineName() + + "\n\tEngine domain: " + eng.getDomainSubstring(), exception); //NON-NLS + return x; + } catch (IllegalArgumentException exception) { //if it fails, return the encoded string + logger.log(Level.SEVERE, "Illegal argument passed to URL decoding, returning undecoded value:" + + "\n\tURL: " + url + + "\n\tUndecoded value: " + x + + "\n\tEngine name: " + eng.getEngineName() + + "\n\tEngine domain: " + eng.getDomainSubstring(), exception); //NON-NLS) return x; } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java index a47d33f22a..8b246b05aa 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Util.java @@ -86,11 +86,12 @@ class Util { public static String getBaseDomain(String url) { String host = null; + //strip protocol - String cleanUrl = url.replaceFirst("/.*:\\/\\//", ""); + String cleanUrl = url.replaceFirst(".*:\\/\\/", ""); //strip after slashes - String dirToks[] = cleanUrl.split("/\\//"); + String dirToks[] = cleanUrl.split("\\/"); if (dirToks.length > 0) { host = dirToks[0]; } else { @@ -141,7 +142,6 @@ class Util { if (result == null || result.trim().isEmpty()) { return getBaseDomain(value); } - return result; } diff --git a/TSKVersion.xml b/TSKVersion.xml index 57096d8d35..7350f25b66 100644 --- a/TSKVersion.xml +++ b/TSKVersion.xml @@ -1,3 +1,3 @@ - + diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 741247d8d3..693f4b9f89 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 15 Jun 2018 12:30:02 -0600 +#Mon, 25 Jun 2018 17:19:36 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.7.0 +currentVersion=Autopsy 4.8.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 5e6893f50b..3de464ea54 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 15 Jun 2018 12:30:02 -0600 -CTL_MainWindow_Title=Autopsy 4.7.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.7.0 +#Mon, 25 Jun 2018 17:19:36 -0400 +CTL_MainWindow_Title=Autopsy 4.8.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.8.0 diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 84605ac34f..ee0a85b2dd 100755 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.7.0 +HTML_OUTPUT = 4.8.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen-user/adHocKeywordSearch.dox b/docs/doxygen-user/adHocKeywordSearch.dox index f8e4881fb4..82c1084aca 100644 --- a/docs/doxygen-user/adHocKeywordSearch.dox +++ b/docs/doxygen-user/adHocKeywordSearch.dox @@ -72,7 +72,7 @@ In general all three types of keyword searches will work as expected but the fea \section ad_hoc_kw_search Keyword Search -Individual keyword or regular expressions can quickly be searched using the search text box widget. You can select "Exact Match", "Substring Match" and "Regular Expression" match. See the earlier \ref ad_hoc_kw_types_section section for information on each keyword type. +Individual keyword or regular expressions can quickly be searched using the search text box widget. You can select "Exact Match", "Substring Match" and "Regular Expression" match. See the earlier \ref ad_hoc_kw_types_section section for information on each keyword type. The search can be restricted to only certain data sources by selecting the checkbox near the bottom and then highlighting the data sources to search within. Multiple data sources can be selected used shift+left click or control+left click. \image html keyword-search-bar.PNG @@ -84,7 +84,7 @@ Results will be opened in a separate Results Viewer for every search executed an In addition to being selected during ingest, keyword lists can also be run through the Keyword Lists button. For information on setting up these keyword lists, see the \ref keywordListsTab section of the ingest module documentation. -Lists created using the Keyword Search Configuration Dialog can be manually searched by the user by pressing on the 'Keyword Lists' button, selecting the check boxes corresponding to the lists to be searched, and pressing the 'Search' button. +Lists created using the Keyword Search Configuration Dialog can be manually searched by the user by pressing on the 'Keyword Lists' button and selecting the check boxes corresponding to the lists to be searched. The search can be restricted to only certain data sources by selecting the checkbox near the bottom and then highlighting the data sources to search within. Multiple data sources can be selected used shift+left click or control+left click. Once everything has been configured, press "Search" to begin the search. \image html keyword-search-list.PNG diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox index 9bcfe9dd07..83d3407e88 100644 --- a/docs/doxygen-user/central_repo.dox +++ b/docs/doxygen-user/central_repo.dox @@ -7,11 +7,11 @@ It is a combination of an ingest module that extracts, stores, and compares prop properties, a database that stores these properties, and an additional panel in Autopsy to display other instances of each property. The central repository database can either be SQLite or PostgreSQL. -The following are some use cases for the Central Repository: +The following are some use cases for the central repository: - Finding Other Instances of a Property - - If you find a file or Autopsy artifact (such as a Web History item), there is a content viewer in the bottom right that will show you other cases that had this same file or that had items with the same feature (such as Domain name). You will also be able to see what other data sources in the same case had this feature. + - If you navigate to a file or Autopsy artifact (such as a Web History item), there is a content viewer in the bottom right that will show you other instances of this property across the data stored in the central repository. - Alerting When Previously Notable Properties Occur - - You can use the Central Repository to record which properties were associated with files and artifacts that were evidence (or notable). Once these properties have been tagged as notable they will be added to the Interesting Items section of the tree when seen again in any future cases. + - You can use the central repository to record which properties were associated with files and artifacts that were evidence (or notable). Once these properties have been tagged as notable they will be added to the Interesting Items section of the tree when seen again in any future cases. - Storing Hash Sets - You can create and import hash sets into the central repository instead of using local copies in the \ref hash_db_page "Hash Lookup module". These hash sets are functionally equivalent to local hash sets but can be shared among multiple analysts (when using a PostgreSQL central repository). @@ -24,13 +24,13 @@ The following are some use cases for the Central Repository: \section cr_setup Setup -To start, open the main options panel and select the Central Repository icon. +To start, open the main options panel and select the "Central Repository" icon. \image html central_repo_options.png \subsection cr_db_setup Setting up the Database -On the Central Repository options panel, check the 'Use a Central Repository' option and then click the Configure button to set up a database. There are two options here: +On the central repository options panel, check the 'Use a Central Repository' option and then click the Configure button to set up a database. There are two options here: - SQLite - This option stores the database in a file. It should only be used when a single client will be accessing the database. - PostgreSQL - This option uses a database server running either on the user's host or a remote server. This option must be used if multiple users will be using the same database. @@ -89,7 +89,13 @@ Organizations are stored in the central repository and contain contact informati One default org, "Not Specified" will always be present in the list. New organizations can be created, edited, and deleted through the appropriate buttons. Note that any organization that is currently in use by a case or hash set can not be deleted. All fields apart from the organization name are optional. \image html central_repo_new_org.png - + +\subsection cr_show_cases Show Cases + +Displays a list of all cases that are in the central repository database. + +\image html central_repo_details.png + \section cr_using_repo Using the Central Repository \subsection cr_ingest_module Correlation Engine Module @@ -103,17 +109,20 @@ other cases/data sources where the Correlation Engine was run. \subsection cr_tagging Tagging Files and Artifacts -Any file or artifact that a user tags with a tag with notable set will be added -to the database as a file or artifact of interest. By default, there will be a tag named "Notable Item" that can be used for this purpose. See the \ref tagging_page "Tagging page" for more information on creating additional tags with notable status. Any future data source ingest (where this module is enabled) -will use those notable files or artifacts in a similar manner as a Known Bad hash set, causing matching files from that -ingest to be added to the Interesting Artifacts list in that currently open case. +Tagging a file or artifact with a "notable" tag will change its associated property in the central repository to notable as well. +By default, there will be a tag named "Notable Item" that can be used for this purpose. See the \ref tagging_page "Tagging page" for more information on creating additional tags with notable status. +Any future data source ingest (where this module is enabled) +will use those notable properties in a similar manner as a Known Bad hash set, causing matching files and artifacts from that +ingest to be added to the Interesting Items list in that currently open case. \image html central_repo_tag_file.png -If a tag is accidentally added to a file or artifact, it can be removed though the context menu. This will remove its -notable status in the Central Repository. +If a tag is accidentally added to a file or artifact, it can be removed though the context menu. This will remove its property's +notable status in the central repository. -If you would like to prevent the Interesting Items from being created in a particular case, you can disable the flagging through the run time ingest properties. Note that this only disables the Interesting Item results - all files and artifacts are still added to the central repository. +If you would like to prevent the Interesting Items from being created in a particular case, you can disable the flagging +through the run time ingest properties. Note that this only disables the Interesting Item results - all properties +are still added to the central repository. \image html central_repo_disable_flagging.png @@ -125,11 +134,16 @@ Results from enabling a central repository and running the Correlation Engine In \subsection cr_content_viewer Content Viewer -The \ref content_viewer_page panel is where previous instances of properties are displayed. This module adds a new tab to the Content Viewer. The tab for this module is called "Other Occurrences". It can display data that is found in other cases, other data sources for the same case, or imported global artifacts. +The \ref content_viewer_page panel is where previous instances of properties are displayed. Without a central repository enabled, +this "Other Occurrences" panel will show files with hashes matching the selected file within the current case. Enabling a central +repository allows this panel to also display matching properties stored in the database, and adds some functionality to the row. +Note that the Correlation Engine Ingest Module does not have to have been run on the current data source to see correlated +properties from the central repository. If the selected file or artifact is associated by one of the supported Correlation Types, +to one or more properties in the database, the associated properties will be displayed. Note: the Content +Viewer will display ALL associated properties available in the database. It ignores the user's enabled/disabled Correlation Properties. -If at least one other case or data source has been ingested with this module enabled, there is a potential that data will be displayed in the Other Occurrences content viewer. Note that the Correlation Engine Ingest Module does not have to have been run on the current data source to see correlated files from other cases/data sources. If the selected file or artifact is associated by one of the supported Correlation Types, to one or more file(s) or artifact(s) in the database, the associated files/artifacts will be displayed. Note: the Content Viewer will display ALL associated files and artifacts available in the database. It ignores the user's enabled/disabled Correlation Properties. - -By default, the rows in the content viewer will have background colors to indicate if they are known to be of interest. Files/artifacts that are notable will have a Red background, all others will have a White background. +By default, the rows in the content viewer will have background colors to indicate if they are known to be of interest. Properties that are notable +will have a Red background, all others will have a White background. \image html central_repo_content_viewer.png @@ -141,6 +155,7 @@ This menu has several options. -# Export Selected Rows to CSV -# Show Case Details -# Show Frequency +-# Add/Edit Comment Select All @@ -170,19 +185,29 @@ the Case -> Case Properties menu. Show Frequency -This shows how common the selected file is. The value is the percentage of case/data source tuples that have the selected file or artifact. +This shows how common the selected file is. The value is the percentage of case/data source tuples that have the selected property. + +Add/Edit Comment + +This allows you to add a comment for this entry or edit an existing comment. If you want instead to edit the comment of the originally selected node, it can be done by right clicking on the original item in the result viewer and selecting "Add/Edit Central Repository Comment". + +\image html central_repo_comment_menu.png \subsection cr_interesting_items Interesting Items -In the Results tree of an open case is an entry called Interesting Items. When this module is enabled, all of the enabled Correlatable Properties will cause matching files to be added to this Interesting Items tree during ingest. +In the Results tree of an open case is an entry called Interesting Items. When this module is enabled, all of the enabled +Correlatable Properties will cause matching files and artifacts to be added to this Interesting Items tree during ingest. \image html central_repo_interesting_items.png -As an example, if the Files Correlatable Property is enabled, and the ingest is currently processing a file, for example "badfile.exe", and the MD5 hash for that file already exists in the database as a notable file, then an entry in the Interesting Items tree will be added for the current instance of "badfile.exe" in the data source currently being ingested. +As an example, suppose the Files Correlatable Property is enabled and the ingest is currently processing a file "badfile.exe", and the MD5 hash +for that file already exists in the database as a notable file property. In this case an entry in the Interesting Items tree will be added for +the current instance of "badfile.exe" in the data source currently being ingested. The same type of thing will happen for each enabled Correlatable Property. -In the case of the phone number correlatable type, the Interesting Items tree will start a sub-tree for each phone number. The sub-tree will then contain each instance of that notable phone number. +In the case of the phone number correlatable type, the Interesting Items tree will start a sub-tree for each phone number. The sub-tree will +then contain each instance of that notable phone number. diff --git a/docs/doxygen-user/common_files.dox b/docs/doxygen-user/common_files.dox new file mode 100644 index 0000000000..bdc6c0bbb9 --- /dev/null +++ b/docs/doxygen-user/common_files.dox @@ -0,0 +1,23 @@ +/*! \page common_files_page Common Files Search + +\section common_files_overview Overview + +The common files feature allows you to search for multiple copies of the same file in different data sources within a case. + +\section common_files_usage Usage + +To start, go to Tools->Common Files Search to bring up the following dialog: + +\image html common_files_dialog.png + +You can choose to find any files with multiple copies in the whole case, or specify that at least one of the copies has to be in the selected data source(s). + +\image html common_files_data_source.png + +You can also choose to restrict the search to only pictures and videos and/or documents. + +Once the search is run, the matching files are displayed in the results tab. The results are grouped by how many matching files were found and then grouped by hash. + +\image html common_files_results.png + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/content_viewer.dox b/docs/doxygen-user/content_viewer.dox index 2251c9ece2..ed030b7ff1 100644 --- a/docs/doxygen-user/content_viewer.dox +++ b/docs/doxygen-user/content_viewer.dox @@ -28,7 +28,7 @@ It will display most image types: \image html content_viewer_app_image.png -It also allows you to browse SQLite tables: +It also allows you to browse SQLite tables and export their contents as CSV: \image html content_viewer_app_sqlite.png @@ -64,7 +64,7 @@ The Results tab is active when selecting entries that are part of the Results tr \section cv_other_occurrences Other Occurrences -The Other Occurrences tab shows what other cases/data sources this file or result has appeared in. The \ref central_repo_page must be enabled to access this tab. See the \ref cr_content_viewer section for more information. +The Other Occurrences tab shows other instances of this file or result. Enabling the \ref central_repo_page adds additional functionality to this tab. See the \ref cr_content_viewer section for more information. \image html content_viewer_other_occurrences.png diff --git a/docs/doxygen-user/experimental.dox b/docs/doxygen-user/experimental.dox new file mode 100644 index 0000000000..4593a16df8 --- /dev/null +++ b/docs/doxygen-user/experimental.dox @@ -0,0 +1,19 @@ +/*! \page experimental_page Experimental Module + +\section exp_overview Overview + +The Experimental module, as the name implies, contains code that is not yet part of the official Autopsy release. These experimental features can be used but may be less polished than other features and will have less documentation. These modules may be changed at any time. + +\section exp_setup Enabling the Experimental Module + +To start, go to Tools->Plugins and select the "Installed" tab, then check the box next to "Experimental" and click "Activate" and go throught the next couple of screens. A reset should not be required. + +\image html experimental_plugins_menu.png + +\section exp_features Current Experimental Features + +- Auto Ingest +- \ref object_detection_page +- \ref volatility_dsp_page + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/file_search.dox b/docs/doxygen-user/file_search.dox index 9eb716233f..ec8c6a9abe 100644 --- a/docs/doxygen-user/file_search.dox +++ b/docs/doxygen-user/file_search.dox @@ -24,16 +24,18 @@ Note: it doesn't support regular expression and keyword matching. Search for all files and directory whose size matches the pattern given. The pattern can be "equal to", "greater than", and "less than". The unit for the size can be "Byte(s)", "KB", "MB", "GB", and "TB". \li MIME Type: Search for all files with the selected MIME type. Multiple types can be used by holding SHIFT or CTRL while selecting. +\li MD5: +Search for all files with the given MD5 hash. \li Date: Search for all files and directory whose "date property" is within the date range given. The "date properties" are "Modified Date", "Accessed Date", "Changed Date", and "Created Date". You must also specify the timezone for the date given. \li Known Status: Search for all files and directory whose known status is recognized as either Unknown, Known, or Known Bad. For more on Known Status, see the \ref hash_db_page. To use any of these filters, check the box next to the category and click "Search" button to start the search process. The result will show up in the "Result Viewer". -\li MD5 -Search for all files with the given MD5 hash. +\li Data Source: +Search only within the specified data source instead of the entire case. Note that multiple data sources can be selected by holding SHIFT or CTRL while selecting. -Here's a contrived example where we try to get all the directories and files whose name contains "hello", has a size greater than 1000 Bytes, is in JPEG format, was created between 09/06/2017 and -09/09/2017 (in GMT-5 timezone), is an unknown file, and has a hash of 1127F348BD4303A4C3D1D587C807B49F: +Here's a contrived example where we try to get all the directories and files whose name contains "hello", has a size greater than 1000 Bytes, is in JPEG format, was created between 06/01/2018 and +06/08/2017 (in GMT-5 timezone), is an unknown file, has a hash of 1127F348BD4303A4C3D1D587C807B49F, and appears in data source "image3.vhd": \image html example-of-file-search.PNG */ \ No newline at end of file diff --git a/docs/doxygen-user/footer.html b/docs/doxygen-user/footer.html index 708afd5dd7..e53036dde6 100644 --- a/docs/doxygen-user/footer.html +++ b/docs/doxygen-user/footer.html @@ -1,5 +1,5 @@
-

Copyright © 2012-2016 Basis Technology. Generated on $date
+

Copyright © 2012-2018 Basis Technology. Generated on $date
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

diff --git a/docs/doxygen-user/images/central_repo_comment_menu.png b/docs/doxygen-user/images/central_repo_comment_menu.png new file mode 100644 index 0000000000..0f3c46e08f Binary files /dev/null and b/docs/doxygen-user/images/central_repo_comment_menu.png differ diff --git a/docs/doxygen-user/images/central_repo_details.png b/docs/doxygen-user/images/central_repo_details.png new file mode 100644 index 0000000000..bc3f871fa7 Binary files /dev/null and b/docs/doxygen-user/images/central_repo_details.png differ diff --git a/docs/doxygen-user/images/central_repo_options.png b/docs/doxygen-user/images/central_repo_options.png index 2ad9701dbf..5a75e0b0ea 100644 Binary files a/docs/doxygen-user/images/central_repo_options.png and b/docs/doxygen-user/images/central_repo_options.png differ diff --git a/docs/doxygen-user/images/central_repo_tag_file.png b/docs/doxygen-user/images/central_repo_tag_file.png index 0c13c9a459..b84d4b52d9 100644 Binary files a/docs/doxygen-user/images/central_repo_tag_file.png and b/docs/doxygen-user/images/central_repo_tag_file.png differ diff --git a/docs/doxygen-user/images/common_files_data_source.png b/docs/doxygen-user/images/common_files_data_source.png new file mode 100644 index 0000000000..c26deb3bc6 Binary files /dev/null and b/docs/doxygen-user/images/common_files_data_source.png differ diff --git a/docs/doxygen-user/images/common_files_dialog.png b/docs/doxygen-user/images/common_files_dialog.png new file mode 100644 index 0000000000..8be9e7c6f1 Binary files /dev/null and b/docs/doxygen-user/images/common_files_dialog.png differ diff --git a/docs/doxygen-user/images/common_files_results.png b/docs/doxygen-user/images/common_files_results.png new file mode 100644 index 0000000000..7b16ce92b7 Binary files /dev/null and b/docs/doxygen-user/images/common_files_results.png differ diff --git a/docs/doxygen-user/images/content_viewer_app_sqlite.png b/docs/doxygen-user/images/content_viewer_app_sqlite.png index e6cd330699..bd652913a7 100644 Binary files a/docs/doxygen-user/images/content_viewer_app_sqlite.png and b/docs/doxygen-user/images/content_viewer_app_sqlite.png differ diff --git a/docs/doxygen-user/images/directory-tree.PNG b/docs/doxygen-user/images/directory-tree.PNG index 3e4dacb4e2..5477b17dba 100644 Binary files a/docs/doxygen-user/images/directory-tree.PNG and b/docs/doxygen-user/images/directory-tree.PNG differ diff --git a/docs/doxygen-user/images/example-of-file-search.PNG b/docs/doxygen-user/images/example-of-file-search.PNG index 99b2994cac..310704a471 100644 Binary files a/docs/doxygen-user/images/example-of-file-search.PNG and b/docs/doxygen-user/images/example-of-file-search.PNG differ diff --git a/docs/doxygen-user/images/experimental_plugins_menu.png b/docs/doxygen-user/images/experimental_plugins_menu.png new file mode 100644 index 0000000000..21895bc953 Binary files /dev/null and b/docs/doxygen-user/images/experimental_plugins_menu.png differ diff --git a/docs/doxygen-user/images/file-search-top-component.PNG b/docs/doxygen-user/images/file-search-top-component.PNG deleted file mode 100644 index d98b73be33..0000000000 Binary files a/docs/doxygen-user/images/file-search-top-component.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/keyword-search-bar.PNG b/docs/doxygen-user/images/keyword-search-bar.PNG index dc9cc74111..09fd2b404d 100644 Binary files a/docs/doxygen-user/images/keyword-search-bar.PNG and b/docs/doxygen-user/images/keyword-search-bar.PNG differ diff --git a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG index e5706a4df8..5dcc5e3d03 100644 Binary files a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG and b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG differ diff --git a/docs/doxygen-user/images/keyword-search-list.PNG b/docs/doxygen-user/images/keyword-search-list.PNG index 112ff6a7c8..56919753d2 100644 Binary files a/docs/doxygen-user/images/keyword-search-list.PNG and b/docs/doxygen-user/images/keyword-search-list.PNG differ diff --git a/docs/doxygen-user/images/keyword-search-ocr-image.png b/docs/doxygen-user/images/keyword-search-ocr-image.png new file mode 100644 index 0000000000..a8fda73cc4 Binary files /dev/null and b/docs/doxygen-user/images/keyword-search-ocr-image.png differ diff --git a/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png b/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png new file mode 100644 index 0000000000..d6c3a0ac8c Binary files /dev/null and b/docs/doxygen-user/images/keyword-search-ocr-indexed-text.png differ diff --git a/docs/doxygen-user/images/object_detection_classifier_dir.PNG b/docs/doxygen-user/images/object_detection_classifier_dir.PNG new file mode 100644 index 0000000000..7915d370de Binary files /dev/null and b/docs/doxygen-user/images/object_detection_classifier_dir.PNG differ diff --git a/docs/doxygen-user/images/object_detection_results.PNG b/docs/doxygen-user/images/object_detection_results.PNG new file mode 100644 index 0000000000..28866ac9c9 Binary files /dev/null and b/docs/doxygen-user/images/object_detection_results.PNG differ diff --git a/docs/doxygen-user/images/object_detection_warning.PNG b/docs/doxygen-user/images/object_detection_warning.PNG new file mode 100644 index 0000000000..b1367d9365 Binary files /dev/null and b/docs/doxygen-user/images/object_detection_warning.PNG differ diff --git a/docs/doxygen-user/images/tagging-1.PNG b/docs/doxygen-user/images/tagging-1.PNG index b2a91afe0b..e6c81c4afa 100644 Binary files a/docs/doxygen-user/images/tagging-1.PNG and b/docs/doxygen-user/images/tagging-1.PNG differ diff --git a/docs/doxygen-user/images/tagging_new_tag.PNG b/docs/doxygen-user/images/tagging_new_tag.PNG new file mode 100644 index 0000000000..4ae64540f0 Binary files /dev/null and b/docs/doxygen-user/images/tagging_new_tag.PNG differ diff --git a/docs/doxygen-user/images/ui_layout_group_tree.PNG b/docs/doxygen-user/images/ui_layout_group_tree.PNG new file mode 100644 index 0000000000..798fe1ac97 Binary files /dev/null and b/docs/doxygen-user/images/ui_layout_group_tree.PNG differ diff --git a/docs/doxygen-user/images/volatility_dsp_config.PNG b/docs/doxygen-user/images/volatility_dsp_config.PNG new file mode 100644 index 0000000000..84bb9a414f Binary files /dev/null and b/docs/doxygen-user/images/volatility_dsp_config.PNG differ diff --git a/docs/doxygen-user/images/volatility_dsp_interesting_items.PNG b/docs/doxygen-user/images/volatility_dsp_interesting_items.PNG new file mode 100644 index 0000000000..83fbc276eb Binary files /dev/null and b/docs/doxygen-user/images/volatility_dsp_interesting_items.PNG differ diff --git a/docs/doxygen-user/images/volatility_dsp_module_output.PNG b/docs/doxygen-user/images/volatility_dsp_module_output.PNG new file mode 100644 index 0000000000..256dc09fc3 Binary files /dev/null and b/docs/doxygen-user/images/volatility_dsp_module_output.PNG differ diff --git a/docs/doxygen-user/images/volatility_dsp_select.png b/docs/doxygen-user/images/volatility_dsp_select.png new file mode 100644 index 0000000000..aa1b6b477a Binary files /dev/null and b/docs/doxygen-user/images/volatility_dsp_select.png differ diff --git a/docs/doxygen-user/keyword_search.dox b/docs/doxygen-user/keyword_search.dox index 6edb313f38..b7509892f8 100644 --- a/docs/doxygen-user/keyword_search.dox +++ b/docs/doxygen-user/keyword_search.dox @@ -36,7 +36,7 @@ Under the Keyword list is the option to send ingest inbox messages for each hit. \image html keyword-search-inbox.PNG ## String Extraction tab {#stringExtractionTab} -The string extraction setting defines how strings are extracted from files from which text cannot be extracted because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. +The string extraction setting defines how strings are extracted from files from which text cannot be extracted normally because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. When we extract strings from binary files we need to interpret sequences of bytes as text differently, depending on the possible text encoding and script/language used. In many cases we don't know in advance what the specific encoding/language the text is encoded in. However, it helps if the investigator is looking for a specific language, because by selecting less languages the indexing performance will be improved and the number of false positives will be reduced. The default setting is to search for English strings only, encoded as either UTF8 or UTF16. This setting has the best performance (shortest ingest time). @@ -44,6 +44,14 @@ The user can also use the String Viewer first and try different script/language \image html keyword-search-configuration-dialog-string-extraction.PNG +There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The following shows a sample image containing text: + +\image html keyword-search-ocr-image.png + +The "Indexed Text" tab shows the results when running the keyword search module with the OCR option enabled. If we were to use Keyword Search to look for the word "forensics", this file would be a match. + +\image html keyword-search-ocr-indexed-text.png + ## General Settings tab {#generalSettingsTab} \image html keyword-search-configuration-dialog-general.PNG diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index 8aabf7a03b..68bc5c4fab 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -47,6 +47,7 @@ The following topics are available here: - \subpage stix_page - \subpage central_repo_page - \subpage communications_page + - \subpage common_files_page - \subpage logs_and_output_page - Reporting - \subpage tagging_page @@ -63,6 +64,7 @@ The following topics are available here: - \subpage multiuser_page - \subpage live_triage_page - \subpage advanced_page +- \subpage experimental_page If the topic you need is not listed, refer to the Autopsy Wiki or join the SleuthKit User List at SourceForge. diff --git a/docs/doxygen-user/object_detection.dox b/docs/doxygen-user/object_detection.dox new file mode 100644 index 0000000000..2685b69c41 --- /dev/null +++ b/docs/doxygen-user/object_detection.dox @@ -0,0 +1,25 @@ +/*! \page object_detection_page Object Detection + +\section object_overview Overview + +The Object Detection module uses OpenCV to try to detect objects in images. + +\section object_setup Setup + +To start, you will need some classifiers, which are xml files. Autopsy can not create classifiers - do a web search for "train OpenCV classifiers" to find information on how to make classifiers, or visit the OpenCV page. + +Once you have your set of classifiers, copy them into the folder "object_detection_classifiers" in your Autopsy user directory. On Windows, this will normally be found in "C:\Users\\AppData\Roaming\Autopsy". If you can't find the directory, try to run the module as described in the next section. The warning message will tell you where the module expects the classifiers to be. + +\image html object_detection_classifier_dir.PNG + +\section object_running Running the Ingest Module + +You can run the object detection module by enabling it when running ingest. There is no further configuration needed. If you have not added any classifiers, or have put the classifiers in the wrong place, you'll see a warning bubble. + +\image html object_detection_warning.PNG + +Any files that had objects detected in them will appear in under "Objects Detected". The result tree will show which classifiers matched each image. + +\image html object_detection_results.PNG + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/tagging.dox b/docs/doxygen-user/tagging.dox index 805ae787f5..4195be7c92 100644 --- a/docs/doxygen-user/tagging.dox +++ b/docs/doxygen-user/tagging.dox @@ -12,11 +12,16 @@ Which to choose depends upon the context and what you desire in the final report \image html tagging-1.PNG -Once you have chosen to tag the file or the result, there are two more options: -- Quick Tag -- use this if you just want the tag +At this point there are three options: +- Use one of the existing tags to add it to the file/result without a comment - Tag and Comment -- use this if you need to add a comment about this tag \image html tagging-2.PNG + +- New tag -- Create a new tag and add it to the file/result + +\image html tagging_new_tag.png +
There are several default tag names: - Bookmark - Default tag for marking files of interest @@ -24,9 +29,7 @@ There are several default tag names: - Follow Up - Default tag for marking files to follow up on - Notable item - Default tag for indicating that an item should be marked as notable in the central repository -You can also create custom tag names. These tag names will be automatically saved for future use. Choose a tag from the list you have created, or create a "New Tag". - -\image html tagging-3.PNG +You can also create custom tag names. These tag names will be automatically saved for future use and will be displayed above the default tag names. If you just want to tag the item with the default "Bookmark" tag, you can also use the keyboard shortcut control+B instead of going through the menus. diff --git a/docs/doxygen-user/tree_viewer.dox b/docs/doxygen-user/tree_viewer.dox index 6a40c02d8d..9e309476d0 100644 --- a/docs/doxygen-user/tree_viewer.dox +++ b/docs/doxygen-user/tree_viewer.dox @@ -1,10 +1,8 @@ /*! \page tree_viewer_page Tree Viewer -The Tree Viewer shows the discovered folders by the data sources they come from, as well as a list of files in the folders. It is located on the left side of the Autopsy screen. +The Tree Viewer shows the discovered folders by the data sources they come from, as well as a list of files in the folders. It is located on the left side of the Autopsy screen. The "Group by Data Source" option on the top left moves all views, results, and tags under their corresponding data source. -Each folder in the tree on the left shows how many items are contained within it in parenthesis after the directory name. See the picture below. +Each folder in the tree on the left shows how many items are contained within it in parentheses after the directory name. See the picture below. -
\image html directory-tree.PNG -
*/ diff --git a/docs/doxygen-user/uilayout.dox b/docs/doxygen-user/uilayout.dox index a8e2b35efe..15914c5271 100644 --- a/docs/doxygen-user/uilayout.dox +++ b/docs/doxygen-user/uilayout.dox @@ -21,12 +21,16 @@ The major areas in the Autopsy User Interface (UI) are:
-The tree on the left-hand side is find saved results from automated procedures (ingest). The tree has four main areas: +The tree on the left-hand side is where you can browse the files in the image and find saved results from automated procedures (ingest). The tree has five main areas: - Data Sources: This shows the directory tree hierarchy of the file systems in the images. You can navigate to a specific file or directory here. Each data source added is represented as a drive. If you add a data source multiple times, it shows up multiple times. - Views: Specific types of files from the data sources are shown here, aggregated by type or other properties. Files here can come from more than one data source. Look here for files of a specific type or property. - Results: Where you can see the results from the background ingest tasks and you can see your previous search results. Go here to see what was found by the ingest modules and to find your previous search results. +- Tags: Where files and results that have been \ref tagging_page "tagged" are shown - Reports: References to reports that you have generated or that ingest modules have created show up here +You can also use the "Group by Data Source" option at the upper left of the tree display to move the views, results, and tags subtrees under their corresponding data sources. This can be helpful on very large cases to reduce the size of each node. + +\image html ui_layout_group_tree.PNG \subsection ui_tree_ds Data Sources diff --git a/docs/doxygen-user/volatility_dsp.dox b/docs/doxygen-user/volatility_dsp.dox new file mode 100644 index 0000000000..be99b7d527 --- /dev/null +++ b/docs/doxygen-user/volatility_dsp.dox @@ -0,0 +1,32 @@ +/*! \page volatility_dsp_page Volatility Data Source Processor + +\section Overview + +The Volatility data source processor runs Volatility on a memory image and saves the individual Volatility module results. If the disk image associated with the memory image is also available, it will create Interesting Item artifacts linking the Volatility results to files in the disk image. + +\section Usage + +If you have a disk image associated with your memory image, ingest the disk image into the case first. Then go to "Add Data Source" and select "Memory Image File". + +\image html volatility_dsp_select.png + +On the next screen, you can select your memory image and then adjust the settings to choose a profile and which Volatility plugins to run. + +\image html volatility_dsp_config.png + +Next you'll see the ingest module configuration panel. No ingest modules will be run when using the Volatility data source processor, so simply hit the "Next" button. When it finishes, you may have some non-critical errors. These frequently come from the data source processor being unable to find files in the original disk image. If you did not add the associated disk image before running the Volatility data source processor on the memory image, there will be a large number of these errors but the Volatility module output will still be available. + +\section Results + +There are two types of results that come from running the Volatility data source processor: Module Output and Interesting Items (if the disk image was added). The Module Output section is found under the memory image in the tree. + +\image html volatility_dsp_module_output.PNG + +You can also view the Volatility output under "ModuleOutput/Volatility" in the Autopsy case folder. The Interesting Items link file paths found by Volatility with files in the disk image. If a disk image was not added, there will not be any Interesting Items. + +\image html volatility_dsp_interesting_items.PNG + + + + +*/ \ No newline at end of file diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 5dbdbf1479..7facd03b19 100755 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,10 +38,10 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears a the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Graphical digital forensics platform for The Sleuth Kit and other tools." @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.7.0/ +HTML_OUTPUT = api-docs/4.8.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html index 550d5d13f7..ac0c0a8d1c 100644 --- a/docs/doxygen/footer.html +++ b/docs/doxygen/footer.html @@ -1,5 +1,5 @@
-

Copyright © 2012-2016 Basis Technology. Generated on: $date
+

Copyright © 2012-2018 Basis Technology. Generated on: $date
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

diff --git a/nbproject/project.properties b/nbproject/project.properties index 3cbde84d3e..55a5b67cfd 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,7 +4,7 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.7.0 +app.version=4.8.0 ### build.type must be one of: DEVELOPMENT, RELEASE #build.type=RELEASE build.type=DEVELOPMENT diff --git a/test/script/regression.py b/test/script/regression.py index bf8068b4c5..e5c608d233 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -884,7 +884,7 @@ class TestConfiguration(object): linkFile = open(os.path.join(self.args.diff_files_output_folder, OUTPUT_DIR_LINK_FILE), "a") index = self.output_dir.find("\\") - linkStr = "file://" + linkStr = "file:\\" linkOutputDir = self.output_dir[index+2:].replace("//", "/").replace("\\\\", "\\") if index == 0: linkStr = linkStr + linkOutputDir diff --git a/thirdparty/opencv/ext/opencv-248.jar b/thirdparty/opencv/ext/opencv-248.jar new file mode 100755 index 0000000000..f09b29077d Binary files /dev/null and b/thirdparty/opencv/ext/opencv-248.jar differ diff --git a/thirdparty/opencv/lib/amd64/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/amd64/opencv_ffmpeg248_64.dll new file mode 100755 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/amd64/opencv_ffmpeg248_64.dll differ diff --git a/thirdparty/opencv/lib/amd64/opencv_java248.dll b/thirdparty/opencv/lib/amd64/opencv_java248.dll new file mode 100755 index 0000000000..ab989a6b11 Binary files /dev/null and b/thirdparty/opencv/lib/amd64/opencv_java248.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_ffmpeg248.dll b/thirdparty/opencv/lib/i386/opencv_ffmpeg248.dll new file mode 100755 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/i386/opencv_ffmpeg248.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_java248.dll b/thirdparty/opencv/lib/i386/opencv_java248.dll new file mode 100755 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/thirdparty/opencv/lib/i386/opencv_java248.dll differ diff --git a/thirdparty/opencv/lib/i586/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/i586/opencv_ffmpeg248_64.dll new file mode 100755 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/i586/opencv_ffmpeg248_64.dll differ diff --git a/thirdparty/opencv/lib/i586/opencv_java248.dll b/thirdparty/opencv/lib/i586/opencv_java248.dll new file mode 100755 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/thirdparty/opencv/lib/i586/opencv_java248.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll new file mode 100755 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/i686/opencv_ffmpeg248_64.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_java248.dll b/thirdparty/opencv/lib/i686/opencv_java248.dll new file mode 100755 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/thirdparty/opencv/lib/i686/opencv_java248.dll differ diff --git a/thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll b/thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll new file mode 100755 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/x86/opencv_ffmpeg248.dll differ diff --git a/thirdparty/opencv/lib/x86/opencv_java248.dll b/thirdparty/opencv/lib/x86/opencv_java248.dll new file mode 100755 index 0000000000..f1c1bc9a43 Binary files /dev/null and b/thirdparty/opencv/lib/x86/opencv_java248.dll differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_ffmpeg248_64.dll b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg248_64.dll new file mode 100755 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg248_64.dll differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_java248.dll b/thirdparty/opencv/lib/x86_64/opencv_java248.dll new file mode 100755 index 0000000000..ab989a6b11 Binary files /dev/null and b/thirdparty/opencv/lib/x86_64/opencv_java248.dll differ diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 110c3b8ede..d4c0a0b53d 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -36,7 +36,7 @@ 10 - 10.11 + 10.12 diff --git a/unix_setup.sh b/unix_setup.sh index 970c35a5c2..1cf9b65888 100755 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -2,7 +2,7 @@ # Verifies programs are installed and copies native code into the Autopsy folder structure -TSK_VERSION=4.6.1 +TSK_VERSION=4.6.2 # Verify PhotoRec was installed photorec_filepath=/usr/bin/photorec