diff --git a/Core/build.xml b/Core/build.xml index da43a4d477..b472585257 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -21,11 +21,6 @@ - - - - - @@ -83,6 +78,8 @@ tofile="${ext.dir}/mchange-commons-java-0.2.9.jar"/> + @@ -94,13 +91,17 @@ - - - - - - - + + + + + + + + + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 7ae5484bc9..b596512161 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -3,7 +3,7 @@ file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar file.reference.commons-compress-1.14.jar=release/modules/ext/commons-compress-1.14.jar file.reference.commons-dbcp2-2.1.1.jar=release\\modules\\ext\\commons-dbcp2-2.1.1.jar file.reference.commons-pool2-2.4.2.jar=release\\modules\\ext\\commons-pool2-2.4.2.jar -file.reference.dd-plist-1.20.jar=release\\modules\\ext\\dd-plist-1.20.jar +file.reference.dd-plist-1.20.jar=release/modules/ext/dd-plist-1.20.jar file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar file.reference.jgraphx-v3.8.0.jar=release/modules/ext/jgraphx-v3.8.0.jar @@ -12,7 +12,6 @@ file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.metadata-extractor-2.10.1.jar=release/modules/ext/metadata-extractor-2.10.1.jar file.reference.postgresql-9.4.1211.jre7.jar=release/modules/ext/postgresql-9.4.1211.jre7.jar -file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar @@ -40,6 +39,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 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index ac3ec60d44..57cbc58c25 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -339,6 +339,10 @@ org.sleuthkit.autopsy.report org.sleuthkit.datamodel + + ext/jackcess-2.1.8.jar + release/modules/ext/jackcess-2.1.8.jar + ext/zookeeper-3.4.6.jar release/modules/ext/zookeeper-3.4.6.jar @@ -348,33 +352,33 @@ release/modules/ext/jdom-2.0.5.jar - ext/sleuthkit-postgresql-4.6.1.jar - release/modules/ext/sleuthkit-postgresql-4.6.1.jar - - - ext/opencv-248.jar - release/modules/ext/opencv-248.jar + ext/cxf-rt-transports-http-3.0.16.jar + release/modules/ext/cxf-rt-transports-http-3.0.16.jar ext/curator-framework-2.8.0.jar release/modules/ext/curator-framework-2.8.0.jar - ext/commons-dbcp2-2.1.1.jar - release/modules/ext/commons-dbcp2-2.1.1.jar - - - ext/jgraphx-v3.8.0.jar - release/modules/ext/jgraphx-v3.8.0.jar + ext/bcprov-jdk15on-1.54.jar + release/modules/ext/bcprov-jdk15on-1.54.jar ext/commons-compress-1.14.jar release/modules/ext/commons-compress-1.14.jar + + ext/fontbox-2.0.8.jar + release/modules/ext/fontbox-2.0.8.jar + ext/commons-dbcp2-2.1.1.jar release\modules\ext\commons-dbcp2-2.1.1.jar + + ext/jgraphx-v3.8.0.jar + release/modules/ext/jgraphx-v3.8.0.jar + ext/jython-standalone-2.7.0.jar release/modules/ext/jython-standalone-2.7.0.jar @@ -387,6 +391,14 @@ ext/mchange-commons-java-0.2.9.jar release/modules/ext/mchange-commons-java-0.2.9.jar + + ext/cxf-core-3.0.16.jar + release/modules/ext/cxf-core-3.0.16.jar + + + ext/javax.ws.rs-api-2.0.1.jar + release/modules/ext/javax.ws.rs-api-2.0.1.jar + ext/postgresql-9.4.1211.jre7.jar release/modules/ext/postgresql-9.4.1211.jre7.jar @@ -399,6 +411,10 @@ ext/metadata-extractor-2.10.1.jar 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/tika-core-1.17.jar release/modules/ext/tika-core-1.17.jar @@ -411,58 +427,18 @@ ext/curator-client-2.8.0.jar release/modules/ext/curator-client-2.8.0.jar - - ext/bcprov-jdk15on-1.54.jar - release/modules/ext/bcprov-jdk15on-1.54.jar - - - ext/jackcess-2.1.8.jar - release/modules/ext/jackcess-2.1.8.jar - - - ext/jackcess-encrypt-2.1.2.jar - release/modules/ext/jackcess-encrypt-2.1.2.jar - - - ext/jempbox-1.8.13.jar - release/modules/ext/jempbox-1.8.13.jar - - - ext/javax.ws.rs-api-2.0.1.jar - release/modules/ext/javax.ws.rs-api-2.0.1.jar - - - ext/cxf-rt-rs-client-3.0.16.jar - release/modules/ext/cxf-rt-rs-client-3.0.16.jar - - - ext/cxf-rt-transports-http-3.0.16.jar - release/modules/ext/cxf-rt-transports-http-3.0.16.jar - - - ext/cxf-core-3.0.16.jar - release/modules/ext/cxf-core-3.0.16.jar - ext/cxf-rt-frontend-jaxrs-3.0.16.jar release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar - - ext/tika-parsers-1.17.jar - release/modules/ext/tika-parsers-1.17.jar - - - ext/fontbox-2.0.8.jar - release/modules/ext/fontbox-2.0.8.jar - - - ext/pdfbox-2.0.8.jar - release/modules/ext/pdfbox-2.0.8.jar - ext/pdfbox-tools-2.0.8.jar release/modules/ext/pdfbox-tools-2.0.8.jar + + ext/tika-parsers-1.17.jar + release/modules/ext/tika-parsers-1.17.jar + ext/sqlite-jdbc-3.8.11.jar release/modules/ext/sqlite-jdbc-3.8.11.jar @@ -483,6 +459,14 @@ ext/dd-plist-1.20.jar release/modules/ext/dd-plist-1.20.jar + + ext/jempbox-1.8.13.jar + release/modules/ext/jempbox-1.8.13.jar + + + ext/cxf-rt-rs-client-3.0.16.jar + release/modules/ext/cxf-rt-rs-client-3.0.16.jar + ext/sevenzipjbinding-AllPlatforms.jar release/modules/ext/sevenzipjbinding-AllPlatforms.jar @@ -491,6 +475,10 @@ ext/commons-pool2-2.4.2.jar release\modules\ext\commons-pool2-2.4.2.jar + + ext/jackcess-encrypt-2.1.2.jar + release/modules/ext/jackcess-encrypt-2.1.2.jar + ext/jsoup-1.10.3.jar release/modules/ext/jsoup-1.10.3.jar @@ -499,6 +487,10 @@ ext/jdom-2.0.5-contrib.jar release/modules/ext/jdom-2.0.5-contrib.jar + + ext/pdfbox-2.0.8.jar + release/modules/ext/pdfbox-2.0.8.jar + ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar @@ -506,6 +498,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 7d0212b51c..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; @@ -83,7 +85,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { */ // @@@ This user interface has some significant usability issues and needs // to be reworked. - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -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()); @@ -99,13 +102,9 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - // Create a "Quick Tag" sub-menu. - JMenu quickTagMenu = new JMenu(NbBundle.getMessage(this.getClass(), "AddTagAction.quickTag")); - add(quickTagMenu); - - // Each tag name in the current set of tags gets its own menu item in - // the "Quick Tags" sub-menu. Selecting one of these menu items adds - // a tag with the associated tag name. + // 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(); @@ -119,28 +118,26 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagNameItem.addActionListener((ActionEvent e) -> { getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); - quickTagMenu.add(tagNameItem); + + // 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); - quickTagMenu.add(empty); + } + + if (getItemCount() > 0) { + addSeparator(); } - - quickTagMenu.addSeparator(); - - // The "Quick Tag" menu also gets an "Choose Tag..." menu item. - // Selecting this item initiates a dialog that can be used to create - // or select a tag name and adds a tag with the resulting name. - JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); - newTagMenuItem.addActionListener((ActionEvent e) -> { - TagName tagName = GetTagNameDialog.doDialog(); - if (null != tagName) { - addTag(tagName, NO_COMMENT); - } + + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); }); - quickTagMenu.add(newTagMenuItem); - + + 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. @@ -153,6 +150,19 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { } }); add(tagAndCommentItem); + + // Create a "New Tag..." menu item. + // Selecting this item initiates a dialog that can be used to create + // or select a tag name and adds a tag with the resulting name. + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent e) -> { + TagName tagName = GetTagNameDialog.doDialog(); + if (null != tagName) { + addTag(tagName, NO_COMMENT); + } + }); + add(newTagMenuItem); + } /** 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 51665012cd..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; @@ -43,10 +47,15 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +/** + * This dialog allows tag assignment with a comment attached. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives 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 { @@ -101,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); } @@ -140,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()); @@ -170,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) { @@ -199,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) { @@ -209,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( @@ -218,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)) @@ -228,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()) ); @@ -243,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) @@ -278,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); } @@ -287,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/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index 21785f2003..8403a301e7 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -46,8 +46,12 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +/** + * Displays existing tag names, and allows the creation of new tags. + */ @Messages({"GetTagNameDialog.descriptionLabel.text=Description:", "GetTagNameDialog.notableCheckbox.text=Tag indicates item is notable."}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class GetTagNameDialog extends JDialog { private static final long serialVersionUID = 1L; 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/AddImageWizardAddingProgressVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java index 3b6772637f..63f4cae069 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressVisual.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,16 +19,14 @@ package org.sleuthkit.autopsy.casemodule; import java.awt.Color; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.JProgressBar; -import org.openide.WizardDescriptor; import org.openide.util.NbBundle; /** * visual component to display progress bar and status updates while adding an * image in the wizard */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardAddingProgressVisual extends javax.swing.JPanel { private static final String ADDING_DATA_SOURCE_COMPLETE = NbBundle 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/AddImageWizardDataSourceSettingsVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java index a5671ce0ee..d44b3ed522 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.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"); @@ -37,8 +37,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * visual component for the first panel of add image wizard. Allows the user to * choose the data source type and then select the data source - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardDataSourceSettingsVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardDataSourceSettingsVisual.class.getName()); 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/AddImageWizardIngestConfigVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java index f1a0211425..3d730e6b0e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigVisual.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"); @@ -25,8 +25,8 @@ import javax.swing.JPanel; /** * UI panel for the ingest job configuration wizard panel of the add data source * wizard. - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigVisual extends JPanel { private final JPanel ingestPanel; 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 43bfed1caa..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; @@ -46,6 +47,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Panel which displays the available DataSourceProcessors and allows selection * of one */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardSelectDspVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardSelectDspVisual.class.getName()); @@ -66,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 } @@ -131,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; @@ -171,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/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index ccce6fdced..d6e601e75a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -132,6 +132,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"; @@ -295,9 +296,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. */ @@ -1115,6 +1117,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(); @@ -1368,6 +1374,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/CaseBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java index 4f25030fd3..402b64d78f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseBrowser.java @@ -49,8 +49,8 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; * * Used to display a list of multi user cases and allow the user to open one of * them. - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index b8542552c4..252a44e577 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.openide.windows.WindowManager; * Panel for displaying the case information, including both case details and * ingest job history. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class CaseInformationPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java index cc3c682830..5a16e0ee1f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CasePropertiesPanel.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"); @@ -31,10 +31,11 @@ import org.sleuthkit.autopsy.coreutils.Logger; * A panel that allows the user to view various properties of a case and change * the display name of the case. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class CasePropertiesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(CasePropertiesPanel.class.getName()); + private static final Logger logger = Logger.getLogger(CasePropertiesPanel.class.getName()); private Case theCase; /** @@ -52,7 +53,7 @@ final class CasePropertiesPanel extends javax.swing.JPanel { try { theCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; } lbCaseNameText.setText(theCase.getDisplayName()); @@ -90,7 +91,7 @@ final class CasePropertiesPanel extends javax.swing.JPanel { currentOrg = correlationCase.getOrg(); } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Unable to access Correlation Case when Central Repo is enabled", ex); + logger.log(Level.SEVERE, "Unable to access Correlation Case when Central Repo is enabled", ex); } } if (currentOrg != null) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java index 478820353f..3ddb97fcfd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /* * The panel in the default Autopsy startup window. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class CueBannerPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java index d1c726d3eb..907c2fd0f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/EditOptionalCasePropertiesPanel.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"); @@ -22,9 +22,9 @@ import java.awt.event.ActionListener; import org.openide.util.NbBundle.Messages; /** - * - * @author wschaefer + * Panel to allow examiner to edit option case properties. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class EditOptionalCasePropertiesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index 56d5e67839..1d2b1ce1e2 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -31,7 +31,6 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import static org.sleuthkit.autopsy.casemodule.Bundle.*; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; @@ -46,6 +45,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; * to select a file as well as choose the timezone and whether to ignore orphan * files in FAT32. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ImageFilePanel extends JPanel implements DocumentListener { private static final Logger logger = Logger.getLogger(ImageFilePanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index 9bce28753f..940f6a9cd5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -39,6 +39,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Panel for displaying ingest job history. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestJobInfoPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName()); @@ -91,7 +92,7 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { "IngestJobInfoPanel.IngestJobTableModel.IngestStatus.header=Ingest Status"}) private class IngestJobTableModel extends AbstractTableModel { - private List columnHeaders = new ArrayList<>(); + private final List columnHeaders = new ArrayList<>(); IngestJobTableModel() { columnHeaders.add(Bundle.IngestJobInfoPanel_IngestJobTableModel_DataSource_header()); @@ -146,8 +147,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { "IngestJobInfoPanel.IngestModuleTableModel.ModuleVersion.header=Module Version"}) private class IngestModuleTableModel extends AbstractTableModel { - private List columnHeaders = new ArrayList<>(); - private IngestJobInfo currJob; + private final List columnHeaders = new ArrayList<>(); + private final IngestJobInfo currJob; IngestModuleTableModel(IngestJobInfo currJob) { columnHeaders.add(Bundle.IngestJobInfoPanel_IngestModuleTableModel_ModuleName_header()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index fc702076a0..cb1ec5ddfd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -34,6 +34,10 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; +/** + * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or + * C:. + */ @NbBundle.Messages({ "LocalDiskPanel.errorMessage.noOpenCaseTitle=No open case available", "LocalDiskPanel.errorMessage.noOpenCaseBody=LocalDiskPanel listener couldn't get the open case.", @@ -45,10 +49,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; "LocalDiskPanel.moduleErrorMessage.body=A module caused an error listening to LocalDiskPanel updates. See log to determine which module. Some data could be incomplete.", "LocalDiskPanel.localDiskMessage.unspecified=Unspecified" }) -/** - * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or - * C:. - */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LocalDiskPanel extends JPanel { private static final Logger logger = Logger.getLogger(LocalDiskPanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 3ab4088094..ef985db2dd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; /** * A panel which allows the user to select local files and/or directories. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LocalFilesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -278,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); @@ -288,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/LogicalEvidenceFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java index d38106a0ab..5e11d5dfa0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalEvidenceFilePanel.java @@ -32,7 +32,6 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PathValidator; @@ -40,6 +39,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; /** * A panel which allows the user to select a Logical Evidence File (L01) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LogicalEvidenceFilePanel extends javax.swing.JPanel implements DocumentListener { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java index 0298c98d69..5f96422a9e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LogicalFilesDspPanel.java @@ -28,13 +28,14 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +/** + * Add input wizard subpanel for adding local files / dirs to the case + */ @Messages({ "LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders", "LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01)" }) -/** - * Add input wizard subpanel for adding local files / dirs to the case - */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class LogicalFilesDspPanel extends JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java index 3b246e920b..5645e1d5d6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MissingImageDialog.java @@ -35,6 +35,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +/** + * Dialog to allow the examiner to locate an image when it cannot be found. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class MissingImageDialog extends javax.swing.JDialog { private static final Logger logger = Logger.getLogger(MissingImageDialog.class.getName()); @@ -50,7 +54,7 @@ class MissingImageDialog extends javax.swing.JDialog { } static final String allDesc = NbBundle.getMessage(MissingImageDialog.class, "MissingImageDialog.allDesc.text"); static final GeneralFilter allFilter = new GeneralFilter(allExt, allDesc); - private JFileChooser fc = new JFileChooser(); + private final JFileChooser fc = new JFileChooser(); /** * Instantiate a MissingImageDialog. 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/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index 7a1ca81862..07d863bc2b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.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,9 +36,10 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * A panel that allows a user to open cases created by auto ingest. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MultiUserCasesPanel extends JPanel{ - private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); + private static final Logger logger = Logger.getLogger(MultiUserCasesPanel.class.getName()); private static final long serialVersionUID = 1L; private final JDialog parentDialog; private final CaseBrowser caseBrowserPanel; @@ -98,7 +99,7 @@ final class MultiUserCasesPanel extends JPanel{ Case.openAsCurrentCase(caseMetadataFilePath); } catch (CaseActionException ex) { if (null != ex.getCause() && !(ex.getCause() instanceof CaseActionCancelledException)) { - LOGGER.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Error opening case with metadata file path %s", caseMetadataFilePath), ex); //NON-NLS MessageNotifyUtil.Message.error(ex.getCause().getLocalizedMessage()); } SwingUtilities.invokeLater(() -> { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index cd1179b52b..6f365af86e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,9 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Component; import org.openide.util.NbBundle; -import java.awt.*; import java.io.File; import javax.swing.JFileChooser; import javax.swing.JPanel; @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; /** * The JPanel for the first page of the new case wizard. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel1 extends JPanel implements DocumentListener { private final JFileChooser fileChooser = new JFileChooser(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java index 2098fc91b3..ec06648248 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel2.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"); @@ -16,20 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * NewCaseVisualPanel2.java - * - * Created on Mar 7, 2012, 11:01:48 AM - */ package org.sleuthkit.autopsy.casemodule; import org.openide.util.NbBundle; /** - * - * @author dfickling + * The JPanel for the second page of the new case wizard. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class NewCaseVisualPanel2 extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java index c686bcad1c..5f164cd00f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OpenRecentCasePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Panel used by the the open recent case option of the start window. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class OpenRecentCasePanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java index 4625727d94..55c64d1f12 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/OptionalCasePropertiesPanel.java @@ -36,9 +36,10 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; * Panel which allows for editing and setting of the case details which are * optional or otherwise able to be edited. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class OptionalCasePropertiesPanel extends javax.swing.JPanel { - private final static Logger LOGGER = Logger.getLogger(OptionalCasePropertiesPanel.class.getName()); + private final static Logger logger = Logger.getLogger(OptionalCasePropertiesPanel.class.getName()); private static final long serialVersionUID = 1L; private EamOrganization selectedOrg = null; private java.util.List orgs = null; @@ -66,7 +67,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { try { openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; } caseDisplayNameTextField.setText(openCase.getDisplayName()); @@ -100,9 +101,9 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { selectedOrg = dbManager.getCase(currentCase).getOrg(); } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Unable to get Organization associated with the case from Central Repo", ex); + logger.log(Level.SEVERE, "Unable to get Organization associated with the case from Central Repo", ex); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); } if (selectedOrg != null) { @@ -145,7 +146,7 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { }); comboBoxOrgName.setSelectedItem(selectedBeforeLoad); } catch (EamDbException ex) { - LOGGER.log(Level.WARNING, "Unable to populate list of Organizations from Central Repo", ex); + logger.log(Level.WARNING, "Unable to populate list of Organizations from Central Repo", ex); } } @@ -598,9 +599,9 @@ final class OptionalCasePropertiesPanel extends javax.swing.JPanel { correlationCase.setNotes(taNotesText.getText()); dbManager.updateCase(correlationCase); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to central repository database", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to central repository database", ex); // NON-NLS } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } finally { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } 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/TagNameDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDialog.java index 276be583c1..eef8d08087 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDialog.java @@ -19,8 +19,6 @@ package org.sleuthkit.autopsy.casemodule.services; import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; @@ -34,6 +32,7 @@ import org.sleuthkit.datamodel.TskData; @Messages({"TagNameDialog.descriptionLabel.text=Description:", "TagNameDialog.notableCheckbox.text=Tag indicates item is notable."}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class TagNameDialog extends javax.swing.JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java index 419a92e8c9..7f63a9d4c4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java @@ -27,7 +27,6 @@ import java.util.TreeSet; import java.util.logging.Level; import javax.swing.DefaultListModel; import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; @@ -43,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel to allow the user to create and delete custom tag types. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class TagOptionsPanel extends javax.swing.JPanel implements OptionsPanel { private static final long serialVersionUID = 1L; 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 new file mode 100755 index 0000000000..654ee7161e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -0,0 +1,148 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository; + +import java.awt.event.ActionEvent; +import java.util.logging.Level; +import javax.swing.AbstractAction; +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. + */ +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 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; + this.correlationAttribute = correlationAttribute; + } + + /** + * Private 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. + */ + private AddEditCentralRepoCommentAction(AbstractFile file, String title) { + + super(title); + this.title = title; + correlationAttribute = EamArtifactUtil.getCorrelationAttributeFromContent(file); + if (correlationAttribute == null) { + addToDatabase = true; + correlationAttribute = EamArtifactUtil.makeCorrelationAttributeFromContent(file); + } + } + + @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 returned in case it is needed to + * update the display. + * + * @return the current comment for this instance + */ + public String addEditCentralRepoComment() { + CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); + centralRepoCommentDialog.display(); + + if (centralRepoCommentDialog.isCommentUpdated()) { + EamDb dbManager; + + try { + dbManager = EamDb.getInstance(); + + if (addToDatabase) { + dbManager.addArtifact(correlationAttribute); + } else { + dbManager.updateAttributeInstanceComment(correlationAttribute); + } + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error adding comment", ex); + } + } + return centralRepoCommentDialog.getComment(); + } + + /** + * Create an instance labeled "Add/Edit Central Repository Comment" given an + * AbstractFile. This is intended for the result view. + * + * @param file The file from which a correlation attribute to modify is + * derived. + * + * @return The instance. + * + * @throws EamDbException + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=Add/Edit Central Repository Comment"}) + public static AddEditCentralRepoCommentAction createAddEditCentralRepoCommentAction(AbstractFile file) { + + return new AddEditCentralRepoCommentAction(file, + Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment()); + } + + /** + * 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. + */ + @Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditComment=Add/Edit Comment"}) + public static AddEditCentralRepoCommentAction createAddEditCommentAction(CorrelationAttribute correlationAttribute) { + + return new AddEditCentralRepoCommentAction(correlationAttribute, + Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditComment()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties index 9e873a514b..3223583037 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/Bundle.properties @@ -5,3 +5,8 @@ OpenIDE-Module-Long-Description=\ Correlation Engine ingest module and central database. \n\n\ The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\ Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest. +CentralRepoCommentDialog.fileLabel.text=File: +CentralRepoCommentDialog.commentLabel.text=Comment: +CentralRepoCommentDialog.pathLabel.text= +CentralRepoCommentDialog.okButton.text=&OK +CentralRepoCommentDialog.cancelButton.text=C&ancel diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form new file mode 100755 index 0000000000..5a9882d4cf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.form @@ -0,0 +1,138 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java new file mode 100755 index 0000000000..c95b0bfde7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -0,0 +1,203 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.centralrepository; + +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; + +/** + * Dialog to allow Central Repository file instance comments to be added and + * modified. + */ +@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); + + 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()); + + this.correlationAttribute = correlationAttribute; + } + + /** + * Display the dialog. + */ + void display() { + setModal(true); + setSize(getPreferredSize()); + setLocationRelativeTo(this.getParent()); + setAlwaysOnTop(false); + pack(); + setVisible(true); + } + + /** + * Has the comment been updated? + * + * @return True if the comment has been updated; otherwise false. + */ + 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. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jScrollPane1 = new javax.swing.JScrollPane(); + commentTextArea = new javax.swing.JTextArea(); + okButton = new javax.swing.JButton(); + cancelButton = new javax.swing.JButton(); + fileLabel = new javax.swing.JLabel(); + pathLabel = new javax.swing.JLabel(); + commentLabel = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setSize(getPreferredSize()); + + commentTextArea.setColumns(20); + commentTextArea.setLineWrap(true); + commentTextArea.setRows(5); + commentTextArea.setTabSize(4); + commentTextArea.setWrapStyleWord(true); + jScrollPane1.setViewportView(commentTextArea); + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.okButton.text")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(fileLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.fileLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.pathLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(CentralRepoCommentDialog.class, "CentralRepoCommentDialog.commentLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + 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(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(fileLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pathLabel)) + .addComponent(commentLabel)) + .addGap(0, 451, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton, javax.swing.GroupLayout.PREFERRED_SIZE, 75, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fileLabel) + .addComponent(pathLabel)) + .addGap(19, 19, 19) + .addComponent(commentLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(okButton) + .addComponent(cancelButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + currentComment = commentTextArea.getText(); + correlationAttribute.getInstances().get(0).setComment(currentComment); + commentUpdated = true; + + dispose(); + }//GEN-LAST:event_okButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelButton; + private javax.swing.JLabel commentLabel; + private javax.swing.JTextArea commentTextArea; + private javax.swing.JLabel fileLabel; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JButton okButton; + private javax.swing.JLabel pathLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java new file mode 100755 index 0000000000..5a6e8fa652 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -0,0 +1,56 @@ +/* + * 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 actions = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + + if (selectedFiles.size() != 1) { + return actions; + } + + for (AbstractFile file : selectedFiles) { + if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { + actions.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); + } + } + + return actions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index 8b18108d52..b3375cda0d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -3,3 +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.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 2d26ab3bc8..60667bae46 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -3,6 +3,9 @@
+ + + @@ -36,6 +39,13 @@ + + + + + + + @@ -70,7 +80,7 @@ - + @@ -93,10 +103,10 @@ - + - + @@ -114,17 +124,36 @@ - - + + + + + + + + + + + + + - - - + + + + + + + + + + + - + @@ -164,6 +193,23 @@ + + + + + + + + + + + + + + + + + @@ -175,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 2a9fd30a49..8ead475d95 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -18,55 +18,50 @@ */ package org.sleuthkit.autopsy.centralrepository.contentviewer; -import java.awt.Color; import java.awt.Component; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; 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.GroupLayout; import javax.swing.JFileChooser; -import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import static javax.swing.JOptionPane.DEFAULT_OPTION; import static javax.swing.JOptionPane.PLAIN_MESSAGE; import static javax.swing.JOptionPane.ERROR_MESSAGE; import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.LayoutStyle; -import javax.swing.ListSelectionModel; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; -import org.openide.awt.Mnemonics; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; import org.openide.nodes.Node; -import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; 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; @@ -76,9 +71,9 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; 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 @@ -88,8 +83,10 @@ import org.sleuthkit.datamodel.TskDataException; @Messages({"DataContentViewerOtherCases.title=Other Occurrences", "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",}) public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { - - private final static Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + + private static final long serialVersionUID = -1L; + + private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private final DataContentViewerOtherCasesTableModel tableModel; private final Collection correlationAttributes; @@ -123,10 +120,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { saveToCSV(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); + } else if (jmi.equals(addCommentMenuItem)) { + try { + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); + AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedNode.createCorrelationAttribute()); + String currentComment = action.addEditCentralRepoComment(); + selectedNode.updateComment(currentComment); + otherCasesTable.repaint(); + } catch (EamDbException ex) { + logger.log(Level.SEVERE, "Error performing Add/Edit Comment action", ex); + } } } }; @@ -135,11 +142,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi selectAllMenuItem.addActionListener(actList); showCaseDetailsMenuItem.addActionListener(actList); showCommonalityMenuItem.addActionListener(actList); + addCommentMenuItem.addActionListener(actList); // Set background of every nth row as light grey. TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer(); otherCasesTable.setDefaultRenderer(Object.class, renderer); tableStatusPanelLabel.setVisible(false); + } @Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", @@ -150,7 +159,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi "DataContentViewerOtherCases.correlatedArtifacts.title=Attribute Frequency", "DataContentViewerOtherCases.correlatedArtifacts.failed=Failed to get frequency details."}) /** - * Show how common the selected correlationAttributes are with details dialog. + * Show how common the selected correlationAttributes are with details + * dialog. */ private void showCommonalityDetails() { if (correlationAttributes.isEmpty()) { @@ -159,7 +169,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(); @@ -174,7 +184,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex); + logger.log(Level.SEVERE, "Error getting commonality details.", ex); JOptionPane.showConfirmDialog(showCommonalityMenuItem, Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(), Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), @@ -189,23 +199,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(), @@ -215,7 +216,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(), @@ -236,6 +237,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, @@ -298,7 +300,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); + logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); } } @@ -309,6 +311,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // start with empty table tableModel.clearTable(); correlationAttributes.clear(); + earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable()); } @Override @@ -389,7 +392,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID()); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS + logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS return null; } @@ -435,78 +438,136 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } else { try { // If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled. - if(this.file != null) { + if (this.file != null) { String md5 = this.file.getMd5Hash(); - if(md5 != null && !md5.isEmpty()) { - ret.add(new CorrelationAttribute(CorrelationAttribute.getDefaultCorrelationTypes().get(0),md5)); + if (md5 != null && !md5.isEmpty()) { + ret.add(new CorrelationAttribute(CorrelationAttribute.getDefaultCorrelationTypes().get(0), md5)); } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } return ret; } + @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Not Enabled."}) /** - * Query the db for artifact instances from other cases correlated to the - * given central repository artifact. Will not show instances from the same - * datasource / device - * - * @param corAttr CorrelationAttribute to query for - * @param dataSourceName Data source to filter results - * @param deviceId Device Id to filter results - * - * @return A collection of correlated artifact instances from other cases + * 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 Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { + 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 + * @param deviceId Device Id to filter results + * + * @return A collection of correlated artifact instances + */ + private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { final Case openCase = Case.getCurrentCase(); String caseUUID = openCase.getName(); - 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.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 + logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { // do nothing. // @@@ Review this behavior - LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS } 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 +583,64 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Adds the file to the artifactInstances map if it does not already exist + * Adds the file to the nodeDataMap map if it does not already exist * * @param autopsyCase - * @param artifactInstances + * @param nodeDataMap * @param newFile + * * @throws TskCoreException - * @throws EamDbException + * @throws EamDbException */ - private void addOrUpdateAttributeInstance(final Case autopsyCase, Map artifactInstances, AbstractFile newFile) throws TskCoreException, EamDbException { + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, 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) { + 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: " + ex); - return; - } - UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); + // Make a key to see if the file is already in the map + UniquePathKey uniquePathKey = new UniquePathKey(newNode); - // 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); + // 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) { + + // 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); - // Is supported if this node - // has correlatable content (File, BlackboardArtifact) OR - // other common files across datasources. - - if(EamDb.isEnabled()){ + if (EamDb.isEnabled()) { return this.file != null - && this.file.getSize() > 0 - && !getCorrelationAttributesFromNode(node).isEmpty(); - } else{ + && 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 +682,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 +702,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi clearMessageOnTableStatusPanel(); setColumnWidths(); } + setEarliestCaseDate(); } private void setColumnWidths() { @@ -690,159 +733,206 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // //GEN-BEGIN:initComponents private void initComponents() { - rightClickPopupMenu = new JPopupMenu(); - selectAllMenuItem = new JMenuItem(); - exportToCSVMenuItem = new JMenuItem(); - showCaseDetailsMenuItem = new JMenuItem(); - showCommonalityMenuItem = new JMenuItem(); - CSVFileChooser = new JFileChooser(); - otherCasesPanel = new JPanel(); - tableContainerPanel = new JPanel(); - tableScrollPane = new JScrollPane(); - otherCasesTable = new JTable(); - tableStatusPanel = new JPanel(); - tableStatusPanelLabel = new JLabel(); + rightClickPopupMenu = new javax.swing.JPopupMenu(); + selectAllMenuItem = new javax.swing.JMenuItem(); + exportToCSVMenuItem = new javax.swing.JMenuItem(); + showCaseDetailsMenuItem = new javax.swing.JMenuItem(); + showCommonalityMenuItem = new javax.swing.JMenuItem(); + addCommentMenuItem = new javax.swing.JMenuItem(); + CSVFileChooser = new javax.swing.JFileChooser(); + otherCasesPanel = new javax.swing.JPanel(); + 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(); - Mnemonics.setLocalizedText(selectAllMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.selectAllMenuItem.text")); // NOI18N + rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { + public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) { + } + public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { + rightClickPopupMenuPopupMenuWillBecomeVisible(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(selectAllMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.selectAllMenuItem.text")); // NOI18N rightClickPopupMenu.add(selectAllMenuItem); - Mnemonics.setLocalizedText(exportToCSVMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.exportToCSVMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(exportToCSVMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.exportToCSVMenuItem.text")); // NOI18N rightClickPopupMenu.add(exportToCSVMenuItem); - Mnemonics.setLocalizedText(showCaseDetailsMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCaseDetailsMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(showCaseDetailsMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCaseDetailsMenuItem.text")); // NOI18N rightClickPopupMenu.add(showCaseDetailsMenuItem); - Mnemonics.setLocalizedText(showCommonalityMenuItem, NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N rightClickPopupMenu.add(showCommonalityMenuItem); - setMinimumSize(new Dimension(1500, 10)); + org.openide.awt.Mnemonics.setLocalizedText(addCommentMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.addCommentMenuItem.text")); // NOI18N + rightClickPopupMenu.add(addCommentMenuItem); + + setMinimumSize(new java.awt.Dimension(1500, 10)); setOpaque(false); - setPreferredSize(new Dimension(1500, 44)); + setPreferredSize(new java.awt.Dimension(1500, 44)); - otherCasesPanel.setPreferredSize(new Dimension(1500, 144)); + otherCasesPanel.setPreferredSize(new java.awt.Dimension(1500, 144)); - tableContainerPanel.setPreferredSize(new Dimension(1500, 63)); + tableContainerPanel.setPreferredSize(new java.awt.Dimension(1500, 63)); - tableScrollPane.setPreferredSize(new Dimension(1500, 30)); + tableScrollPane.setPreferredSize(new java.awt.Dimension(1500, 30)); otherCasesTable.setAutoCreateRowSorter(true); otherCasesTable.setModel(tableModel); - otherCasesTable.setToolTipText(NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N + otherCasesTable.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.table.toolTip.text")); // NOI18N otherCasesTable.setComponentPopupMenu(rightClickPopupMenu); - otherCasesTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + otherCasesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); tableScrollPane.setViewportView(otherCasesTable); - tableStatusPanel.setPreferredSize(new 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 Color(255, 0, 51)); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N - GroupLayout tableStatusPanelLayout = new GroupLayout(tableStatusPanel); + tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); + + javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel); tableStatusPanel.setLayout(tableStatusPanelLayout); - tableStatusPanelLayout.setHorizontalGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableStatusPanelLayout.setHorizontalGroup( + tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 0, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(tableStatusPanelLabel, GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) - .addContainerGap())) ); - tableStatusPanelLayout.setVerticalGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableStatusPanelLayout.setVerticalGroup( + tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 16, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanelLabel, GroupLayout.PREFERRED_SIZE, 16, GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE))) ); - GroupLayout tableContainerPanelLayout = new GroupLayout(tableContainerPanel); + 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(GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(tableStatusPanel, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - tableContainerPanelLayout.setVerticalGroup(tableContainerPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + tableContainerPanelLayout.setHorizontalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .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(tableScrollPane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(tableStatusPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .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()) ); - - GroupLayout otherCasesPanelLayout = new GroupLayout(otherCasesPanel); - otherCasesPanel.setLayout(otherCasesPanelLayout); - otherCasesPanelLayout.setHorizontalGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGap(0, 1500, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(tableContainerPanel, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + tableContainerPanelLayout.setVerticalGroup( + tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 176, Short.MAX_VALUE) + .addGap(0, 0, 0) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate)) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) + .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) ); - otherCasesPanelLayout.setVerticalGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGap(0, 60, Short.MAX_VALUE) - .addGroup(otherCasesPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + + javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); + otherCasesPanel.setLayout(otherCasesPanelLayout); + otherCasesPanelLayout.setHorizontalGroup( + otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 1500, Short.MAX_VALUE) + .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + otherCasesPanelLayout.setVerticalGroup( + otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 483, Short.MAX_VALUE) + .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, GroupLayout.DEFAULT_SIZE, 60, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) .addGap(0, 0, 0))) ); - GroupLayout layout = new GroupLayout(this); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); - layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); - layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, GroupLayout.DEFAULT_SIZE, 60, Short.MAX_VALUE) + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .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 enableCentralRepoActions = false; + + if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { + int rowIndex = otherCasesTable.getSelectedRow(); + OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); + if (selectedNode.isCentralRepoNode()) { + enableCentralRepoActions = true; + } + } + + addCommentMenuItem.setVisible(enableCentralRepoActions); + showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); + showCommonalityMenuItem.setVisible(enableCentralRepoActions); + }//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible // Variables declaration - do not modify//GEN-BEGIN:variables - private JFileChooser CSVFileChooser; - private JMenuItem exportToCSVMenuItem; - private JPanel otherCasesPanel; - private JTable otherCasesTable; - private JPopupMenu rightClickPopupMenu; - private JMenuItem selectAllMenuItem; - private JMenuItem showCaseDetailsMenuItem; - private JMenuItem showCommonalityMenuItem; - private JPanel tableContainerPanel; - private JScrollPane tableScrollPane; - private JPanel tableStatusPanel; - private JLabel tableStatusPanelLabel; + 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; + private javax.swing.JPopupMenu rightClickPopupMenu; + private javax.swing.JMenuItem selectAllMenuItem; + private javax.swing.JMenuItem showCaseDetailsMenuItem; + private javax.swing.JMenuItem showCommonalityMenuItem; + private javax.swing.JPanel tableContainerPanel; + private javax.swing.JScrollPane tableScrollPane; + private javax.swing.JPanel tableStatusPanel; + private javax.swing.JLabel tableStatusPanelLabel; // End of variables declaration//GEN-END:variables /** - * Used as a key to ensure we eliminate duplicates from the result set by not overwriting CR correlation instances. + * Used as a key to ensure we eliminate duplicates from the result set by + * not overwriting CR correlation instances. */ static final class UniquePathKey { 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; } @@ -852,7 +942,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 60cbb519e6..f535d58ccd 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.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"); @@ -35,30 +35,36 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; 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; /** * - * SQLite manager implementation + * Generic JDBC methods * */ -public abstract class AbstractSqlEamDb implements EamDb { +abstract class AbstractSqlEamDb implements EamDb { - private final static Logger LOGGER = Logger.getLogger(AbstractSqlEamDb.class.getName()); + private final static Logger logger = Logger.getLogger(AbstractSqlEamDb.class.getName()); - protected final List DEFAULT_CORRELATION_TYPES; + protected final List defaultCorrelationTypes; private int bulkArtifactsCount; 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; + /** * Connect to the DB and initialize it. * @@ -68,8 +74,8 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkArtifactsCount = 0; bulkArtifacts = new HashMap<>(); - DEFAULT_CORRELATION_TYPES = CorrelationAttribute.getDefaultCorrelationTypes(); - DEFAULT_CORRELATION_TYPES.forEach((type) -> { + defaultCorrelationTypes = CorrelationAttribute.getDefaultCorrelationTypes(); + defaultCorrelationTypes.forEach((type) -> { bulkArtifacts.put(type.getDbTableName(), new ArrayList<>()); }); } @@ -82,7 +88,7 @@ public abstract class AbstractSqlEamDb implements EamDb { /** * 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 @@ -102,7 +108,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error adding new name/value pair to db_info.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -135,7 +141,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting value for name.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -146,7 +152,7 @@ public abstract class AbstractSqlEamDb implements EamDb { /** * 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 @@ -165,7 +171,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating value for name.", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -176,11 +182,12 @@ public abstract class AbstractSqlEamDb implements EamDb { * Expects the Organization for this case to already exist in the database. * * @param eamCase The case to add + * * @returns New Case class with populated database ID */ @Override public synchronized CorrelationCase newCase(CorrelationCase eamCase) throws EamDbException { - + // check if there is already an existing CorrelationCase for this Case CorrelationCase cRCase = getCaseByUUID(eamCase.getCaseUUID()); if (cRCase != null) { @@ -236,7 +243,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new case.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -281,10 +288,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void updateCase(CorrelationCase eamCase) throws EamDbException { - if(eamCase == null) { - throw new EamDbException("CorrelationCase argument is null"); + if (eamCase == null) { + throw new EamDbException("Correlation case is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -335,7 +342,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating case.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -373,7 +380,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting case details.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -410,7 +417,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all cases.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -425,6 +432,12 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void newDataSource(CorrelationDataSource eamDataSource) throws EamDbException { + if (eamDataSource.getCaseID() == -1) { + throw new EamDbException("Case ID is -1"); + } else if (eamDataSource.getID() != -1) { + // This data source is already in the central repo + return; + } Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -443,7 +456,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new data source.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -451,18 +464,18 @@ public abstract class AbstractSqlEamDb implements EamDb { /** * 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 { - if(correlationCase == null) { - throw new EamDbException("CorrelationCase argument is null"); + if (correlationCase == null) { + throw new EamDbException("Correlation case is null"); } - + Connection conn = connect(); CorrelationDataSource eamDataSourceResult = null; @@ -482,7 +495,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting data source.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -516,7 +529,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all data sources.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -532,16 +545,23 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException { - if(eamArtifact == null) { + if (eamArtifact == null) { throw new EamDbException("CorrelationAttribute is null"); } - if(eamArtifact.getCorrelationType() == null) { + if (eamArtifact.getCorrelationType() == null) { throw new EamDbException("Correlation type is null"); } - if(eamArtifact.getCorrelationValue() == null) { + 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(); List eamInstances = eamArtifact.getInstances(); @@ -549,28 +569,28 @@ public abstract class AbstractSqlEamDb implements EamDb { // @@@ We should cache the case and data source IDs in memory String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); - StringBuilder sql = new StringBuilder(); - sql.append("INSERT INTO "); - sql.append(tableName); - sql.append("(case_id, data_source_id, value, file_path, known_status, comment) "); - sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "); - sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "); - sql.append(getConflictClause()); - + String sql + = "INSERT INTO " + + tableName + + "(case_id, data_source_id, value, file_path, known_status, comment) " + + "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), " + + "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) " + + getConflictClause(); + try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { - if(eamInstance.getCorrelationCase() == null) { - throw new EamDbException("CorrelationAttributeInstance has null case"); + if (eamInstance.getCorrelationCase() == null) { + throw new EamDbException("CorrelationAttributeInstance case is null"); } - if(eamInstance.getCorrelationDataSource() == null) { - throw new EamDbException("CorrelationAttributeInstance has null data source"); + if (eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("CorrelationAttributeInstance data source is null"); } - if(eamInstance.getKnownStatus() == null) { - throw new EamDbException("CorrelationAttributeInstance has null known status"); + if (eamInstance.getKnownStatus() == null) { + throw new EamDbException("CorrelationAttributeInstance known status is null"); } - + preparedStatement.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); preparedStatement.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); preparedStatement.setInt(3, eamInstance.getCorrelationDataSource().getCaseID()); @@ -589,7 +609,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new artifact into artifacts table.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -607,7 +627,7 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } Connection conn = connect(); @@ -619,19 +639,21 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE value=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE value=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { @@ -641,7 +663,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -653,7 +675,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 @@ -662,10 +684,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - if(filePath == null) { + if (filePath == null) { throw new EamDbException("Correlation value is null"); } Connection conn = connect(); @@ -677,19 +699,21 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE file_path=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE file_path=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, filePath.toLowerCase()); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { @@ -699,7 +723,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -715,17 +739,17 @@ public abstract class AbstractSqlEamDb implements EamDb { * @param value The correlation value * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ @Override public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - if(value == null) { + if (value == null) { throw new EamDbException("Correlation value is null"); } - + Connection conn = connect(); Long instanceCount = 0L; @@ -733,13 +757,13 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM "); - sql.append(tableName); - sql.append(" WHERE value=?"); + String sql + = "SELECT count(*) FROM " + + tableName + + " WHERE value=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value.toLowerCase()); resultSet = preparedStatement.executeQuery(); resultSet.next(); @@ -747,7 +771,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting count of artifact instances by artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -758,7 +782,7 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException { if (corAttr == null) { - throw new EamDbException("Correlation attribute is null"); + throw new EamDbException("CorrelationAttribute is null"); } Double uniqueTypeValueTuples = getCountUniqueCaseDataSourceTuplesHavingTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).doubleValue(); Double uniqueCaseDataSourceTuples = getCountUniqueDataSources().doubleValue(); @@ -778,10 +802,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Long instanceCount = 0L; @@ -789,15 +813,15 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM (SELECT DISTINCT case_id, data_source_id FROM "); - sql.append(tableName); - sql.append(" WHERE value=?) AS "); - sql.append(tableName); - sql.append("_distinct_case_data_source_tuple"); + String sql + = "SELECT count(*) FROM (SELECT DISTINCT case_id, data_source_id FROM " + + tableName + + " WHERE value=?) AS " + + tableName + + "_distinct_case_data_source_tuple"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); resultSet = preparedStatement.executeQuery(); resultSet.next(); @@ -805,7 +829,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting unique caseDisplayName/dataSource tuples having artifactType and artifactValue.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -831,7 +855,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting data sources.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -844,11 +868,11 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 { @@ -860,19 +884,19 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; // Figure out sql variables or subqueries - StringBuilder sql = new StringBuilder(); - sql.append("SELECT 0 "); + String sql = "SELECT 0 "; for (CorrelationAttribute.Type type : artifactTypes) { String table_name = EamDbUtil.correlationTypeToInstanceTableName(type); - sql.append("+ (SELECT count(*) FROM "); - sql.append(table_name); - sql.append(" WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) and data_source_id=(SELECT id FROM data_sources WHERE device_id=?))"); + sql + += "+ (SELECT count(*) FROM " + + table_name + + " 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 { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); for (int i = 0; i < artifactTypes.size(); ++i) { preparedStatement.setString(2 * i + 1, caseUUID); @@ -885,7 +909,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error counting artifact instances by caseName/dataSource.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -903,10 +927,10 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public void prepareBulkArtifact(CorrelationAttribute eamArtifact) throws EamDbException { - if(eamArtifact.getCorrelationType() == null) { + if (eamArtifact.getCorrelationType() == null) { throw new EamDbException("Correlation type is null"); } - + synchronized (bulkArtifacts) { bulkArtifacts.get(eamArtifact.getCorrelationType().getDbTableName()).add(eamArtifact); bulkArtifactsCount++; @@ -940,21 +964,19 @@ public abstract class AbstractSqlEamDb implements EamDb { if (bulkArtifactsCount == 0) { return; } - - TimingMetric timingMetric = EnterpriseHealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); for (CorrelationAttribute.Type type : artifactTypes) { String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); - StringBuilder sql = new StringBuilder(); - sql.append("INSERT INTO "); - sql.append(tableName); - sql.append(" (case_id, data_source_id, value, file_path, known_status, comment) "); - sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), "); - sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) "); - sql.append(getConflictClause()); + String sql + = "INSERT INTO " + + tableName + + " (case_id, data_source_id, value, file_path, known_status, comment) " + + "VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), " + + "(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?) " + + getConflictClause(); - bulkPs = conn.prepareStatement(sql.toString()); + bulkPs = conn.prepareStatement(sql); Collection eamArtifacts = bulkArtifacts.get(type.getDbTableName()); for (CorrelationAttribute eamArtifact : eamArtifacts) { @@ -962,29 +984,52 @@ public abstract class AbstractSqlEamDb implements EamDb { for (CorrelationAttributeInstance eamInstance : eamInstances) { if (!eamArtifact.getCorrelationValue().isEmpty()) { - - if(eamInstance.getCorrelationCase() == null) { - throw new EamDbException("Correlation attribute instance has null case"); + + if (eamInstance.getCorrelationCase() == 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("Correlation attribute instance has null data source"); + if (eamInstance.getCorrelationDataSource() == 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("Correlation attribute instance has null known known status"); + if (eamInstance.getKnownStatus() == 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(); } } } @@ -992,8 +1037,9 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkPs.executeBatch(); bulkArtifacts.get(type.getDbTableName()).clear(); } - - EnterpriseHealthMonitor.submitTimingMetric(timingMetric); + + TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Bulk insert"); + HealthMonitor.submitTimingMetric(timingMetric); // Reset state bulkArtifactsCount = 0; @@ -1001,7 +1047,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting bulk artifacts.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } @@ -1011,16 +1057,16 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertCases(List cases) throws EamDbException { - if(cases == null) { + if (cases == null) { throw new EamDbException("cases argument is null"); } - + if (cases.isEmpty()) { return; } Connection conn = connect(); - + int counter = 0; PreparedStatement bulkPs = null; try { @@ -1081,44 +1127,177 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting bulk cases.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } /** - * 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. + * Update a correlation attribute instance in the database with that in the + * associated CorrelationAttribute object. + * + * @param eamArtifact The correlation attribute whose database instance will + * be updated. + * + * @throws EamDbException + */ + @Override + public void updateAttributeInstanceComment(CorrelationAttribute eamArtifact) throws EamDbException { + if (eamArtifact == null) { + throw new EamDbException("CorrelationAttribute is null"); + } + + CorrelationAttributeInstance eamInstance = eamArtifact.getInstances().get(0); + + if (eamInstance == null) { + throw new EamDbException("CorrelationAttributeInstance is null"); + } + if (eamInstance.getCorrelationCase() == null) { + throw new EamDbException("Correlation case is null"); + } + if (eamInstance.getCorrelationDataSource() == null) { + throw new EamDbException("Correlation data source is null"); + } + + Connection conn = connect(); + PreparedStatement preparedQuery = null; + String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); + + String sqlUpdate + = "UPDATE " + + tableName + + " SET comment=? " + + "WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) " + + "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) " + + "AND value=? " + + "AND file_path=?"; + + try { + preparedQuery = conn.prepareStatement(sqlUpdate); + preparedQuery.setString(1, eamInstance.getComment()); + preparedQuery.setString(2, eamInstance.getCorrelationCase().getCaseUUID()); + preparedQuery.setString(3, eamInstance.getCorrelationDataSource().getDeviceID()); + preparedQuery.setString(4, eamArtifact.getCorrelationValue()); + preparedQuery.setString(5, eamInstance.getFilePath()); + preparedQuery.executeUpdate(); + } catch (SQLException ex) { + throw new EamDbException("Error getting/setting artifact instance comment=" + eamInstance.getComment(), ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedQuery); + EamDbUtil.closeConnection(conn); + } + } + + /** + * Find a correlation attribute in the Central Repository database given the + * instance type, case, data source, value, and file path. + * + * @param type The type of instance. + * @param correlationCase The case tied to the instance. + * @param correlationDataSource The data source tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. + * + * @return The correlation attribute if it exists; otherwise null. + * + * @throws EamDbException + */ + @Override + public CorrelationAttribute getCorrelationAttribute(CorrelationAttribute.Type type, CorrelationCase correlationCase, + CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException { + + if (type == null) { + throw new EamDbException("Correlation type is null"); + } + if (correlationCase == null) { + throw new EamDbException("Correlation case is null"); + } + if (correlationDataSource == null) { + throw new EamDbException("Correlation data source is null"); + } + if (value == null) { + throw new EamDbException("Correlation value is null"); + } + if (filePath == null) { + throw new EamDbException("Correlation file path is null"); + } + + Connection conn = connect(); + + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + CorrelationAttribute correlationAttribute = null; + + try { + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); + String sql + = "SELECT id, known_status, comment FROM " + + tableName + + " WHERE case_id=?" + + " AND data_source_id=?" + + " AND value=?" + + " AND file_path=?"; + + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setInt(1, correlationCase.getID()); + preparedStatement.setInt(2, correlationDataSource.getID()); + preparedStatement.setString(3, value.toLowerCase()); + preparedStatement.setString(4, filePath.toLowerCase()); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + int instanceId = resultSet.getInt(1); + int knownStatus = resultSet.getInt(2); + String comment = resultSet.getString(3); + + correlationAttribute = new CorrelationAttribute(type, value); + CorrelationAttributeInstance artifactInstance = new CorrelationAttributeInstance( + instanceId, correlationCase, correlationDataSource, filePath, comment, TskData.FileKnown.valueOf((byte) knownStatus)); + correlationAttribute.addInstance(artifactInstance); + } + } catch (SQLException ex) { + throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + + return correlationAttribute; + } + + /** + * 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 { - if(eamArtifact == null) { - throw new EamDbException("Correlation attribute is null"); + if (eamArtifact == null) { + throw new EamDbException("CorrelationAttribute is null"); } - if(knownStatus == null) { + if (knownStatus == null) { throw new EamDbException("Known status is null"); } if (1 != eamArtifact.getInstances().size()) { throw new EamDbException("Error: Artifact must have exactly one (1) Artifact Instance to set as notable."); // NON-NLS } - + List eamInstances = eamArtifact.getInstances(); CorrelationAttributeInstance eamInstance = eamInstances.get(0); - if(eamInstance.getCorrelationCase() == null) { + if (eamInstance.getCorrelationCase() == null) { throw new EamDbException("Correlation case is null"); } - if(eamInstance.getCorrelationDataSource() == null) { + if (eamInstance.getCorrelationDataSource() == null) { throw new EamDbException("Correlation data source is null"); } - - Connection conn = connect(); + + Connection conn = connect(); PreparedStatement preparedUpdate = null; PreparedStatement preparedQuery = null; @@ -1126,22 +1305,22 @@ public abstract class AbstractSqlEamDb implements EamDb { String tableName = EamDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); - StringBuilder sqlQuery = new StringBuilder(); - sqlQuery.append("SELECT id FROM "); - sqlQuery.append(tableName); - sqlQuery.append(" WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) "); - sqlQuery.append("AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) "); - sqlQuery.append("AND value=? "); - sqlQuery.append("AND file_path=?"); + String sqlQuery + = "SELECT id FROM " + + tableName + + " WHERE case_id=(SELECT id FROM cases WHERE case_uid=?) " + + "AND data_source_id=(SELECT id FROM data_sources WHERE device_id=?) " + + "AND value=? " + + "AND file_path=?"; - StringBuilder sqlUpdate = new StringBuilder(); - sqlUpdate.append("UPDATE "); - sqlUpdate.append(tableName); - sqlUpdate.append(" SET known_status=?, comment=? "); - sqlUpdate.append("WHERE id=?"); + String sqlUpdate + = "UPDATE " + + tableName + + " SET known_status=?, comment=? " + + "WHERE id=?"; try { - preparedQuery = conn.prepareStatement(sqlQuery.toString()); + preparedQuery = conn.prepareStatement(sqlQuery); preparedQuery.setString(1, eamInstance.getCorrelationCase().getCaseUUID()); preparedQuery.setString(2, eamInstance.getCorrelationDataSource().getDeviceID()); preparedQuery.setString(3, eamArtifact.getCorrelationValue()); @@ -1149,7 +1328,7 @@ public abstract class AbstractSqlEamDb implements EamDb { resultSet = preparedQuery.executeQuery(); if (resultSet.next()) { int instance_id = resultSet.getInt("id"); - preparedUpdate = conn.prepareStatement(sqlUpdate.toString()); + preparedUpdate = conn.prepareStatement(sqlUpdate); preparedUpdate.setByte(1, knownStatus.getFileKnownValue()); // NOTE: if the user tags the same instance as BAD multiple times, @@ -1184,8 +1363,8 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting/setting artifact instance knownStatus=" + knownStatus.getName(), ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedUpdate); - EamDbUtil.closePreparedStatement(preparedQuery); + EamDbUtil.closeStatement(preparedUpdate); + EamDbUtil.closeStatement(preparedQuery); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1202,10 +1381,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); List artifactInstances = new ArrayList<>(); @@ -1215,19 +1394,21 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM "); - sql.append(tableName); - sql.append(" LEFT JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id"); - sql.append(" LEFT JOIN data_sources ON "); - sql.append(tableName); - sql.append(".data_source_id=data_sources.id"); - sql.append(" WHERE value=? AND known_status=?"); + String sql + = "SELECT " + + tableName + + ".id, cases.case_name, cases.case_uid, data_sources.id AS data_source_id, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE value=? AND known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1238,7 +1419,66 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeConnection(conn); + } + + return artifactInstances; + } + + /** + * + * 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 { + if (aType == null) { + throw new EamDbException("Correlation type is null"); + } + + Connection conn = connect(); + + List artifactInstances = new ArrayList<>(); + + CorrelationAttributeInstance artifactInstance; + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + + String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); + String sql + = "SELECT cases.case_name, cases.case_uid, data_sources.name, device_id, file_path, known_status, comment, data_sources.case_id FROM " + + tableName + + " LEFT JOIN cases ON " + + tableName + + ".case_id=cases.id" + + " LEFT JOIN data_sources ON " + + tableName + + ".data_source_id=data_sources.id" + + " WHERE known_status=?" + + " GROUP BY " + + tableName + + ".value"; + + try { + preparedStatement = conn.prepareStatement(sql); + preparedStatement.setByte(1, TskData.FileKnown.BAD.getFileKnownValue()); + resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + artifactInstance = getEamArtifactInstanceFromResultSet(resultSet); + artifactInstances.add(artifactInstance); + } + } catch (SQLException ex) { + throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS + } finally { + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1256,10 +1496,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Long badInstances = 0L; @@ -1267,13 +1507,13 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT count(*) FROM "); - sql.append(tableName); - sql.append(" WHERE value=? AND known_status=?"); + String sql + = "SELECT count(*) FROM " + + tableName + + " WHERE value=? AND known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1282,7 +1522,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting count of notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1298,16 +1538,16 @@ public abstract class AbstractSqlEamDb implements EamDb { * @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 { - if(aType == null) { + if (aType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); Collection caseNames = new LinkedHashSet<>(); @@ -1316,19 +1556,19 @@ public abstract class AbstractSqlEamDb implements EamDb { ResultSet resultSet = null; String tableName = EamDbUtil.correlationTypeToInstanceTableName(aType); - StringBuilder sql = new StringBuilder(); - sql.append("SELECT DISTINCT case_name FROM "); - sql.append(tableName); - sql.append(" INNER JOIN cases ON "); - sql.append(tableName); - sql.append(".case_id=cases.id WHERE "); - sql.append(tableName); - sql.append(".value=? AND "); - sql.append(tableName); - sql.append(".known_status=?"); + String sql + = "SELECT DISTINCT case_name FROM " + + tableName + + " INNER JOIN cases ON " + + tableName + + ".case_id=cases.id WHERE " + + tableName + + ".value=? AND " + + tableName + + ".known_status=?"; try { - preparedStatement = conn.prepareStatement(sql.toString()); + preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1, value); preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue()); resultSet = preparedStatement.executeQuery(); @@ -1338,7 +1578,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1350,6 +1590,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * Remove a reference set and all entries contained in it. * * @param referenceSetID + * * @throws EamDbException */ @Override @@ -1362,6 +1603,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * Remove the entry for this set from the reference_sets table * * @param referenceSetID + * * @throws EamDbException */ private void deleteReferenceSetEntry(int referenceSetID) throws EamDbException { @@ -1377,7 +1619,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error deleting reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1387,6 +1629,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * (Currently only removes entries from the reference_file table) * * @param referenceSetID + * * @throws EamDbException */ private void deleteReferenceSetEntries(int referenceSetID) throws EamDbException { @@ -1405,7 +1648,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error deleting files from reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1418,13 +1661,15 @@ public abstract class AbstractSqlEamDb implements EamDb { * @param referenceSetID * @param setName * @param version + * * @return true if a matching entry exists in the central repository + * * @throws EamDbException */ @Override public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException { EamGlobalSet refSet = this.getReferenceSetByID(referenceSetID); - if(refSet == null) { + if (refSet == null) { return false; } @@ -1437,7 +1682,9 @@ public abstract class AbstractSqlEamDb implements EamDb { * * @param hash * @param referenceSetID + * * @return true if the hash is found in the reference set + * * @throws EamDbException */ @Override @@ -1451,6 +1698,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * @param value * @param referenceSetID * @param correlationTypeID + * * @return true if the value is found in the reference set */ @Override @@ -1475,7 +1723,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error determining if value (" + value + ") is in reference set " + referenceSetID, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1493,8 +1741,8 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException { - if(aType == null) { - throw new EamDbException("null correlation type"); + if (aType == null) { + throw new EamDbException("Correlation type is null"); } // TEMP: Only support file correlation type @@ -1519,7 +1767,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error determining if artifact is notable by reference.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1528,20 +1776,52 @@ public abstract class AbstractSqlEamDb implements EamDb { } /** - * Add a new organization + * Process the Artifact instance in the EamDb * - * @return the Organization ID of the newly created organization. - * - * @param eamOrg The organization to add + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance * * @throws EamDbException */ @Override - public long newOrganization(EamOrganization eamOrg) throws EamDbException { - if(eamOrg == null) { - throw new EamDbException("EamOrganization is null"); + public void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException { + if (type == null) { + throw new EamDbException("Correlation type is null"); } - + + if (instanceTableCallback == null) { + throw new EamDbException("Callback interface is null"); + } + + Connection conn = connect(); + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + String tableName = EamDbUtil.correlationTypeToInstanceTableName(type); + StringBuilder sql = new StringBuilder(); + sql.append("select * from "); + sql.append(tableName); + + 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) { + throw new EamDbException("EamOrganization is null"); + } else if (eamOrg.getOrgID() != -1) { + throw new EamDbException("EamOrganization already has an ID"); + } + Connection conn = connect(); ResultSet generatedKeys = null; PreparedStatement preparedStatement = null; @@ -1558,14 +1838,15 @@ public abstract class AbstractSqlEamDb implements EamDb { preparedStatement.executeUpdate(); generatedKeys = preparedStatement.getGeneratedKeys(); if (generatedKeys.next()) { - return generatedKeys.getLong(1); + eamOrg.setOrgID((int) generatedKeys.getLong(1)); + return eamOrg; } else { throw new SQLException("Creating user failed, no ID obtained."); } } catch (SQLException ex) { throw new EamDbException("Error inserting new organization.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(generatedKeys); EamDbUtil.closeConnection(conn); } @@ -1598,7 +1879,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all organizations.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1631,7 +1912,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting organization by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1641,33 +1922,48 @@ public abstract class AbstractSqlEamDb implements EamDb { * Get the organization associated with the given reference set. * * @param referenceSetID ID of the reference set + * * @return The organization object + * * @throws EamDbException */ @Override public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException { EamGlobalSet globalSet = getReferenceSetByID(referenceSetID); - if(globalSet == null) { + if (globalSet == null) { throw new EamDbException("Reference set with ID " + referenceSetID + " not found"); } return (getOrganizationByID(globalSet.getOrgID())); } + /** + * Tests that an organization passed in as an argument is valid + * + * @param org + * + * @throws EamDbException if invalid + */ + private void testArgument(EamOrganization org) throws EamDbException { + if (org == null) { + throw new EamDbException("EamOrganization is null"); + } else if (org.getOrgID() == -1) { + throw new EamDbException("Organization has -1 row ID"); + } + } + /** * Update an existing organization. * * @param updatedOrganization the values the Organization with the same ID - * will be updated to in the database. + * will be updated to in the database. * * @throws EamDbException */ @Override public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException { - if(updatedOrganization == null) { - throw new EamDbException("null updatedOrganization"); - } - + testArgument(updatedOrganization); + Connection conn = connect(); PreparedStatement preparedStatement = null; String sql = "UPDATE organizations SET org_name = ?, poc_name = ?, poc_email = ?, poc_phone = ? WHERE id = ?"; @@ -1682,20 +1978,15 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating organization.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } - @Messages({"AbstractSqlEamDb.deleteOrganization.inUseException.message=Can not delete organization " - + "which is currently in use by a case or reference set in the central repository.", - "AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."}) @Override public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException { - if(organizationToDelete == null) { - throw new EamDbException("Organization to delete is null"); - } - + testArgument(organizationToDelete); + Connection conn = connect(); PreparedStatement checkIfUsedStatement = null; ResultSet resultSet = null; @@ -1709,16 +2000,16 @@ public abstract class AbstractSqlEamDb implements EamDb { resultSet = checkIfUsedStatement.executeQuery(); resultSet.next(); if (resultSet.getLong(1) > 0) { - throw new EamDbException(Bundle.AbstractSqlEamDb_deleteOrganization_inUseException_message()); + throw new EamDbException("Can not delete organization which is currently in use by a case or reference set in the central repository."); } deleteOrgStatement = conn.prepareStatement(deleteOrgSql); deleteOrgStatement.setInt(1, organizationToDelete.getOrgID()); deleteOrgStatement.executeUpdate(); } catch (SQLException ex) { - throw new EamDbException(Bundle.AbstractSqlEamDb_deleteOrganization_errorDeleting_message(), ex); // NON-NLS + throw new EamDbException("Error executing query when attempting to delete organization by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(checkIfUsedStatement); - EamDbUtil.closePreparedStatement(deleteOrgStatement); + EamDbUtil.closeStatement(checkIfUsedStatement); + EamDbUtil.closeStatement(deleteOrgStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1735,18 +2026,18 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException { - if(eamGlobalSet == null){ - throw new EamDbException("EamGlobalSet argument is null"); + if (eamGlobalSet == null) { + throw new EamDbException("EamGlobalSet is null"); } - - if(eamGlobalSet.getFileKnownStatus() == null){ + + if (eamGlobalSet.getFileKnownStatus() == null) { throw new EamDbException("File known status on the EamGlobalSet is null"); } - - if(eamGlobalSet.getType() == null){ + + if (eamGlobalSet.getType() == null) { throw new EamDbException("Type on the EamGlobalSet is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement1 = null; @@ -1781,8 +2072,8 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new global set.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); - EamDbUtil.closePreparedStatement(preparedStatement2); + EamDbUtil.closeStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement2); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1809,7 +2100,7 @@ public abstract class AbstractSqlEamDb implements EamDb { preparedStatement1 = conn.prepareStatement(sql1); preparedStatement1.setInt(1, referenceSetID); resultSet = preparedStatement1.executeQuery(); - if(resultSet.next()) { + if (resultSet.next()) { return getEamGlobalSetFromResultSet(resultSet); } else { return null; @@ -1818,7 +2109,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference set by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1828,18 +2119,18 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 { - - if(correlationType == null){ + + if (correlationType == null) { throw new EamDbException("Correlation type is null"); } - + List results = new ArrayList<>(); Connection conn = connect(); @@ -1857,7 +2148,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference sets.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1868,19 +2159,20 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 { - if(eamGlobalFileInstance.getKnownStatus() == null){ - throw new EamDbException("known status of EamGlobalFileInstance is null"); + if (eamGlobalFileInstance.getKnownStatus() == null) { + throw new EamDbException("Known status of EamGlobalFileInstance is null"); } - if(correlationType == null){ + if (correlationType == null) { throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -1898,7 +2190,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new reference instance into reference_ table.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } } @@ -1910,7 +2202,9 @@ public abstract class AbstractSqlEamDb implements EamDb { * * @param referenceSetName * @param version + * * @return true if a matching set is found + * * @throws EamDbException */ @Override @@ -1932,7 +2226,7 @@ public abstract class AbstractSqlEamDb implements EamDb { throw new EamDbException("Error testing whether reference set exists (name: " + referenceSetName + " version: " + version, ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -1945,10 +2239,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public void bulkInsertReferenceTypeEntries(Set globalInstances, CorrelationAttribute.Type contentType) throws EamDbException { - if(contentType == null) { - throw new EamDbException("Null correlation type"); + if (contentType == null) { + throw new EamDbException("Correlation type is null"); } - if(globalInstances == null) { + if (globalInstances == null) { throw new EamDbException("Null set of EamGlobalFileInstance"); } @@ -1965,10 +2259,10 @@ public abstract class AbstractSqlEamDb implements EamDb { bulkPs = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(contentType))); for (EamGlobalFileInstance globalInstance : globalInstances) { - if(globalInstance.getKnownStatus() == null){ + if (globalInstance.getKnownStatus() == null) { throw new EamDbException("EamGlobalFileInstance with value " + globalInstance.getMD5Hash() + " has null known status"); } - + bulkPs.setInt(1, globalInstance.getGlobalSetID()); bulkPs.setString(2, globalInstance.getMD5Hash()); bulkPs.setByte(3, globalInstance.getKnownStatus().getFileKnownValue()); @@ -1986,7 +2280,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } throw new EamDbException("Error inserting bulk artifacts.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(bulkPs); + EamDbUtil.closeStatement(bulkPs); EamDbUtil.closeConnection(conn); } } @@ -1994,7 +2288,7 @@ public abstract class AbstractSqlEamDb implements EamDb { /** * 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 @@ -2003,10 +2297,10 @@ public abstract class AbstractSqlEamDb implements EamDb { */ @Override public List getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException { - if(aType == null) { - throw new EamDbException("correlation type is null"); + if (aType == null) { + throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); List globalFileInstances = new ArrayList<>(); @@ -2026,7 +2320,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting reference instances by type and value.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement1); + EamDbUtil.closeStatement(preparedStatement1); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2044,9 +2338,9 @@ public abstract class AbstractSqlEamDb implements EamDb { @Override public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException { if (newType == null) { - throw new EamDbException("null correlation type"); + throw new EamDbException("Correlation type is null"); } - + Connection conn = connect(); PreparedStatement preparedStatement = null; @@ -2093,8 +2387,8 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error inserting new correlation type.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); - EamDbUtil.closePreparedStatement(preparedStatementQuery); + EamDbUtil.closeStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatementQuery); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2121,7 +2415,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2132,7 +2426,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 */ @@ -2156,7 +2450,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting enabled correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2167,7 +2461,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * 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 */ @@ -2191,7 +2485,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting supported correlation types.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2223,7 +2517,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error updating correlation type.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeConnection(conn); } @@ -2251,7 +2545,7 @@ public abstract class AbstractSqlEamDb implements EamDb { preparedStatement = conn.prepareStatement(sql); preparedStatement.setInt(1, typeId); resultSet = preparedStatement.executeQuery(); - if(resultSet.next()) { + if (resultSet.next()) { aType = getCorrelationTypeFromResultSet(resultSet); return aType; } else { @@ -2261,7 +2555,7 @@ public abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting correlation type by id.", ex); // NON-NLS } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } @@ -2271,7 +2565,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * Convert a ResultSet to a EamCase object * * @param resultSet A resultSet with a set of values to create a EamCase - * object. + * object. * * @return fully populated EamCase object, or null * @@ -2341,7 +2635,7 @@ public abstract class AbstractSqlEamDb implements EamDb { * Convert a ResultSet to a EamArtifactInstance object * * @param resultSet A resultSet with a set of values to create a - * EamArtifactInstance object. + * EamArtifactInstance object. * * @return fully populated EamArtifactInstance, or null * @@ -2351,15 +2645,15 @@ public abstract class AbstractSqlEamDb implements EamDb { if (null == resultSet) { return null; } - CorrelationAttributeInstance eamArtifactInstance = new CorrelationAttributeInstance( + // @@@ We should have data source ID in the previous query instead of passing -1 into the below constructor + return new CorrelationAttributeInstance( + resultSet.getInt("id"), new CorrelationCase(resultSet.getInt("case_id"), resultSet.getString("case_uid"), resultSet.getString("case_name")), - new CorrelationDataSource(-1, resultSet.getInt("case_id"), resultSet.getString("device_id"), resultSet.getString("name")), + new CorrelationDataSource(resultSet.getInt("case_id"), resultSet.getInt("data_source_id"), resultSet.getString("device_id"), resultSet.getString("name")), resultSet.getString("file_path"), resultSet.getString("comment"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")) ); - - return eamArtifactInstance; } private EamOrganization getEamOrganizationFromResultSet(ResultSet resultSet) throws SQLException { @@ -2367,15 +2661,13 @@ public abstract class AbstractSqlEamDb implements EamDb { return null; } - EamOrganization eamOrganization = new EamOrganization( + return new EamOrganization( resultSet.getInt("id"), resultSet.getString("org_name"), resultSet.getString("poc_name"), resultSet.getString("poc_email"), resultSet.getString("poc_phone") ); - - return eamOrganization; } private EamGlobalSet getEamGlobalSetFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { @@ -2383,7 +2675,7 @@ public abstract class AbstractSqlEamDb implements EamDb { return null; } - EamGlobalSet eamGlobalSet = new EamGlobalSet( + return new EamGlobalSet( resultSet.getInt("id"), resultSet.getInt("org_id"), resultSet.getString("set_name"), @@ -2393,8 +2685,6 @@ public abstract class AbstractSqlEamDb implements EamDb { EamDb.getInstance().getCorrelationTypeById(resultSet.getInt("type")), LocalDate.parse(resultSet.getString("import_date")) ); - - return eamGlobalSet; } private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException { @@ -2402,15 +2692,13 @@ public abstract class AbstractSqlEamDb implements EamDb { return null; } - EamGlobalFileInstance eamGlobalFileInstance = new EamGlobalFileInstance( + return new EamGlobalFileInstance( resultSet.getInt("id"), resultSet.getInt("reference_set_id"), resultSet.getString("value"), TskData.FileKnown.valueOf(resultSet.getByte("known_status")), resultSet.getString("comment") ); - - return eamGlobalFileInstance; } /** @@ -2422,7 +2710,7 @@ public abstract class AbstractSqlEamDb implements EamDb { public void upgradeSchema() throws EamDbException, SQLException { ResultSet resultSet = null; - Statement statement; + Statement statement = null; Connection conn = null; try { @@ -2431,30 +2719,30 @@ public abstract class AbstractSqlEamDb implements EamDb { statement = conn.createStatement(); int minorVersion = 0; - int majorVersion = 0; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'"); if (resultSet.next()) { String minorVersionStr = resultSet.getString("value"); try { minorVersion = Integer.parseInt(minorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt"); + throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt", ex); } } + int majorVersion = 0; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); if (resultSet.next()) { String majorVersionStr = resultSet.getString("value"); try { majorVersion = Integer.parseInt(majorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt"); + throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt", ex); } } CaseDbSchemaVersionNumber dbSchemaVersion = new CaseDbSchemaVersionNumber(majorVersion, minorVersion); if (dbSchemaVersion.equals(CURRENT_DB_SCHEMA_VERSION)) { - LOGGER.log(Level.INFO, "Central Repository is up to date"); + logger.log(Level.INFO, "Central Repository is up to date"); return; } @@ -2475,18 +2763,19 @@ public abstract class AbstractSqlEamDb implements EamDb { } conn.commit(); - LOGGER.log(Level.INFO, "Central Repository upgraded to version " + CURRENT_DB_SCHEMA_VERSION); + logger.log(Level.INFO, "Central Repository upgraded to version " + CURRENT_DB_SCHEMA_VERSION); } catch (SQLException | EamDbException ex) { try { if (conn != null) { conn.rollback(); } } catch (SQLException ex2) { - LOGGER.log(Level.SEVERE, "Database rollback failed", ex2); + logger.log(Level.SEVERE, "Database rollback failed", ex2); } throw ex; } finally { EamDbUtil.closeResultSet(resultSet); + EamDbUtil.closeStatement(statement); EamDbUtil.closeConnection(conn); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index 9e0c3b6cec..3ccca9f293 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.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"); @@ -24,8 +24,8 @@ import org.sleuthkit.datamodel.TskData; /** * - * Used to store details about a specific instance of a - * CorrelationAttribute. Includes its data source, path, etc. + * Used to store details about a specific instance of a CorrelationAttribute. + * Includes its data source, path, etc. * */ @Messages({ @@ -43,13 +43,6 @@ public class CorrelationAttributeInstance implements Serializable { private String comment; private TskData.FileKnown knownStatus; - public CorrelationAttributeInstance( - CorrelationCase eamCase, - CorrelationDataSource eamDataSource - ) throws EamDbException { - this(-1, eamCase, eamDataSource, "", null, TskData.FileKnown.UNKNOWN); - } - public CorrelationAttributeInstance( CorrelationCase eamCase, CorrelationDataSource eamDataSource, @@ -58,14 +51,6 @@ public class CorrelationAttributeInstance implements Serializable { this(-1, eamCase, eamDataSource, filePath, null, TskData.FileKnown.UNKNOWN); } - public CorrelationAttributeInstance( - CorrelationCase eamCase, - CorrelationDataSource eamDataSource, - String filePath, - String comment - ) throws EamDbException { - this(-1, eamCase, eamDataSource, filePath, comment, TskData.FileKnown.UNKNOWN); - } public CorrelationAttributeInstance( CorrelationCase eamCase, @@ -85,10 +70,10 @@ public class CorrelationAttributeInstance implements Serializable { String comment, TskData.FileKnown knownStatus ) throws EamDbException { - if(filePath == null) { + if (filePath == null) { throw new EamDbException("file path is null"); } - + this.ID = ID; this.correlationCase = eamCase; this.correlationDataSource = eamDataSource; @@ -117,6 +102,16 @@ public class CorrelationAttributeInstance implements Serializable { + this.getComment(); } + /** + * Is this a database instance? + * + * @return True if the instance ID is greater or equal to zero; otherwise + * false. + */ + public boolean isDatabaseInstance() { + return (ID >= 0); + } + /** * @return the database ID */ @@ -160,9 +155,9 @@ public class CorrelationAttributeInstance implements Serializable { } /** - * Get this knownStatus. This only indicates whether an item has been - * tagged as notable and should never return KNOWN. - * + * Get this knownStatus. This only indicates whether an item has been tagged + * as notable and should never return KNOWN. + * * @return BAD if the item has been tagged as notable, UNKNOWN otherwise */ public TskData.FileKnown getKnownStatus() { @@ -170,10 +165,11 @@ public class CorrelationAttributeInstance implements Serializable { } /** - * Set the knownStatus. This only indicates whether an item has been - * tagged as notable and should never be set to KNOWN. - * - * @param knownStatus Should be BAD if the item is tagged as notable, UNKNOWN otherwise + * Set the knownStatus. This only indicates whether an item has been tagged + * as notable and should never be set to KNOWN. + * + * @param knownStatus Should be BAD if the item is tagged as notable, + * UNKNOWN otherwise */ public void setKnownStatus(TskData.FileKnown knownStatus) { this.knownStatus = knownStatus; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 864bf50923..84dce834a1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -40,15 +40,21 @@ public class CorrelationDataSource implements Serializable { private final String name; /** - * - * @param caseId - * @param deviceId - * @param name + * @param correlationCase CorrelationCase object data source is associated with. Must have been created by EamDB and have a valid ID. + * @param deviceId User specified case-specific ID + * @param name Display name of data source */ - public CorrelationDataSource(int caseId, String deviceId, String name) { - this(caseId, -1, deviceId, name); + public CorrelationDataSource(CorrelationCase correlationCase, String deviceId, String name) { + this(correlationCase.getID(), -1, deviceId, name); } + /** + * + * @param caseId Row ID for Case in DB + * @param dataSourceId Row ID for this data source in DB (or -1) + * @param deviceId User specified ID for device (unique per case) + * @param name User specified name + */ CorrelationDataSource(int caseId, int dataSourceId, String deviceId, @@ -61,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 @@ -84,7 +91,18 @@ public class CorrelationDataSource implements Serializable { } catch (TskDataException | TskCoreException ex) { throw new EamDbException("Error getting data source info: " + ex.getMessage()); } - return new CorrelationDataSource(correlationCase.getID(), -1, deviceId, dataSource.getName()); + + CorrelationDataSource correlationDataSource = null; + if (EamDbUtil.useCentralRepo()) { + correlationDataSource = EamDb.getInstance().getDataSource(correlationCase, deviceId); + } + if (correlationDataSource == null) { + correlationDataSource = new CorrelationDataSource(correlationCase, deviceId, dataSource.getName()); + if (EamDbUtil.useCentralRepo()) { + EamDb.getInstance().newDataSource(correlationDataSource); + } + } + return correlationDataSource; } @Override @@ -102,7 +120,7 @@ public class CorrelationDataSource implements Serializable { /** * Get the database row ID * - * @return the ID + * @return the ID or -1 if unknown */ int getID() { return dataSourceID; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java index 84c23256f0..6aad75d38e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamArtifactUtil.java @@ -39,7 +39,7 @@ import org.sleuthkit.datamodel.TskData; public class EamArtifactUtil { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(EamArtifactUtil.class.getName()); + private static final Logger logger = Logger.getLogger(EamArtifactUtil.class.getName()); public EamArtifactUtil() { } @@ -76,14 +76,14 @@ public class EamArtifactUtil { // have switch based on artifact type for (CorrelationAttribute.Type aType : EamDb.getInstance().getDefinedCorrelationTypes()) { if ((checkEnabled && aType.isEnabled()) || !checkEnabled) { - CorrelationAttribute eamArtifact = EamArtifactUtil.getCorrelationAttributeFromBlackboardArtifact(aType, bbArtifact); - if (eamArtifact != null) { - eamArtifacts.add(eamArtifact); + CorrelationAttribute correlationAttribute = EamArtifactUtil.getCorrelationAttributeFromBlackboardArtifact(aType, bbArtifact); + if (correlationAttribute != null) { + eamArtifacts.add(correlationAttribute); } } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS return eamArtifacts; } @@ -115,10 +115,10 @@ public class EamArtifactUtil { eamArtifact.addInstance(eamInstance); } } catch (TskCoreException | EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS return eamArtifacts; } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Case is closed.", ex); // NON-NLS + logger.log(Level.SEVERE, "Case is closed.", ex); // NON-NLS return eamArtifacts; } } @@ -136,7 +136,7 @@ public class EamArtifactUtil { * @return the new EamArtifact, or null if one was not created because * bbArtifact did not contain the needed data */ - private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, + private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, BlackboardArtifact bbArtifact) throws EamDbException { String value = null; int artifactTypeID = bbArtifact.getArtifactTypeID(); @@ -202,10 +202,10 @@ public class EamArtifactUtil { } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS return null; - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS return null; } @@ -216,6 +216,45 @@ public class EamArtifactUtil { } } + /** + * Retrieve CorrelationAttribute from the given Content. + * + * @param content The content object + * + * @return The new CorrelationAttribute, or null if retrieval failed. + */ + public static CorrelationAttribute getCorrelationAttributeFromContent(Content content) { + + if (!(content instanceof AbstractFile)) { + return null; + } + + final AbstractFile file = (AbstractFile) content; + + if (!isSupportedAbstractFileType(file)) { + return null; + } + + CorrelationAttribute correlationAttribute = null; + + try { + CorrelationAttribute.Type type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); + CorrelationCase 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) { + logger.log(Level.SEVERE, "Error retrieving correlation attribute.", ex); + } + + return correlationAttribute; + } + /** * Create an EamArtifact from the given Content. Will return null if an * artifact can not be created - this is not necessarily an error case, it @@ -225,7 +264,7 @@ public class EamArtifactUtil { * * Does not add the artifact to the database. * - * @param content The content object + * @param content The content object * * @return The new EamArtifact or null if creation failed */ @@ -237,7 +276,7 @@ public class EamArtifactUtil { final AbstractFile af = (AbstractFile) content; - if (!isValidCentralRepoFile(af)) { + if (!isSupportedAbstractFileType(af)) { return null; } @@ -262,7 +301,7 @@ public class EamArtifactUtil { eamArtifact.addInstance(cei); return eamArtifact; } catch (TskCoreException | EamDbException | NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Error making correlation attribute.", ex); + logger.log(Level.SEVERE, "Error making correlation attribute.", ex); return null; } } @@ -271,21 +310,17 @@ public class EamArtifactUtil { * Check whether the given abstract file should be processed for the central * repository. * - * @param af The file to test + * @param file The file to test * * @return true if the file should be added to the central repo, false * otherwise */ - public static boolean isValidCentralRepoFile(AbstractFile af) { - if (af == null) { + public static boolean isSupportedAbstractFileType(AbstractFile file) { + if (file == null) { return false; } - if (af.getKnown() == TskData.FileKnown.KNOWN) { - return false; - } - - switch (af.getType()) { + switch (file.getType()) { case UNALLOC_BLOCKS: case UNUSED_BLOCKS: case SLACK: @@ -297,9 +332,9 @@ public class EamArtifactUtil { case LOCAL: return true; case FS: - return af.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC); + return file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC); default: - LOGGER.log(Level.WARNING, "Unexpected file type {0}", af.getType().getName()); + logger.log(Level.WARNING, "Unexpected file type {0}", file.getType().getName()); return false; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java index 2b05cb1d70..7ced4a1d7d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDb.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"); @@ -34,7 +34,8 @@ public interface EamDb { public static final int SCHEMA_VERSION = 1; public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1); - + + /** * Get the instance * @@ -103,7 +104,7 @@ public interface EamDb { /** * 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 @@ -124,7 +125,7 @@ public interface EamDb { /** * 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 @@ -158,7 +159,9 @@ public interface EamDb { * Retrieves Central Repo case based on an Autopsy Case * * @param autopsyCase Autopsy case to find corresponding CR case for + * * @return CR Case + * * @throws EamDbException */ CorrelationCase getCase(Case autopsyCase) throws EamDbException; @@ -189,8 +192,8 @@ public interface EamDb { /** * 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 @@ -227,7 +230,7 @@ public interface EamDb { * 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 @@ -244,7 +247,7 @@ public interface EamDb { * @param value Value to search for * * @return Number of artifact instances having ArtifactType and - * ArtifactValue. + * ArtifactValue. */ Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException; @@ -281,11 +284,11 @@ public interface EamDb { * 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 */ Long getCountArtifactInstancesByCaseDataSource(String caseUUID, String dataSourceID) throws EamDbException; @@ -309,6 +312,34 @@ public interface EamDb { */ void bulkInsertCases(List cases) throws EamDbException; + /** + * Update a correlation attribute instance comment in the database with that + * in the associated CorrelationAttribute object. + * + * @param eamArtifact The correlation attribute whose database instance will + * be updated. + * + * @throws EamDbException + */ + void updateAttributeInstanceComment(CorrelationAttribute eamArtifact) throws EamDbException; + + /** + * Find a correlation attribute in the Central Repository database given the + * instance type, case, data source, value, and file path. + * + * @param type The type of instance. + * @param correlationCase The case tied to the instance. + * @param correlationDataSource The data source tied to the instance. + * @param value The value tied to the instance. + * @param filePath The file path tied to the instance. + * + * @return The correlation attribute if it exists; otherwise null. + * + * @throws EamDbException + */ + CorrelationAttribute getCorrelationAttribute(CorrelationAttribute.Type type, CorrelationCase correlationCase, + CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException; + /** * Sets an eamArtifact instance to the given known status. If eamArtifact * exists, it is updated. If eamArtifact does not exist nothing happens @@ -329,6 +360,15 @@ public interface EamDb { */ List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException; + /** + * 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 + */ + List getArtifactInstancesKnownBad(CorrelationAttribute.Type aType) throws EamDbException; /** * Count matching eamArtifacts instances that have knownStatus = "Bad". * @@ -347,7 +387,7 @@ public interface EamDb { * @param value Value to search for * * @return List of cases containing this artifact with instances marked as - * bad + * bad * * @throws EamDbException */ @@ -357,6 +397,7 @@ public interface EamDb { * Remove a reference set and all values contained in it. * * @param referenceSetID + * * @throws EamDbException */ public void deleteReferenceSet(int referenceSetID) throws EamDbException; @@ -369,7 +410,9 @@ public interface EamDb { * @param referenceSetID * @param referenceSetName * @param version + * * @return true if a matching entry exists in the central repository + * * @throws EamDbException */ public boolean referenceSetIsValid(int referenceSetID, String referenceSetName, String version) throws EamDbException; @@ -381,7 +424,9 @@ public interface EamDb { * * @param referenceSetName * @param version + * * @return true if a matching set is found + * * @throws EamDbException */ public boolean referenceSetExists(String referenceSetName, String version) throws EamDbException; @@ -392,7 +437,9 @@ public interface EamDb { * * @param hash * @param referenceSetID + * * @return true if the hash is found in the reference set + * * @throws EamDbException */ public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException; @@ -403,6 +450,7 @@ public interface EamDb { * @param value * @param referenceSetID * @param correlationTypeID + * * @return true if the hash is found in the reference set */ public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException; @@ -422,11 +470,11 @@ public interface EamDb { * * @param eamOrg The organization to add * - * @return the Organization ID of the newly created organization. + * @return The organization with the org ID set. * * @throws EamDbException */ - long newOrganization(EamOrganization eamOrg) throws EamDbException; + EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException; /** * Get all organizations @@ -452,7 +500,9 @@ public interface EamDb { * Get the organization associated with the given reference set. * * @param referenceSetID ID of the reference set + * * @return The organization object + * * @throws EamDbException */ EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException; @@ -461,7 +511,7 @@ public interface EamDb { * Update an existing organization. * * @param updatedOrganization the values the Organization with the same ID - * will be updated to in the database. + * will be updated to in the database. * * @throws EamDbException */ @@ -502,7 +552,7 @@ public interface EamDb { * Get all reference sets * * @param correlationType Type of sets to return - * + * * @return List of all reference sets in the central repository * * @throws EamDbException @@ -513,7 +563,8 @@ public interface EamDb { * 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 */ @@ -523,8 +574,8 @@ public interface EamDb { * Insert the bulk collection of Global File Instances * * @param globalInstances a Set of EamGlobalFileInstances to insert into the - * db. - * @param contentType the Type of the global instances + * db. + * @param contentType the Type of the global instances * * @throws EamDbException */ @@ -533,7 +584,7 @@ public interface EamDb { /** * 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 @@ -558,7 +609,7 @@ public interface EamDb { * 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 */ @@ -569,7 +620,7 @@ public interface EamDb { * 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 */ @@ -580,7 +631,7 @@ public interface EamDb { * 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 */ @@ -620,8 +671,18 @@ public interface EamDb { * (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 + * to get the lock */ public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException; + + /** + * Process the Artifact instance in the EamDb + * + * @param type EamArtifact.Type to search for + * @param instanceTableCallback callback to process the instance + * @throws EamDbException + */ + void processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java index e2fc4bf371..9b8a4e1ed5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java @@ -42,18 +42,18 @@ public class EamDbUtil { private static final String DEFAULT_ORG_NAME = "Not Specified"; /** - * Close the prepared statement. + * Close the statement. * - * @param preparedStatement + * @param statement The statement to be closed. * * @throws EamDbException */ - public static void closePreparedStatement(PreparedStatement preparedStatement) { - if (null != preparedStatement) { + public static void closeStatement(Statement statement) { + if (null != statement) { try { - preparedStatement.close(); + statement.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing PreparedStatement.", ex); + LOGGER.log(Level.SEVERE, "Error closing Statement.", ex); } } } @@ -361,4 +361,18 @@ public class EamDbUtil { return "reference_" + type.getDbTableName(); } + /** + * Close the prepared statement. + * + * @param preparedStatement The prepared statement to be closed. + * + * @deprecated Use closeStatement() instead. + * + * @throws EamDbException + */ + @Deprecated + public static void closePreparedStatement(PreparedStatement preparedStatement) { + closeStatement(preparedStatement); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java index 3bebf6d5f3..ba88798128 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamOrganization.java @@ -29,7 +29,7 @@ public class EamOrganization { private String pocEmail; private String pocPhone; - public EamOrganization( + EamOrganization( int orgID, String name, String pocName, @@ -84,7 +84,7 @@ public class EamOrganization { /** * @param orgID the orgID to set */ - public void setOrgID(int orgID) { + void setOrgID(int orgID) { this.orgID = orgID; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java new file mode 100644 index 0000000000..d7da43bbf4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/InstanceTableCallback.java @@ -0,0 +1,109 @@ +/* + * Central Repository + * + * Copyright 2015-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.centralrepository.datamodel; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * CallBack Interface to process attribute instance Table. Used in EamDb.processInstanceTable + * is called only once. The implementation of this method needs to call resultset.next to + * loop through each row of attribute instance table. + */ +public interface InstanceTableCallback { + + /** + * Process the attribute instance + * + * @param resultSet attribute instance table. + */ + void process(ResultSet resultSet); + + + /** + * + * @param resultSet attribute instance table + * @return ID of the instance + * @throws SQLException + */ + static int getId(ResultSet resultSet) throws SQLException{ + return resultSet.getInt("id"); + } + + /** + * + * @param resultSet attribute instance table + * @return Case ID of a given instance + * @throws SQLException + */ + static int getCaseId(ResultSet resultSet) throws SQLException { + return resultSet.getInt("case_id"); + } + + /** + * + * @param resultSet attribute instance table + * @return Data source id of a particular instance + * @throws SQLException + */ + static int getDataSourceId(ResultSet resultSet) throws SQLException { + return resultSet.getInt("data_source_id"); + } + + /** + * + * @param resultSet attribute instance table + * @return md5 hash value of the instance + * @throws SQLException + */ + static String getValue(ResultSet resultSet) throws SQLException { + return resultSet.getString("value"); + } + + /** + * + * @param resultSet attribute instance table + * @return file path of the instance + * @throws SQLException + */ + static String getFilePath(ResultSet resultSet) throws SQLException { + return resultSet.getString("file_path"); + } + + /** + * + * @param resultSet attribute instance table + * @return status integer based on whether instance is marked notable or not + * @throws SQLException + */ + static int getKnownStatus(ResultSet resultSet) throws SQLException { + return resultSet.getInt("known_status"); + } + + /** + * + * @param resultSet attribute instance table + * @return previous comment made for the instance + * @throws SQLException + */ + static String getComment(ResultSet resultSet) throws SQLException { + return resultSet.getString("comment"); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java index 46d579ad87..f008070cea 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java @@ -24,8 +24,6 @@ import java.sql.Statement; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.apache.commons.dbcp2.BasicDataSource; -import org.sleuthkit.autopsy.casemodule.CaseActionCancelledException; -import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; @@ -34,7 +32,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Central Repository database implementation using Postgres as a * backend */ -public class PostgresEamDb extends AbstractSqlEamDb { +final class PostgresEamDb extends AbstractSqlEamDb { private final static Logger LOGGER = Logger.getLogger(PostgresEamDb.class.getName()); @@ -116,7 +114,7 @@ public class PostgresEamDb extends AbstractSqlEamDb { String instancesTemplate = "TRUNCATE TABLE %s_instances RESTART IDENTITY CASCADE"; String referencesTemplate = "TRUNCATE TABLE reference_%s RESTART IDENTITY CASCADE"; - for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) { + for (CorrelationAttribute.Type type : defaultCorrelationTypes) { dropContent.executeUpdate(String.format(instancesTemplate, type.getDbTableName())); // FUTURE: support other reference types if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java index 37a2970082..32de4e7646 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java @@ -35,6 +35,8 @@ import org.sleuthkit.autopsy.coreutils.TextConverterException; /** * Settings for the Postgres implementation of the Central Repository database + * + * NOTE: This is public scope because the options panel calls it directly to set/get */ public final class PostgresEamDbSettings { @@ -42,7 +44,6 @@ public final class PostgresEamDbSettings { private final String DEFAULT_HOST = ""; // NON-NLS private final int DEFAULT_PORT = 5432; private final String DEFAULT_DBNAME = "central_repository"; // NON-NLS - private final int DEFAULT_BULK_THRESHHOLD = 1000; private final String DEFAULT_USERNAME = ""; private final String DEFAULT_PASSWORD = ""; private final String VALIDATION_QUERY = "SELECT version()"; // NON-NLS @@ -89,15 +90,15 @@ public final class PostgresEamDbSettings { try { String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.bulkThreshold"); // NON-NLS if (bulkThresholdString == null || bulkThresholdString.isEmpty()) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } else { this.bulkThreshold = Integer.parseInt(bulkThresholdString); if (getBulkThreshold() <= 0) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } } } catch (NumberFormatException ex) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } userName = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.user"); // NON-NLS @@ -139,7 +140,7 @@ public final class PostgresEamDbSettings { * * @return */ - public String getConnectionURL(boolean usePostgresDb) { + String getConnectionURL(boolean usePostgresDb) { StringBuilder url = new StringBuilder(); url.append(getJDBCBaseURI()); url.append(getHost()); @@ -220,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); } @@ -231,7 +232,7 @@ public final class PostgresEamDbSettings { * Use the current settings and the schema version query to test the * database schema. * - * @return true if successfull connection, else false. + * @return true if successful connection, else false. */ public boolean verifyDatabaseSchema() { Connection conn = getEphemeralConnection(false); @@ -493,7 +494,7 @@ public final class PostgresEamDbSettings { return result; } - public boolean isChanged() { + boolean isChanged() { String hostString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS String portString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.port"); // NON-NLS String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.dbName"); // NON-NLS @@ -568,7 +569,7 @@ public final class PostgresEamDbSettings { /** * @return the bulkThreshold */ - public int getBulkThreshold() { + int getBulkThreshold() { return bulkThreshold; } @@ -622,21 +623,21 @@ public final class PostgresEamDbSettings { /** * @return the VALIDATION_QUERY */ - public String getValidationQuery() { + String getValidationQuery() { return VALIDATION_QUERY; } /** * @return the POSTGRES_DRIVER */ - public String getDriver() { + String getDriver() { return JDBC_DRIVER; } /** * @return the JDBC_BASE_URI */ - public String getJDBCBaseURI() { + String getJDBCBaseURI() { return JDBC_BASE_URI; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index d9d1ea2fa3..4a3ef36530 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; -import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; @@ -38,7 +37,7 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService; * All methods in AbstractSqlEamDb that read or write to the database should * be overriden here and use appropriate locking. */ -public class SqliteEamDb extends AbstractSqlEamDb { +final class SqliteEamDb extends AbstractSqlEamDb { private final static Logger LOGGER = Logger.getLogger(SqliteEamDb.class.getName()); @@ -125,7 +124,7 @@ public class SqliteEamDb extends AbstractSqlEamDb { String instancesTemplate = "DELETE FROM %s_instances"; String referencesTemplate = "DELETE FROM global_files"; - for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) { + for (CorrelationAttribute.Type type : defaultCorrelationTypes) { dropContent.executeUpdate(String.format(instancesTemplate, type.getDbTableName())); // FUTURE: support other reference types if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) { @@ -417,8 +416,8 @@ public class SqliteEamDb extends AbstractSqlEamDb { } finally { releaseSharedLock(); } - } - + } + /** * Retrieves eamArtifact instances from the database that are associated * with the aType and filePath @@ -590,6 +589,24 @@ public class SqliteEamDb extends AbstractSqlEamDb { } } + /** + * + * 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{ + acquireSharedLock(); + return super.getArtifactInstancesKnownBad(aType); + } finally { + releaseSharedLock(); + } + } + /** * Count matching eamArtifacts instances that have knownStatus = "Bad". * @@ -662,6 +679,22 @@ public class SqliteEamDb extends AbstractSqlEamDb { } } + /** + * 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 processInstanceTable(CorrelationAttribute.Type type, InstanceTableCallback instanceTableCallback) throws EamDbException { + try { + acquireSharedLock(); + super.processInstanceTable(type, 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. @@ -708,7 +741,7 @@ public class SqliteEamDb extends AbstractSqlEamDb { * @throws EamDbException */ @Override - public long newOrganization(EamOrganization eamOrg) throws EamDbException { + public EamOrganization newOrganization(EamOrganization eamOrg) throws EamDbException { try{ acquireExclusiveLock(); return super.newOrganization(eamOrg); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java index f006cc7e24..c73fa55a0e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDbSettings.java @@ -35,13 +35,14 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** * Settings for the sqlite implementation of the Central Repository database + * + * NOTE: This is public scope because the options panel calls it directly to set/get */ public final class SqliteEamDbSettings { private final static Logger LOGGER = Logger.getLogger(SqliteEamDbSettings.class.getName()); private final String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS private final String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS - private final int DEFAULT_BULK_THRESHHOLD = 1000; private final String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS private final String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS private final String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS @@ -75,15 +76,15 @@ public final class SqliteEamDbSettings { try { String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS if (bulkThresholdString == null || bulkThresholdString.isEmpty()) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } else { this.bulkThreshold = Integer.parseInt(bulkThresholdString); if (getBulkThreshold() <= 0) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } } } catch (NumberFormatException ex) { - this.bulkThreshold = DEFAULT_BULK_THRESHHOLD; + this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD; } } @@ -162,7 +163,7 @@ public final class SqliteEamDbSettings { * * @return */ - public String getConnectionURL() { + String getConnectionURL() { StringBuilder url = new StringBuilder(); url.append(getJDBCBaseURI()); url.append(getFileNameWithPath()); @@ -439,7 +440,7 @@ public final class SqliteEamDbSettings { return result; } - public boolean isChanged() { + boolean isChanged() { String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS String dbDirectoryString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS @@ -474,14 +475,14 @@ public final class SqliteEamDbSettings { /** * @return the bulkThreshold */ - public int getBulkThreshold() { + int getBulkThreshold() { return bulkThreshold; } /** * @param bulkThreshold the bulkThreshold to set */ - public void setBulkThreshold(int bulkThreshold) throws EamDbException { + void setBulkThreshold(int bulkThreshold) throws EamDbException { if (bulkThreshold > 0) { this.bulkThreshold = bulkThreshold; } else { @@ -525,21 +526,21 @@ public final class SqliteEamDbSettings { /** * @return the DRIVER */ - public String getDriver() { + String getDriver() { return JDBC_DRIVER; } /** * @return the VALIDATION_QUERY */ - public String getValidationQuery() { + String getValidationQuery() { return VALIDATION_QUERY; } /** * @return the JDBC_BASE_URI */ - public String getJDBCBaseURI() { + String getJDBCBaseURI() { return JDBC_BASE_URI; } 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 1e98edb327..12a1ddc93d 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 72058435e6..89978f0052 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.BlackboardAttribute; import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; /** @@ -102,10 +102,14 @@ final class IngestModule implements FileIngestModule { return ProcessResult.ERROR; } - if (!EamArtifactUtil.isValidCentralRepoFile(abstractFile)) { + if (!EamArtifactUtil.isSupportedAbstractFileType(abstractFile)) { return ProcessResult.OK; } + if (abstractFile.getKnown() == TskData.FileKnown.KNOWN) { + return ProcessResult.OK; + } + EamDb dbManager; try { dbManager = EamDb.getInstance(); @@ -131,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/ingestmodule/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java index 57d4f0a098..ed36c71287 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java @@ -24,6 +24,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * Ingest job settings panel for the Correlation Engine module. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java index 5ebfd1a629..79ce921dfe 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/AddNewOrganizationDialog.java @@ -36,9 +36,10 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; /** * Dialog to add a new organization to the Central Repository database */ -public class AddNewOrganizationDialog extends javax.swing.JDialog { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +class AddNewOrganizationDialog extends javax.swing.JDialog { - private static final Logger LOGGER = Logger.getLogger(AddNewOrganizationDialog.class.getName()); + private static final Logger logger = Logger.getLogger(AddNewOrganizationDialog.class.getName()); private static final long serialVersionUID = 1L; private final Collection textBoxes; @@ -51,7 +52,7 @@ public class AddNewOrganizationDialog extends javax.swing.JDialog { * Creates new form AddNewOrganizationDialog */ @Messages({"AddNewOrganizationDialog.addNewOrg.msg=Add New Organization"}) - public AddNewOrganizationDialog() { + AddNewOrganizationDialog() { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.AddNewOrganizationDialog_addNewOrg_msg(), true); // NON-NLS @@ -65,6 +66,7 @@ public class AddNewOrganizationDialog extends javax.swing.JDialog { display(); } + // populates the dialog with existing case information to edit public AddNewOrganizationDialog(EamOrganization orgToEdit) { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.AddNewOrganizationDialog_addNewOrg_msg(), @@ -193,10 +195,19 @@ public class AddNewOrganizationDialog extends javax.swing.JDialog { } } + /** + * + * @return True if new org was added or existing org changed + */ public boolean isChanged() { return hasChanged; } + /** + * Only valid if isChanged() is true. + * + * @return Org that was added or changed. null if nothing changed + */ public EamOrganization getNewOrg() { return newOrg; } @@ -332,12 +343,12 @@ public class AddNewOrganizationDialog extends javax.swing.JDialog { try { EamDb dbManager = EamDb.getInstance(); if (organizationToEdit != null) { - //check if new name exists with ID other than the one in use here - newOrg = new EamOrganization(organizationToEdit.getOrgID(), - tfOrganizationName.getText(), - tfPocName.getText(), - tfPocEmail.getText(), - tfPocPhone.getText()); + // make a copy in case the update fails + newOrg = dbManager.getOrganizationByID(organizationToEdit.getOrgID()); + newOrg.setName(tfOrganizationName.getText()); + newOrg.setPocName(tfPocName.getText()); + newOrg.setPocEmail(tfPocEmail.getText()); + newOrg.setPocPhone(tfPocPhone.getText()); dbManager.updateOrganization(newOrg); } else { newOrg = new EamOrganization( @@ -345,13 +356,14 @@ public class AddNewOrganizationDialog extends javax.swing.JDialog { tfPocName.getText(), tfPocEmail.getText(), tfPocPhone.getText()); - newOrg.setOrgID((int)dbManager.newOrganization(newOrg)); + newOrg = dbManager.newOrganization(newOrg); } hasChanged = true; dispose(); } catch (EamDbException ex) { lbWarningMsg.setText(Bundle.AddNewOrganizationDialog_bnOk_addFailed_text()); - LOGGER.log(Level.SEVERE, "Failed adding new organization.", ex); + logger.log(Level.SEVERE, "Failed adding new organization.", ex); + newOrg = null; } }//GEN-LAST:event_bnOKActionPerformed 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/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 55ea941d33..c504f666e4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -47,9 +47,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresEamDbSettings; import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteEamDbSettings; /** - * - * @author nick + * Configuration dialog for Central Repository database settings. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class EamDbSettingsDialog extends JDialog { private static final Logger logger = Logger.getLogger(EamDbSettingsDialog.class.getName()); @@ -96,11 +96,8 @@ public class EamDbSettingsDialog extends JDialog { public boolean accept(File pathname) { if (pathname.isDirectory()) { return true; - } else if (pathname.getName().toLowerCase().equals((CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT).toLowerCase())) { - return true; - } else { - return false; } + return pathname.getName().toLowerCase().equals((CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT).toLowerCase()); } @Override 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 a470247a66..925edee795 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteEamDbSettings; /** * Main settings panel for the Central Repository */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; @@ -123,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 @@ -275,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))) @@ -291,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 @@ -300,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) @@ -328,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); @@ -342,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 @@ -373,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() { @@ -552,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; @@ -576,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/ManageOrganizationsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java index 3f3e89f5d5..26a63f03a4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageOrganizationsDialog.java @@ -37,6 +37,10 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamOrganization; import org.sleuthkit.autopsy.coreutils.Logger; +/** + * Configuration dialog to manage organizations for the Central Repository. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class ManageOrganizationsDialog extends JDialog { private static final long serialVersionUID = 1L; 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/AllDataSourcesCommonFilesAlgorithm.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java index f752fbabad..111cf4aed9 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/AllDataSourcesCommonFilesAlgorithm.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 class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilder { +final public class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilder { - 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 class AllDataSourcesCommonFilesAlgorithm extends CommonFilesMetadataBuilde * @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 */ - AllDataSourcesCommonFilesAlgorithm(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + public AllDataSourcesCommonFilesAlgorithm(Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties index 72ddffb748..a2b42155d7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -7,7 +7,7 @@ CommonFilesPanel.selectedFileCategoriesButton.text=Match on the following file c 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.commonFilesSearchLabel.text=Find files in multiple data sources in the current case. CommonFilesPanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results... CommonFilesPanel.allFileCategoriesRadioButton.text=Match on all file types CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates: diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java index 7b7b10b828..b04792fb6b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadata.java @@ -20,22 +20,24 @@ package org.sleuthkit.autopsy.commonfilesearch; import java.util.Collections; +import java.util.List; import java.util.Map; /** * Utility and wrapper model around data required for Common Files Search results. * Subclass this to implement different selections of files from the case. */ -final class CommonFilesMetadata { +final public class CommonFilesMetadata { + + private final Map> metadata; - private final Map metadata; - /** - * Create meta dat object which can be handed off to the node factories - * - * @param metadata map of md5 to parent-level node meta data + * 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) { + CommonFilesMetadata(Map> metadata){ this.metadata = metadata; } @@ -48,11 +50,11 @@ final class CommonFilesMetadata { * @param md5 key * @return */ - Md5Metadata getMetadataForMd5(String md5) { - return this.metadata.get(md5); + List getMetadataForMd5(Integer instanceCount) { + return this.metadata.get(instanceCount); } - Map getMetadata() { + public Map> getMetadata() { return Collections.unmodifiableMap(this.metadata); } @@ -60,10 +62,13 @@ final class CommonFilesMetadata { * How many distinct file instances exist for this metadata? * @return number of file instances */ - int size() { + public int size() { + int count = 0; - for (Md5Metadata data : this.metadata.values()) { - count += data.size(); + for (List data : this.metadata.values()) { + for(Md5Metadata md5 : data){ + count += md5.size(); + } } return count; } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java index d137368cfe..0272278440 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesMetadataBuilder.java @@ -27,6 +27,7 @@ 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; @@ -46,12 +47,12 @@ import org.sleuthkit.datamodel.TskCoreException; * This entire thing runs on a background thread where exceptions are handled. */ @SuppressWarnings("PMD.AbstractNaming") -abstract class CommonFilesMetadataBuilder { +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 + private static final String FILTER_BY_MIME_TYPES_WHERE_CLAUSE = " and mime_type in (%s)"; //NON-NLS // where %s is csv list of mime_types to filter on /* * The set of the MIME types that will be checked for extension mismatches @@ -198,8 +199,22 @@ abstract class CommonFilesMetadataBuilder { } } } + + Map> instanceCollatedCommonFiles = new TreeMap<>(); - return new CommonFilesMetadata(commonFiles); + for(Md5Metadata md5Metadata : commonFiles.values()){ + Integer size = md5Metadata.size(); + + if(instanceCollatedCommonFiles.containsKey(size)){ + instanceCollatedCommonFiles.get(size).add(md5Metadata); + } else { + ArrayList value = new ArrayList<>(); + value.add(md5Metadata); + instanceCollatedCommonFiles.put(size, value); + } + } + + return new CommonFilesMetadata(instanceCollatedCommonFiles); } /** @@ -228,7 +243,7 @@ abstract class CommonFilesMetadataBuilder { mimeTypeFilter.append("'").append(mimeType).append("',"); } mimeTypeString = mimeTypeFilter.toString().substring(0, mimeTypeFilter.length() - 1); - mimeTypeString = String.format(filterByMimeTypesWhereClause, new Object[]{mimeTypeString}); + mimeTypeString = String.format(FILTER_BY_MIME_TYPES_WHERE_CLAUSE, new Object[]{mimeTypeString}); } return mimeTypeString; } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java index 677d6ba8b5..a07aa59d36 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -23,20 +23,18 @@ import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.Md5Node; 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. + * results in the top right pane. Calls InstanceCountNodeFactory. */ final public class CommonFilesNode extends DisplayableItemNode { CommonFilesNode(CommonFilesMetadata metadataList) { - super(Children.create(new Md5NodeFactory(metadataList), true), Lookups.singleton(CommonFilesNode.class)); + super(Children.create(new InstanceCountNodeFactory(metadataList), true)); } @NbBundle.Messages({ @@ -60,37 +58,33 @@ final public class CommonFilesNode extends DisplayableItemNode { public String getItemType() { return getClass().getName(); } - + /** - * ChildFactory which builds CommonFileParentNodes from the - * CommonFilesMetaaData models. + * Used to generate InstanceCountNodes. */ - static class Md5NodeFactory extends ChildFactory { + static class InstanceCountNodeFactory extends ChildFactory{ + private final CommonFilesMetadata metadata; + /** - * List of models, each of which is a parent node matching a single md5, - * containing children FileNodes. + * Build a factory which converts a CommonFilesMetadata + * object into DisplayableItemNodes. + * @param metadata */ - private CommonFilesMetadata metadata; - - Md5NodeFactory(CommonFilesMetadata metadata) { + InstanceCountNodeFactory(CommonFilesMetadata metadata){ this.metadata = metadata; } - - protected void removeNotify() { - metadata = null; - } - + @Override - protected Node createNodeForKey(String md5){ - Md5Metadata metadata = this.metadata.getMetadataForMd5(md5); - return new Md5Node(metadata); - } - - @Override - protected boolean createKeys(List list) { + protected boolean createKeys(List list) { list.addAll(this.metadata.getMetadata().keySet()); return true; } + + @Override + protected Node createNodeForKey(Integer instanceCount){ + List md5Metadata = this.metadata.getMetadataForMd5(instanceCount); + return new InstanceCountNode(instanceCount, md5Metadata); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index 630efaead3..0e94e561b2 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -18,12 +18,9 @@ */ package org.sleuthkit.autopsy.commonfilesearch; -import java.io.File; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; @@ -31,10 +28,11 @@ import java.util.logging.Level; import javax.swing.ComboBoxModel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; @@ -42,8 +40,6 @@ 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.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; import org.sleuthkit.datamodel.TskCoreException; /** @@ -76,7 +72,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { initComponents(); this.setupDataSources(); - + this.errorText.setVisible(false); } @@ -97,10 +93,6 @@ public final class CommonFilesPanel extends javax.swing.JPanel { new SwingWorker, Void>() { - private static final String SELECT_DATA_SOURCES_LOGICAL = "select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; - - private static final String SELECT_DATA_SOURCES_IMAGE = "select obj_id, name from tsk_image_names where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; - private void updateUi() { String[] dataSourcesNames = new String[CommonFilesPanel.this.dataSourceMap.size()]; @@ -112,12 +104,15 @@ public final class CommonFilesPanel extends javax.swing.JPanel { CommonFilesPanel.this.selectDataSourceComboBox.setModel(CommonFilesPanel.this.dataSourcesList); boolean multipleDataSources = this.caseHasMultipleSources(); - CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(multipleDataSources); - CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(multipleDataSources); - + + CommonFilesPanel.this.allDataSourcesRadioButton.setEnabled(true); + CommonFilesPanel.this.allDataSourcesRadioButton.setSelected(true); + if (!multipleDataSources) { - CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(true); - withinDataSourceSelected(true); + CommonFilesPanel.this.withinDataSourceRadioButton.setEnabled(false); + CommonFilesPanel.this.withinDataSourceRadioButton.setSelected(false); + withinDataSourceSelected(false); + CommonFilesPanel.this.selectDataSourceComboBox.setEnabled(false); } CommonFilesPanel.this.searchButton.setEnabled(true); @@ -128,51 +123,13 @@ public final class CommonFilesPanel extends javax.swing.JPanel { } private boolean caseHasMultipleSources() { - return CommonFilesPanel.this.dataSourceMap.size() >= 2; - } - - private void loadLogicalSources(SleuthkitCase tskDb, Map dataSouceMap) throws TskCoreException, SQLException { - //try block releases resources - exceptions are handled in done() - try ( - CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_LOGICAL); - ResultSet resultSet = query.getResultSet()) { - while (resultSet.next()) { - Long objectId = resultSet.getLong(1); - String dataSourceName = resultSet.getString(2); - dataSouceMap.put(objectId, dataSourceName); - } - } - } - - private void loadImageSources(SleuthkitCase tskDb, Map dataSouceMap) throws SQLException, TskCoreException { - //try block releases resources - exceptions are handled in done() - try ( - CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_IMAGE); - ResultSet resultSet = query.getResultSet()) { - - while (resultSet.next()) { - Long objectId = resultSet.getLong(1); - String dataSourceName = resultSet.getString(2); - File image = new File(dataSourceName); - String dataSourceNameTrimmed = image.getName(); - dataSouceMap.put(objectId, dataSourceNameTrimmed); - } - } + return CommonFilesPanel.this.dataSourceMap.size() >= 3; } @Override protected Map doInBackground() throws NoCurrentCaseException, TskCoreException, SQLException { - - Map dataSouceMap = new HashMap<>(); - - Case currentCase = Case.getCurrentCaseThrows(); - SleuthkitCase tskDb = currentCase.getSleuthkitCase(); - - loadLogicalSources(tskDb, dataSouceMap); - - loadImageSources(tskDb, dataSouceMap); - - return dataSouceMap; + DataSourceLoader loader = new DataSourceLoader(); + return loader.getDataSourceMap(); } @Override @@ -212,6 +169,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { "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.", @@ -223,7 +182,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { new SwingWorker() { private String tabTitle; - + private ProgressHandle progress; + private void setTitleForAllDataSources() { this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll(); } @@ -250,7 +210,11 @@ public final class CommonFilesPanel extends javax.swing.JPanel { @Override @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) - protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException { + protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { + progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering()); + progress.start(); + progress.switchToIndeterminate(); + Long dataSourceId = determineDataSourceId(); CommonFilesMetadataBuilder builder; @@ -285,22 +249,21 @@ public final class CommonFilesPanel extends javax.swing.JPanel { protected void done() { try { super.done(); - CommonFilesMetadata metadata = get(); CommonFilesNode commonFilesNode = new CommonFilesNode(metadata); + //TODO this could be enumerating the children!!! DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonFilesPanel.this)); - TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode); + TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); + + DataResultViewerTable table = new CommonFilesSearchResultsViewerTable(); - DataResultViewerTable table = new DataResultViewerTable(); - Collection viewers = new ArrayList<>(1); viewers.add(table); - + progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay()); DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); - } catch (InterruptedException ex) { LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex); MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted()); @@ -321,6 +284,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { errorMessage = Bundle.CommonFilesPanel_search_done_exception(); } MessageNotifyUtil.Message.error(errorMessage); + } finally { + progress.finish(); } } }.execute(); @@ -590,7 +555,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { this.pictureVideoCheckbox.setEnabled(true); this.documentsCheckbox.setEnabled(true); - + this.toggleErrorTextAndSearchBox(); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java index e470fd16bc..5a3f887564 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchAction.java @@ -19,11 +19,13 @@ 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.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Encapsulates a menu action which triggers the common files search dialog. @@ -32,15 +34,22 @@ final public class CommonFilesSearchAction extends CallableSystemAction { private static CommonFilesSearchAction instance = null; private static final long serialVersionUID = 1L; - + private static final Logger logger = Logger.getLogger(CommonFilesSearchAction.class.getName()); + 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; + } catch(TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting data sources for action enabled check", ex); + } + return super.isEnabled() && shouldBeEnabled; } public static synchronized CommonFilesSearchAction getDefault() { diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java new file mode 100644 index 0000000000..cacbdd83f9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesSearchResultsViewerTable.java @@ -0,0 +1,92 @@ +/* + * + * 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; + +/** + * DataResultViewerTable which overrides the default column header + * width calculations. The CommonFilesSearchResultsViewerTable + * presents multiple tiers of data which are not always present and it may not + * make sense to try to calculate the column widths for such tables by sampling + * rows and looking for wide cells. Rather, we just pick some reasonable values. + */ +public class CommonFilesSearchResultsViewerTable extends DataResultViewerTable { + + private static final Map COLUMN_WIDTHS; + private static final long serialVersionUID = 1L; + + private static final Logger LOGGER = Logger.getLogger(CommonFilesSearchResultsViewerTable.class.getName()); + + private static final 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_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); + } + + @NbBundle.Messages({ + "CommonFilesSearchResultsViewerTable.filesColLbl=Files", + "CommonFilesSearchResultsViewerTable.instancesColLbl=Instances", + "CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path", + "CommonFilesSearchResultsViewerTable.hashsetHitsColLbl=Hash Set Hits", + "CommonFilesSearchResultsViewerTable.dataSourceColLbl=Data Source(s)", + "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/DataSourceLoader.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceLoader.java new file mode 100644 index 0000000000..69af771dad --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/DataSourceLoader.java @@ -0,0 +1,95 @@ +/* + * + * 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.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Encapsulates logic required to create a mapping of data sources in the + * current case to their data source IDs. + * + * Intended to be used within the context of a SwingWorker or other background + * thread. + */ +public class DataSourceLoader { + + private static final String SELECT_DATA_SOURCES_LOGICAL = "select obj_id, name from tsk_files where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; + + private static final String SELECT_DATA_SOURCES_IMAGE = "select obj_id, name from tsk_image_names where obj_id in (SELECT obj_id FROM tsk_objects WHERE obj_id in (select obj_id from data_source_info))"; + + private void loadLogicalSources(SleuthkitCase tskDb, Map dataSouceMap) throws TskCoreException, SQLException { + //try block releases resources - exceptions are handled in done() + try ( + SleuthkitCase.CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_LOGICAL); + ResultSet resultSet = query.getResultSet() + ) { + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String dataSourceName = resultSet.getString(2); + dataSouceMap.put(objectId, dataSourceName); + } + } + } + + private void loadImageSources(SleuthkitCase tskDb, Map dataSouceMap) throws SQLException, TskCoreException { + //try block releases resources - exceptions are handled in done() + try ( + SleuthkitCase.CaseDbQuery query = tskDb.executeQuery(SELECT_DATA_SOURCES_IMAGE); + ResultSet resultSet = query.getResultSet()) { + + while (resultSet.next()) { + Long objectId = resultSet.getLong(1); + String dataSourceName = resultSet.getString(2); + File image = new File(dataSourceName); + String dataSourceNameTrimmed = image.getName(); + dataSouceMap.put(objectId, dataSourceNameTrimmed); + } + } + } + + /** + * Get a map of data source Ids to their string names for the current case. + * + * @return Map of Long (id) to String (name) + * @throws NoCurrentCaseException + * @throws TskCoreException + * @throws SQLException + */ + public Map getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException { + Map dataSouceMap = new HashMap<>(); + + Case currentCase = Case.getCurrentCaseThrows(); + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + + loadLogicalSources(tskDb, dataSouceMap); + + loadImageSources(tskDb, dataSouceMap); + + return dataSouceMap; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java new file mode 100644 index 0000000000..07c87035e1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/FileInstanceNode.java @@ -0,0 +1,88 @@ +/* + * 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.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Used by the Common Files search feature to encapsulate instances of a given + MD5s matched in the search. These nodes will be children of Md5Nodes. + */ +public class FileInstanceNode extends FileNode { + + 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 FileInstanceNode(AbstractFile fsContent, String dataSource) { + super(fsContent); + this.dataSource = dataSource; + + this.setDisplayName(fsContent.getName()); + } + + @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); + } + + String getDataSource() { + return this.dataSource; + } + + @NbBundle.Messages({"FileInstanceNode.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.FileInstanceNode_createSheet_noDescription(); + + 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, getHashSetHitsForFile(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/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java new file mode 100644 index 0000000000..987e6faa08 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/InstanceCountNode.java @@ -0,0 +1,146 @@ +/* + * + * 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 List metadataList; + + /** + * Create a node with the given number of instances, and the given + * selection of metadata. + * @param instanceCount + * @param md5Metadata + */ + @NbBundle.Messages({ + "InstanceCountNode.displayName=Files with %s instances (%s)" + }) + public InstanceCountNode(int instanceCount, List md5Metadata) { + super(Children.create(new Md5NodeFactory(md5Metadata), true)); + + this.instanceCount = instanceCount; + this.metadataList = md5Metadata; + + this.setDisplayName(String.format(Bundle.InstanceCountNode_displayName(), Integer.toString(instanceCount), md5Metadata.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; + } + + /** + * Get a list of metadata for the MD5s which are children of this object. + * @return List + */ + List getMetadata() { + return Collections.unmodifiableList(this.metadataList); + } + + @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 Md5NodeFactory extends ChildFactory { + + /** + * List of models, each of which is a parent node matching a single md5, + * containing children FileNodes. + */ + private final Map metadata; + + Md5NodeFactory(List metadata) { + this.metadata = new HashMap<>(); + + Iterator iterator = metadata.iterator(); + while (iterator.hasNext()) { + Md5Metadata md5Metadata = iterator.next(); + this.metadata.put(md5Metadata.getMd5(), md5Metadata); + } + } + + @Override + protected Node createNodeForKey(String md5) { + Md5Metadata md5Metadata = this.metadata.get(md5); + return new Md5Node(md5Metadata); + } + + @Override + protected boolean createKeys(List list) { + list.addAll(this.metadata.keySet()); + return true; + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java similarity index 63% rename from Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java rename to Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java index 96de7eba36..0b10d73e30 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Md5Node.java @@ -17,23 +17,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.commonfilesearch; -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.openide.util.lookup.Lookups; 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.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -52,30 +50,50 @@ public class Md5Node extends DisplayableItemNode { private final int commonFileCount; 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 Md5Node(Md5Metadata data) { super(Children.create( - new FileInstanceNodeFactory(data), true), - Lookups.singleton(data.getMd5())); + new FileInstanceNodeFactory(data), true)); this.commonFileCount = data.size(); this.dataSources = String.join(", ", data.getDataSources()); this.md5Hash = data.getMd5(); - - this.setDisplayName(this.md5Hash); + + this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.md5Hash)); + 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; } + /** + * 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 getMd5() { return this.md5Hash; } + @NbBundle.Messages({"Md5Node.createSheet.noDescription= "}) @Override protected Sheet createSheet() { Sheet sheet = new Sheet(); @@ -85,30 +103,15 @@ public class Md5Node extends DisplayableItemNode { 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))); - } + 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; } - /** - * 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.File.toString(), node.getMd5()); - map.put(CommonFileParentPropertyType.InstanceCount.toString(), node.getCommonFileCount()); - map.put(CommonFileParentPropertyType.DataSource.toString(), node.getDataSources()); - } @Override public T accept(DisplayableItemNodeVisitor visitor) { @@ -144,9 +147,7 @@ public class Md5Node extends DisplayableItemNode { AbstractFile abstractFile = tskDb.findAllFilesWhere(String.format("obj_id in (%s)", file.getObjectId())).get(0); return new FileInstanceNode(abstractFile, file.getDataSourceName()); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, String.format("Unable to create node for file with obj_id: %s.", new Object[]{file.getObjectId()}), ex); - } catch (TskCoreException ex) { + } 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; @@ -159,25 +160,4 @@ public class Md5Node extends DisplayableItemNode { } } - @NbBundle.Messages({ - "CommonFileParentPropertyType.fileColLbl=File", - "CommonFileParentPropertyType.instanceColLbl=Instance Count", - "CommonFileParentPropertyType.dataSourceColLbl=Data Source"}) - public enum CommonFileParentPropertyType { - - File(Bundle.CommonFileParentPropertyType_fileColLbl()), - InstanceCount(Bundle.CommonFileParentPropertyType_instanceColLbl()), - DataSource(Bundle.CommonFileParentPropertyType_dataSourceColLbl()); - - final private String displayString; - - private CommonFileParentPropertyType(String displayString) { - this.displayString = displayString; - } - - @Override - public String toString() { - return displayString; - } - } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java index bc506f5988..f22715960f 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/SingleDataSource.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 class SingleDataSource extends CommonFilesMetadataBuilder { +final public class SingleDataSource extends CommonFilesMetadataBuilder { - 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; @@ -35,10 +36,12 @@ final class SingleDataSource extends CommonFilesMetadataBuilder { * once in the given data source * @param dataSourceId data source id for which common files must appear at least once * @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 + * @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 */ - SingleDataSource(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { + public SingleDataSource(Long dataSourceId, Map dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType) { super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType); this.selectedDataSourceId = dataSourceId; this.dataSourceName = dataSourceIdMap.get(this.selectedDataSourceId); diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java index a0417855fe..006fbd846f 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountsBrowser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,6 +48,7 @@ import org.sleuthkit.datamodel.TskCoreException; * CVTTopComponent when this tab is active allowing for context sensitive * actions to work correctly. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AccountsBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index 09052d9d9e..1e1004de25 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; @TopComponent.Registration(mode = "cvt", openAtStartup = false) @RetainLocation("cvt") @NbBundle.Messages("CVTTopComponent.name= Communications Visualization") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class CVTTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 266c0d91f2..81ffcb4805 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +57,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Panel that holds the Filter control widgets and triggers queries against the * CommunicationsManager on user filtering changes. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final public class FiltersPanel extends JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index dca1770463..339e842280 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; * messages and other account details, and a ContentViewer to show individual * messages. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class MessageBrowser extends JPanel implements ExplorerManager.Provider, Lookup.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index eb33b557e2..8f5e6e5c94 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -112,6 +112,7 @@ import org.sleuthkit.datamodel.TskCoreException; * CVTTopComponent when this tab is active allowing for context sensitive * actions to work correctly. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final public class VisualizationPanel extends JPanel implements Lookup.Provider { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index c6bd24f38e..f2abc866da 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -75,3 +75,4 @@ SQLiteViewer.jLabel2.text=Page SQLiteViewer.numEntriesField.text=num Entries SQLiteViewer.jLabel1.text=Table PListViewer.exportButton.text=Export +SQLiteViewer.exportCsvButton.text=Export to CSV diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java index cc5e2008b5..07736120c7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FXVideoPanel.java @@ -76,6 +76,7 @@ import org.sleuthkit.datamodel.TskData; @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class FXVideoPanel extends MediaViewVideoPanel { // Refer to https://docs.oracle.com/javafx/2/api/javafx/scene/media/package-summary.html diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index 74d2ae1927..516ea11df2 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -37,6 +37,7 @@ import org.sleuthkit.datamodel.AbstractFile; * for pictures, video, etc. */ @ServiceProvider(service = DataContentViewer.class, position = 3) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class FileViewer extends javax.swing.JPanel implements DataContentViewer { private static final int CONFIDENCE_LEVEL = 5; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java index 5ebd894517..a1ca715a19 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/GstVideoPanel.java @@ -69,6 +69,7 @@ import org.sleuthkit.datamodel.TskData; @ServiceProviders(value = { @ServiceProvider(service = FrameCapture.class) }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class GstVideoPanel extends MediaViewVideoPanel { private static final String[] EXTENSIONS = new String[]{".mov", ".m4v", ".flv", ".mp4", ".3gp", ".avi", ".mpg", ".mpeg", ".wmv"}; //NON-NLS @@ -365,7 +366,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { playbin.getState(); if (!playbin.seek(timeStamp, unit)) { - logger.log(Level.INFO, "There was a problem seeking to " + timeStamp + " " + unit.name().toLowerCase()); //NON-NLS + logger.log(Level.INFO, "There was a problem seeking to {0} {1}", new Object[]{timeStamp, unit.name().toLowerCase()}); //NON-NLS } ret = playbin.play(); @@ -395,7 +396,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { } if (image == null) { - logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file " + file.getName()); //NON-NLS + logger.log(Level.WARNING, "There was a problem while trying to capture a frame from file {0}", file.getName()); //NON-NLS badVideoFiles.add(file.getName()); break; } @@ -672,7 +673,7 @@ public class GstVideoPanel extends MediaViewVideoPanel { try { get(); } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.WARNING, "Error updating video progress: " + ex.getMessage()); //NON-NLS + logger.log(Level.WARNING, "Error updating video progress: {0}", ex.getMessage()); //NON-NLS infoLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.infoLabel.updateErr", ex.getMessage())); } // catch and ignore if we were cancelled diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 92da0733d8..963e3f8b39 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -30,6 +30,7 @@ import org.sleuthkit.datamodel.AbstractFile; /** * Media content viewer for videos, sounds and images. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { private static final Logger LOGGER = Logger.getLogger(MediaFileViewer.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index b04473990c..da526803a8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -48,7 +48,6 @@ import org.openide.util.NbBundle; import org.python.google.common.collect.Lists; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.ImageUtils; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.datamodel.AbstractFile; @@ -60,12 +59,11 @@ import org.sleuthkit.datamodel.AbstractFile; @NbBundle.Messages({"MediaViewImagePanel.externalViewerButton.text=Open in External Viewer", "MediaViewImagePanel.errorLabel.text=Could not load file into Media View.", "MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory."}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel { private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm()); - private static final Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName()); - private final boolean fxInited; private JFXPanel fxPanel; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index 62b9996f22..8f08b248fd 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -71,6 +71,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Shows SMS/MMS/EMail messages */ @ServiceProvider(service = DataContentViewer.class, position = 5) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class MessageContentViewer extends javax.swing.JPanel implements DataContentViewer { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java index 1bfa05d5b8..03a84d4181 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Metadata.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2014 Basis Technology Corp. + * Copyright 2013-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.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; * different order and allows the full path to be visible in the bottom area. */ @ServiceProvider(service = DataContentViewer.class, position = 6) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class Metadata extends javax.swing.JPanel implements DataContentViewer { /** @@ -222,7 +223,6 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer { @Override public void resetComponent() { setText(""); - return; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java index 6478114314..506084f0c3 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PListViewer.java @@ -63,11 +63,12 @@ import org.xml.sax.SAXException; * PListViewer - a file viewer for binary plist files. * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider { private static final long serialVersionUID = 1L; private static final String[] MIMETYPES = new String[]{"application/x-bplist"}; - private static final Logger LOGGER = Logger.getLogger(PListViewer.class.getName()); + private static final Logger logger = Logger.getLogger(PListViewer.class.getName()); private final org.openide.explorer.view.OutlineView outlineView; private final Outline outline; @@ -198,7 +199,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer Bundle.PListViewer_ExportFailed_message(), JOptionPane.ERROR_MESSAGE); - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; } @@ -227,7 +228,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer Bundle.PListViewer_ExportFailed_message(), JOptionPane.ERROR_MESSAGE); - LOGGER.log(Level.SEVERE, "Error exporting plist to XML file " + selectedFile.getName(), ex); + logger.log(Level.SEVERE, "Error exporting plist to XML file " + selectedFile.getName(), ex); } } }//GEN-LAST:event_exportButtonActionPerformed @@ -305,7 +306,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer setColumnWidths(); }); } catch (InterruptedException ex) { - LOGGER.log(Level.SEVERE, "Interruption while parsing/dislaying plist file " + plistFile.getName(), ex); + logger.log(Level.SEVERE, "Interruption while parsing/dislaying plist file " + plistFile.getName(), ex); JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getMessage(), @@ -313,7 +314,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer JOptionPane.ERROR_MESSAGE); } catch (ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Exception while parsing/dislaying plist file " + plistFile.getName(), ex); + logger.log(Level.SEVERE, "Exception while parsing/dislaying plist file " + plistFile.getName(), ex); JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage(), Bundle.PListViewer_processPlist_errorMessage(), @@ -407,7 +408,7 @@ class PListViewer extends javax.swing.JPanel implements FileTypeViewer, Explorer pkv.setChildren(children.toArray(new PropKeyValue[children.size()])); return pkv; } else { - LOGGER.log(Level.SEVERE, "Can''t parse Plist for key = {0} value of type {1}", new Object[]{key, value.getClass()}); + logger.log(Level.SEVERE, "Can''t parse Plist for key = {0} value of type {1}", new Object[]{key, value.getClass()}); } return null; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java index 77c0814c55..7861a7e05d 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteTableView.java @@ -49,6 +49,7 @@ import org.openide.util.actions.Presenter; /** * Panel to display a SQLite table */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SQLiteTableView extends JPanel implements ExplorerManager.Provider { private final org.openide.explorer.view.OutlineView outlineView; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form index 0469da7b73..d1629bdd2c 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form @@ -16,7 +16,7 @@ - + @@ -60,7 +60,9 @@ - + + + @@ -69,6 +71,7 @@ + @@ -199,6 +202,16 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java index 250b74f36a..40a667e3d7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -22,6 +22,7 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Cursor; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; @@ -39,6 +40,10 @@ import java.util.Objects; import java.util.TreeMap; import java.util.logging.Level; import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; @@ -55,6 +60,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * A file content viewer for SQLite database files. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { private static final long serialVersionUID = 1L; @@ -95,6 +101,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { numPagesLabel = new javax.swing.JLabel(); prevPageButton = new javax.swing.JButton(); nextPageButton = new javax.swing.JButton(); + exportCsvButton = new javax.swing.JButton(); jTableDataPanel = new javax.swing.JPanel(); jHdrPanel.setPreferredSize(new java.awt.Dimension(536, 40)); @@ -146,6 +153,13 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { } }); + org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.exportCsvButton.text")); // NOI18N + exportCsvButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + exportCsvButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel); jHdrPanel.setLayout(jHdrPanelLayout); jHdrPanelLayout.setHorizontalGroup( @@ -169,13 +183,16 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(133, Short.MAX_VALUE)) + .addGap(29, 29, 29) + .addComponent(exportCsvButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); jHdrPanelLayout.setVerticalGroup( jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jHdrPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(exportCsvButton) .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) @@ -195,7 +212,7 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE) .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( @@ -249,9 +266,50 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_tablesDropdownListActionPerformed + /** + * The action when the Export Csv button is pressed. The file chooser window will pop + * up to choose where the user wants to save the csv file. The default location is case export directory. + * + * @param evt the action event + */ + + @NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.", + "SQLiteViewer.csvExport.title=Export to csv file", + "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"}) + private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed + Case openCase = Case.getCurrentCase(); + File caseDirectory = new File(openCase.getExportDirectory()); + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDragEnabled(false); + fileChooser.setCurrentDirectory(caseDirectory); + //Set a filter to let the filechooser only work for csv files + FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("*.csv", "csv"); + fileChooser.addChoosableFileFilter(csvFilter); + 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) { + File file = fileChooser.getSelectedFile(); + 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)) { + } else { + return; + } + } + + exportTableToCsv(file); + } + }//GEN-LAST:event_exportCsvButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel currPageLabel; + private javax.swing.JButton exportCsvButton; private javax.swing.JPanel jHdrPanel; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; @@ -436,9 +494,11 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { prevPageButton.setEnabled(false); if (numRows > 0) { + exportCsvButton.setEnabled(true); nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE))); readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE); } else { + exportCsvButton.setEnabled(false); nextPageButton.setEnabled(false); selectedTableView.setupTable(Collections.emptyList()); } @@ -495,4 +555,67 @@ class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { return rowlist; } + + @NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.", + "SQLiteViewer.exportTableToCsv.FileName=File name: ", + "SQLiteViewer.exportTableToCsv.TableName=Table name: " + }) + private void exportTableToCsv(File file) { + String tableName = (String) this.tablesDropdownList.getSelectedItem(); + try ( + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM " + tableName)) { + List> currentTableRows = resultSetToArrayList(resultSet); + + 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 { + File csvFile; + String fileName = file.getName(); + if (FilenameUtils.getExtension(fileName).equalsIgnoreCase("csv")) { + csvFile = file; + } else { + csvFile = new File(file.toString() + ".csv"); + } + + try (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()); + + 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) { + logger.log(Level.SEVERE, String.format("Failed to read table %s from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), sqliteDbFile.getId()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName)); + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Failed to export table %s to file '%s'", tableName, file.getName()), ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText()); + } + } + + } diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 63ac880171..e45055b077 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.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"); @@ -206,7 +206,7 @@ public class Installer extends ModuleInstall { // Prevent the Autopsy UI from shrinking on high DPI displays System.setProperty("sun.java2d.dpiaware", "false"); System.setProperty("prism.allowhidpi", "false"); - + // Update existing configuration in case of unsupported settings updateConfig(); @@ -218,16 +218,16 @@ public class Installer extends ModuleInstall { packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault()); } - + /** * If the mode in the configuration file is 'REVIEW' (2, now invalid), this * method will set it to 'STANDALONE' (0) and disable auto ingest. */ private void updateConfig() { String mode = ModuleSettings.getConfigSetting(SETTINGS_PROPERTIES, "AutopsyMode"); - if(mode != null) { + if (mode != null) { int ordinal = Integer.parseInt(mode); - if(ordinal > 1) { + if (ordinal > 1) { UserPreferences.setMode(UserPreferences.SelectedMode.STANDALONE); ModuleSettings.setConfigSetting(UserPreferences.SETTINGS_PROPERTIES, "JoinAutoModeCluster", Boolean.toString(false)); } @@ -268,6 +268,19 @@ public class Installer extends ModuleInstall { } } + /** + * Make a folder in the config directory for object detection classifiers if one does not + * exist. + */ + private static void ensureClassifierFolderExists() { + File objectDetectionClassifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath()); + objectDetectionClassifierDir.mkdir(); + } + + /** + * Make a folder in the config directory for Python Modules if one does not + * exist. + */ private static void ensurePythonModulesFolderExists() { File pythonModulesDir = new File(PlatformUtil.getUserPythonModulesPath()); pythonModulesDir.mkdir(); @@ -277,6 +290,7 @@ public class Installer extends ModuleInstall { public void restored() { super.restored(); ensurePythonModulesFolderExists(); + ensureClassifierFolderExists(); initJavaFx(); for (ModuleInstall mi : packageInstallers) { try { 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/AboutWindowPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java index 9506058b66..a7f8d2c095 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AboutWindowPanel.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"); @@ -19,14 +19,12 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Cursor; -import java.awt.Image; import java.awt.Window; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.Locale; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JPanel; @@ -45,6 +43,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI; /** * Custom "About" window panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AboutWindowPanel extends JPanel implements HyperlinkListener { private static final long serialVersionUID = 1L; @@ -212,7 +211,7 @@ private void logoLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:e private void showUrl() { if (url != null) { org.openide.awt.StatusDisplayer.getDefault().setStatusText( - NbBundle.getBundle(HTMLViewAction.class).getString("CTL_OpeningBrowser")); //NON-NLS + NbBundle.getMessage(HTMLViewAction.class, "CTL_OpeningBrowser")); //NON-NLS HtmlBrowser.URLDisplayer.getDefault().showURL(url); } } 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/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index de9a8b8e69..6c5a67a3f7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -66,7 +66,7 @@ import org.sleuthkit.autopsy.report.ReportBranding; "AutopsyOptionsPanel.agencyLogoPathFieldValidationLabel.pathNotSet.text=Agency logo path must be set.", "AutopsyOptionsPanel.logNumAlert.invalidInput.text=A positive integer is required here." }) - +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; 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/CriterionChooser.java b/Core/src/org/sleuthkit/autopsy/corecomponents/CriterionChooser.java index d23091c007..29dcdbd7e8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/CriterionChooser.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/CriterionChooser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterio /** * A Gui for choosing a SortCriterion from a list of available properties. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class CriterionChooser extends javax.swing.JPanel { private DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java index 9b94c9a7b5..642ba5f4b0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentPanel.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"); @@ -38,8 +38,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Data content panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentPanel extends javax.swing.JPanel implements DataContent, ChangeListener { private static Logger logger = Logger.getLogger(DataContentPanel.class.getName()); @@ -241,7 +242,7 @@ public class DataContentPanel extends javax.swing.JPanel implements DataContent, private static class UpdateWrapper { - private DataContentViewer wrapped; + private final DataContentViewer wrapped; private boolean outdated; UpdateWrapper(DataContentViewer wrapped) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java index 6331d67023..af015d0b4a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentTopComponent.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; //@TopComponent.Description(preferredID = "DataContentTopComponent") //@TopComponent.Registration(mode = "output", openAtStartup = true) //@TopComponent.OpenActionRegistration(displayName = "#CTL_DataContentAction", preferredID = "DataContentTopComponent") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataContentTopComponent extends TopComponent implements DataContent, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataContentTopComponent.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index e2775df40e..2cccd31428 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.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"); @@ -63,6 +63,7 @@ import org.netbeans.swing.etable.ETable; * in a JTable representation of its BlackboardAttributes. */ @ServiceProvider(service = DataContentViewer.class, position = 7) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentViewerArtifact extends javax.swing.JPanel implements DataContentViewer { @NbBundle.Messages({ @@ -484,7 +485,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat if ((artifact == null) || (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_INTERESTING_FILE_HIT.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/DataContentViewerHex.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java index 2b75990d68..6639856099 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerHex.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"); @@ -27,7 +27,6 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -import javax.swing.JTextPane; import javax.swing.text.BadLocationException; import javax.swing.text.Utilities; import org.openide.nodes.Node; @@ -40,6 +39,7 @@ import org.sleuthkit.datamodel.TskException; /** * Hex view of file contents. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @ServiceProvider(service = DataContentViewer.class, position = 1) public class DataContentViewerHex extends javax.swing.JPanel implements DataContentViewer { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java index ff48a678f2..bc0980c58c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerString.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"); @@ -28,9 +28,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -import javax.swing.JTextPane; import org.openide.nodes.Node; -import org.openide.util.Lookup; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.StringExtract; @@ -44,11 +42,12 @@ import org.sleuthkit.datamodel.TskException; * Viewer displays strings extracted from contents. */ @ServiceProvider(service = DataContentViewer.class, position = 2) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentViewerString extends javax.swing.JPanel implements DataContentViewer { private static long currentOffset = 0; - private static final long pageLength = 16384; - private final byte[] data = new byte[(int) pageLength]; + private static final long PAGE_LENGTH = 16384; + private final byte[] data = new byte[(int) PAGE_LENGTH]; private static int currentPage = 1; private Content dataSource; //string extract utility @@ -62,7 +61,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC initComponents(); customizeComponents(); this.resetComponent(); - logger.log(Level.INFO, "Created StringView instance: " + this); //NON-NLS + logger.log(Level.INFO, "Created StringView instance: {0}", this); //NON-NLS } private void customizeComponents() { @@ -264,7 +263,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed //@@@ this is part of the code dealing with the data viewer. could be copied/removed to implement the scrollbar - currentOffset -= pageLength; + currentOffset -= PAGE_LENGTH; currentPage = currentPage - 1; currentPageLabel.setText(Integer.toString(currentPage)); setDataView(dataSource, currentOffset); @@ -272,7 +271,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed //@@@ this is part of the code dealing with the data viewer. could be copied/removed to implement the scrollbar - currentOffset += pageLength; + currentOffset += PAGE_LENGTH; currentPage = currentPage + 1; currentPageLabel.setText(Integer.toString(currentPage)); setDataView(dataSource, currentOffset); @@ -281,7 +280,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC private void goToPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToPageTextFieldActionPerformed String pageNumberStr = goToPageTextField.getText(); int pageNumber; - int maxPage = Math.round((dataSource.getSize() - 1) / pageLength) + 1; + int maxPage = Math.round((dataSource.getSize() - 1) / PAGE_LENGTH) + 1; try { pageNumber = Integer.parseInt(pageNumberStr); } catch (NumberFormatException ex) { @@ -297,7 +296,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC JOptionPane.WARNING_MESSAGE); return; } - currentOffset = (pageNumber - 1) * pageLength; + currentOffset = (pageNumber - 1) * PAGE_LENGTH; currentPage = pageNumber; currentPageLabel.setText(Integer.toString(currentPage)); setDataView(dataSource, currentOffset); @@ -352,11 +351,11 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC String text = ""; if (dataSource.getSize() > 0) { try { - bytesRead = dataSource.read(data, offset, pageLength); // read the data + bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data } catch (TskException ex) { text = NbBundle.getMessage(this.getClass(), "DataContentViewerString.setDataView.errorText", currentOffset, - currentOffset + pageLength); + currentOffset + PAGE_LENGTH); logger.log(Level.WARNING, "Error while trying to show the String content.", ex); //NON-NLS } } @@ -370,15 +369,15 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC if (text.trim().isEmpty()) { text = NbBundle.getMessage(this.getClass(), "DataContentViewerString.setDataView.errorNoText", currentOffset, - currentOffset + pageLength); + currentOffset + PAGE_LENGTH); } } else { text = NbBundle.getMessage(this.getClass(), "DataContentViewerString.setDataView.errorText", currentOffset, - currentOffset + pageLength); + currentOffset + PAGE_LENGTH); } // disable or enable the next button - if (offset + pageLength < dataSource.getSize()) { + if (offset + PAGE_LENGTH < dataSource.getSize()) { nextPageButton.setEnabled(true); } else { nextPageButton.setEnabled(false); @@ -391,7 +390,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC prevPageButton.setEnabled(true); } - int totalPage = Math.round((dataSource.getSize() - 1) / pageLength) + 1; + int totalPage = Math.round((dataSource.getSize() - 1) / PAGE_LENGTH) + 1; totalPageLabel.setText(Integer.toString(totalPage)); currentPageLabel.setText(Integer.toString(currentPage)); outputViewPane.setText(text); // set the output view @@ -500,11 +499,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC return false; } Content content = node.getLookup().lookup(Content.class); - if (content != null && content.getSize() > 0) { - return true; - } - - return false; + return (content != null && content.getSize() > 0); } @Override 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 4aa7a63117..ced154edf7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -72,6 +72,7 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; * (DataContentTopComponent) that is normally docked into the lower right hand * side of the main application window, or it could be a custom content view. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { private static final long serialVersionUID = 1L; @@ -451,7 +452,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } } } - }; + } if (tabToSelect == NO_TAB_SELECTED) { tabToSelect = resultViewerTabs.getSelectedIndex(); if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index dc88be9fee..29e289d67e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -68,6 +68,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * viewers to the actions global context. */ @RetainLocation("editor") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 1089bc62f9..6ccf1a3c46 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -77,7 +77,8 @@ import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; * ancestor top component's explorer manager at runtime. */ @ServiceProvider(service = DataResultViewer.class) -public final class DataResultViewerTable extends AbstractDataResultViewer { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName()); @@ -143,7 +144,6 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { outline = outlineView.getOutline(); outline.setRowSelectionAllowed(true); - outline.setColumnSelectionAllowed(true); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); outline.setRootVisible(false); outline.setDragEnabled(false); @@ -179,6 +179,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { /** * Gets the title of this tabular result viewer. + * @return title of tab. */ @Override @NbBundle.Messages("DataResultViewerTable.title=Table") @@ -364,7 +365,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) { @@ -400,6 +401,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 diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index c3f1d19b64..ea4f4d3136 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -71,6 +71,7 @@ import org.sleuthkit.datamodel.TskCoreException; * ancestor top component's explorer manager at runtime. */ @ServiceProvider(service = DataResultViewer.class) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; @@ -372,10 +373,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public boolean isSupported(Node selectedNode) { - if (selectedNode == null) { - return false; - } - return true; + return (selectedNode != null); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java new file mode 100755 index 0000000000..4c49490373 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/GSTVideoPanel.java @@ -0,0 +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.corecomponents; + +/** + * 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. + */ +@Deprecated +public class GSTVideoPanel { + +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java index 20948ac77d..97a645da3c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.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"); @@ -43,6 +43,10 @@ import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +/** + * Configuration panel for multi-user settings. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class MultiUserSettingsPanel extends javax.swing.JPanel { private static final String HOST_NAME_OR_IP_PROMPT = NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbDbHostname.toolTipText"); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java b/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java index e06d61d778..3981ac63b9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/OfflineHelpAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,8 +52,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; @Messages("CTL_OfflineHelpAction=Offline Autopsy Documentation") public final class OfflineHelpAction implements ActionListener { - private URI uri; - private static final Logger Logger + private static final Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(AboutWindowPanel.class.getName()); @Override @@ -71,7 +70,8 @@ public final class OfflineHelpAction implements ActionListener { String fileForHelp = ""; String indexForHelp = ""; String currentDirectory = ""; - + URI uri = null; + try { // Match the form: file:///C:/some/directory/AutopsyXYZ/docs/index.html fileForHelp = NbBundle.getMessage(OfflineHelpAction.class, "FILE_FOR_LOCAL_HELP"); @@ -79,7 +79,7 @@ public final class OfflineHelpAction implements ActionListener { currentDirectory = System.getProperty("user.dir").replace("\\", "/").replace(" ", "%20"); //NON-NLS uri = new URI(fileForHelp + currentDirectory + indexForHelp); } catch (Exception ex) { - Logger.log(Level.SEVERE, "Unable to load Offline Documentation: " + logger.log(Level.SEVERE, "Unable to load Offline Documentation: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } if (uri != null) { @@ -89,7 +89,7 @@ public final class OfflineHelpAction implements ActionListener { try { desktop.browse(uri); } catch (IOException ex) { - Logger.log(Level.SEVERE, "Unable to launch the system browser: " + logger.log(Level.SEVERE, "Unable to launch the system browser: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } } else { @@ -98,7 +98,7 @@ public final class OfflineHelpAction implements ActionListener { try { HtmlBrowser.URLDisplayer.getDefault().showURL(uri.toURL()); } catch (MalformedURLException ex) { - Logger.log(Level.SEVERE, "Unable to launch the built-in browser: " + logger.log(Level.SEVERE, "Unable to launch the built-in browser: " + fileForHelp + currentDirectory + indexForHelp, ex); //NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java b/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java index 2c73a50a67..8c7bda3e6e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/SortChooser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; * A dialog that allows the user to choose sort criteria for the thumbnail * viewer. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class SortChooser extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java index 7766098437..d5715845a3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildren.java @@ -23,29 +23,28 @@ import org.openide.nodes.FilterNode; import org.openide.nodes.Node; /** - * 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. + * 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 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. + 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 TableFilterNode. + * @param wrappedNode The node wrapped by the TableFilterNode. * @param createChildren True if a children (child factory) object should be - * created for the wrapped node. + * created for the wrapped node. * - * @return A children (child factory) object for a node wrapped by a - * TableFilterNode. + * @return A children (child factory) object for a node wrapped by a TableFilterNode. */ public static Children createInstance(Node wrappedNode, boolean createChildren) { - + if (createChildren) { return new TableFilterChildren(wrappedNode); } else { @@ -54,9 +53,10 @@ class TableFilterChildren extends FilterNode.Children { } /** - * 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 + * 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 TableFilterNode. @@ -64,10 +64,10 @@ class TableFilterChildren extends FilterNode.Children { TableFilterChildren(Node wrappedNode) { super(wrappedNode); } - + /** - * Copies a TableFilterNode, 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 TableFilterNode to copy. * @@ -90,5 +90,4 @@ class TableFilterChildren extends FilterNode.Children { protected Node[] createNodes(Node key) { return new Node[]{this.copyNode(key)}; } - } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java index 5a5c865bfc..6f5f32b247 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterChildrenWithDescendants.java @@ -27,21 +27,39 @@ import org.openide.nodes.Node; * of rows (plus/minus buttons for each row with children). */ final class TableFilterChildrenWithDescendants extends TableFilterChildren { - - private TableFilterChildrenWithDescendants(Node wrappedNode) { + + 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){ - if(createChildren){ - return new TableFilterChildrenWithDescendants(wrappedNode); - } else { + /** + * 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 { + return new TableFilterChildrenWithDescendants(wrappedNode, childLayerDepth - 1); } } @Override protected Node copyNode(Node nodeToCopy){ - return new TableFilterNode(nodeToCopy, true, true); - } + 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 879f625b6e..eb36cf2e87 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/TableFilterNode.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.directorytree.DataResultFilterNode; public class TableFilterNode extends FilterNode { private final boolean createChildren; - private boolean forceUseWrappedDisplayName = false; + private final boolean forceUseWrappedDisplayName; private String columnOrderKey = "NONE"; /** @@ -48,34 +48,8 @@ public class TableFilterNode extends FilterNode { */ public TableFilterNode(Node node, boolean createChildren) { super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); - this.createChildren = createChildren; - } - - /** - * 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 TableFilterNode(Node node) { - super(node, TableFilterChildrenWithDescendants.createInstance(node, true), Lookups.proxy(node)); - this.createChildren = true; this.forceUseWrappedDisplayName = false; - } - - /** - * To be used in TableFilterChildrenWithDescendants. - * - * @param node node to wrap - * @param createChildren node has children? - * @param forceUseWrappedDisplayName allow use of custom getDisplayName() . - */ - TableFilterNode(Node node, boolean createChildren, boolean forceUseWrappedDisplayName) { - super(node, TableFilterChildren.createInstance(node, createChildren), Lookups.proxy(node)); this.createChildren = createChildren; - this.forceUseWrappedDisplayName = forceUseWrappedDisplayName; } /** @@ -92,9 +66,16 @@ public class TableFilterNode extends FilterNode { */ public TableFilterNode(Node node, boolean createChildren, String columnOrderKey) { super(node, TableFilterChildren.createInstance(node, createChildren)); + this.forceUseWrappedDisplayName = false; this.createChildren = createChildren; this.columnOrderKey = columnOrderKey; } + + 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 @@ -113,10 +94,6 @@ public class TableFilterNode extends FilterNode { } } - protected String getParentDisplayName() { - return super.getDisplayName(); - } - /** * Adds information about which child node of this node, if any, should be * selected. Can be null. @@ -159,8 +136,7 @@ public class TableFilterNode extends FilterNode { * DataResultViewerTable. The key should represent what kinds of items the * table is showing. */ - String getColumnOrderKey() { + public String getColumnOrderKey() { return columnOrderKey; } - } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 11e92836ac..e856b25418 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -59,10 +59,11 @@ import javax.imageio.stream.ImageInputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.opencv.core.Core; 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.corelibs.OpenCvLoader; import org.sleuthkit.autopsy.corelibs.ScalrWrapper; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException; @@ -96,7 +97,7 @@ public class ImageUtils { private static final List SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>(); private static final SortedSet SUPPORTED_IMAGE_MIME_TYPES; - private static final boolean OPEN_CV_LOADED; + private static final boolean FFMPEG_LOADED; /** * Map from tsk object id to Java File object. Used to get the same File for @@ -105,6 +106,8 @@ public class ImageUtils { * * NOTE: Must be cleared when the case is changed. */ + @Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg", + "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"}) private static final ConcurrentHashMap cacheFileMap = new ConcurrentHashMap<>(); static { @@ -117,25 +120,23 @@ public class ImageUtils { tempImage = null; } DEFAULT_THUMBNAIL = tempImage; - - //load opencv libraries - boolean openCVLoadedTemp; - try { - System.loadLibrary(Core.NATIVE_LIBRARY_NAME); - if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS - System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS - } else { - System.loadLibrary("opencv_ffmpeg248"); //NON-NLS + boolean tempFfmpegLoaded = false; + if (OpenCvLoader.isOpenCvLoaded()) { + try { + if (System.getProperty("os.arch").equals("amd64") || System.getProperty("os.arch").equals("x86_64")) { //NON-NLS + System.loadLibrary("opencv_ffmpeg248_64"); //NON-NLS + } else { + System.loadLibrary("opencv_ffmpeg248"); //NON-NLS + } + tempFfmpegLoaded = true; + } catch (UnsatisfiedLinkError e) { + tempFfmpegLoaded = false; + LOGGER.log(Level.SEVERE, Bundle.ImageUtils_ffmpegLoadedError_msg(), e); //NON-NLS + MessageNotifyUtil.Notify.show(Bundle.ImageUtils_ffmpegLoadedError_title(), Bundle.ImageUtils_ffmpegLoadedError_msg(), MessageNotifyUtil.MessageType.WARNING); } - - openCVLoadedTemp = true; - } catch (UnsatisfiedLinkError e) { - openCVLoadedTemp = false; - LOGGER.log(Level.SEVERE, "OpenCV Native code library failed to load", e); //NON-NLS - MessageNotifyUtil.Notify.show("Open CV", "OpenCV native library failed to load, see log for more details", MessageNotifyUtil.MessageType.WARNING); } + FFMPEG_LOADED = tempFfmpegLoaded; - OPEN_CV_LOADED = openCVLoadedTemp; SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes())); SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files SUPPORTED_IMAGE_EXTENSIONS.removeIf("db"::equals); // remove db files @@ -165,8 +166,8 @@ public class ImageUtils { /** * Thread/Executor that saves generated thumbnails to disk in the background */ - private static final Executor imageSaver = - Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() + private static final Executor imageSaver + = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() .namingPattern("thumbnail-saver-%d").build()); //NON-NLS public static List getSupportedImageExtensions() { @@ -687,7 +688,7 @@ public class ImageUtils { //There was no correctly-sized cached thumbnail so make one. BufferedImage thumbnail = null; if (VideoUtils.isVideoThumbnailSupported(file)) { - if (OPEN_CV_LOADED) { + if (FFMPEG_LOADED) { updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName())); if (isCancelled()) { return null; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index 7d66dc4e3c..f0a3086588 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2014 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,6 +51,7 @@ import org.sleuthkit.datamodel.TskCoreException; public class PlatformUtil { private static final String PYTHON_MODULES_SUBDIRECTORY = "python_modules"; //NON-NLS + private static final String CLASSIFIERS_SUBDIRECTORY = "object_detection_classifiers"; //NON-NLS private static String javaPath = null; public static final String OS_NAME_UNKNOWN = NbBundle.getMessage(PlatformUtil.class, "PlatformUtil.nameUnknown"); public static final String OS_VERSION_UNKNOWN = NbBundle.getMessage(PlatformUtil.class, "PlatformUtil.verUnknown"); @@ -116,6 +117,15 @@ public class PlatformUtil { return getUserDirectory().getAbsolutePath() + File.separator + PYTHON_MODULES_SUBDIRECTORY; } + /** + * Get root path where the user's object detection classifiers are stored. + * + * @return Absolute path to the object detection classifiers root directory. + */ + public static String getObjectDetectionClassifierPath() { + return getUserDirectory().getAbsolutePath() + File.separator + CLASSIFIERS_SUBDIRECTORY; + } + /** * get file path to the java executable binary use embedded java if * available, otherwise use system java in PATH no validation is done if @@ -310,19 +320,17 @@ public class PlatformUtil { return (System.getProperty("os.arch").contains("64")); //NON-NLS } } - - + /** - * Attempts to determine whether the JVM is 64-bit or 32-bit. - * May not be completely reliable for non-Windows operating systems. + * Attempts to determine whether the JVM is 64-bit or 32-bit. May not be + * completely reliable for non-Windows operating systems. * * @return True if the JVM is 64-bit. False otherwise. */ public static boolean is64BitJVM() { return (System.getProperty("sun.arch.data.model").equals("64")); } - - + /** * Get a list of all physical drives attached to the client's machine. Error * threshold of 4 non-existent physical drives before giving up. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 3f1ca56e47..28329f17c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,12 +21,14 @@ 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.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; +import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Children; import org.openide.nodes.Sheet; @@ -36,6 +38,9 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*; 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/AutopsyTreeChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java new file mode 100644 index 0000000000..9573347eb9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.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 class AutopsyTreeChildrenFactory extends ChildFactory.Detachable { + + private static final Logger logger = Logger.getLogger(AutopsyTreeChildrenFactory.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 ab64cead5e..d6fb9afc04 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -48,6 +48,8 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; @@ -68,7 +70,7 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class BlackboardArtifactNode extends AbstractContentNode { - private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName()); + private static final Logger logger = Logger.getLogger(BlackboardArtifactNode.class.getName()); private static 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, @@ -217,6 +219,7 @@ public class BlackboardArtifactNode extends AbstractContentNode actionsList = new ArrayList<>(); actionsList.addAll(Arrays.asList(super.getActions(context))); + AbstractFile file = getLookup().lookup(AbstractFile.class); //if this artifact has a time stamp add the action to view it in the timeline try { @@ -224,7 +227,7 @@ 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); @@ -472,7 +503,7 @@ public class BlackboardArtifactNode extends AbstractContentNode("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); @@ -490,7 +521,7 @@ public class BlackboardArtifactNode extends AbstractContentNode sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; 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; @@ -132,8 +131,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..547303878b 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; @@ -128,8 +127,9 @@ class ContentTagNode extends DisplayableItemNode { 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 6d90aa9b0e..9cd13a2f4a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -19,6 +19,9 @@ package org.sleuthkit.autopsy.datamodel; import org.sleuthkit.autopsy.commonfilesearch.CommonFilesNode; +import org.sleuthkit.autopsy.commonfilesearch.FileInstanceNode; +import org.sleuthkit.autopsy.commonfilesearch.InstanceCountNode; +import org.sleuthkit.autopsy.commonfilesearch.Md5Node; 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 +64,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); @@ -115,8 +120,8 @@ public interface DisplayableItemNodeVisitor { T visit(CommonFilesNode cfn); T visit(FileInstanceNode fin); - - T visit(CommonFileChildNodeLoading cfcnl); + + T visit(InstanceCountNode icn); /* * Tags @@ -200,10 +205,10 @@ public interface DisplayableItemNodeVisitor { public T visit(CommonFilesNode cfn) { return defaultVisit(cfn); } - + @Override - public T visit(CommonFileChildNodeLoading cfcnl) { - return defaultVisit(cfcnl); + public T visit(InstanceCountNode icn){ + return defaultVisit(icn); } @Override @@ -336,6 +341,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 abf6b5f63b..b8d7b9829c 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; @@ -58,12 +60,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); @@ -272,7 +293,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() { @@ -334,7 +358,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 @@ -456,7 +482,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 dea5535a26..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileInstanceNode.java +++ /dev/null @@ -1,127 +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 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.File.toString(), node.getName()); - map.put(CommonFilePropertyType.ParentPath.toString(), node.getContent().getParentPath()); - map.put(CommonFilePropertyType.HashsetHits.toString(), getHashSetHitsForFile(node.getContent())); - 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.mimeTypeColLbl=MIME Type" - }) - public enum CommonFilePropertyType { - - File(Bundle.CommonFilePropertyType_fileColLbl()), - ParentPath(Bundle.CommonFilePropertyType_pathColLbl()), - HashsetHits(Bundle.CommonFilePropertyType_hashsetHitsColLbl()), - 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/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/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/KeyValueNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java index 2904a1aecb..fe063ebf9c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeyValueNode.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,7 +28,9 @@ import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -107,17 +109,21 @@ public class KeyValueNode extends AbstractNode { * * @return actions */ + @Messages({ + "KeyValueNode.menuItemText.viewFileInDir=View Source File in Directory" + }) @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList<>(); - actions.addAll(Arrays.asList(super.getActions(popup))); - //if this artifact has associated content, add the action to view the content in the timeline + List actionsList = new ArrayList<>(); + actionsList.addAll(Arrays.asList(super.getActions(popup))); + // If this artifact has associated content, add the actions. AbstractFile file = getLookup().lookup(AbstractFile.class); if (null != file) { - actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); + actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); + actionsList.add(new ViewContextAction(Bundle.KeyValueNode_menuItemText_viewFileInDir(), file)); } - actions.add(null); // creates a menu separator + actionsList.add(null); // creates a menu separator - return actions.toArray(new Action[actions.size()]); + return actionsList.toArray(new Action[actionsList.size()]); } } 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 c723506363..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; @@ -96,8 +95,8 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); - actionsList.addAll(Arrays.asList(super.getActions(true))); + actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "LocalFileNode.viewFileInDir.text"), this.content)); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction( @@ -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/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/datasourceprocessors/RawDSInputPanel.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java index baf9b15223..431d71d19f 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSInputPanel.java @@ -33,6 +33,10 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; +/** + * Allows examiner to supply a raw data source. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class RawDSInputPanel extends JPanel implements DocumentListener { private static final long TWO_GB = 2000000000L; private static final long serialVersionUID = 1L; //default diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java index d98cf86527..790c4ae11e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/RawDSProcessor.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"); @@ -47,7 +47,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; public class RawDSProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor { private final RawDSInputPanel configPanel; - private AddRawImageTask addImageTask; private static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC); private static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC); private static final List filtersList = new ArrayList<>(); @@ -163,7 +162,7 @@ public class RawDSProcessor implements DataSourceProcessor, AutoIngestDataSource * @param callback Callback to call when processing is done. */ private void run(String deviceId, String imageFilePath, String timeZone, long chunkSize, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddRawImageTask(deviceId, imageFilePath, timeZone, chunkSize, progressMonitor, callback); + AddRawImageTask addImageTask = new AddRawImageTask(deviceId, imageFilePath, timeZone, chunkSize, progressMonitor, callback); new Thread(addImageTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java index d50f31e383..00cd992eb1 100644 --- a/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java +++ b/Core/src/org/sleuthkit/autopsy/diagnostics/PerformancePanel.java @@ -42,6 +42,10 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; +/** + * Display statistics on system performance. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class PerformancePanel extends javax.swing.JDialog { /** 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/AddExternalViewerRulePanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java index 66705cc174..3cf1d2445a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRulePanel.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; /** * Panel found in an AddRuleDialog */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddExternalViewerRulePanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(AddExternalViewerRulePanel.class.getName()); 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 3aef1e5a2a..fd13e30d62 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -41,6 +41,7 @@ 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.FileInstanceNode; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; @@ -54,6 +55,8 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; +import org.sleuthkit.autopsy.commonfilesearch.InstanceCountNode; +import org.sleuthkit.autopsy.commonfilesearch.Md5Node; import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; @@ -398,10 +401,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) { @@ -435,10 +436,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()); @@ -526,6 +523,21 @@ public class DataResultFilterNode extends FilterNode { */ private class GetPreferredActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default { + @Override + public AbstractAction visit(InstanceCountNode icn){ + return null; + } + + @Override + public AbstractAction visit(Md5Node md5n){ + return null; + } + + @Override + public AbstractAction visit(FileInstanceNode fin){ + 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 0fd19c98a1..95ba2f110c 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; @@ -61,11 +68,10 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataExplorer; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; 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.AutopsyTreeChildrenFactory; 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; /** @@ -97,6 +98,7 @@ import org.sleuthkit.datamodel.TskCoreException; @Messages({ "DirectoryTreeTopComponent.resultsView.title=Listing" }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class DirectoryTreeTopComponent extends TopComponent implements DataExplorer, ExplorerManager.Provider { private final transient ExplorerManager em = new ExplorerManager(); @@ -106,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 AutopsyTreeChildrenFactory autopsyTreeChildrenFactory; + 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 @@ -129,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()); } /** @@ -141,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: @@ -150,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); @@ -181,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); @@ -218,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 @@ -295,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 @@ -351,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 @@ -358,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 @@ -373,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 + autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory(); + autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, 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 @@ -421,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 @@ -463,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) { @@ -485,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) { @@ -569,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 @@ -587,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); @@ -619,20 +725,27 @@ 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) { + 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. + */ + if (!this.isOpened()) { SwingUtilities.invokeLater(CoreComponentControl::openCoreWindows); } - } catch (NoCurrentCaseException | TskCoreException notUsed) { + } 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. @@ -728,7 +841,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]; } @@ -772,25 +885,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. + autopsyTreeChildrenFactory.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); + } + } } /** @@ -1059,10 +1215,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) treeNode).getOriginal(); undecoratedParentNode.setChildNodeSelectionInfo(new ArtifactNodeSelectionInfo(art)); getTree().expandNode(treeNode); - try { - em.setExploredContextAndSelection(treeNode, new Node[]{treeNode}); - } catch (PropertyVetoException ex) { - LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS + if (this.getSelectedNode().equals(treeNode)) { + this.setDirectoryListingActive(); + this.respondSelection(em.getSelectedNodes(), new Node[]{treeNode}); + } else { + try { + em.setExploredContextAndSelection(treeNode, new Node[]{treeNode}); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS + } } // Another thread is needed because we have to wait for dataResult to populate } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java index 29241cf02e..f5351393dc 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerGlobalSettingsPanel.java @@ -34,6 +34,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * opening files in external viewers. Users can associate a file by either MIME * type or by extension to an executable file. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ExternalViewerGlobalSettingsPanel extends javax.swing.JPanel implements OptionsPanel { private static final Logger logger = Logger.getLogger(ExternalViewerGlobalSettingsPanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java index ce5b915b41..29fd7d3bf4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/FileSystemDetailsPanel.java @@ -27,9 +27,8 @@ import org.sleuthkit.datamodel.TskCoreException; /** * This is the form / panel to show the File System Details. - * - * @author jantonius */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FileSystemDetailsPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(FileSystemDetailsPanel.class.getName()); private static final long serialVersionUID = 1L; 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/ImageDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java index b551c11c69..e26c0605ab 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ImageDetailsPanel.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,21 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * ImageDetailsPanel.java - * - * Created on May 2, 2011, 3:53:49 PM - */ package org.sleuthkit.autopsy.directorytree; -import java.awt.*; import java.awt.event.ActionListener; /** - * - * @author jantonius + * Image details panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ImageDetailsPanel extends javax.swing.JPanel { /** 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/VolumeDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java index cea09df745..825de50796 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/VolumeDetailsPanel.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,12 +18,12 @@ */ package org.sleuthkit.autopsy.directorytree; -import java.awt.*; import java.awt.event.ActionListener; /** * This is the form / panel to show the Volume Details. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class VolumeDetailsPanel extends javax.swing.JPanel { /** 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/events/RemoteEventPublisher.java b/Core/src/org/sleuthkit/autopsy/events/RemoteEventPublisher.java index b2b7267225..4afb45117a 100644 --- a/Core/src/org/sleuthkit/autopsy/events/RemoteEventPublisher.java +++ b/Core/src/org/sleuthkit/autopsy/events/RemoteEventPublisher.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"); @@ -56,7 +56,6 @@ final class RemoteEventPublisher { private final MessageProducer producer; @GuardedBy("this") private final MessageConsumer consumer; - private final MessageReceiver receiver; /** * Constructs an object for publishing events to registered subscribers on @@ -84,7 +83,7 @@ final class RemoteEventPublisher { producer = session.createProducer(topic); producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); consumer = session.createConsumer(topic, "events = '" + ALL_MESSAGE_SELECTOR + "'", true); //NON-NLS - receiver = new MessageReceiver(); + MessageReceiver receiver = new MessageReceiver(); consumer.setMessageListener(receiver); } catch (URISyntaxException | JMSException ex) { logger.log(Level.SEVERE, "Failed to connect to event channel", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java b/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java index 17c73388aa..f8f2e14902 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleContentViewer.java @@ -31,7 +31,6 @@ package org.sleuthkit.autopsy.examples; import java.awt.Component; import org.openide.nodes.Node; -import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -45,6 +44,7 @@ import org.sleuthkit.datamodel.TskCoreException; * compiled each time to ensure that it is compliant with the API. */ // @ServiceProvider(service = DataContentViewer.class) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SampleContentViewer extends javax.swing.JPanel implements DataContentViewer { /** diff --git a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java index 2d0f511963..22a7cb3e2f 100644 --- a/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/examples/SampleIngestModuleIngestJobSettingsPanel.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * UI component used to make per ingest job settings for sample ingest modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class SampleIngestModuleIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.form index e1725962ea..2287e3b4df 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.form @@ -6,7 +6,7 @@ - + @@ -46,8 +46,8 @@ - - + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/DataSourcePanel.java index beb3a05277..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; @@ -41,41 +38,34 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Subpanel with controls for data source filtering. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives 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) { - 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 */ @@ -90,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) { @@ -104,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<>(); @@ -121,6 +111,7 @@ public class DataSourcePanel extends javax.swing.JPanel { /** * Is dataSourceCheckBox selected + * * @return true if the dataSoureCheckBox is selected */ boolean isSelected() { @@ -128,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(); @@ -151,7 +143,7 @@ public class DataSourcePanel extends javax.swing.JPanel { dataSourceNoteLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(150, 150)); - setPreferredSize(new java.awt.Dimension(100, 100)); + setPreferredSize(new java.awt.Dimension(150, 150)); dataSourceList.setModel(new javax.swing.AbstractListModel() { List strings = getDataSourceArray(); @@ -194,8 +186,8 @@ public class DataSourcePanel extends javax.swing.JPanel { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(dataSourceCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 95, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 103, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(dataSourceNoteLabel) .addContainerGap()) ); diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java index dedc5a2899..d13b4490a2 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.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"); @@ -39,6 +39,7 @@ import java.util.Date; /** * Subpanel with controls for file data filtering. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class DateSearchPanel extends javax.swing.JPanel { private final DatePickerSettings fromDateSettings = new DatePickerSettings(); 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.form b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.form index 89fe3dc17f..f6fd00903f 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.form @@ -70,12 +70,12 @@
- - - + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 0219b76367..55a90487a5 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.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,17 +16,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * FileSearchPanel.java - * - * Created on Mar 5, 2012, 1:51:50 PM - */ package org.sleuthkit.autopsy.filesearch; import java.awt.Component; import java.awt.Cursor; -import java.awt.Dimension; +import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; @@ -37,9 +31,11 @@ import java.util.Collections; import java.util.List; import java.util.logging.Level; import javax.swing.JLabel; +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; @@ -48,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; 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; @@ -56,9 +53,10 @@ import org.sleuthkit.datamodel.TskCoreException; /** * FileSearchPanel that present search options */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchPanel extends javax.swing.JPanel { - private final List filterAreas = new ArrayList<>(); + private final List filters = new ArrayList<>(); private static int resultWindowCount = 0; //keep track of result windows so they get unique names private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text"); @@ -79,32 +77,57 @@ class FileSearchPanel extends javax.swing.JPanel { * This method is called from within the constructor to initialize the form. */ private void customizeComponents() { - + JLabel label = new JLabel(NbBundle.getMessage(this.getClass(), "FileSearchPanel.custComp.label.text")); label.setAlignmentX(Component.LEFT_ALIGNMENT); label.setBorder(new EmptyBorder(0, 0, 10, 0)); - filterPanel.add(label); + + JPanel panel1 = new JPanel(); + panel1.setLayout(new GridLayout(1,2)); + panel1.add(new JLabel("")); + JPanel panel2 = new JPanel(); + panel2.setLayout(new GridLayout(1,2, 20, 0)); + JPanel panel3 = new JPanel(); + panel3.setLayout(new GridLayout(1,2, 20, 0)); + JPanel panel4 = new JPanel(); + panel4.setLayout(new GridLayout(1,2, 20, 0)); + JPanel panel5 = new JPanel(); + panel5.setLayout(new GridLayout(1,2, 20, 0)); // Create and add filter areas - this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.name"), new NameSearchFilter())); - - List metadataFilters = new ArrayList<>(); - metadataFilters.add(new SizeSearchFilter()); - metadataFilters.add(new MimeTypeFilter()); - metadataFilters.add(new DateSearchFilter()); + NameSearchFilter nameFilter = new NameSearchFilter(); + SizeSearchFilter sizeFilter = new SizeSearchFilter(); + DateSearchFilter dateFilter = new DateSearchFilter(); + KnownStatusSearchFilter knowStatusFilter = new KnownStatusSearchFilter(); + HashSearchFilter hashFilter = new HashSearchFilter(); + MimeTypeFilter mimeTypeFilter = new MimeTypeFilter(); + DataSourceFilter dataSourceFilter = new DataSourceFilter(); - this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), metadataFilters)); - - this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.knownStatus"), new KnownStatusSearchFilter())); + panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.name"),nameFilter)); + + panel3.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"),sizeFilter)); + + panel2.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), dateFilter)); + panel3.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.knownStatus"), knowStatusFilter)); + + panel5.add(new FilterArea(NbBundle.getMessage(this.getClass(), "HashSearchPanel.md5CheckBox.text"), hashFilter)); + panel5.add(new JLabel("")); + panel4.add(new FilterArea(NbBundle.getMessage(this.getClass(), "FileSearchPanel.filterTitle.metadata"), mimeTypeFilter)); + panel4.add(new FilterArea(NbBundle.getMessage(this.getClass(), "DataSourcePanel.dataSourceCheckBox.text"), dataSourceFilter)); + filterPanel.add(panel1); + filterPanel.add(panel2); + filterPanel.add(panel3); + filterPanel.add(panel4); + filterPanel.add(panel5); + + filters.add(nameFilter); + filters.add(sizeFilter); + filters.add(dateFilter); + filters.add(knowStatusFilter); + filters.add(hashFilter); + filters.add(mimeTypeFilter); + filters.add(dataSourceFilter); - this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "HashSearchPanel.md5CheckBox.text"), new HashSearchFilter())); - this.filterAreas.add(new FilterArea(NbBundle.getMessage(this.getClass(), "DataSourcePanel.dataSourceCheckBox.text"), new DataSourceFilter())); - for (FilterArea fa : this.filterAreas) { - fa.setMaximumSize(new Dimension(Integer.MAX_VALUE, fa.getMinimumSize().height)); - fa.setAlignmentX(Component.LEFT_ALIGNMENT); - filterPanel.add(fa); - } - for (FileSearchFilter filter : this.getFilters()) { filter.addPropertyChangeListener(new PropertyChangeListener() { @Override @@ -145,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)); @@ -171,9 +195,16 @@ class FileSearchPanel extends javax.swing.JPanel { } SearchNode sn = new SearchNode(contentList); - final TopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText, - new TableFilterNode(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 /** @@ -242,12 +273,6 @@ class FileSearchPanel extends javax.swing.JPanel { } private Collection getFilters() { - Collection filters = new ArrayList<>(); - - for (FilterArea fa : this.filterAreas) { - filters.addAll(fa.getFilters()); - } - return filters; } @@ -265,10 +290,8 @@ class FileSearchPanel extends javax.swing.JPanel { void addListenerToAll(ActionListener l) { searchButton.addActionListener(l); - for (FilterArea fa : this.filterAreas) { - for (FileSearchFilter fsf : fa.getFilters()) { - fsf.addActionListener(l); - } + for (FileSearchFilter fsf : getFilters()) { + fsf.addActionListener(l); } } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java index 3f0d5176ac..6a5491ea08 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/HashSearchPanel.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"); @@ -27,8 +27,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * + * Panel to allow examiner to search for a hash value. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class HashSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java index 57d968ed23..b1a68652b2 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.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,20 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * KnownStatusSearchPanel.java - * - * Created on Oct 19, 2011, 11:45:44 AM - */ package org.sleuthkit.autopsy.filesearch; import javax.swing.JCheckBox; /** - * - * @author pmartel + * Search for known, unknown, and bad files. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class KnownStatusSearchPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form index 772e74f9fc..fd2cc895f6 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form @@ -6,7 +6,7 @@ - + @@ -46,10 +46,10 @@ - - + + - +
@@ -89,12 +89,12 @@
- - - + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java index 0f775a8320..4dda56ce21 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.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"); @@ -25,6 +25,10 @@ import javax.swing.event.ListSelectionEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +/** + * Enter MIME types for search. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class MimeTypePanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(MimeTypePanel.class.getName()); @@ -81,7 +85,7 @@ public class MimeTypePanel extends javax.swing.JPanel { jLabel1 = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(150, 150)); - setPreferredSize(new java.awt.Dimension(100, 100)); + setPreferredSize(new java.awt.Dimension(150, 150)); mimeTypeList.setModel(new javax.swing.AbstractListModel() { String[] strings = getMimeTypeArray(); @@ -98,8 +102,8 @@ public class MimeTypePanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MimeTypePanel.class, "MimeTypePanel.jLabel1.text")); // NOI18N jLabel1.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MimeTypePanel.class, "MimeTypePanel.jLabel1.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -122,10 +126,10 @@ public class MimeTypePanel extends javax.swing.JPanel { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(mimeTypeCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 94, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 103, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jLabel1) - .addContainerGap()) + .addGap(40, 40, 40)) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form index af80aa264a..6bcc3522bf 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form @@ -54,14 +54,16 @@ - - + - + + + + - + @@ -117,9 +119,11 @@ + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java index 81ff01fd0f..dce02e5d90 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.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"); @@ -16,12 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - /* - * NameSearchPanel.java - * - * Created on Oct 19, 2011, 11:58:53 AM - */ package org.sleuthkit.autopsy.filesearch; import java.awt.event.ActionEvent; @@ -33,9 +27,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * - * @author pmartel + * Provide a name for search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class NameSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -146,7 +140,9 @@ class NameSearchPanel extends javax.swing.JPanel { searchTextField.setText(org.openide.util.NbBundle.getMessage(NameSearchPanel.class, "NameSearchPanel.searchTextField.text")); // NOI18N noteNameLabel.setFont(noteNameLabel.getFont().deriveFont(noteNameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 10)); + noteNameLabel.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING); noteNameLabel.setText(org.openide.util.NbBundle.getMessage(NameSearchPanel.class, "NameSearchPanel.noteNameLabel.text")); // NOI18N + noteNameLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); noteNameLabel.setMaximumSize(new java.awt.Dimension(250, 30)); noteNameLabel.setMinimumSize(new java.awt.Dimension(250, 30)); noteNameLabel.setPreferredSize(new java.awt.Dimension(250, 40)); @@ -155,14 +151,15 @@ class NameSearchPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(0, 0, 0) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(noteNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(noteNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addComponent(nameCheckBox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(searchTextField))) + .addComponent(searchTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 191, Short.MAX_VALUE))) .addGap(0, 0, 0)) ); layout.setVerticalGroup( diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java index 089b83530a..6a69ba36f9 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.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"); @@ -29,9 +29,9 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; /** - * - * @author pmartel + * Provide file size for search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SizeSearchPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java similarity index 62% rename from Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java rename to Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java index d28ef1b475..251fe35572 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitor.java @@ -34,8 +34,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -54,45 +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, 0); - - private static final AtomicBoolean isEnabled = new AtomicBoolean(false); - private static EnterpriseHealthMonitor instance; - - private final ExecutorService healthMonitorExecutor; - private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; - + 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 static final int CONN_POOL_SIZE = 10; + private final List userInfoList; + 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<>(); - - // Set up the executor to handle case events - healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); - + + // 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(); @@ -101,134 +96,212 @@ 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())) { + upgradeDatabaseSchema(); + } + } catch (CoordinationService.CoordinationServiceException ex) { throw new HealthMonitorException("Error releasing database lock", ex); } - + // Clear out any old data timingInfoMap.clear(); + userInfoList.clear(); } - + /** - * 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 + * 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) { + throw new HealthMonitorException("Error getting database connection"); + } + + try (Statement statement = conn.createStatement()) { + conn.setAutoCommit(false); + + // Upgrade from 1.0 to 1.1 + // Changes: user_data table added + 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" + + ")"); + } + + // 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) { + try { + conn.rollback(); + } catch (SQLException ex2) { + logger.log(Level.SEVERE, "Rollback error"); + } + throw new HealthMonitorException("Error upgrading database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing connection.", ex); + } + } + } + + /** + * 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(); + 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 + */ + 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); } @@ -236,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); @@ -272,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); @@ -294,130 +370,160 @@ 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)); } } } - + /** - * 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 + * Add a user event to the list. + * + * @param eventType + */ + private void addUserEvent(UserEvent eventType) { + UserData userInfo = new UserData(eventType); + 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 */ 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 { - // Time a database query 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 (right now we only have the timing map) - if(timingMapCopy.keySet().isEmpty()) { + + // Check if there's anything to report + 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"); } - // Add timing metrics to the database + // Add metrics to the database String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; - try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + 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)) { - for(String name:timingMapCopy.keySet()) { + for (String name : timingMapCopy.keySet()) { TimingInfo info = timingMapCopy.get(name); - statement.setString(1, name); - statement.setString(2, hostName); - statement.setLong(3, System.currentTimeMillis()); - statement.setLong(4, info.getCount()); - statement.setDouble(5, info.getAverage()); - statement.setDouble(6, info.getMax()); - statement.setDouble(7, info.getMin()); + timingStatement.setString(1, name); + timingStatement.setString(2, hostName); + timingStatement.setLong(3, System.currentTimeMillis()); + timingStatement.setLong(4, info.getCount()); + timingStatement.setDouble(5, info.getAverage()); + timingStatement.setDouble(6, info.getMax()); + timingStatement.setDouble(7, info.getMin()); - statement.execute(); + timingStatement.execute(); + } + + for (UserData userInfo : userDataCopy) { + userStatement.setString(1, hostName); + userStatement.setLong(2, userInfo.getTimestamp()); + userStatement.setInt(3, userInfo.getEventType().getEventValue()); + userStatement.setBoolean(4, userInfo.isExaminerNode()); + userStatement.setString(5, userInfo.getCaseName()); + userStatement.execute(); } } catch (SQLException ex) { @@ -433,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(); @@ -448,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(); } } @@ -463,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 { @@ -486,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"); @@ -516,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() } @@ -533,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) { @@ -553,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(); @@ -574,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) { @@ -586,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 { @@ -655,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; @@ -730,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) { @@ -744,45 +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); - - String createTimingTable = - "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(createTimingTable); - - String createDbInfoTable = - "CREATE TABLE IF NOT EXISTS db_info (" + - "id SERIAL PRIMARY KEY NOT NULL," + - "name text NOT NULL," + - "value text NOT NULL" + - ")"; - statement.execute(createDbInfoTable); - + + 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 { @@ -799,31 +926,37 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } } - + /** - * The task called by the ScheduledThreadPoolExecutor to handle - * the database checks/writes. + * The task called by the ScheduledThreadPoolExecutor to handle the periodic + * database update */ static final class PeriodicHealthMonitorTask implements Runnable { - /** - * 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 - */ @Override public void run() { - try { - getInstance().updateFromGlobalEnabledStatus(); - getInstance().gatherTimerBasedMetrics(); - getInstance().writeCurrentStateToDatabase(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS - } + 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. + */ + private static void recordMetrics() { + try { + getInstance().updateFromGlobalEnabledStatus(); + if (monitorIsEnabled()) { + getInstance().gatherTimerBasedMetrics(); + getInstance().writeCurrentStateToDatabase(); + } + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS + } + } + @Override public void propertyChange(PropertyChangeEvent evt) { @@ -831,45 +964,50 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { case CURRENT_CASE: if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { - // When a case is closed, write the current metrics to the database - healthMonitorExecutor.submit(new EnterpriseHealthMonitor.PeriodicHealthMonitorTask()); + // Case is closing + addUserEvent(UserEvent.CASE_CLOSE); + + } 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"}; // NON-NLS - + + 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 @@ -877,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; @@ -902,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; @@ -919,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 { @@ -953,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); @@ -972,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); @@ -985,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<>(); @@ -1045,129 +1183,359 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { 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. - * @return The lock - * @throws HealthMonitorException + * 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 */ - private CoordinationService.Lock getExclusiveDbLock() 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()) { + 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) { + 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)) { + + while (resultSet.next()) { + resultList.add(new UserData(resultSet)); + } + return resultList; + } catch (SQLException ex) { + throw new HealthMonitorException("Error reading user metrics from database", ex); + } + } 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. + * + * @return The lock + * + * @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"); } - } - + } + /** - * 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. + * Types of user events being logged + */ + enum UserEvent { + LOG_ON(0), + 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 + */ + static UserEvent valueOf(int value) throws HealthMonitorException { + for (UserEvent v : UserEvent.values()) { + 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 true if a case is open, false otherwise + */ + boolean caseIsOpen() { + 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)); + } + } + + /** + * 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. + * + * @param eventType The type of event being recorded + */ + private UserData(UserEvent eventType) { + this.eventType = eventType; + 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(); + } catch (NoCurrentCaseException ex) { + // It's not an error if there's no case open + this.caseName = ""; + } + } + + /** + * Create a UserData object from a database result set. + * + * @param resultSet The result set containing the data + * + * @throws SQLException + * @throws HealthMonitorException + */ + UserData(ResultSet resultSet) throws SQLException, HealthMonitorException { + this.timestamp = resultSet.getLong("timestamp"); + this.hostname = resultSet.getString("host"); + this.eventType = UserEvent.valueOf(resultSet.getInt("event_type")); + 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) { + UserData userData = new UserData(UserEvent.CASE_CLOSE); + 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. */ 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 @@ -1183,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 9dbd08233e..265cab388f 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorDashboard.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.HashSet; import java.util.HashMap; import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -60,14 +61,17 @@ 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; + Map> timingData; + List userData; - private JComboBox dateComboBox = null; - private JComboBox hostComboBox = null; - private JCheckBox hostCheckBox = null; - private JCheckBox showTrendLineCheckBox = null; - private JCheckBox skipOutliersCheckBox = null; - private JPanel graphPanel = null; + private JComboBox timingDateComboBox = null; + private JComboBox timingHostComboBox = null; + private JCheckBox timingHostCheckBox = null; + private JCheckBox timingShowTrendLineCheckBox = null; + private JCheckBox timingSkipOutliersCheckBox = null; + private JPanel timingGraphPanel = null; + private JComboBox userDateComboBox = null; + private JPanel userGraphPanel = null; private JDialog dialog = null; private final Container parentWindow; @@ -78,6 +82,7 @@ public class HealthMonitorDashboard { */ public HealthMonitorDashboard(Container parent) { timingData = new HashMap<>(); + userData = new ArrayList<>(); parentWindow = parent; } @@ -85,16 +90,18 @@ 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 // the sub panels. JPanel timingPanel; + JPanel userPanel; JPanel adminPanel; try { updateData(); timingPanel = createTimingPanel(); + userPanel = createUserPanel(); adminPanel = createAdminPanel(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error creating panels for health monitor dashboard", ex); @@ -109,6 +116,9 @@ public class HealthMonitorDashboard { // Add the timing panel mainPanel.add(timingPanel); + // Add the user panel + mainPanel.add(userPanel); + // Add the admin panel if the admin file is present File adminFile = new File(ADMIN_ACCESS_FILE_PATH); if(adminFile.exists()) { @@ -143,11 +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 = HealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.getMaximumTimestampRange()); } } @@ -161,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())); @@ -185,12 +198,12 @@ public class HealthMonitorDashboard { timingMetricPanel.add(new JSeparator()); // Create panel to hold graphs - graphPanel = new JPanel(); - graphPanel.setLayout(new GridLayout(0,2)); + timingGraphPanel = new JPanel(); + timingGraphPanel.setLayout(new GridLayout(0,2)); // Update the graph panel, put it in a scroll pane, and add to the timing metric panel updateTimingMetricGraphs(); - JScrollPane scrollPane = new JScrollPane(graphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + JScrollPane scrollPane = new JScrollPane(timingGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); timingMetricPanel.add(scrollPane); timingMetricPanel.revalidate(); timingMetricPanel.repaint(); @@ -210,17 +223,17 @@ 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; } // Create the combo box for selecting how much data to display String[] dateOptionStrings = Arrays.stream(DateRange.values()).map(e -> e.getLabel()).toArray(String[]::new); - dateComboBox = new JComboBox<>(dateOptionStrings); - dateComboBox.setSelectedItem(DateRange.ONE_DAY.getLabel()); + timingDateComboBox = new JComboBox<>(dateOptionStrings); + timingDateComboBox.setSelectedItem(DateRange.ONE_DAY.getLabel()); // Set up the listener on the date combo box - dateComboBox.addActionListener(new ActionListener() { + timingDateComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { @@ -234,20 +247,20 @@ 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()); } } // Load the host names into the combo box - hostComboBox = new JComboBox<>(hostNameSet.toArray(new String[hostNameSet.size()])); + timingHostComboBox = new JComboBox<>(hostNameSet.toArray(new String[hostNameSet.size()])); // Set up the listener on the combo box - hostComboBox.addActionListener(new ActionListener() { + timingHostComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { - if((hostCheckBox != null) && hostCheckBox.isSelected()) { + if((timingHostCheckBox != null) && timingHostCheckBox.isSelected()) { updateTimingMetricGraphs(); } } catch (HealthMonitorException ex) { @@ -257,16 +270,16 @@ public class HealthMonitorDashboard { }); // Create the host checkbox - hostCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost()); - hostCheckBox.setSelected(false); - hostComboBox.setEnabled(false); + timingHostCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_filterByHost()); + timingHostCheckBox.setSelected(false); + timingHostComboBox.setEnabled(false); // Set up the listener on the checkbox - hostCheckBox.addActionListener(new ActionListener() { + timingHostCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { - hostComboBox.setEnabled(hostCheckBox.isSelected()); + timingHostComboBox.setEnabled(timingHostCheckBox.isSelected()); updateTimingMetricGraphs(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error populating timing metric panel", ex); @@ -275,11 +288,11 @@ public class HealthMonitorDashboard { }); // Create the checkbox for showing the trend line - showTrendLineCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_showTrendLine()); - showTrendLineCheckBox.setSelected(true); + timingShowTrendLineCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_showTrendLine()); + timingShowTrendLineCheckBox.setSelected(true); // Set up the listener on the checkbox - showTrendLineCheckBox.addActionListener(new ActionListener() { + timingShowTrendLineCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { @@ -291,11 +304,11 @@ public class HealthMonitorDashboard { }); // Create the checkbox for omitting outliers - skipOutliersCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_skipOutliers()); - skipOutliersCheckBox.setSelected(false); + timingSkipOutliersCheckBox = new JCheckBox(Bundle.HealthMonitorDashboard_createTimingControlPanel_skipOutliers()); + timingSkipOutliersCheckBox.setSelected(false); // Set up the listener on the checkbox - skipOutliersCheckBox.addActionListener(new ActionListener() { + timingSkipOutliersCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { try { @@ -308,26 +321,26 @@ public class HealthMonitorDashboard { // Add the date range combo box and label to the panel timingControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createTimingControlPanel_maxDays())); - timingControlPanel.add(dateComboBox); + timingControlPanel.add(timingDateComboBox); // Put some space between the elements timingControlPanel.add(Box.createHorizontalStrut(100)); // Add the host combo box and checkbox to the panel - timingControlPanel.add(hostCheckBox); - timingControlPanel.add(hostComboBox); + timingControlPanel.add(timingHostCheckBox); + timingControlPanel.add(timingHostComboBox); // Put some space between the elements timingControlPanel.add(Box.createHorizontalStrut(100)); // Add the skip outliers checkbox - timingControlPanel.add(this.showTrendLineCheckBox); + timingControlPanel.add(this.timingShowTrendLineCheckBox); // Put some space between the elements timingControlPanel.add(Box.createHorizontalStrut(100)); // Add the skip outliers checkbox - timingControlPanel.add(this.skipOutliersCheckBox); + timingControlPanel.add(this.timingSkipOutliersCheckBox); return timingControlPanel; } @@ -340,20 +353,20 @@ public class HealthMonitorDashboard { private void updateTimingMetricGraphs() throws HealthMonitorException { // Clear out any old graphs - graphPanel.removeAll(); + timingGraphPanel.removeAll(); if(timingData.keySet().isEmpty()) { // There are no timing metrics in the database - graphPanel.add(new JLabel(Bundle.HealthMonitorDashboard_updateTimingMetricGraphs_noData())); + timingGraphPanel.add(new JLabel(Bundle.HealthMonitorDashboard_updateTimingMetricGraphs_noData())); return; } for(String metricName:timingData.keySet()) { // If necessary, trim down the list of results to fit the selected time range - List intermediateTimingDataForDisplay; - if(dateComboBox.getSelectedItem() != null) { - DateRange selectedDateRange = DateRange.fromLabel(dateComboBox.getSelectedItem().toString()); + List intermediateTimingDataForDisplay; + if(timingDateComboBox.getSelectedItem() != null) { + DateRange selectedDateRange = DateRange.fromLabel(timingDateComboBox.getSelectedItem().toString()); long threshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); intermediateTimingDataForDisplay = timingData.get(metricName).stream() .filter(t -> t.getTimestamp() > threshold) @@ -366,25 +379,150 @@ public class HealthMonitorDashboard { // The graph always uses the data from all hosts to generate the x and y scales // so we don't filter anything out here. String hostToDisplay = null; - if(hostCheckBox.isSelected() && (hostComboBox.getSelectedItem() != null)) { - hostToDisplay = hostComboBox.getSelectedItem().toString(); + if(timingHostCheckBox.isSelected() && (timingHostComboBox.getSelectedItem() != null)) { + hostToDisplay = timingHostComboBox.getSelectedItem().toString(); } // Generate the graph TimingMetricGraphPanel singleTimingGraphPanel = new TimingMetricGraphPanel(intermediateTimingDataForDisplay, - hostToDisplay, true, metricName, skipOutliersCheckBox.isSelected(), showTrendLineCheckBox.isSelected()); + hostToDisplay, true, metricName, timingSkipOutliersCheckBox.isSelected(), timingShowTrendLineCheckBox.isSelected()); singleTimingGraphPanel.setPreferredSize(new Dimension(700,200)); - graphPanel.add(singleTimingGraphPanel); + timingGraphPanel.add(singleTimingGraphPanel); } - graphPanel.revalidate(); - graphPanel.repaint(); + timingGraphPanel.revalidate(); + timingGraphPanel.repaint(); + } + + /** + * Create the user panel. + * This displays cases open and users logged in + * @return the user panel + */ + @NbBundle.Messages({"HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled", + "HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics"}) + private JPanel createUserPanel() throws HealthMonitorException { + // If the monitor isn't enabled, just add a message + if(! HealthMonitor.monitorIsEnabled()) { + JPanel emptyUserMetricPanel = new JPanel(); + emptyUserMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle())); + emptyUserMetricPanel.add(new JLabel(" ")); + emptyUserMetricPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_noData())); + + return emptyUserMetricPanel; + } + + JPanel userMetricPanel = new JPanel(); + userMetricPanel.setLayout(new BoxLayout(userMetricPanel, BoxLayout.PAGE_AXIS)); + userMetricPanel.setBorder(BorderFactory.createEtchedBorder()); + + // Add title + JLabel userMetricTitle = new JLabel(Bundle.HealthMonitorDashboard_createUserPanel_userMetricsTitle()); + userMetricPanel.add(userMetricTitle); + userMetricPanel.add(new JSeparator()); + + // Add the controls + userMetricPanel.add(createUserControlPanel()); + userMetricPanel.add(new JSeparator()); + + // Create panel to hold graphs + userGraphPanel = new JPanel(); + userGraphPanel.setLayout(new GridLayout(0,2)); + + // Update the graph panel, put it in a scroll pane, and add to the timing metric panel + updateUserMetricGraphs(); + JScrollPane scrollPane = new JScrollPane(userGraphPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + userMetricPanel.add(scrollPane); + userMetricPanel.revalidate(); + userMetricPanel.repaint(); + + return userMetricPanel; + } + + /** + * Create the panel with controls for the user panel + * @return the control panel + */ + @NbBundle.Messages({"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display"}) + private JPanel createUserControlPanel() { + JPanel userControlPanel = new JPanel(); + + // If the monitor is not enabled, don't add any components + if(! HealthMonitor.monitorIsEnabled()) { + return userControlPanel; + } + + // Create the combo box for selecting how much data to display + String[] dateOptionStrings = Arrays.stream(DateRange.values()).map(e -> e.getLabel()).toArray(String[]::new); + userDateComboBox = new JComboBox<>(dateOptionStrings); + userDateComboBox.setSelectedItem(DateRange.ONE_DAY.getLabel()); + + // Set up the listener on the date combo box + userDateComboBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + try { + updateUserMetricGraphs(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error updating user metric panel", ex); + } + } + }); + + // Add the date range combo box and label to the panel + userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays())); + userControlPanel.add(userDateComboBox); + + return userControlPanel; + } + + /** + * Update the user graphs. + * @throws HealthMonitorException + */ + @NbBundle.Messages({"HealthMonitorDashboard.updateUserMetricGraphs.noData=No data to display"}) + private void updateUserMetricGraphs() throws HealthMonitorException { + + // Clear out any old graphs + userGraphPanel.removeAll(); + + if(userData.isEmpty()) { + // There are no user metrics in the database + userGraphPanel.add(new JLabel(Bundle.HealthMonitorDashboard_updateUserMetricGraphs_noData())); + return; + } + + // Calculate the minimum timestamp for the graph. + // Unlike the timing graphs, we do not filter the list of user metrics here. + // This is because even if we're only displaying one day, the + // last metric for a host may be that it logged on two days ago, so we would want + // to show that node as logged on. + long timestampThreshold; + if(userDateComboBox.getSelectedItem() != null) { + DateRange selectedDateRange = DateRange.fromLabel(userDateComboBox.getSelectedItem().toString()); + timestampThreshold = System.currentTimeMillis() - selectedDateRange.getTimestampRange(); + + } else { + timestampThreshold = System.currentTimeMillis() - DateRange.getMaximumTimestampRange(); + } + + // Generate the graphs + UserMetricGraphPanel caseGraphPanel = new UserMetricGraphPanel(userData, timestampThreshold, true); + caseGraphPanel.setPreferredSize(new Dimension(700,200)); + + UserMetricGraphPanel logonGraphPanel = new UserMetricGraphPanel(userData, timestampThreshold, false); + logonGraphPanel.setPreferredSize(new Dimension(700,200)); + + userGraphPanel.add(caseGraphPanel); + userGraphPanel.add(logonGraphPanel); + userGraphPanel.revalidate(); + userGraphPanel.repaint(); } /** * Create the admin panel. * This allows the health monitor to be enabled and disabled. - * @return + * @return the admin panel */ @NbBundle.Messages({"HealthMonitorDashboard.createAdminPanel.enableButton=Enable monitor", "HealthMonitorDashboard.createAdminPanel.disableButton=Disable monitor"}) @@ -397,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); @@ -407,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); @@ -423,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 61ea5a5244..4a709e79fb 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -45,9 +45,18 @@ 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); } } + + @Override + public void close() { + try { + HealthMonitor.shutdown(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error stopping health services monitor", ex); + } + } } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java index 6d995ed9ac..c88ce1e627 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetricGraphPanel.java @@ -39,11 +39,12 @@ 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 */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class TimingMetricGraphPanel extends JPanel { private final static Logger logger = Logger.getLogger(TimingMetricGraphPanel.class.getName()); @@ -373,7 +374,7 @@ class TimingMetricGraphPanel extends JPanel { } } else if (y0value > maxValueOnYAxis) { try { - y0value = minValueOnYAxis; + y0value = maxValueOnYAxis; x0value = trendLine.getXGivenY(y0value); } catch (HealthMonitorException ex) { // The exception is caused by a slope of zero on the trend line, which diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java new file mode 100644 index 0000000000..b5b76b6993 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/UserMetricGraphPanel.java @@ -0,0 +1,455 @@ +/* + * 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.healthmonitor; + +import java.awt.Color; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.util.Collections; +import java.util.Comparator; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.HashMap; +import java.util.HashSet; +import java.util.TreeSet; +import java.util.Calendar; +import java.util.GregorianCalendar; +import javax.swing.JPanel; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitor.UserData; + +/** + * Creates graphs using the given user metric data + */ +class UserMetricGraphPanel extends JPanel { + + private static final int padding = 25; + private static final int labelPadding = 25; + private final Color examinerColor = new Color(0x12, 0x20, 0xdb, 255); + private final Color autoIngestColor = new Color(0x12, 0x80, 0x20, 255); + private final Color gridColor = new Color(200, 200, 200, 200); + private static final int pointWidth = 4; + private static final int numberYDivisions = 10; + private final List dataToPlot; + private final String graphLabel; + private final long dataInterval; + private final long MILLISECONDS_PER_HOUR = 1000 * 60 * 60; + private final long MILLISECONDS_PER_DAY = MILLISECONDS_PER_HOUR * 24; + private final long maxTimestamp; + private final long minTimestamp; + private int maxCount; + private static final int minCount = 0; // The bottom of the graph will always be zero + + @NbBundle.Messages({"UserMetricGraphPanel.constructor.casesOpen=Cases open", + "UserMetricGraphPanel.constructor.loggedIn=Users logged in - examiner nodes in blue, auto ingest nodes in green" + }) + UserMetricGraphPanel(List userResults, long timestampThreshold, boolean plotCases) { + + maxTimestamp = System.currentTimeMillis(); + minTimestamp = timestampThreshold; + + // Make the label + if (plotCases) { + graphLabel = Bundle.UserMetricGraphPanel_constructor_casesOpen(); + } else { + graphLabel = Bundle.UserMetricGraphPanel_constructor_loggedIn(); + } + + // Comparator for the set of UserData objects + Comparator sortOnTimestamp = new Comparator() { + @Override + public int compare(UserData o1, UserData o2) { + return Long.compare(o1.getTimestamp(), o2.getTimestamp()); + } + }; + + // Create a map from host name to data and get the timestamp bounds. + // We're using TreeSets here because they support the floor function. + Map> userDataMap = new HashMap<>(); + for(UserData result:userResults) { + if(userDataMap.containsKey(result.getHostname())) { + userDataMap.get(result.getHostname()).add(result); + } else { + TreeSet resultTreeSet = new TreeSet<>(sortOnTimestamp); + resultTreeSet.add(result); + userDataMap.put(result.getHostname(), resultTreeSet); + } + } + + // Create a list of data points to plot + // The idea here is that starting at maxTimestamp, we go backwards in increments, + // see what the state of each node was at that time and make the counts of nodes + // that are logged in/ have a case open. + // A case is open if the last event was "case open"; closed otherwise + // A user is logged in if the last event was anything but "log out";logged out otherwise + dataToPlot = new ArrayList<>(); + dataInterval = MILLISECONDS_PER_HOUR; + maxCount = Integer.MIN_VALUE; + for (long timestamp = maxTimestamp;timestamp > minTimestamp;timestamp -= dataInterval) { + + // Collect both counts so that we can use the same scale in the open case graph and + // the logged in users graph + UserCount openCaseCount = new UserCount(timestamp); + UserCount loggedInUserCount = new UserCount(timestamp); + + Set openCaseNames = new HashSet<>(); + UserData timestampUserData = UserData.createDummyUserData(timestamp); + + for (String hostname:userDataMap.keySet()) { + // Get the most recent record before this timestamp + UserData lastRecord = userDataMap.get(hostname).floor(timestampUserData); + + if (lastRecord != null) { + + // Update the case count. + if (lastRecord.getEventType().caseIsOpen()) { + + // Only add each case once regardless of how many users have it open + if ( ! openCaseNames.contains(lastRecord.getCaseName())) { + + // Store everything as examiner nodes. The graph will represent + // the number of distinct cases open, not anything about the + // nodes that have them open. + openCaseCount.addExaminer(); + openCaseNames.add(lastRecord.getCaseName()); + } + } + + // Update the logged in user count + if (lastRecord.getEventType().userIsLoggedIn()) { + if(lastRecord.isExaminerNode()) { + loggedInUserCount.addExaminer(); + } else { + loggedInUserCount.addAutoIngestNode(); + } + } + } + } + + // Check if this is a new maximum. + // Assuming we log all the events, there should never be more cases open than + // there are logged in users, but it could happen if we lose data. + maxCount = Integer.max(maxCount, openCaseCount.getTotalNodeCount()); + maxCount = Integer.max(maxCount, loggedInUserCount.getTotalNodeCount()); + + // Add the count to be plotted + if(plotCases) { + dataToPlot.add(openCaseCount); + } else { + dataToPlot.add(loggedInUserCount); + } + } + } + + /** + * Setup of the graphics panel: + * Origin (0,0) is at the top left corner + * + * Horizontally (from the left): (padding)(label padding)(the graph)(padding) + * For plotting data on the x-axis, we scale it to the size of the graph and then add the padding and label padding + * + * Vertically (from the top): (padding)(the graph)(label padding)(padding) + * For plotting data on the y-axis, we subtract from the max value in the graph and then scale to the size of the graph + * @param g + */ + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Get the max and min timestamps to create the x-axis. + // We add a small buffer to each side so the data won't overwrite the axes. + double maxValueOnXAxis = maxTimestamp + TimeUnit.HOURS.toMillis(2); // Two hour buffer (the last bar graph will take up one of the hours) + double minValueOnXAxis = minTimestamp - TimeUnit.HOURS.toMillis(1); // One hour buffer + + // Get the max and min times to create the y-axis + // To make the intervals even, make sure the maximum is a multiple of five + if((maxCount % 5) != 0) { + maxCount += (5 - (maxCount % 5)); + } + int maxValueOnYAxis = Integer.max(maxCount, 5); + int minValueOnYAxis = minCount; + + // The graph itself has the following corners: + // (padding + label padding, padding + font height) -> top left + // (padding + label padding, getHeight() - label padding - padding) -> bottom left + // (getWidth() - padding, padding + font height) -> top right + // (padding + label padding, getHeight() - label padding - padding) -> bottom right + int leftGraphPadding = padding + labelPadding; + int rightGraphPadding = padding; + int topGraphPadding = padding + g2.getFontMetrics().getHeight(); + int bottomGraphPadding = labelPadding; + + // Calculate the scale for each axis. + // The size of the graph area is the width/height of the panel minus any padding. + // The scale is calculated based on this size of the graph compared to the data range. + // For example: + // getWidth() = 575 => graph width = 500 + // If our max x value to plot is 10000 and our min is 0, then the xScale would be 0.05 - i.e., + // our original x values will be multipled by 0.05 to translate them to an x-coordinate in the + // graph (plus the padding) + int graphWidth = getWidth() - leftGraphPadding - rightGraphPadding; + int graphHeight = getHeight() - topGraphPadding - bottomGraphPadding; + double xScale = ((double) graphWidth) / (maxValueOnXAxis - minValueOnXAxis); + double yScale = ((double) graphHeight) / (maxValueOnYAxis - minValueOnYAxis); + + // Draw white background + g2.setColor(Color.WHITE); + g2.fillRect(leftGraphPadding, topGraphPadding, graphWidth, graphHeight); + + // Create hatch marks and grid lines for y axis. + int labelWidth; + int positionForMetricNameLabel = 0; + Map countToGraphPosition = new HashMap<>(); + for (int i = 0; i < numberYDivisions + 1; i++) { + int x0 = leftGraphPadding; + int x1 = pointWidth + leftGraphPadding; + int y0 = getHeight() - ((i * graphHeight) / numberYDivisions + bottomGraphPadding); + int y1 = y0; + + if ( ! dataToPlot.isEmpty()) { + // Draw the grid line + g2.setColor(gridColor); + g2.drawLine(leftGraphPadding + 1 + pointWidth, y0, getWidth() - rightGraphPadding, y1); + + // Create the label + g2.setColor(Color.BLACK); + double yValue = minValueOnYAxis + ((maxValueOnYAxis - minValueOnYAxis) * ((i * 1.0) / numberYDivisions)); + int intermediateLabelVal = (int) (yValue * 100); + if ((i == numberYDivisions) || ((intermediateLabelVal % 100) == 0)) { + countToGraphPosition.put(intermediateLabelVal / 100, y0); + String yLabel = Integer.toString(intermediateLabelVal / 100); + FontMetrics fontMetrics = g2.getFontMetrics(); + labelWidth = fontMetrics.stringWidth(yLabel); + g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (fontMetrics.getHeight() / 2) - 3); + + // The nicest looking alignment for this label seems to be left-aligned with the top + // y-axis label. Save this position to be used to write the label later. + if (i == numberYDivisions) { + positionForMetricNameLabel = x0 - labelWidth - 5; + } + } + } + + // Draw the small hatch mark + g2.setColor(Color.BLACK); + g2.drawLine(x0, y0, x1, y1); + } + + // On the x-axis, the farthest right grid line should represent midnight preceding the last recorded value + Calendar maxDate = new GregorianCalendar(); + maxDate.setTimeInMillis(maxTimestamp); + maxDate.set(Calendar.HOUR_OF_DAY, 0); + maxDate.set(Calendar.MINUTE, 0); + maxDate.set(Calendar.SECOND, 0); + maxDate.set(Calendar.MILLISECOND, 0); + long maxMidnightInMillis = maxDate.getTimeInMillis(); + + // We don't want to display more than 20 grid lines. If we have more + // data then that, put multiple days within one division + long totalDays = (maxMidnightInMillis - (long)minValueOnXAxis) / MILLISECONDS_PER_DAY; + long daysPerDivision; + if(totalDays <= 20) { + daysPerDivision = 1; + } else { + daysPerDivision = (totalDays / 20); + if((totalDays % 20) != 0) { + daysPerDivision++; + } + } + + // Draw the vertical grid lines and labels + // The vertical grid lines will be at midnight, and display the date underneath them + // At present we use GMT because of some complications with daylight savings time. + for (long currentDivision = maxMidnightInMillis; currentDivision >= minValueOnXAxis; currentDivision -= MILLISECONDS_PER_DAY * daysPerDivision) { + + int x0 = (int) ((currentDivision - minValueOnXAxis) * xScale + leftGraphPadding); + int x1 = x0; + int y0 = getHeight() - bottomGraphPadding; + int y1 = y0 - pointWidth; + + // Draw the light grey grid line + g2.setColor(gridColor); + g2.drawLine(x0, getHeight() - bottomGraphPadding - 1 - pointWidth, x1, topGraphPadding); + + // Draw the hatch mark + g2.setColor(Color.BLACK); + g2.drawLine(x0, y0, x1, y1); + + // Draw the label + Calendar thisDate = new GregorianCalendar(); + thisDate.setTimeZone(TimeZone.getTimeZone("GMT")); // Stick with GMT to avoid daylight savings issues + thisDate.setTimeInMillis(currentDivision); + int month = thisDate.get(Calendar.MONTH) + 1; + int day = thisDate.get(Calendar.DAY_OF_MONTH); + + String xLabel = month + "/" + day; + FontMetrics metrics = g2.getFontMetrics(); + labelWidth = metrics.stringWidth(xLabel); + g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3); + } + + // Create x and y axes + g2.setColor(Color.BLACK); + g2.drawLine(leftGraphPadding, getHeight() - bottomGraphPadding, leftGraphPadding, topGraphPadding); + g2.drawLine(leftGraphPadding, getHeight() - bottomGraphPadding, getWidth() - rightGraphPadding, getHeight() - bottomGraphPadding); + + // Sort dataToPlot on timestamp + Collections.sort(dataToPlot, new Comparator(){ + @Override + public int compare(UserCount o1, UserCount o2){ + return Long.compare(o1.getTimestamp(), o2.getTimestamp()); + } + }); + + // Create the bars + for(int i = 0;i < dataToPlot.size();i++) { + UserCount userCount = dataToPlot.get(i); + int x = (int) ((userCount.getTimestamp() - minValueOnXAxis) * xScale + leftGraphPadding); + int yTopOfExaminerBox; + if(countToGraphPosition.containsKey(userCount.getTotalNodeCount())) { + // If we've drawn a grid line for this count, use the recorded value. If we don't do + // this, rounding differences lead to the bar graph not quite lining up with the existing grid. + yTopOfExaminerBox = countToGraphPosition.get(userCount.getTotalNodeCount()); + } else { + yTopOfExaminerBox = (int) ((maxValueOnYAxis - userCount.getTotalNodeCount()) * yScale + topGraphPadding); + } + + // Calculate the width. If this isn't the last column, set this to one less than + // the distance to the next column starting point. + int width; + if(i < dataToPlot.size() - 1) { + width = Integer.max((int)((dataToPlot.get(i + 1).getTimestamp() - minValueOnXAxis) * xScale + leftGraphPadding) - x - 1, + 1); + } else { + width = Integer.max((int)(dataInterval * xScale), 1); + } + + // The examiner bar goes all the way to the bottom of the graph. + // The bottom will be overwritten by the auto ingest bar for displaying + // logged in users. + int heightExaminerBox = (getHeight() - bottomGraphPadding) - yTopOfExaminerBox; + + // Plot the examiner bar + g2.setColor(examinerColor); + g2.fillRect(x, yTopOfExaminerBox, width, heightExaminerBox); + + // Check that there is an auto ingest node count before plotting its bar. + // For the cases open graph, this will always be empty. + if (userCount.getAutoIngestNodeCount() > 0) { + int yTopOfAutoIngestBox; + if(countToGraphPosition.containsKey(userCount.getAutoIngestNodeCount())) { + // As above, if we've drawn a grid line for this count, use the recorded value. If we don't do + // this, rounding differences lead to the bar graph not quite lining up with the existing grid. + yTopOfAutoIngestBox =countToGraphPosition.get(userCount.getAutoIngestNodeCount()); + } else { + yTopOfAutoIngestBox = yTopOfExaminerBox + heightExaminerBox; + } + int heightAutoIngestBox = (getHeight() - bottomGraphPadding) - yTopOfAutoIngestBox; + + // Plot the auto ingest bar + g2.setColor(autoIngestColor); + g2.fillRect(x, yTopOfAutoIngestBox, width, heightAutoIngestBox); + } + } + + // The graph lines may have extended up past the bounds of the graph. Overwrite that + // area with the original background color. + g2.setColor(this.getBackground()); + g2.fillRect(leftGraphPadding, 0, graphWidth, topGraphPadding); + + // Write the scale. Do this after we erase the top block of the graph. + g2.setColor(Color.BLACK); + String titleStr = graphLabel; + g2.drawString(titleStr, positionForMetricNameLabel, padding); + } + + /** + * Utility class to keep track of the data to be graphed. + * Used for tracking logging in users and open cases. + */ + private class UserCount { + private final long timestamp; + private int examinerCount; + private int autoIngestCount; + + /** + * Create a UserCount object with counts initialized to zero. + * @param timestamp + */ + UserCount(long timestamp) { + this.timestamp = timestamp; + this.examinerCount = 0; + this.autoIngestCount = 0; + } + + /** + * Add one examiner node to the count. + */ + void addExaminer() { + examinerCount++; + } + + /** + * Add one auto ingest node to the count. + */ + void addAutoIngestNode() { + autoIngestCount++; + } + + /** + * Get the number of examiner nodes. + * @return number of examiner nodes + */ + int getExaminerNodeCount() { + return examinerCount; + } + + /** + * Get the number of auto ingest nodes. + * @return number of auto ingest nodes + */ + int getAutoIngestNodeCount() { + return autoIngestCount; + } + + /** + * Get the total number of nodes + * @return the sum of the examiner and auto ingest nodes + */ + int getTotalNodeCount() { + return examinerCount + autoIngestCount; + } + + /** + * Get the timestamp for this metric + * @return the timestamp + */ + long getTimestamp() { + return timestamp; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java index 2d13e3fadb..318dc6e05a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestCancellationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ package org.sleuthkit.autopsy.ingest; * A UI panel that allows a user to make data source ingest cancellation * requests. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class DataSourceIngestCancellationPanel extends javax.swing.JPanel { private boolean cancelAllIngestModules; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 93a1f1505d..6563dd3540 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -22,7 +22,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; @@ -224,8 +224,8 @@ public final class DataSourceIngestJob { /** * Make mappings of ingest module factory class names to templates. */ - Map dataSourceModuleTemplates = new HashMap<>(); - Map fileModuleTemplates = new HashMap<>(); + Map dataSourceModuleTemplates = new LinkedHashMap<>(); + Map fileModuleTemplates = new LinkedHashMap<>(); for (IngestModuleTemplate template : ingestModuleTemplates) { if (template.isDataSourceIngestModuleTemplate()) { dataSourceModuleTemplates.put(template.getModuleFactory().getClass().getCanonicalName(), template); 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/IngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java index 88fb0b347e..dcaaca9c35 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettingsPanel.java @@ -58,6 +58,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager; /** * A panel to allow a user to make ingest job settings. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestJobSettingsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java index 17645ccb68..27400766b0 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageDetailsPanel.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.ingest; -import java.awt.*; +import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JMenuItem; @@ -36,6 +36,7 @@ import org.sleuthkit.datamodel.TskException; /** * Details panel within IngestMessagePanel */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessageDetailsPanel extends javax.swing.JPanel { private final IngestMessageMainPanel mainPanel; @@ -240,7 +241,7 @@ class IngestMessageDetailsPanel extends javax.swing.JPanel { this.messageDetailsPane.setText(""); } //show artifact/content only for a message group with a single message - BlackboardArtifact artifact = messageGroup.getData();; + BlackboardArtifact artifact = messageGroup.getData(); if (artifact != null && messageGroup.getCount() == 1) { viewArtifactButton.setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java index 1fa70462cc..d2dd0c2ad2 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageMainPanel.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"); @@ -25,6 +25,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * the main layered pane container for messages table (IngestMessagePanel) and * details view (IngestMessageDetailsPanel) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessageMainPanel extends javax.swing.JPanel { private IngestMessagePanel messagePanel; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java index f9063b289c..a8f4d39dea 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagePanel.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"); @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import javax.swing.DefaultComboBoxModel; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTable; @@ -51,14 +52,13 @@ import javax.swing.table.TableCellRenderer; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.ingest.IngestMessage.*; import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType; import org.sleuthkit.datamodel.BlackboardArtifact; /** * Notification window showing messages from modules to user - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessagePanel extends JPanel implements TableModelListener { private final MessageTableModel tableModel; @@ -245,7 +245,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { * It is not possible to internationalize the list of options in a ComboBox * inside of the generated form code. So, it is done here. */ - sortByComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { + sortByComboBox.setModel(new DefaultComboBoxModel<>(new String[] { NbBundle.getMessage(this.getClass(), "IngestMessagePanel.sortByComboBox.model.time"), NbBundle.getMessage(this.getClass(), "IngestMessagePanel.sortByComboBox.model.priority")})); @@ -519,7 +519,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { if (moduleName != null && m.getMessageType() == IngestMessage.MessageType.DATA) { //not a manager message, a data message, then group if (!groupings.containsKey(moduleName)) { - groupings.put(moduleName, new HashMap>()); + groupings.put(moduleName, new HashMap<>()); } final Map> groups = groupings.get(moduleName); //groups for this uniqueness @@ -564,7 +564,7 @@ class IngestMessagePanel extends JPanel implements TableModelListener { messageGroup = first; //move to bottom of table //remove from existing position - int toRemove = 0; + int toRemove; while ((toRemove = getTableEntryIndex(uniqueness)) != -1) { messageData.remove(toRemove); //remove the row, will be added to the bottom diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java index 04052b1ce6..767c19b52f 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessageTopComponent.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"); @@ -40,6 +40,7 @@ import org.sleuthkit.datamodel.Content; /** * Top component which displays something. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestMessageTopComponent extends TopComponent { private static IngestMessageTopComponent instance; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java index 432cb48810..261ef90352 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestMessagesToolbar.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; * Tool bar for an ingest messages button that allows a user to open the ingest * messages inbox top component. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestMessagesToolbar extends javax.swing.JPanel { private IngestMessagesButton ingestMessagesButton = new IngestMessagesButton(); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java index 3d2c66deb9..a8df988a01 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory; import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory; import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; +import org.sleuthkit.autopsy.modules.encryptiondetection.EncryptionDetectionModuleFactory; import org.sleuthkit.autopsy.python.JythonModuleLoader; /** @@ -60,6 +61,7 @@ final class IngestModuleFactoryLoader { add("org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory"); //NON-NLS add(FileExtMismatchDetectorModuleFactory.class.getCanonicalName()); add(E01VerifierModuleFactory.class.getCanonicalName()); + add(EncryptionDetectionModuleFactory.class.getCanonicalName()); add(InterestingItemsIngestModuleFactory.class.getCanonicalName()); add(PhotoRecCarverIngestModuleFactory.class.getCanonicalName()); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java index 40f3d1c298..cbfd996473 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestOptionsPanel.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,6 +32,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY /** * Global options panel for keyword searching. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class IngestOptionsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { @NbBundle.Messages({"IngestOptionsPanel.settingsTab.text=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/ingest/IngestProgressSnapshotPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java index be4661ce5b..0448382309 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.openide.util.NbBundle; /** * A panel that displays ingest task progress snapshots. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class IngestProgressSnapshotPanel extends javax.swing.JPanel { private final JDialog parent; diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java index ed02db96cb..11b3d1f349 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; /** * Options panel that allow users to set application preferences. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestSettingsPanel extends IngestModuleGlobalSettingsPanel { IngestSettingsPanel() { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java index ec09fbca6e..dc145a6824 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/ProfilePanel.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"); @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.ingest.IngestProfiles.IngestProfile; /** * Panel to display options for profile creation and editing. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ProfilePanel extends IngestModuleGlobalSettingsPanel { @NbBundle.Messages({"ProfilePanel.title.text=Profile", diff --git a/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java index 7e97fe0e7d..bb0c75e750 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/ProfileSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,10 @@ import org.sleuthkit.autopsy.ingest.IngestProfiles.IngestProfile; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet; import org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager; +/** + * Panel for managing ingest module profiles. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ProfileSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { @NbBundle.Messages({"ProfileSettingsPanel.title=Profile Settings", diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java index e16fc98aa0..12cc78ecd5 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/IngestProfileSelectionPanel.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"); @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.ingest.IngestProfiles.IngestProfile; * Visual panel for the choosing of ingest profiles by the user when running * ingest. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class IngestProfileSelectionPanel extends JPanel { @Messages({"IngestProfileSelectionPanel.customSettings.name=Custom Settings", diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java index add27f8146..451b0e4bc7 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesWizardIterator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,6 @@ import org.sleuthkit.datamodel.Content; final class RunIngestModulesWizardIterator implements WizardDescriptor.Iterator { private final static String PROP_LASTPROFILE_NAME = "RIMW_LASTPROFILE_NAME"; //NON-NLS - private final IngestJobSettings.IngestType ingestType; private final List panels; private int currentPanelIndex; @@ -47,14 +46,13 @@ final class RunIngestModulesWizardIterator implements WizardDescriptor.Iterator< * @param ingestType The type of ingest to be configured. */ RunIngestModulesWizardIterator(String executionContext, IngestJobSettings.IngestType ingestType, List dataSources) { - this.ingestType = ingestType; panels = new ArrayList<>(); List profiles = IngestProfiles.getIngestProfiles(); - if (!profiles.isEmpty() && IngestJobSettings.IngestType.FILES_ONLY != this.ingestType) { + if (!profiles.isEmpty() && IngestJobSettings.IngestType.FILES_ONLY != ingestType) { panels.add(new IngestProfileSelectionWizardPanel(executionContext, PROP_LASTPROFILE_NAME)); } - panels.add(new IngestModulesConfigWizardPanel(executionContext, this.ingestType, dataSources)); + panels.add(new IngestModulesConfigWizardPanel(executionContext, ingestType, dataSources)); String[] steps = new String[panels.size()]; for (int i = 0; i < panels.size(); i++) { Component c = panels.get(i).getComponent(); diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java index c13d82d324..2b973aa92f 100644 --- a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.livetriage; @@ -22,8 +35,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; /** - * + * Dialog to allow for live triage drive selection. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class SelectDriveDialog extends javax.swing.JDialog { private List disks = new ArrayList<>(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties index c80ccaa863..89cf1636b7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties @@ -9,11 +9,11 @@ OpenIDE-Module-Name=Embedded File Extraction OpenIDE-Module-Short-Description=Embedded File Extraction Ingest Module EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin\: {0} EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.read.exception.errReadStream=Error reading content stream. -EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=File-level Encryption -EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull=Full Encryption +EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=Content-only Encryption (Archive File) +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 5e67e9da8b..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,12 +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 - private String moduleDirRelative; - private String moduleDirAbsolute; + //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 @@ -66,10 +70,14 @@ 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(); - moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + final Case currentCase = Case.getCurrentCaseThrows(); + moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_NoOpenCase_errMsg(), ex); } @@ -93,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. @@ -112,6 +122,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex); } + } @Override @@ -143,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 bee2c4b407..2293d0472d 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(); } /** @@ -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().getSleuthkitCase().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/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index f182f80f2c..54a82abdb6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -75,6 +75,8 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter private static final String MIME_TYPE_MSACCESS = "application/x-msaccess"; private static final String MIME_TYPE_PDF = "application/pdf"; + private static final String[] FILE_IGNORE_LIST = {"hiberfile.sys", "pagefile.sys"}; + private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; @@ -122,32 +124,40 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter try { /* - * Qualify the file type. + * Qualify the file type, qualify it against hash databases, and + * verify the file hasn't been deleted. */ if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) - && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { + && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed) + && !file.getKnown().equals(TskData.FileKnown.KNOWN) + && !file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.UNALLOC)) { /* - * Qualify the file against hash databases. + * Is the file in FILE_IGNORE_LIST? */ - if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { - /* - * Qualify the MIME type. - */ - String mimeType = fileTypeDetector.getMIMEType(file); - if (mimeType.equals("application/octet-stream")) { - if (isFileEncryptionSuspected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, - String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy)); - } - } else { - if (isFilePasswordProtected(file)) { - return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password()); + String filePath = file.getParentPath(); + if (filePath.equals("/")) { + String fileName = file.getName(); + for (String listEntry : FILE_IGNORE_LIST) { + if (fileName.equalsIgnoreCase(listEntry)) { + // Skip this file. + return IngestModule.ProcessResult.OK; } } } + + /* + * Qualify the MIME type. + */ + String mimeType = fileTypeDetector.getMIMEType(file); + if (mimeType.equals("application/octet-stream") && isFileEncryptionSuspected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, + String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy)); + } else if (isFilePasswordProtected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Bundle.EncryptionDetectionFileIngestModule_artifactComment_password()); + } } } catch (ReadContentInputStreamException | SAXException | TikaException | UnsupportedCodecException ex) { logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex); @@ -376,7 +386,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter fileSizeQualified = true; } } - + if (fileSizeQualified) { /* * Qualify the entropy. @@ -386,7 +396,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter possiblyEncrypted = true; } } - + return possiblyEncrypted; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettingsPanel.java index b4e2ed487a..ca92d1b256 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettingsPanel.java @@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * Ingest job settings panel for the Encryption Detection module. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class EncryptionDetectionIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { private static final int MEGABYTE_SIZE = 1048576; diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchModuleSettingsPanel.java index 7c141f8733..442511a557 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchModuleSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchModuleSettingsPanel.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"); @@ -25,6 +25,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; * UI component used to set ingest job options for the file extension mismatch * detection ingest module. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FileExtMismatchModuleSettingsPanel extends IngestModuleIngestJobSettingsPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java index 3306d57192..508e1048d1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchSettingsPanel.java @@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; * Container panel for File Extension Mismatch Ingest Module advanced * configuration options */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FileExtMismatchSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final Logger logger = Logger.getLogger(FileExtMismatchSettingsPanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java index 5095f4948e..e98460684e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeDialog.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; @@ -39,6 +38,7 @@ import org.openide.windows.WindowManager; /** * Dialog used for editing or adding file types. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddFileTypeDialog extends JDialog { /** @@ -52,7 +52,7 @@ class AddFileTypeDialog extends JDialog { private static final long serialVersionUID = 1L; private FileType fileType; - private AddFileTypePanel addMimeTypePanel; + final private AddFileTypePanel addMimeTypePanel; private BUTTON_PRESSED result; private JButton okButton; private JButton closeButton; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java index 7008f76ef9..557bf1f8fd 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypePanel.java @@ -32,11 +32,11 @@ import static org.sleuthkit.autopsy.modules.filetypeid.AddFileTypePanel.EVENT.SI import org.sleuthkit.autopsy.modules.filetypeid.AddFileTypeSignatureDialog.BUTTON_PRESSED; import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; -@Messages("AddFileTypePanel.mimeFormatLabel.text=Form of MIME type should be: media type/media subtype") - /** * Panel for adding or editing file types. */ +@Messages("AddFileTypePanel.mimeFormatLabel.text=Form of MIME type should be: media type/media subtype") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddFileTypePanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; 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/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java index 600bf48a2f..5e788c04d3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; * being an ingest module global settings panel, an instance of this class also * appears in the NetBeans options dialog as an options panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(FileTypeIdGlobalSettingsPanel.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java index 9f7061b618..68535928c7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseDialog.java @@ -33,9 +33,9 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.datamodel.HashEntry; /** - * - * @author sidhesh + * Dialog for supplying MD5 hash values for the hash database. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AddHashValuesToDatabaseDialog extends javax.swing.JDialog { HashDb hashDb; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java index 06c584e643..3715816801 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java @@ -36,9 +36,9 @@ import org.sleuthkit.datamodel.HashEntry; import org.sleuthkit.datamodel.TskCoreException; /** - * - * @author sidhesh + * Progress dialog for MD5 hash values being added to the hash database. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { private final AddHashValuesToDatabaseDialog parentRef; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 4aa976be97..873dc5e32b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -50,6 +50,7 @@ import org.sleuthkit.datamodel.TskCoreException; * to the set of hash databases used to classify files as unknown, known or * notable. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private static final String DEFAULT_FILE_NAME = NbBundle diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index a2d041280d..d779629846 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -47,9 +47,10 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; * add it to the set of hash databases used to classify files as unknown, known, * or notable. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class HashDbImportDatabaseDialog extends javax.swing.JDialog { - private JFileChooser fileChooser = new JFileChooser(); + private final JFileChooser fileChooser; private String selectedFilePath = ""; private HashDb selectedHashDb = null; private final static String LAST_FILE_PATH_KEY = "HashDbImport_Path"; @@ -65,6 +66,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { super((JFrame) WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.importHashDbMsg"), true); + this.fileChooser = new JFileChooser(); initComponents(); enableComponents(); initFileChooser(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index e5dc7d355d..296e1d264d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -32,7 +32,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; 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; @@ -184,7 +184,7 @@ public class HashDbIngestModule implements FileIngestModule { 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 +192,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); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java index a75b47646e..93ee3ce836 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbSearchPanel.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** * Searches for files by md5 hash, based off the hash given in this panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class HashDbSearchPanel extends javax.swing.JPanel implements ActionListener { private static final Logger logger = Logger.getLogger(HashDbSearchPanel.class.getName()); @@ -329,7 +330,7 @@ class HashDbSearchPanel extends javax.swing.JPanel implements ActionListener { errorField.setVisible(false); // Get all the rows in the table int numRows = hashTable.getRowCount(); - ArrayList hashes = new ArrayList(); + ArrayList hashes = new ArrayList<>(); for (int i = 0; i < numRows; i++) { hashes.add((String) hashTable.getValueAt(i, 0)); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java index 257f90ad2f..068c52cf5e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleFactory.java @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.modules.hashdatabase; -import java.util.ArrayList; -import java.util.List; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java index fbac01235e..6dcd567226 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.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"); @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; /** * Ingest job settings panel for hash lookup file ingest modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSettingsPanel implements PropertyChangeListener { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 1b7bf461d4..9c2962797f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -18,7 +18,10 @@ */ package org.sleuthkit.autopsy.modules.hashdatabase; -import java.awt.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.EventQueue; +import java.awt.Frame; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -55,6 +58,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; * Instances of this class provide a comprehensive UI for managing the hash sets * configuration. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final String NO_SELECTION_TEXT = NbBundle diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java index 88251f1f6e..d793f1dc2d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.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"); @@ -45,6 +45,7 @@ import org.sleuthkit.datamodel.TskData; /** * Imports a hash set into the central repository and updates a progress dialog */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements PropertyChangeListener { private CentralRepoImportWorker worker; // Swing worker that will import the file and send updates to the dialog diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java index 2d0d4f9bf4..874a5c136b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ModalNoButtons.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"); @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.modules.hashdatabase; -import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -41,6 +40,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SleuthkitHashSet * completion. Furthermore, it does not delete any files left over from a * half-indexed state, forcing the user to perform cleanup. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ModalNoButtons extends javax.swing.JDialog implements PropertyChangeListener { List unindexed; diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java index eea0d54a6a..b4c49e4aa1 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java @@ -50,7 +50,6 @@ final class CallLogAnalyzer { private Connection connection = null; private ResultSet resultSet = null; private Statement statement = null; - private String dbPath = ""; private long fileId = 0; private java.io.File jFile = null; private final String moduleName = iOSModuleFactory.getModuleName(); @@ -79,6 +78,7 @@ final class CallLogAnalyzer { return; } for (AbstractFile file : absFiles) { + String dbPath = ""; try { jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); dbPath = jFile.toString(); //path of file as string diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index 2294351a1f..e49d685733 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -54,8 +54,6 @@ import org.sleuthkit.datamodel.TskCoreException; final class ContactAnalyzer { private Connection connection = null; - private ResultSet resultSet = null; - private Statement statement = null; private String dbPath = ""; private long fileId = 0; private java.io.File jFile = null; @@ -114,6 +112,7 @@ final class ContactAnalyzer { if (DatabasePath == null || DatabasePath.isEmpty()) { return; } + Case currentCase; try { currentCase = Case.getCurrentCaseThrows(); @@ -121,6 +120,8 @@ final class ContactAnalyzer { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return; } + + Statement statement = null; try { Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS @@ -137,6 +138,7 @@ final class ContactAnalyzer { return; } + ResultSet resultSet = null; try { // get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype) //sorted by name, so phonenumber/email would be consecutive for a person if they exist. 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/FilesIdentifierIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java index 934fde0fef..70aa5c4795 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,6 +34,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * Ingest job settings panel for interesting files identifier ingest modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FilesIdentifierIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel implements Observer { @Messages({ diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java index fc930fadd0..71a4be086a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java @@ -49,6 +49,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; /** * A panel that allows a user to make interesting item definitions. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java index daf3bede83..85b4ac19e3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2017 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY * A panel that allows a user to create and edit interesting files set * definitions. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class FilesSetPanel extends javax.swing.JPanel { @NbBundle.Messages({"FilesSetPanel.filter.title=File Filter", "FilesSetPanel.rule.title=File Filter Rule", "FilesSetPanel.ingest.createNewFilter=Create/edit file ingest filters...", "FilesSetPanel.ingest.messages.filtersMustBeNamed=File ingest filters must be named."}) diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java index 4965652c5f..36888279a9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY /** * A panel that allows a user to create and edit files set membership rules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class FilesSetRulePanel extends javax.swing.JPanel { @Messages({ 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/modules/photoreccarver/PhotoRecCarverIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestJobSettingsPanel.java index 9af6902a2d..48f6109950 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestJobSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/photoreccarver/PhotoRecCarverIngestJobSettingsPanel.java @@ -24,6 +24,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * Ingest job settings panel for the Encryption Detection module. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class PhotoRecCarverIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java index c24cba6447..b8d03d7cb4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/STIXReportModuleConfigPanel.java @@ -23,8 +23,9 @@ import javax.swing.JFileChooser; import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** - * + * Configuration panel for STIX report generation. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class STIXReportModuleConfigPanel extends javax.swing.JPanel { String stixFile = null; diff --git a/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java index b918d1f9f0..f6794e1637 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ProgressPanel.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"); @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.progress; /** * A progress panel consisting of a message label and a progress bar. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ProgressPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; 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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index 2da0862826..8efbfd9178 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -199,6 +199,10 @@ ReportHTML.writeSum.examiner=Examiner\: ReportHTML.writeSum.noExaminer=No examiner ReportHTML.writeSum.numImages=Number of Images\: ReportHTML.writeSum.imageInfoHeading=

Image Information\:

+ReportHTML.writeSum.softwareInfoHeading=

Software Information\:

+ReportHTML.writeSum.ingestHistoryHeading=

Ingest History\:

+ReportHTML.writeSum.modulesEnabledHeading=Enabled Modules\: +ReportHTML.writeSum.autopsyVersion=Autopsy Version\: ReportHTML.writeSum.timezone=Timezone\: ReportHTML.writeSum.path=Path\: ReportProgressPanel.progress.queuing=Queuing... @@ -249,4 +253,4 @@ ReportGenerator.errList.coreExceptionWhileGenRptRow=Core exception while generat ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n ReportKML.latLongEndPoint={0};{1};;{2} (End)\n ReportGenerationPanel.cancelButton.actionCommand=Cancel -ReportGenerationPanel.cancelButton.text=Cancel +ReportGenerationPanel.cancelButton.text=Cancel \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java b/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java index ad1ce529dc..2f18ddf3d2 100644 --- a/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/DefaultReportConfigurationPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,10 @@ */ package org.sleuthkit.autopsy.report; -import java.awt.*; - /** * The panel shown for all TableReportModules when configuring report modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DefaultReportConfigurationPanel extends javax.swing.JPanel { /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java index d08acea3a6..c9bf291314 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerationPanel.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; * A panel that displays a panel used by a report generation module to show * progress. It provides OK and Cancel buttons. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ReportGenerationPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 2f0cb5f770..50dfe318c3 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -40,6 +40,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -55,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.AbstractFile; @@ -63,6 +65,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.IngestModuleInfo; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -552,17 +556,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++; @@ -589,7 +605,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; @@ -643,7 +659,7 @@ class ReportHTML implements TableReportModule { int pages = 1; for (Content content : images) { if (currentRow.size() == THUMBNAIL_COLUMNS) { - addRow(currentRow); + addRow(currentRow, false); currentRow.clear(); } @@ -723,7 +739,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. @@ -1049,15 +1065,19 @@ class ReportHTML implements TableReportModule { head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS - head.append("table td { padding-right: 25px; }\n"); //NON-NLS + head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n"); + head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS head.append(".clear { clear: both; }\n"); //NON-NLS + head.append(".info { padding: 10px 0;}\n"); head.append(".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n"); //NON-NLS - head.append(".info table { margin: 0 25px 20px 25px; }\n"); //NON-NLS + head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS + head.append("ul {padding: 0;margin: 0;list-style-type: none;}"); + head.append("li {padding-bottom: 5px;}"); head.append("\n"); //NON-NLS head.append("\n\n"); //NON-NLS out.write(head.toString()); @@ -1066,25 +1086,15 @@ class ReportHTML implements TableReportModule { Date date = new Date(); String datetime = datetimeFormat.format(date); - String caseName = currentCase.getDisplayName(); - String caseNumber = currentCase.getNumber(); - String examiner = currentCase.getExaminer(); - int imagecount; - try { - imagecount = currentCase.getDataSources().size(); - } catch (TskCoreException ex) { - imagecount = 0; - } - StringBuilder summary = new StringBuilder(); boolean running = false; if (IngestManager.getInstance().isIngestRunning()) { running = true; } - + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + List ingestJobs = skCase.getIngestJobs(); final String reportTitle = reportBranding.getReportTitle(); final String reportFooter = reportBranding.getReportFooter(); - final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty(); final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty(); summary.append("
\n"); //NON-NLS @@ -1094,55 +1104,10 @@ class ReportHTML implements TableReportModule { summary.append("

").append( //NON-NLS NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("

\n"); //NON-NLS 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 - summary.append("
\n"); //NON-NLS - summary.append("
\n"); //NON-NLS - summary.append("
\n"); //NON-NLS - summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading")); - summary.append("
\n"); //NON-NLS - try { - for (Content c : currentCase.getDataSources()) { - summary.append("

").append(c.getName()).append("

\n"); //NON-NLS - if (c instanceof Image) { - Image img = (Image) c; - - summary.append("\n"); //NON-NLS - summary.append("\n"); //NON-NLS - for (String imgPath : img.getPaths()) { - summary.append("\n"); //NON-NLS - } - summary.append("
").append( //NON-NLS - NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone")) - .append("").append(img.getTimeZone()).append("
").append( //NON-NLS - NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path")) - .append("").append(imgPath).append("
\n"); //NON-NLS - } - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS - } - summary.append("
\n"); //NON-NLS + summary.append(writeSummaryCaseDetails()); + summary.append(writeSummaryImageInfo()); + summary.append(writeSummarySoftwareInfo(skCase, ingestJobs)); + summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs)); if (generatorLogoSet) { summary.append("
\n"); //NON-NLS summary.append("\n"); //NON-NLS @@ -1161,6 +1126,8 @@ class ReportHTML implements TableReportModule { logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS } catch (IOException ex) { logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report."); } finally { try { if (out != null) { @@ -1172,6 +1139,159 @@ class ReportHTML implements TableReportModule { } } + /** + * Write the case details section of the summary for this report. + * + * @return StringBuilder updated html report with case details + */ + private StringBuilder writeSummaryCaseDetails() { + StringBuilder summary = new StringBuilder(); + String caseName = currentCase.getDisplayName(); + String caseNumber = currentCase.getNumber(); + String examiner = currentCase.getExaminer(); + final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty(); + int imagecount; + try { + imagecount = currentCase.getDataSources().size(); + } catch (TskCoreException ex) { + 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 + 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")); + summary.append("
\n"); //NON-NLS + try { + for (Content c : currentCase.getDataSources()) { + summary.append("

").append(c.getName()).append("

\n"); //NON-NLS + if (c instanceof Image) { + Image img = (Image) c; + + summary.append("\n"); //NON-NLS + summary.append("\n"); //NON-NLS + for (String imgPath : img.getPaths()) { + summary.append("\n"); //NON-NLS + } + summary.append("
").append( //NON-NLS + NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone")) + .append("").append(img.getTimeZone()).append("
").append( //NON-NLS + NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path")) + .append("").append(imgPath).append("
\n"); //NON-NLS + } + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS + } + 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")); + summary.append("
\n"); + summary.append("\n"); + summary.append("\n"); + Map moduleInfoHashMap = new HashMap<>(); + for (IngestJobInfo ingestJob : ingestJobs) { + List ingestModules = ingestJob.getIngestModuleInfo(); + for (IngestModuleInfo ingestModule : ingestModules) { + if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) { + moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule); + } + } + } + TreeMap modules = new TreeMap<>(); + for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) { + modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion()); + } + for (Map.Entry module : modules.entrySet()) { + summary.append("\n"); + } + summary.append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion")) + .append("").append(Version.getVersion()).append("
").append(module.getKey()).append(" Module:") + .append("").append(module.getValue()).append("
\n"); + summary.append("
\n"); + 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 { + summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading")); + summary.append("
\n"); + int jobnumber = 1; + + for (IngestJobInfo ingestJob : ingestJobs) { + summary.append("

Job ").append(jobnumber).append(":

\n"); + summary.append("\n"); + summary.append("\n"); + summary.append("\n"); + summary.append("\n"); + summary.append("
").append("Data Source:") + .append("").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("
").append("Status:") + .append("").append(ingestJob.getStatus()).append("
").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading")) + .append(""); + List ingestModules = ingestJob.getIngestModuleInfo(); + summary.append("
    \n"); + for (IngestModuleInfo ingestModule : ingestModules) { + summary.append("
  • ").append(ingestModule.getDisplayName()).append("
  • "); + } + summary.append("
\n"); + jobnumber++; + summary.append("
\n"); + } + summary.append("
\n"); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report."); + } + return summary; + } + private String prepareThumbnail(AbstractFile file) { BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM); File thumbFile = Paths.get(thumbsPath, file.getName() + ".png").toFile(); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java index c6fd374c36..9d3fd1bf41 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportProgressPanel.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"); @@ -19,7 +19,10 @@ package org.sleuthkit.autopsy.report; import org.openide.util.NbBundle; -import java.awt.*; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.EventQueue; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; @@ -30,6 +33,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; /** * A panel used by a report generation module to show progress. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ReportProgressPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java index 93acae830a..a032f44381 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel1.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,14 +40,18 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.python.JythonModuleLoader; +/** + * Display reports modules. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { private static final Logger logger = Logger.getLogger(ReportVisualPanel1.class.getName()); - private ReportWizardPanel1 wizPanel; - private List modules = new ArrayList<>(); - private List generalModules = new ArrayList<>(); - private List tableModules = new ArrayList<>(); - private List fileModules = new ArrayList<>(); + private final ReportWizardPanel1 wizPanel; + private final List modules = new ArrayList<>(); + private final List generalModules = new ArrayList<>(); + private final List tableModules = new ArrayList<>(); + private final List fileModules = new ArrayList<>(); private Integer selectedIndex; /** diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java index 98160874c9..82de40aac9 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportVisualPanel2.java @@ -49,11 +49,15 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +/** + * Display data on which to allow reports module to report. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ReportVisualPanel2 extends JPanel { - private ReportWizardPanel2 wizPanel; - private Map tagStates = new LinkedHashMap<>(); - private List tags = new ArrayList<>(); + private final ReportWizardPanel2 wizPanel; + private final Map tagStates = new LinkedHashMap<>(); + private final List tags = new ArrayList<>(); ArtifactSelectionDialog dialog = new ArtifactSelectionDialog((JFrame) WindowManager.getDefault().getMainWindow(), true); private Map artifactStates = new HashMap<>(); private List artifacts = new ArrayList<>(); @@ -367,11 +371,11 @@ final class ReportVisualPanel2 extends JPanel { public Component getListCellRendererComponent(JList list, String value, int index, boolean isSelected, boolean cellHasFocus) { if (value != null) { setEnabled(list.isEnabled()); - setSelected(tagStates.get(value.toString())); + setSelected(tagStates.get(value)); setFont(list.getFont()); setBackground(list.getBackground()); setForeground(list.getForeground()); - setText(value.toString()); + setText(value); return this; } return new JLabel(); diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java index 5dceb24650..eb844b2bba 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardFileOptionsVisualPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,15 +35,14 @@ import org.openide.util.NbBundle; /** * Visual component of the File Report Configuration panel of the Report Wizard. - * - * @author jwallace */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ReportWizardFileOptionsVisualPanel extends javax.swing.JPanel { private List options; - private Map optionStates = new EnumMap<>(FileReportDataTypes.class); + private final Map optionStates = new EnumMap<>(FileReportDataTypes.class); private ListModel model; - private ReportWizardFileOptionsPanel wizPanel; + private final ReportWizardFileOptionsPanel wizPanel; public ReportWizardFileOptionsVisualPanel(ReportWizardFileOptionsPanel wizPanel) { this.wizPanel = wizPanel; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index 9d5d41c836..f87851fe87 100644 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -44,6 +44,7 @@ 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,6 +54,7 @@ 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; @@ -538,6 +540,65 @@ 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() + ")) "; //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 } else { @@ -546,16 +607,25 @@ class TableReportGenerator { 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 += "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<>(); @@ -579,6 +649,7 @@ class TableReportGenerator { 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 @@ -588,9 +659,10 @@ class TableReportGenerator { } 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 +680,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 = keywordListsQuery + " UNION " + keywordAdHocQuery + orderByClause; try (SleuthkitCase.CaseDbQuery dbQuery = openCase.getSleuthkitCase().executeQuery(keywordsQuery)) { ResultSet resultSet = dbQuery.getResultSet(); @@ -1623,14 +1710,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/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java index 06484468a4..5f95f18bbd 100644 --- a/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/report/taggedhashes/AddTaggedHashesToHashDbConfigPanel.java @@ -46,6 +46,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this class are used to configure the report module plug in that * provides a convenient way to add content hashes to hash set databases. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddTaggedHashesToHashDbConfigPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java new file mode 100644 index 0000000000..d54ce01120 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/PerCaseTimelineProperties.java @@ -0,0 +1,175 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016-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.timeline; + +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.Objects; +import java.util.Properties; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Provides access to per-case timeline properties (key-value store). + */ +class PerCaseTimelineProperties { + + private static final String STALE_KEY = "stale"; //NON-NLS + private static final String WAS_INGEST_RUNNING_KEY = "was_ingest_running"; // NON-NLS + + private final Path propertiesPath; + + PerCaseTimelineProperties(Case autopsyCase) { + Objects.requireNonNull(autopsyCase, "Case must not be null"); + propertiesPath = Paths.get(autopsyCase.getModuleDirectory(), "Timeline", "timeline.properties"); //NON-NLS + } + + /** + * Is the DB stale, i.e. does it need to be updated because new datasources + * (eg) have been added to the case. + * + * @return true if the db is stale + * + * @throws IOException if there is a problem reading the state from disk + */ + public synchronized boolean isDBStale() throws IOException { + + String stale = getProperty(STALE_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + + } + + /** + * record the state of the events db as stale(true) or not stale(false). + * + * @param stale the new state of the event db. true for stale, false for not + * stale. + * + * @throws IOException if there was a problem writing the state to disk. + */ + public synchronized void setDbStale(Boolean stale) throws IOException { + setProperty(STALE_KEY, stale.toString()); + } + + /** + * Was ingest running the last time the database was updated? + * + * @return true if ingest was running the last time the db was updated + * + * @throws IOException if there was a problem reading from disk + */ + public synchronized boolean wasIngestRunning() throws IOException { + String stale = getProperty(WAS_INGEST_RUNNING_KEY); + return StringUtils.isBlank(stale) ? true : Boolean.valueOf(stale); + } + + /** + * record whether ingest was running during the last time the database was + * updated + * + * @param ingestRunning true if ingest was running + * + * @throws IOException if there was a problem writing to disk + */ + public synchronized void setIngestRunning(Boolean ingestRunning) throws IOException { + setProperty(WAS_INGEST_RUNNING_KEY, ingestRunning.toString()); + } + + /** + * Get a {@link Path} to the properties file. If the file does not exist, it + * will be created. + * + * @return the Path to the properties file. + * + * @throws IOException if there was a problem creating the properties file + */ + private synchronized Path getPropertiesPath() throws IOException { + + if (!Files.exists(propertiesPath)) { + Path parent = propertiesPath.getParent(); + Files.createDirectories(parent); + Files.createFile(propertiesPath); + } + return propertiesPath; + } + + /** + * Returns the property with the given key. + * + * @param propertyKey - The property key to get the value for. + * + * @return - the value associated with the property. + * + * @throws IOException if there was a problem reading the property from disk + */ + private synchronized String getProperty(String propertyKey) throws IOException { + return getProperties().getProperty(propertyKey); + } + + /** + * Sets the given property to the given value. + * + * @param propertyKey - The key of the property to be modified. + * @param propertyValue - the value to set the property to. + * + * @throws IOException if there was a problem writing the property to disk + */ + private synchronized void setProperty(String propertyKey, String propertyValue) throws IOException { + Path propertiesFile = getPropertiesPath(); + Properties props = getProperties(propertiesFile); + props.setProperty(propertyKey, propertyValue); + + try (OutputStream fos = Files.newOutputStream(propertiesFile)) { + props.store(fos, ""); //NON-NLS + } + } + + /** + * Get a {@link Properties} object used to store the timeline properties. + * + * @return a properties object + * + * @throws IOException if there was a problem reading the .properties file + */ + private synchronized Properties getProperties() throws IOException { + return getProperties(getPropertiesPath()); + } + + /** + * Gets a {@link Properties} object populated form the given .properties + * file. + * + * @param propertiesFile a path to the .properties file to load + * + * @return a properties object + * + * @throws IOException if there was a problem reading the .properties file + */ + private synchronized Properties getProperties(final Path propertiesFile) throws IOException { + try (InputStream inputStream = Files.newInputStream(propertiesFile)) { + Properties props = new Properties(); + props.load(inputStream); + return props; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 864b726c55..3d5bea437c 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -74,6 +74,7 @@ import org.sleuthkit.datamodel.timeline.SingleEvent; * 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 3d19bd4226..2f6c6ed559 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -87,9 +87,10 @@ import org.sleuthkit.datamodel.TskCoreException; persistenceType = TopComponent.PERSISTENCE_NEVER) @TopComponent.Registration(mode = "timeline", openAtStartup = false) @RetainLocation("timeline") +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider { - private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName()); + private static final Logger logger = Logger.getLogger(TimeLineTopComponent.class.getName()); @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final DataContentExplorerPanel contentViewerPanel; @@ -182,7 +183,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer explorerManager.setSelectedNodes(childArray); } catch (PropertyVetoException ex) { //I don't know why this would ever happen. - LOGGER.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS + logger.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS } //if there is only one event selected push it into content viewer. if (childArray.length == 1) { @@ -193,9 +194,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer }); } catch (NoCurrentCaseException ex) { //Since the case is closed, the user probably doesn't care about this, just log it as a precaution. - LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + logger.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS + logger.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS Platform.runLater(() -> { Notifications.create() .owner(jFXViewPanel.getScene().getWindow()) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index b7fe2de313..ac0d9d10a7 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -66,7 +66,6 @@ public class SaveSnapshotAsReport extends Action { private static final ButtonType OPEN = new ButtonType(Bundle.OpenReportAction_DisplayName(), ButtonBar.ButtonData.NO); private static final ButtonType OK = new ButtonType(ButtonType.OK.getText(), ButtonBar.ButtonData.CANCEL_CLOSE); - private final TimeLineController controller; private final Case currentCase; /** @@ -96,7 +95,6 @@ public class SaveSnapshotAsReport extends Action { setLongText(Bundle.SaveSnapShotAsReport_action_longText()); setGraphic(new ImageView(SNAP_SHOT)); - this.controller = controller; this.currentCase = controller.getAutopsyCase(); setEventHandler(actionEvent -> { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java new file mode 100644 index 0000000000..0139614836 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/filters/TagNameFilter.java @@ -0,0 +1,78 @@ +/* + * Autopsy Forensic Browser + * + * 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.timeline.filters; + +import java.util.Objects; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.TagName; + +/** + * Filter for an individual TagName + */ +public class TagNameFilter extends AbstractFilter { + + private final TagName tagName; + private final Case autoCase; + + public TagNameFilter(TagName tagName, Case autoCase) { + this.autoCase = autoCase; + this.tagName = tagName; + setSelected(Boolean.TRUE); + } + + public TagName getTagName() { + return tagName; + } + + @Override + synchronized public TagNameFilter copyOf() { + TagNameFilter filterCopy = new TagNameFilter(getTagName(), autoCase); + filterCopy.setSelected(isSelected()); + filterCopy.setDisabled(isDisabled()); + return filterCopy; + } + + @Override + public String getDisplayName() { + return tagName.getDisplayName(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.tagName); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TagNameFilter other = (TagNameFilter) obj; + if (!Objects.equals(this.tagName, other.tagName)) { + return false; + } + + return isSelected() == other.isSelected(); + } +} 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 99dbd9204d..ce96fc0efe 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 @@ -27,6 +27,8 @@ import java.util.Set; import java.util.HashSet; import java.nio.file.Path; import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.stream.Collectors; import junit.framework.Test; import junit.framework.TestCase; @@ -44,13 +46,15 @@ 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 { private static final String PROPERTIES_FILE = "CentralRepository"; private static final String CR_DB_NAME = "testcentralrepo.db"; private static final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); + private static final int DEFAULT_BULK_THRESHOLD = 1000; // hard coded from EamDb + SqliteEamDbSettings dbSettingsSqlite; private CorrelationCase case1; @@ -61,6 +65,7 @@ public class CentralRepoDatamodelTest extends TestCase { private EamOrganization org1; private EamOrganization org2; CorrelationAttribute.Type fileType; + CorrelationAttribute.Type usbDeviceType; private Map propertiesMap = null; @@ -110,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); } @@ -128,30 +133,32 @@ public class CentralRepoDatamodelTest extends TestCase { case2 = EamDb.getInstance().newCase(case2); assertTrue("Failed to create test object case2", case2 != null); - dataSource1fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource1_deviceID", "dataSource1"); + dataSource1fromCase1 = new CorrelationDataSource(case1, "dataSource1_deviceID", "dataSource1"); EamDb.getInstance().newDataSource(dataSource1fromCase1); dataSource1fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource1fromCase1.getDeviceID()); assertTrue("Failed to create test object dataSource1fromCase1", dataSource1fromCase1 != null); - dataSource2fromCase1 = new CorrelationDataSource(case1.getID(), "dataSource2_deviceID", "dataSource2"); + dataSource2fromCase1 = new CorrelationDataSource(case1, "dataSource2_deviceID", "dataSource2"); EamDb.getInstance().newDataSource(dataSource2fromCase1); dataSource2fromCase1 = EamDb.getInstance().getDataSource(case1, dataSource2fromCase1.getDeviceID()); assertTrue("Failed to create test object dataSource2fromCase1", dataSource2fromCase1 != null); - dataSource1fromCase2 = new CorrelationDataSource(case2.getID(), "dataSource3_deviceID", "dataSource3"); + dataSource1fromCase2 = new CorrelationDataSource(case2, "dataSource3_deviceID", "dataSource3"); EamDb.getInstance().newDataSource(dataSource1fromCase2); dataSource1fromCase2 = EamDb.getInstance().getDataSource(case2, dataSource1fromCase2.getDeviceID()); assertTrue("Failed to create test object dataSource1fromCase2", dataSource1fromCase2 != null); org1 = new EamOrganization("org1"); - org1.setOrgID((int) EamDb.getInstance().newOrganization(org1)); + org1 = EamDb.getInstance().newOrganization(org1); org2 = new EamOrganization("org2"); - org2.setOrgID((int) EamDb.getInstance().newOrganization(org2)); + org2 = EamDb.getInstance().newOrganization(org2); // Store the file type object for later use fileType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); assertTrue("getCorrelationTypeById(FILES_TYPE_ID) returned null", fileType != null); + usbDeviceType = EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.USBID_TYPE_ID); + assertTrue("getCorrelationTypeById(USBID_TYPE_ID) returned null", usbDeviceType != null); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -467,7 +474,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Create the first list, which will have (bulkThreshold / 2) entries List list1 = new ArrayList<>(); - for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() / 2; i++) { + for (int i = 0; i < DEFAULT_BULK_THRESHOLD / 2; i++) { String value = "bulkInsertValue1_" + String.valueOf(i); String path = "C:\\bulkInsertPath1\\file" + String.valueOf(i); @@ -487,7 +494,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Make a second list with length equal to bulkThreshold List list2 = new ArrayList<>(); - for (int i = 0; i < dbSettingsSqlite.getBulkThreshold(); i++) { + for (int i = 0; i < DEFAULT_BULK_THRESHOLD; i++) { String value = "bulkInsertValue2_" + String.valueOf(i); String path = "C:\\bulkInsertPath2\\file" + String.valueOf(i); @@ -503,7 +510,7 @@ public class CentralRepoDatamodelTest extends TestCase { // There should now be bulkThreshold artifacts in the database long count = EamDb.getInstance().getCountArtifactInstancesByCaseDataSource(case1.getCaseUUID(), dataSource1fromCase1.getDeviceID()); - assertTrue("Artifact count " + count + " does not match bulkThreshold " + dbSettingsSqlite.getBulkThreshold(), count == dbSettingsSqlite.getBulkThreshold()); + assertTrue("Artifact count " + count + " does not match bulkThreshold " + DEFAULT_BULK_THRESHOLD, count == DEFAULT_BULK_THRESHOLD); // Now call bulkInsertArtifacts() to insert the rest of queue EamDb.getInstance().bulkInsertArtifacts(); @@ -629,6 +636,9 @@ public class CentralRepoDatamodelTest extends TestCase { String inDataSource1twicePath2 = "C:\\files\\path2.txt"; String onlyInDataSource3Hash = "2af54305f183778d87de0c70c591fae4"; String onlyInDataSource3Path = "C:\\files\\path3.txt"; + String callbackTestFilePath1 = "C:\\files\\processinstancecallback\\path1.txt"; + String callbackTestFilePath2 = "C:\\files\\processinstancecallback\\path2.txt"; + String callbackTestFileHash = "fb9dd8f04dacd3e82f4917f1a002223c"; // These will all go in dataSource1fromCase1 String emailValue = "test@gmail.com"; @@ -781,7 +791,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test adding instance with invalid data source ID try { - CorrelationDataSource badDS = new CorrelationDataSource(case1.getID(), "badDSUuid", "badDSName"); + CorrelationDataSource badDS = new CorrelationDataSource(case1, "badDSUuid", "badDSName"); CorrelationAttributeInstance inst = new CorrelationAttributeInstance(case1, badDS, "badPath"); failAttr.addInstance(inst); EamDb.getInstance().addArtifact(failAttr); @@ -995,6 +1005,25 @@ public class CentralRepoDatamodelTest extends TestCase { } catch (EamDbException ex) { // This is the expected behavior } + + // Test updating a correlation attribute instance comment + try { + CorrelationAttribute correlationAttribute = EamDb.getInstance().getCorrelationAttribute( + usbDeviceType, case1, dataSource1fromCase1, devIdValue, devIdPath); + assertNotNull("getCorrelationAttribute returned null", correlationAttribute); + + correlationAttribute.getInstances().get(0).setComment("new comment"); + EamDb.getInstance().updateAttributeInstanceComment(correlationAttribute); + + // Get a fresh copy to verify the update. + correlationAttribute = EamDb.getInstance().getCorrelationAttribute( + usbDeviceType, case1, dataSource1fromCase1, devIdValue, devIdPath); + assertEquals("updateAttributeInstanceComment did not set comment to \"new comment\".", + "new comment", correlationAttribute.getInstances().get(0).getComment()); + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } // Test getting count for dataSource1fromCase1 (includes all types) try { @@ -1066,6 +1095,34 @@ public class CentralRepoDatamodelTest extends TestCase { Exceptions.printStackTrace(ex); Assert.fail(ex); } + + // 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.processInstanceTable(fileType, 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().processInstanceTable(null, null); + Assert.fail("processinstance method failed to throw exception for null type value"); + } catch (EamDbException ex) { + // This is the expected + } } /** @@ -1283,7 +1340,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test adding a basic organization try { orgA = new EamOrganization(orgAname); - orgA.setOrgID((int) EamDb.getInstance().newOrganization(orgA)); + orgA = EamDb.getInstance().newOrganization(orgA); assertTrue("Organization ID is still -1 after adding to db", orgA.getOrgID() != -1); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -1294,7 +1351,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test adding an organization with additional fields try { orgB = new EamOrganization(orgBname, orgBpocName, orgBpocEmail, orgBpocPhone); - orgB.setOrgID((int) EamDb.getInstance().newOrganization(orgB)); + orgB = EamDb.getInstance().newOrganization(orgB); assertTrue("Organization ID is still -1 after adding to db", orgB.getOrgID() != -1); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -1386,14 +1443,13 @@ public class CentralRepoDatamodelTest extends TestCase { } // Test updating invalid org - // Shouldn't do anything + try { EamOrganization temp = new EamOrganization("invalidOrg"); - temp.setOrgID(3434); EamDb.getInstance().updateOrganization(temp); + Assert.fail("updateOrganization worked for invalid ID"); } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); + // this is the expected behavior } // Test updating null org @@ -1417,7 +1473,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test deleting existing org that isn't in use try { EamOrganization orgToDelete = new EamOrganization("deleteThis"); - orgToDelete.setOrgID((int) EamDb.getInstance().newOrganization(orgToDelete)); + orgToDelete = EamDb.getInstance().newOrganization(orgToDelete); int orgCount = EamDb.getInstance().getOrganizations().size(); EamDb.getInstance().deleteOrganization(orgToDelete); @@ -1431,7 +1487,7 @@ public class CentralRepoDatamodelTest extends TestCase { try { // Make a new org EamOrganization inUseOrg = new EamOrganization("inUseOrg"); - inUseOrg.setOrgID((int) EamDb.getInstance().newOrganization(inUseOrg)); + inUseOrg = EamDb.getInstance().newOrganization(inUseOrg); // Make a reference set that uses it EamGlobalSet tempSet = new EamGlobalSet(inUseOrg.getOrgID(), "inUseOrgTest", "1.0", TskData.FileKnown.BAD, false, fileType); @@ -1445,14 +1501,12 @@ public class CentralRepoDatamodelTest extends TestCase { } // Test deleting non-existent org - // Should do nothing try { EamOrganization temp = new EamOrganization("temp"); - temp.setOrgID(9876); EamDb.getInstance().deleteOrganization(temp); + Assert.fail("deleteOrganization failed to throw exception for non-existent organization"); } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); + // This is the expected behavior } // Test deleting null org @@ -1610,7 +1664,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Create a list of global file instances. Make enough that the bulk threshold should be hit once. Set instances = new HashSet<>(); String bulkTestHash = "bulktesthash_"; - for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { + for (int i = 0; i < DEFAULT_BULK_THRESHOLD * 1.5; i++) { String hash = bulkTestHash + String.valueOf(i); instances.add(new EamGlobalFileInstance(notableSet2id, hash, TskData.FileKnown.BAD, null)); } @@ -1619,7 +1673,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDb.getInstance().bulkInsertReferenceTypeEntries(instances, fileType); // There's no way to get a count of the number of entries in the database, so just do a spot check - if (dbSettingsSqlite.getBulkThreshold() > 10) { + if (DEFAULT_BULK_THRESHOLD > 10) { String hash = bulkTestHash + "10"; assertTrue("Sample bulk insert instance not found", EamDb.getInstance().isFileHashInReferenceSet(hash, notableSet2id)); } @@ -2141,7 +2195,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with valid case, name, and ID try { - dataSourceA = new CorrelationDataSource(case2.getID(), dataSourceAid, dataSourceAname); + dataSourceA = new CorrelationDataSource(case2, dataSourceAid, dataSourceAname); EamDb.getInstance().newDataSource(dataSourceA); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -2151,7 +2205,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with the same case, name, and ID try { - CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), dataSourceAid, dataSourceAname); + CorrelationDataSource temp = new CorrelationDataSource(case2, dataSourceAid, dataSourceAname); EamDb.getInstance().newDataSource(temp); Assert.fail("newDataSource did not throw exception from duplicate data source"); } catch (EamDbException ex) { @@ -2160,7 +2214,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with the same name and ID but different case try { - dataSourceB = new CorrelationDataSource(case1.getID(), dataSourceAid, dataSourceAname); + dataSourceB = new CorrelationDataSource(case1, dataSourceAid, dataSourceAname); EamDb.getInstance().newDataSource(dataSourceB); } catch (EamDbException ex) { Exceptions.printStackTrace(ex); @@ -2170,7 +2224,8 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with an invalid case ID try { - CorrelationDataSource temp = new CorrelationDataSource(5000, "tempID", "tempName"); + CorrelationCase correlationCase = new CorrelationCase("1", "test"); + CorrelationDataSource temp = new CorrelationDataSource(correlationCase, "tempID", "tempName"); EamDb.getInstance().newDataSource(temp); Assert.fail("newDataSource did not throw exception from invalid case ID"); } catch (EamDbException ex) { @@ -2179,7 +2234,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with null device ID try { - CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), null, "tempName"); + CorrelationDataSource temp = new CorrelationDataSource(case2, null, "tempName"); EamDb.getInstance().newDataSource(temp); Assert.fail("newDataSource did not throw exception from null device ID"); } catch (EamDbException ex) { @@ -2188,7 +2243,7 @@ public class CentralRepoDatamodelTest extends TestCase { // Test creating a data source with null name try { - CorrelationDataSource temp = new CorrelationDataSource(case2.getID(), "tempID", null); + CorrelationDataSource temp = new CorrelationDataSource(case2, "tempID", null); EamDb.getInstance().newDataSource(temp); Assert.fail("newDataSource did not throw exception from null name"); } catch (EamDbException ex) { @@ -2467,7 +2522,7 @@ public class CentralRepoDatamodelTest extends TestCase { List cases = new ArrayList<>(); String bulkTestUuid = "bulkTestUUID_"; String bulkTestName = "bulkTestName_"; - for (int i = 0; i < dbSettingsSqlite.getBulkThreshold() * 1.5; i++) { + for (int i = 0; i < DEFAULT_BULK_THRESHOLD * 1.5; i++) { String name = bulkTestUuid + String.valueOf(i); String uuid = bulkTestName + String.valueOf(i); cases.add(new CorrelationCase(uuid, name)); @@ -2499,7 +2554,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) { @@ -2618,4 +2673,34 @@ public class CentralRepoDatamodelTest extends TestCase { } } + public class AttributeInstanceTableCallback implements InstanceTableCallback { + + int counterNamingConvention = 0; + int counter = 0; + + @Override + public void process(ResultSet resultSet) { + try { + while(resultSet.next()){ + if(InstanceTableCallback.getFilePath(resultSet).contains("processinstancecallback")){ + counterNamingConvention++; + }else{ + counter++; + } + } + } catch (SQLException ex) { + Exceptions.printStackTrace(ex); + } + } + + public int getCounter() { + return counter; + } + + public int getCounterNamingConvention(){ + return counterNamingConvention; + } + + } + } 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 new file mode 100644 index 0000000000..a599763346 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithHashAndFileType.java @@ -0,0 +1,465 @@ +/* + * + * 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/IngestedWithNoFileTypes.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypes.java new file mode 100644 index 0000000000..0090d0f699 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IngestedWithNoFileTypes.java @@ -0,0 +1,140 @@ +/* + * + * 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 static junit.framework.Assert.assertTrue; +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 org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import static org.sleuthkit.autopsy.testutils.IngestUtils.getIngestModuleTemplate; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Ingested w/o mime type info added to DB. + * + * Setup: + * + * 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 static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(IngestedWithNoFileTypes.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + private final IntraCaseUtils utils; + + public IngestedWithNoFileTypes(String name) { + super(name); + + this.utils = new IntraCaseUtils(this, "IngestedWithNoFileTypes"); + } + + @Override + public void setUp() { + this.utils.setUp(); + + IngestModuleTemplate hashLookupTemplate = getIngestModuleTemplate(new HashLookupModuleFactory()); + + ArrayList templates = new ArrayList<>(); + templates.add(hashLookupTemplate); + + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestedWithHashAndFileType.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(); + } + + /** + * Search using all data sources and filtering for media types. We should + * find nothing and no errors should arise. + */ + public void testOne() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, true, false); + CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); + + Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); + + List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); + + assertTrue(files.isEmpty()); + + } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + Exceptions.printStackTrace(ex); + } + } + + /** + * Search using single data source and filtering for doc types. Observe that + * nothing is found and that nothing blows up. + */ + public void testTwo() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long third = IntraCaseUtils.getDataSourceIdByName(IntraCaseUtils.SET3, dataSources); + + CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(third, dataSources, true, false); + CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); + + Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); + + List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); + + assertTrue(files.isEmpty()); + + } catch (NoCurrentCaseException | TskCoreException | SQLException 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/IntraCaseUtils.java new file mode 100644 index 0000000000..f81e561b38 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java @@ -0,0 +1,241 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.commonfilessearch; + +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 java.util.Set; +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.commonfilesearch.CommonFilesMetadata; +import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader; +import org.sleuthkit.autopsy.commonfilesearch.FileInstanceMetadata; +import org.sleuthkit.autopsy.commonfilesearch.Md5Metadata; +import org.sleuthkit.autopsy.testutils.CaseUtils; +import org.sleuthkit.autopsy.testutils.IngestUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * Provides setup and utility for testing presence of files in different data + * sets discoverable by Common Files Features. + * + * Data set definitions: + * + * set 1 + * + file1 + * - IMG_6175.jpg + * + file2 + * - IMG_6175.jpg + * + file3 + * - BasicStyleGuide.doc + * + * set 2 + * - adsf.pdf + * - IMG_6175.jpg + * + * set 3 + * - BasicStyleGuide.doc + * - IMG_6175.jpg + * + * set 4 + * - file.dat (empty file) + */ +class IntraCaseUtils { + + private static final String CASE_NAME = "IntraCaseCommonFilesSearchTest"; + static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); + + private final Path imagePath1; + 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_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(), SET1); + imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); + imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); + imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); + + this.dataSourceLoader = new DataSourceLoader(); + + this.caseName = caseName; + } + + void setUp() { + this.createAsCurrentCase(); + + final ImageDSProcessor imageDSProcessor = new ImageDSProcessor(); + + this.addImageOne(imageDSProcessor); + this.addImageTwo(imageDSProcessor); + this.addImageThree(imageDSProcessor); + this.addImageFour(imageDSProcessor); + } + + void addImageFour(final ImageDSProcessor imageDSProcessor) { + IngestUtils.addDataSource(imageDSProcessor, imagePath4); + } + + 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() { + CaseUtils.closeCurrentCase(false); + try { + CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH.toFile()); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + //does not represent a failure in the common files search feature + } + } + + /** + * 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 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 + */ + static boolean verifyFileExistanceAndCount(List files, Map objectIdToDataSource, String name, String dataSource, int count) { + + int tally = 0; + + for (AbstractFile file : files) { + + Long objectId = file.getId(); + + String fileName = file.getName(); + + String dataSourceName = objectIdToDataSource.get(objectId); + + if (fileName.equals(name) && dataSourceName.equals(dataSource)) { + tally++; + } + } + + return tally == count; + } + + /** + * 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 + */ + static boolean verifySingularFileExistance(List files, Map objectIdToDataSource, String name, String dataSource) { + return verifyFileExistanceAndCount(files, objectIdToDataSource, name, dataSource, 1); + } + + static Map mapFileInstancesToDataSources(CommonFilesMetadata metadata) { + Map instanceIdToDataSource = new HashMap<>(); + + for (Map.Entry> entry : metadata.getMetadata().entrySet()) { + for (Md5Metadata md : entry.getValue()) { + for (FileInstanceMetadata fim : md.getMetadata()) { + instanceIdToDataSource.put(fim.getObjectId(), fim.getDataSourceName()); + } + } + } + + return instanceIdToDataSource; + } + + static List getFiles(Set objectIds) { + List files = new ArrayList<>(objectIds.size()); + + for (Long id : objectIds) { + try { + AbstractFile file = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id); + files.add(file); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + return files; + } + + 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/MatchesInAtLeastTwoSources.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSources.java new file mode 100644 index 0000000000..ba178a59f6 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/MatchesInAtLeastTwoSources.java @@ -0,0 +1,128 @@ +/* + * + * 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.commonfilesearch.AllDataSourcesCommonFilesAlgorithm; +import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadata; +import org.sleuthkit.autopsy.commonfilesearch.CommonFilesMetadataBuilder; +import static org.sleuthkit.autopsy.commonfilessearch.IntraCaseUtils.*; +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 MatchesInAtLeastTwoSources extends NbTestCase { + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(MatchesInAtLeastTwoSources.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + private final IntraCaseUtils utils; + + public MatchesInAtLeastTwoSources(String name) { + super(name); + + this.utils = new IntraCaseUtils(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(IngestedWithHashAndFileType.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(); + + CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, false, false); + CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); + + Map objectIdToDataSource = IntraCaseUtils.mapFileInstancesToDataSources(metadata); + + List files = IntraCaseUtils.getFiles(objectIdToDataSource.keySet()); + + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, IMG, SET1, 0)); + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, IMG, SET4, 0)); + + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, DOC, SET1, 0)); + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, DOC, SET4, 0)); + + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, EMPTY, SET1, 0)); + assertTrue(IntraCaseUtils.verifyFileExistanceAndCount(files, dataSources, 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/UningestedCases.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCases.java new file mode 100644 index 0000000000..9bd7a02bae --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/UningestedCases.java @@ -0,0 +1,114 @@ +/* + * + * 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.Map; +import static junit.framework.Assert.assertEquals; +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.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; + +/** + * Test that cases which are created but have not run any ingest modules turn up + * no results. + * + * Setup: + * + * Add images set 1, set 2, set 3, and set 4 to case. Do not ingest. + * + */ +public class UningestedCases extends NbTestCase { + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(UningestedCases.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + private final IntraCaseUtils utils; + + public UningestedCases(String name) { + super(name); + + this.utils = new IntraCaseUtils(this, "UningestedCasesTests"); + } + + @Override + public void setUp(){ + this.utils.setUp(); + } + + @Override + public void tearDown(){ + this.utils.tearDown(); + } + + /** + * Find all matches & all file types. Confirm no matches are found (since + * there are no hashes to match). + */ + public void testOne() { + try { + Map dataSources = this.utils.getDataSourceMap(); + + CommonFilesMetadataBuilder allSourcesBuilder = new AllDataSourcesCommonFilesAlgorithm(dataSources, false, false); + CommonFilesMetadata metadata = allSourcesBuilder.findCommonFiles(); + + int resultCount = metadata.size(); + assertEquals(resultCount, 0); + + } catch (NoCurrentCaseException | TskCoreException | SQLException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + /** + * Find all matches on image #1 & all file types. Confirm no matches. + */ + public void testTwo() { + try { + Map dataSources = this.utils.getDataSourceMap(); + Long first = getDataSourceIdByName(SET1, dataSources); + + CommonFilesMetadataBuilder singleSourceBuilder = new SingleDataSource(first, dataSources, false, false); + CommonFilesMetadata metadata = singleSourceBuilder.findCommonFiles(); + + int resultCount = metadata.size(); + assertEquals(resultCount, 0); + + } catch (NoCurrentCaseException | TskCoreException | SQLException 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..8f562d698b 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 @@ -39,11 +39,13 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; 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_img1_v1.vhd"); public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; private static final int DEEP_FOLDER_COUNT = 25; private Case openCase; 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 42d6c83f6d..587b420d52 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/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java index a1bbd0ae24..224f2c9dd6 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -125,5 +125,4 @@ public final class CaseUtils { */ private CaseUtils() { } - } diff --git a/CoreLibs/build.xml b/CoreLibs/build.xml index f4c8cf0844..0e4a3701f8 100644 --- a/CoreLibs/build.xml +++ b/CoreLibs/build.xml @@ -20,8 +20,16 @@ - - + + + + + + + + + + diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 4031d14fc3..08ccc6fea1 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -56,6 +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-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 ca89b8c1c2..b2aa57c54e 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -619,6 +619,18 @@ org.joda.time.field org.joda.time.format org.joda.time.tz + org.opencv.calib3d + org.opencv.contrib + org.opencv.core + org.opencv.features2d + org.opencv.gpu + org.opencv.highgui + org.opencv.imgproc + org.opencv.ml + org.opencv.objdetect + org.opencv.photo + org.opencv.utils + org.opencv.video org.openxmlformats.schemas.drawingml.x2006.chart org.openxmlformats.schemas.drawingml.x2006.chart.impl org.openxmlformats.schemas.drawingml.x2006.main @@ -733,6 +745,10 @@ ext/jfxtras-common-8.0-r4.jar release/modules/ext/jfxtras-common-8.0-r4.jar + + ext/opencv-248.jar + release/modules/ext/opencv-248.jar + ext/jsr305-1.3.9.jar release/modules/ext/jsr305-1.3.9.jar diff --git a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java new file mode 100644 index 0000000000..7cfad05bc8 --- /dev/null +++ b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/OpenCvLoader.java @@ -0,0 +1,57 @@ +/* + * 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.corelibs; + +import org.opencv.core.Core; + +public final class OpenCvLoader { + + private static final boolean OPEN_CV_LOADED; + private static UnsatisfiedLinkError exception = null; + + static { + boolean tempOpenCvLoaded = false; + try { + System.loadLibrary(Core.NATIVE_LIBRARY_NAME); + tempOpenCvLoaded = true; + } catch (UnsatisfiedLinkError e) { + tempOpenCvLoaded = false; + exception = e; //save relevant error for throwing at appropriate time + } + OPEN_CV_LOADED = tempOpenCvLoaded; + } + + /** + * Return whether or not the OpenCV library has been loaded. + * + * @return - true if the opencv library is loaded or false if it is not + */ + public static boolean isOpenCvLoaded() throws UnsatisfiedLinkError { + if (!OPEN_CV_LOADED) { + //exception should never be null if the open cv isn't loaded but just in case + if (exception != null) { + throw exception; + } else { + throw new UnsatisfiedLinkError("OpenCV native library failed to load"); + } + + } + return OPEN_CV_LOADED; + } +} 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/AinStatusDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form index 9f0ddc84c8..9bd8b349dd 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.form @@ -26,7 +26,9 @@ - + + + @@ -45,6 +47,7 @@ + @@ -89,5 +92,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java index 9a80706699..3cfe48da5d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java @@ -23,10 +23,12 @@ import java.awt.EventQueue; import java.util.Observable; import java.util.Observer; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState; +import org.sleuthkit.autopsy.healthmonitor.HealthMonitorDashboard; /** * A dashboard for monitoring the existing AutoIngestNodes and their status. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AinStatusDashboard extends javax.swing.JPanel implements Observer { private final AutoIngestMonitor autoIngestMonitor; @@ -73,6 +75,7 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { clusterMetricsButton = new javax.swing.JButton(); nodeStatusScrollPane = new javax.swing.JScrollPane(); nodeStatusTableTitle = new javax.swing.JLabel(); + healthMonitorButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(refreshButton, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.refreshButton.text")); // NOI18N refreshButton.setToolTipText(org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.refreshButton.toolTipText")); // NOI18N @@ -92,6 +95,16 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { nodeStatusTableTitle.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(nodeStatusTableTitle, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.nodeStatusTableTitle.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(healthMonitorButton, org.openide.util.NbBundle.getMessage(AinStatusDashboard.class, "AinStatusDashboard.healthMonitorButton.text")); // NOI18N + healthMonitorButton.setMaximumSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.setMinimumSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.setPreferredSize(new java.awt.Dimension(133, 23)); + healthMonitorButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + healthMonitorButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -105,7 +118,9 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 715, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 576, Short.MAX_VALUE) + .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(clusterMetricsButton))) .addContainerGap()) ); @@ -122,7 +137,8 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { .addGap(382, 382, 382) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(refreshButton) - .addComponent(clusterMetricsButton)) + .addComponent(clusterMetricsButton) + .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); }// //GEN-END:initComponents @@ -141,9 +157,14 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { new AutoIngestMetricsDialog(this.getTopLevelAncestor()); }//GEN-LAST:event_clusterMetricsButtonActionPerformed + private void healthMonitorButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_healthMonitorButtonActionPerformed + new HealthMonitorDashboard(this.getTopLevelAncestor()).display(); + }//GEN-LAST:event_healthMonitorButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; + private javax.swing.JButton healthMonitorButton; private javax.swing.JScrollPane nodeStatusScrollPane; private javax.swing.JLabel nodeStatusTableTitle; private javax.swing.JButton refreshButton; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java index 6ea478699e..090932e154 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; @Messages({ "CTL_AinStatusDashboardAction=Auto Ingest Nodes", "CTL_AinStatusDashboardTopComponent=Auto Ingest Nodes"}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AinStatusDashboardTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java index 8516151027..1cc10fab76 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; * A panel which displays an outline view with all auto ingest nodes and their * status. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java index e0a5378444..3a8e844812 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ArchiveFilePanel.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.coreutils.PathValidator; * "zip", "rar", "arj", "7z", "7zip", "gzip, etc). Allows the user to select a * file. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ArchiveFilePanel extends JPanel implements DocumentListener { private static final Logger logger = Logger.getLogger(ArchiveFilePanel.class.getName()); 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 341632a083..db8e25a592 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -113,6 +113,7 @@ import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; "AutoIngestControlPanel.ConfigLockedTitle=Configuration directory locked", "AutoIngestControlPanel.PauseDueToSystemError=Paused due to system error, please consult the auto ingest system log" }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AutoIngestControlPanel extends JPanel implements Observer { private static final long serialVersionUID = 1L; @@ -141,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; @@ -268,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.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 5e4da63ab8..103160060a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -34,7 +34,7 @@ - + @@ -45,11 +45,8 @@ - + - - - @@ -77,11 +74,7 @@ - - - - - + @@ -184,34 +177,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index f191823dc2..031aa64daf 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -38,13 +38,12 @@ 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.healthmonitor.HealthMonitorDashboard; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.RefreshChildrenEvent; /** * A dashboard for monitoring an automated ingest cluster. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AutoIngestDashboard extends JPanel implements Observer { private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; @@ -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)); } /** @@ -331,8 +330,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { refreshButton = new javax.swing.JButton(); lbServicesStatus = new javax.swing.JLabel(); tbServicesStatusMessage = new javax.swing.JTextField(); - clusterMetricsButton = new javax.swing.JButton(); - healthMonitorButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N @@ -366,23 +363,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { tbServicesStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N tbServicesStatusMessage.setBorder(null); - org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N - clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - clusterMetricsButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(healthMonitorButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.healthMonitorButton.text")); // NOI18N - healthMonitorButton.setMaximumSize(new java.awt.Dimension(133, 23)); - healthMonitorButton.setMinimumSize(new java.awt.Dimension(133, 23)); - healthMonitorButton.setPreferredSize(new java.awt.Dimension(133, 23)); - healthMonitorButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - healthMonitorButtonActionPerformed(evt); - } - }); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -396,7 +376,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(lbServicesStatus) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE)) + .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 871, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) @@ -405,15 +385,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(clusterMetricsButton))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addContainerGap()) ); - - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton}); - layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() @@ -434,10 +408,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(completedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(refreshButton) - .addComponent(clusterMetricsButton) - .addComponent(healthMonitorButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(refreshButton) .addContainerGap()) ); }// //GEN-END:initComponents @@ -455,18 +426,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_refreshButtonActionPerformed - private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed - new AutoIngestMetricsDialog(this.getTopLevelAncestor()); - }//GEN-LAST:event_clusterMetricsButtonActionPerformed - - private void healthMonitorButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_healthMonitorButtonActionPerformed - new HealthMonitorDashboard(this.getTopLevelAncestor()).display(); - }//GEN-LAST:event_healthMonitorButtonActionPerformed - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; - private javax.swing.JButton healthMonitorButton; private javax.swing.JButton jButton1; private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbPending; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 195df4e4e7..4ce309b6ab 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; @Messages({ "CTL_AutoIngestDashboardAction=Auto Ingest Jobs", "CTL_AutoIngestDashboardTopComponent=Auto Ingest Jobs"}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class AutoIngestDashboardTopComponent extends TopComponent { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java index 25a3b3d495..4083d448f7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJob.java @@ -87,7 +87,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn private int numberOfCrashes; @GuardedBy("this") private StageDetails stageDetails; - + /* * Version 2 fields. */ @@ -100,7 +100,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn private List ingestThreadsSnapshot; private List ingestJobsSnapshot; private Map moduleRunTimesSnapshot; - + /** * Constructs a new automated ingest job. All job state not specified in the * job manifest is set to the default state for a new job. @@ -132,14 +132,14 @@ final class AutoIngestJob implements Comparable, IngestProgressSn this.processingStatus = ProcessingStatus.PENDING; this.numberOfCrashes = 0; this.stageDetails = this.getProcessingStageDetails(); - + /* * Version 2 fields. */ this.dataSourceSize = 0; - + /* - * Version 3 fields. + * Version 3 fields. */ this.ingestThreadsSnapshot = Collections.emptyList(); this.ingestJobsSnapshot = Collections.emptyList(); @@ -181,12 +181,12 @@ final class AutoIngestJob implements Comparable, IngestProgressSn this.processingStatus = nodeData.getProcessingStatus(); this.numberOfCrashes = nodeData.getNumberOfCrashes(); this.stageDetails = this.getProcessingStageDetails(); - + /* * Version 2 fields. */ this.dataSourceSize = nodeData.getDataSourceSize(); - + /* * Version 3 fields */ @@ -257,7 +257,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn * Sets the processing stage of the job. The start date/time for the stage * is set when the stage is set. * - * @param newStage The processing stage. + * @param newStage The processing stage. * @param stageStartDate The date and time this stage started. */ synchronized void setProcessingStage(Stage newStage, Date stageStartDate) { @@ -377,20 +377,22 @@ final class AutoIngestJob implements Comparable, IngestProgressSn /** * Sets the ingest job snapshot for the auto ingest job. - * @param snapshot + * + * @param snapshot */ synchronized void setIngestJobsSnapshot(List snapshot) { this.ingestJobsSnapshot = snapshot; } - + /** * Sets the module run times snapshot for the auto ingest job. - * @param snapshot + * + * @param snapshot */ synchronized void setModuleRuntimesSnapshot(Map snapshot) { this.moduleRunTimesSnapshot = snapshot; } - + /** * Cancels the job. */ @@ -589,7 +591,21 @@ final class AutoIngestJob implements Comparable, IngestProgressSn */ @Override public int compareTo(AutoIngestJob otherJob) { - return -this.getManifest().getDateFileCreated().compareTo(otherJob.getManifest().getDateFileCreated()); + int comparisonResult = -(this.getPriority().compareTo(otherJob.getPriority())); + if (comparisonResult == 0) { + //if the priority is the same compare with the jobs manifest creation date + comparisonResult = this.getManifest().getDateFileCreated().compareTo(otherJob.getManifest().getDateFileCreated()); + if (comparisonResult == 0) { + //if the manifest files were created at the same time compare with the jobs case name + comparisonResult = -this.getManifest().getCaseName().compareTo(otherJob.getManifest().getCaseName()); + if (comparisonResult == 0) { + //if the case name is the same compare with the jobs datasource file name + comparisonResult = -this.getManifest().getDataSourcePath().getFileName().toString().compareTo(otherJob.getManifest().getDataSourcePath().getFileName().toString()); + //if they are still the same at this point they may be ordered inconsistently + } + } + } + return comparisonResult; } @Override @@ -607,31 +623,6 @@ final class AutoIngestJob implements Comparable, IngestProgressSn return this.moduleRunTimesSnapshot; } - /** - * Comparator that supports doing a descending sort of jobs based on job - * completion date. - */ - static class CompletedDateDescendingComparator implements Comparator { - - @Override - public int compare(AutoIngestJob o1, AutoIngestJob o2) { - return -o1.getCompletedDate().compareTo(o2.getCompletedDate()); - } - - } - - /** - * Comparator that orders jobs in descending order by job priority. - */ - public static class PriorityComparator implements Comparator { - - @Override - public int compare(AutoIngestJob job, AutoIngestJob anotherJob) { - return -(job.getPriority().compareTo(anotherJob.getPriority())); - } - - } - /** * Comparator that orders jobs such that those running on the local host * appear first, then the remaining jobs are sorted alphabetically by case @@ -653,18 +644,6 @@ final class AutoIngestJob implements Comparable, IngestProgressSn } - /** - * Comparator that orders jobs by data source name. - */ - static class DataSourceFileNameComparator implements Comparator { - - @Override - public int compare(AutoIngestJob aJob, AutoIngestJob anotherJob) { - return aJob.getManifest().getDataSourceFileName().compareToIgnoreCase(anotherJob.getManifest().getDataSourceFileName()); - } - - } - /** * Processing statuses for an auto ingest job. */ @@ -730,7 +709,7 @@ final class AutoIngestJob implements Comparable, IngestProgressSn } } - + /** * Exception thrown when there is a problem creating auto ingest job. */ 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 15913dfba7..552e44834c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -23,6 +23,7 @@ import com.google.common.eventbus.Subscribe; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import org.openide.nodes.AbstractNode; @@ -32,7 +33,6 @@ 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.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; @@ -41,7 +41,7 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; * Each job with the specified status will have a child node representing it. */ final class AutoIngestJobsNode extends AbstractNode { - + //Event bus is non static so that each instance of this will only listen to events sent to that instance private final EventBus refreshChildrenEventBus; @@ -59,9 +59,14 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. + * + * @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(AutoIngestJobStatus status, EventBus eventBus) { - super(Children.create(new AutoIngestNodeChildren(status, eventBus), false)); + AutoIngestJobsNode(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { + super(Children.create(new AutoIngestNodeChildren(monitor, status, eventBus), false)); refreshChildrenEventBus = eventBus; } @@ -78,7 +83,7 @@ final class AutoIngestJobsNode extends AbstractNode { 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; @@ -86,11 +91,13 @@ 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(AutoIngestJobStatus status, EventBus eventBus) { - jobsSnapshot = new JobsSnapshot(); + AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status, EventBus eventBus) { + this.monitor = monitor; autoIngestJobStatus = status; refreshEventBus = eventBus; refreshChildrenSubscriber.register(refreshEventBus); @@ -101,14 +108,14 @@ final class AutoIngestJobsNode extends AbstractNode { List jobs; switch (autoIngestJobStatus) { case PENDING_JOB: - jobs = jobsSnapshot.getPendingJobs(); - jobs.sort(new AutoIngestJob.PriorityComparator()); + 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<>(); @@ -159,7 +166,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); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 7818dd8d68..fb6f79ab0c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -30,12 +30,12 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent; /** * A panel which displays an outline view with all jobs for a specified status. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; @@ -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(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 9040c63709..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,12 +405,14 @@ 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); // Add to pending jobs table and re-sort. pendingJobs.add(job); - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); setChanged(); notifyObservers(Event.REPROCESS_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(); @@ -461,6 +490,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen 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 @@ -656,12 +702,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } 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(); } } @@ -705,12 +752,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } 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(); } } @@ -755,13 +803,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } 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(); } } @@ -811,13 +861,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } 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(); } } @@ -868,7 +920,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } } - Collections.sort(pendingJobs, new AutoIngestJob.PriorityComparator()); + Collections.sort(pendingJobs); } } @@ -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; @@ -1168,7 +1220,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen newPendingJobsList.clear(); newCompletedJobsList.clear(); Files.walkFileTree(rootInputDirectory, EnumSet.of(FOLLOW_LINKS), Integer.MAX_VALUE, this); - Collections.sort(newPendingJobsList, new AutoIngestJob.PriorityComparator()); + Collections.sort(newPendingJobsList); AutoIngestManager.this.pendingJobs = newPendingJobsList; AutoIngestManager.this.completedJobs = newCompletedJobsList; @@ -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(); } } @@ -2746,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()) { @@ -2754,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/AutoIngestMetricsDialog.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsDialog.java index de90616481..dcd6fd4982 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsDialog.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMetricsDialog.java @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMetricsCollector. /** * Displays auto ingest metrics for a cluster. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AutoIngestMetricsDialog extends javax.swing.JDialog { private static final int GIGABYTE_SIZE = 1073741824; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 4e262c738a..876a56c47e 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); @@ -652,7 +702,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen 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,26 @@ 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 +777,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 +881,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 +911,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen nodeState = State.UNKNOWN; break; } + lastSeenTime = Instant.now(); } String getName() { @@ -898,6 +921,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/AutoIngestRowSorter.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java index 31ecd5f4ca..4f90a12f17 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestRowSorter.java @@ -37,21 +37,25 @@ class AutoIngestRowSorter extends TableRowSorter @Override public void toggleSortOrder(int column) { - if (!this.getModel().getColumnClass(column).equals(Date.class) && !this.getModel().getColumnClass(column).equals(Integer.class)) { - //currently the only Integer column this sorter is being applied to is the Priority column - super.toggleSortOrder(column); //if it isn't a date or Integer column perform the regular sorting - } else { - ArrayList sortKeys = new ArrayList<>(getSortKeys()); - if (sortKeys.isEmpty() || sortKeys.get(0).getColumn() != column) { //sort descending - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else if (sortKeys.get(0).getSortOrder() == SortOrder.ASCENDING) { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.DESCENDING)); - } else { - sortKeys.removeIf(key -> key.getColumn() == column); - sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.ASCENDING)); - } - setSortKeys(sortKeys); + SortOrder firstSort = SortOrder.ASCENDING; + SortOrder secondSort = SortOrder.DESCENDING; + + if (this.getModel().getColumnClass(column).equals(Date.class) || this.getModel().getColumnClass(column).equals(Integer.class)) { + firstSort = SortOrder.DESCENDING; + secondSort = SortOrder.ASCENDING; } + + ArrayList sortKeys = new ArrayList<>(getSortKeys()); + if (sortKeys.isEmpty() || sortKeys.get(0).getSortOrder() == SortOrder.UNSORTED) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, firstSort)); + } else if (sortKeys.get(0).getSortOrder() == firstSort) { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, secondSort)); + } else { + sortKeys.removeIf(key -> key.getColumn() == column); + sortKeys.add(0, new RowSorter.SortKey(column, SortOrder.UNSORTED)); + } + setSortKeys(sortKeys); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 8e2853adf4..d53f52c0cc 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -220,7 +220,6 @@ AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables AutoIngestDashboard.refreshButton.text=&Refresh AutoIngestDashboard.jButton1.text=jButton1 AutoIngestMetricsDialog.reportTextArea.text= -AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report AutoIngestMetricsDialog.closeButton.text=Close AutoIngestMetricsDialog.datePicker.toolTipText=Choose a date @@ -258,8 +257,8 @@ AutoIngestControlPanel.bnPrioritizeJob.toolTipText=Move this folder to the top o AutoIngestControlPanel.bnPrioritizeCase.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestControlPanel.bnPrioritizeJob.actionCommand=Prioritize Job AutoIngestControlPanel.bnDeprioritizeJob.actionCommand=Deprioritize Job -AutoIngestDashboard.healthMonitorButton.text=Health Monitor AinStatusDashboard.refreshButton.toolTipText=Refresh displayed tables AinStatusDashboard.refreshButton.text=&Refresh AinStatusDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AinStatusDashboard.nodeStatusTableTitle.text=Auto Ingest Nodes +AinStatusDashboard.healthMonitorButton.text=Health Monitor diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java index 2e4866b010..55ad830fb4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseImportPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Color; -import java.awt.Cursor; import java.awt.Desktop; import java.awt.EventQueue; import java.awt.Font; @@ -43,8 +42,8 @@ import org.sleuthkit.autopsy.experimental.configuration.AutoIngestUserPreference /** * This panel shows up in a tab pane next to the copy files panel for the * automated ingest copy node. - * */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class CaseImportPanel extends javax.swing.JPanel implements ImportDoneCallback { private final CaseImportPanelController controller; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java index cea7338f51..517c1e46a6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java @@ -88,6 +88,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; * Global settings panel for data-source-level ingest modules that export and * catalog files based on user-defined export rules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class FileExporterSettingsPanel extends JPanel { private static final long serialVersionUID = 1L; 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.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java index 59e135c867..96d5009df2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AdvancedAutoIngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,14 @@ */ package org.sleuthkit.autopsy.experimental.configuration; -import java.util.ArrayList; -import java.util.Collection; import javax.swing.DefaultComboBoxModel; -import javax.swing.JComponent; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.core.UserPreferences; +/** + * Configuration panel for advanced settings, such as number of concurrent jobs, + * number of retries, etc. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AdvancedAutoIngestSettingsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java index 85737799e9..d88ece8ddd 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestSettingsPanel.java @@ -45,17 +45,18 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.FileExporterSettingsPanel; /** - * + * Configuration panel for auto ingest settings. */ @Messages({"AutoIngestSettingsPanel.examinerModeRadioButton.text=Examiner mode", "AutoIngestSettingsPanel.autoIngestModeRadioButton.text=Auto Ingest mode"}) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AutoIngestSettingsPanel extends javax.swing.JPanel { private final AutoIngestSettingsPanelController controller; private final JFileChooser fc = new JFileChooser(); private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(AutoIngestSettingsPanel.class.getName()); - private Integer oldIngestThreads; + private final Integer oldIngestThreads; private static final String MULTI_USER_SETTINGS_MUST_BE_ENABLED = NbBundle.getMessage(AutoIngestSettingsPanel.class, "AutoIngestSettingsPanel.validationErrMsg.MUdisabled"); enum OptionsUiMode { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/AutoIngestUserPreferences.java index 8cd99b89c4..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); } /** @@ -297,10 +288,7 @@ public final class AutoIngestUserPreferences { * @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)); } /** @@ -320,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); } /** @@ -341,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); } /** @@ -362,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); } /** @@ -385,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); } /** @@ -399,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); + } } /** @@ -408,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); } /** @@ -429,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); } /** @@ -443,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/NodeStatusLogPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/NodeStatusLogPanel.java index e17009feda..88163b67df 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/NodeStatusLogPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/configuration/NodeStatusLogPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,8 +39,9 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.experimental.autoingest.StatusDatabaseLogger; /** - * + * Node status log panel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class NodeStatusLogPanel extends javax.swing.JPanel { private static final String HOST_NAME_OR_IP_PROMPT = NbBundle.getMessage(NodeStatusLogPanel.class, "NodeStatusLogPanel.tbDbHostname.toolTipText"); @@ -57,6 +58,7 @@ public class NodeStatusLogPanel extends javax.swing.JPanel { /** * Creates new form DatabaseLogPanell + * @param jDialog */ public NodeStatusLogPanel(JDialog jDialog) { initComponents(); 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 new file mode 100644 index 0000000000..d6bef92ab8 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectectionFileIngestModule.java @@ -0,0 +1,179 @@ +/* + * 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.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; +import org.opencv.core.MatOfRect; +import org.opencv.highgui.Highgui; +import org.opencv.objdetect.CascadeClassifier; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.corelibs.OpenCvLoader; +import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +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; + +/** + * Data source module to detect objects in images. + */ +public class ObjectDetectectionFileIngestModule extends FileIngestModuleAdapter { + + private final static Logger logger = Logger.getLogger(ObjectDetectectionFileIngestModule.class.getName()); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private long jobId; + private Map classifiers; + private final IngestServices services = IngestServices.getInstance(); + private Blackboard blackboard; + + @Messages({"ObjectDetectionFileIngestModule.noClassifiersFound.subject=No classifiers found.", + "# {0} - classifierDir", "ObjectDetectionFileIngestModule.noClassifiersFound.message=No classifiers were found in {0}, object detection will not be executed."}) + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + jobId = context.getJobId(); + File classifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath()); + classifiers = new HashMap<>(); + //Load all classifiers found in PlatformUtil.getObjectDetectionClassifierPath() + if (OpenCvLoader.isOpenCvLoaded() && classifierDir.exists() && classifierDir.isDirectory()) { + for (File classifier : classifierDir.listFiles()) { + if (classifier.isFile() && FilenameUtils.getExtension(classifier.getName()).equalsIgnoreCase("xml")) { + classifiers.put(classifier.getName(), new CascadeClassifier(classifier.getAbsolutePath())); + } + } + } else { + throw new IngestModule.IngestModuleException("Unable to load classifiers for object detection module."); + } + if (refCounter.incrementAndGet(jobId) == 1 && classifiers.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage(ObjectDetectionModuleFactory.getModuleName(), + Bundle.ObjectDetectionFileIngestModule_noClassifiersFound_subject(), + Bundle.ObjectDetectionFileIngestModule_noClassifiersFound_message(PlatformUtil.getObjectDetectionClassifierPath()))); + } + try { + blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); + } catch (NoCurrentCaseException ex) { + throw new IngestModule.IngestModuleException("Exception while getting open case.", ex); + } + } + + @Messages({"# {0} - detectionCount", "ObjectDetectionFileIngestModule.classifierDetection.text=Classifier detected {0} object(s)"}) + @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; + try { + imageInMemory = IOUtils.toByteArray(inputStream); + } catch (IOException 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; + } + + 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 + try { + 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 + 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; + } + + if (!detectionRectangles.empty()) { + //if any detections occurred create an artifact for this classifier and file combination + try { + BlackboardArtifact artifact = file.newArtifact(TSK_OBJECT_DETECTED); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, + ObjectDetectionModuleFactory.getModuleName(), + classifierKey)); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + ObjectDetectionModuleFactory.getModuleName(), + Bundle.ObjectDetectionFileIngestModule_classifierDetection_text((int) detectionRectangles.size().height))); + + 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 + } + + /* + * Send an event to update the view with the new result. + */ + services.fireModuleDataEvent(new ModuleDataEvent(ObjectDetectionModuleFactory.getModuleName(), TSK_OBJECT_DETECTED, Collections.singletonList(artifact))); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", file.getParentPath() + file.getName()), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + } + } + + return IngestModule.ProcessResult.OK; + } + + @Override + public void shutDown() { + refCounter.decrementAndGet(jobId); + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java new file mode 100644 index 0000000000..829b4e5c02 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/objectdetection/ObjectDetectionModuleFactory.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.objectdetection; + +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * A factory that creates ingest modules which uses classifiers to detect + * objects in pictures. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class ObjectDetectionModuleFactory extends IngestModuleFactoryAdapter { + + + /** + * Get the name of the Object Detection module + * + * @return the name of the Object Detection module + */ + static String getModuleName() { + return Bundle.ObjectDetectionModuleFactory_moduleName_text(); + } + + @Messages({"ObjectDetectionModuleFactory.moduleName.text=Object Detection"}) + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @Messages({"ObjectDetectionModuleFactory.moduleDescription.text=Use object classifiers to identify objects in pictures."}) + @Override + public String getModuleDescription() { + return Bundle.ObjectDetectionModuleFactory_moduleDescription_text(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + return new ObjectDetectectionFileIngestModule(); + } + +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java index beb2bc0a3d..7fbcaac1f0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Uses {@link ImageGalleryPreferences} and {@link PerCaseProperties} to persist * settings */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ImageGalleryOptionsPanel extends javax.swing.JPanel { ImageGalleryOptionsPanel(ImageGalleryOptionsPanelController controller) { @@ -202,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/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index ec5b511b7f..a6ce70c3ac 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,6 +65,7 @@ import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList; "CTL_ImageGalleryAction=Image/Video Gallery", "CTL_ImageGalleryTopComponent=Image/Video Gallery" }) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider { public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS 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/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index ced485d9c6..accc19cb9a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -121,6 +121,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 +213,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); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java index 9afb18e100..c085af7a30 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.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.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Level; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; @@ -41,11 +42,13 @@ class AdHocSearchDelegator { private final List keywordLists; private List queryDelegates; + private final Set dataSourceIds; private static int resultWindowCount = 0; //keep track of unique window ids to display private static final Logger logger = Logger.getLogger(AdHocSearchDelegator.class.getName()); - public AdHocSearchDelegator(List keywordLists) { + public AdHocSearchDelegator(List keywordLists, Set dataSourceIds) { this.keywordLists = keywordLists; + this.dataSourceIds = dataSourceIds; init(); } @@ -56,6 +59,13 @@ class AdHocSearchDelegator { for (KeywordList keywordList : keywordLists) { for (Keyword keyword : keywordList.getKeywords()) { KeywordSearchQuery query = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); + + //Limit search to a set of data sources + if (dataSourceIds != null && !dataSourceIds.isEmpty()) { + final KeywordQueryFilter dataSourceFilter = new KeywordQueryFilter(KeywordQueryFilter.FilterType.DATA_SOURCE, dataSourceIds); + query.addFilter(dataSourceFilter); + } + queryDelegates.add(query); } } 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 13798614c5..a8dbfb6268 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.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"); @@ -20,20 +20,31 @@ package org.sleuthkit.autopsy.keywordsearch; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import org.sleuthkit.autopsy.ingest.IngestManager; import org.openide.util.NbBundle; - +import org.sleuthkit.datamodel.DataSource; +import javax.swing.DefaultListModel; /** * Common functionality among keyword search widgets / panels. This is extended * by the various panels and interfaces that perform the keyword searches. This * 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 List dataSources = new ArrayList<>(); + private final DefaultListModel dataSourceListModel = new DefaultListModel<>(); AdHocSearchPanel() { initListeners(); @@ -48,7 +59,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { Object newValue = evt.getNewValue(); if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { - int newFilesIndexed = ((Integer) newValue).intValue(); + int newFilesIndexed = ((Integer) newValue); filesIndexed = newFilesIndexed; postFilesIndexedChange(); } @@ -68,6 +79,13 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { */ abstract List getKeywordLists(); + /** + * Get a set of data source object ids that are selected. + * + * @return A set of selected object ids. + */ + abstract Set getDataSourcesSelected(); + /** * Set the number of files that have been indexed * @@ -110,8 +128,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(), @@ -119,8 +135,8 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new AdHocSearchDelegator(keywordLists); - + + AdHocSearchDelegator man = new AdHocSearchDelegator(keywordLists, getDataSourcesSelected()); if (man.validate()) { man.execute(); } else { @@ -128,4 +144,47 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { "AbstractKeywordSearchPerformer.search.invalidSyntaxHeader"), KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); } } + + /** + * Get a list of data source display name. + * + * @return The list of data source name + */ + synchronized List getDataSourceArray() { + List dsList = new ArrayList<>(); + 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); + dsList.add(displayName); + } + return dsList; + } + + /** + * Set dataSources + * @param dataSources A list of DataSource + */ + synchronized void setDataSources(List dataSources) { + this.dataSources = dataSources; + } + + /** + * 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 DataSourceListModel + * @return A list of DataSourceListModel + */ + final DefaultListModel getDataSourceListModel() { + return dataSourceListModel; + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AddKeywordsDialog.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AddKeywordsDialog.java index 5e42e5d468..d877c3ece6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AddKeywordsDialog.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AddKeywordsDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * Dialog to add one or more keywords to a list */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddKeywordsDialog extends javax.swing.JDialog { List newKeywords = new ArrayList<>(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 9774106659..ee49ae2c2b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -310,4 +310,6 @@ ExtractedContentPanel.pageOfLabel.text=of ExtractedContentPanel.pageTotalLabel.text=- ExtractedContentPanel.pageButtonsLabel.text=Page 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: diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties index 2442e5bbc4..201e85bde2 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties @@ -278,3 +278,4 @@ ExtractedContentPanel.pageOfLabel.text=of ExtractedContentPanel.pageTotalLabel.text=- ExtractedContentPanel.pageButtonsLabel.text=\u30da\u30fc\u30b8 ExtractedContentPanel.pagesLabel.text=\u30da\u30fc\u30b8\uff1a +DropdownSingleTermSearchPanel.ingestIndexLabel.text=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\uff1a 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 bfb20406e3..873824840b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.form @@ -25,32 +25,34 @@ + + - - - - - - - - - - - - + + + + + + + + + - - - + + + + + + @@ -171,6 +173,9 @@ + + + @@ -191,5 +196,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 89e59cd2eb..0c7566f52e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,14 +18,18 @@ */ 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.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.logging.Level; import javax.swing.JCheckBox; import javax.swing.JTable; @@ -63,6 +67,11 @@ class DropdownListSearchPanel extends AdHocSearchPanel { keywordsTableModel = new KeywordsTableModel(); initComponents(); customizeComponents(); + dataSourceList.setModel(getDataSourceListModel()); + + dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { + firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); + }); } static synchronized DropdownListSearchPanel getDefault() { @@ -194,6 +203,9 @@ class DropdownListSearchPanel extends AdHocSearchPanel { manageListsButton = new javax.swing.JButton(); searchAddButton = new javax.swing.JButton(); ingestIndexLabel = new javax.swing.JLabel(); + dataSourceCheckBox = new javax.swing.JCheckBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + dataSourceList = new javax.swing.JList<>(); setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11)); @@ -232,6 +244,7 @@ class DropdownListSearchPanel extends AdHocSearchPanel { }); searchAddButton.setFont(searchAddButton.getFont().deriveFont(searchAddButton.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); + searchAddButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/search-icon.png"))); // NOI18N searchAddButton.setText(org.openide.util.NbBundle.getMessage(DropdownListSearchPanel.class, "KeywordSearchListsViewerPanel.searchAddButton.text")); // NOI18N searchAddButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -242,33 +255,50 @@ class DropdownListSearchPanel extends AdHocSearchPanel { ingestIndexLabel.setFont(ingestIndexLabel.getFont().deriveFont(ingestIndexLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 10)); ingestIndexLabel.setText(org.openide.util.NbBundle.getMessage(DropdownListSearchPanel.class, "KeywordSearchListsViewerPanel.ingestIndexLabel.text")); // NOI18N + dataSourceCheckBox.setText(org.openide.util.NbBundle.getMessage(DropdownListSearchPanel.class, "DropdownListSearchPanel.dataSourceCheckBox.text")); // NOI18N + dataSourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourceCheckBoxActionPerformed(evt); + } + }); + + dataSourceList.setMinimumSize(new java.awt.Dimension(0, 200)); + jScrollPane1.setViewportView(dataSourceList); + 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() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(searchAddButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 220, Short.MAX_VALUE) - .addComponent(manageListsButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(ingestIndexLabel) - .addGap(0, 317, Short.MAX_VALUE))) - .addContainerGap()) + .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(jScrollPane1) ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {manageListsButton, searchAddButton}); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jSplitPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 268, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 7, Short.MAX_VALUE) - .addComponent(ingestIndexLabel) + .addComponent(jSplitPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .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) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(manageListsButton) - .addComponent(searchAddButton)) + .addComponent(searchAddButton) + .addComponent(ingestIndexLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE)) .addContainerGap()) ); }// //GEN-END:initComponents @@ -277,12 +307,19 @@ class DropdownListSearchPanel extends AdHocSearchPanel { SystemAction.get(KeywordSearchConfigurationAction.class).performAction(); }//GEN-LAST:event_manageListsButtonActionPerformed + private void dataSourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourceCheckBoxActionPerformed + updateDataSourceListModel(); + }//GEN-LAST:event_dataSourceCheckBoxActionPerformed + private void searchAddButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchAddButtonActionPerformed // TODO add your handling code here: }//GEN-LAST:event_searchAddButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox dataSourceCheckBox; + private javax.swing.JList dataSourceList; private javax.swing.JLabel ingestIndexLabel; + private javax.swing.JScrollPane jScrollPane1; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JTable keywordsTable; private javax.swing.JScrollPane leftPane; @@ -311,6 +348,24 @@ class DropdownListSearchPanel extends AdHocSearchPanel { searchAddButton.addActionListener(al); } + /** + * Get a set of data source object ids that are selected. + * @return A set of selected object ids. + */ + @Override + Set getDataSourcesSelected() { + Set dataSourceObjIdSet = new HashSet<>(); + for (Long key : getDataSourceMap().keySet()) { + String value = getDataSourceMap().get(key); + for (String dataSource : this.dataSourceList.getSelectedValuesList()) { + if (value.equals(dataSource)) { + dataSourceObjIdSet.add(key); + } + } + } + return dataSourceObjIdSet; + } + private class KeywordListsTableModel extends AbstractTableModel { //data @@ -592,4 +647,42 @@ class DropdownListSearchPanel extends AdHocSearchPanel { return this; } } + + /** + * Update the dataSourceListModel + */ + @NbBundle.Messages({"DropdownListSearchPanel.selected=Ad Hoc Search data source filter is selected"}) + void updateDataSourceListModel() { + getDataSourceListModel().removeAllElements(); + for (String dsName : getDataSourceArray()) { + getDataSourceListModel().addElement(dsName); + } + setComponentsEnabled(); + firePropertyChange(Bundle.DropdownListSearchPanel_selected(), null, null); + + } + + /** + * Set the dataSourceList enabled if the dataSourceCheckBox is selected + */ + private void setComponentsEnabled() { + + 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 54e3cdee7f..19fb132f0d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.form @@ -59,21 +59,23 @@ - - - - - - + + + + + + + + - + @@ -81,17 +83,23 @@ - - - - + - + + + + + + + + + + @@ -142,7 +150,6 @@ - @@ -168,5 +175,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index 8dd9a20b0b..bdc80084c7 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.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,16 +18,23 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import javax.swing.JMenuItem; +import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; /** * A dropdown panel that provides GUI components that allow a user to do three @@ -49,11 +56,14 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName()); private static DropdownSingleTermSearchPanel defaultInstance = null; + private boolean ingestRunning; + /** * Gets the default instance of a dropdown panel that provides GUI * components that allow a user to do three types of ad hoc single keyword * searches. + * * @return the default instance of DropdownSingleKeywordSearchPanel */ public static synchronized DropdownSingleTermSearchPanel getDefault() { @@ -67,9 +77,14 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { * Constructs a dropdown panel that provides GUI components that allow a * user to do three types of ad hoc single keyword searches. */ - public DropdownSingleTermSearchPanel() { + @NbBundle.Messages({"DropdownSingleTermSearchPanel.selected=Ad Hoc Search data source filter is selected"}) + private DropdownSingleTermSearchPanel() { initComponents(); customizeComponents(); + dataSourceList.setModel(getDataSourceListModel()); + this.dataSourceList.addListSelectionListener((ListSelectionEvent evt) -> { + firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); + }); } /** @@ -103,6 +118,22 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { keywordTextField.selectAll(); } }; + ingestRunning = IngestManager.getInstance().isIngestRunning(); + updateIngestIndexLabel(); + + IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + Object source = evt.getSource(); + if (source instanceof String && ((String) source).equals("LOCAL")) { //NON-NLS + EventQueue.invokeLater(() -> { + ingestRunning = IngestManager.getInstance().isIngestRunning(); + updateIngestIndexLabel(); + }); + } + } + }); + cutMenuItem.addActionListener(actList); copyMenuItem.addActionListener(actList); pasteMenuItem.addActionListener(actList); @@ -130,7 +161,7 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { exactRadioButton.setSelected(true); regexRadioButton.setEnabled(enabled); } - + /** * Gets a single keyword list consisting of a single keyword encapsulating * the input term(s)/phrase/substring/regex. @@ -138,10 +169,9 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { * @return The keyword list. */ @NbBundle.Messages({"DropdownSingleTermSearchPanel.warning.title=Warning", - "DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) + "DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) @Override List getKeywordLists() { - if (regexRadioButton.isSelected()) { if((keywordTextField.getText() != null) && (keywordTextField.getText().startsWith("^") || @@ -152,7 +182,6 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); } } - List keywords = new ArrayList<>(); keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected())); List keywordLists = new ArrayList<>(); @@ -165,6 +194,7 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { */ @Override protected void postFilesIndexedChange() { + updateIngestIndexLabel(); } /** @@ -187,6 +217,10 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { exactRadioButton = new javax.swing.JRadioButton(); substringRadioButton = new javax.swing.JRadioButton(); regexRadioButton = new javax.swing.JRadioButton(); + dataSourceCheckBox = new javax.swing.JCheckBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + dataSourceList = new javax.swing.JList<>(); + ingestIndexLabel = new javax.swing.JLabel(); org.openide.awt.Mnemonics.setLocalizedText(cutMenuItem, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.cutMenuItem.text")); // NOI18N rightClickMenu.add(cutMenuItem); @@ -225,7 +259,6 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { }); queryTypeButtonGroup.add(exactRadioButton); - exactRadioButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(exactRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.exactRadioButton.text")); // NOI18N queryTypeButtonGroup.add(substringRadioButton); @@ -234,6 +267,19 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { queryTypeButtonGroup.add(regexRadioButton); org.openide.awt.Mnemonics.setLocalizedText(regexRadioButton, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSearchPanel.regexRadioButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(dataSourceCheckBox, org.openide.util.NbBundle.getMessage(DropdownSingleTermSearchPanel.class, "DropdownSingleTermSearchPanel.dataSourceCheckBox.text")); // NOI18N + dataSourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourceCheckBoxActionPerformed(evt); + } + }); + + dataSourceList.setMinimumSize(new java.awt.Dimension(0, 200)); + jScrollPane1.setViewportView(dataSourceList); + + 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 + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -241,31 +287,39 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { .addGroup(layout.createSequentialGroup() .addGap(5, 5, 5) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(searchButton)) .addGroup(layout.createSequentialGroup() .addComponent(exactRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(substringRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(regexRadioButton) - .addGap(0, 27, Short.MAX_VALUE))) - .addGap(5, 5, 5)) + .addComponent(regexRadioButton)) + .addComponent(dataSourceCheckBox) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 297, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(searchButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ingestIndexLabel)) + .addComponent(keywordTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 296, 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() .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(searchButton, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE)) + .addComponent(keywordTextField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .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) + .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.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()) ); }// //GEN-END:initComponents @@ -303,10 +357,81 @@ public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { } }//GEN-LAST:event_keywordTextFieldMouseClicked + private void dataSourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dataSourceCheckBoxActionPerformed + updateDataSourceListModel(); + }//GEN-LAST:event_dataSourceCheckBoxActionPerformed + + /** + * Update the dataSourceListModel + */ + void updateDataSourceListModel() { + getDataSourceListModel().removeAllElements(); + for (String dsName : getDataSourceArray()) { + getDataSourceListModel().addElement(dsName); + } + setComponentsEnabled(); + firePropertyChange(Bundle.DropdownSingleTermSearchPanel_selected(), null, null); + } + + /** + * Set the dataSourceList enabled if the dataSourceCheckBox is selected + */ + private void setComponentsEnabled() { + 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]); + } + } + + /** + * Get a set of data source object ids that are selected. + * @return A set of selected object ids. + */ + @Override + Set getDataSourcesSelected() { + Set dataSourceObjIdSet = new HashSet<>(); + for (Long key : getDataSourceMap().keySet()) { + String value = getDataSourceMap().get(key); + for (String dataSource : this.dataSourceList.getSelectedValuesList()) { + if (value.equals(dataSource)) { + dataSourceObjIdSet.add(key); + } + } + } + return dataSourceObjIdSet; + } + + /** + * Update ingestIndexLabel + */ + private void updateIngestIndexLabel() { + if (ingestRunning) { + ingestIndexLabel.setText(NbBundle.getMessage(this.getClass(), "KeywordSearchListsViewerPanel.initIngest.ongoingIngestMsg", filesIndexed)); + } else { + ingestIndexLabel.setText(NbBundle.getMessage(this.getClass(), "KeywordSearchListsViewerPanel.initIngest.fileIndexCtMsg", filesIndexed)); + } + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem copyMenuItem; private javax.swing.JMenuItem cutMenuItem; + private javax.swing.JCheckBox dataSourceCheckBox; + private javax.swing.JList dataSourceList; private javax.swing.JRadioButton exactRadioButton; + private javax.swing.JLabel ingestIndexLabel; + private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField keywordTextField; private javax.swing.JMenuItem pasteMenuItem; private javax.swing.ButtonGroup queryTypeButtonGroup; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 11452e917b..03c9f87fe1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.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,20 +23,26 @@ import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.logging.Level; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; /** * A panel that provides a toolbar button for the dropdown keyword list search * panel and dropdown single keyword search panel. Displayed in the upper right * hand corner of the application by default. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class DropdownToolbar extends javax.swing.JPanel { private static final long serialVersionUID = 1L; @@ -45,7 +51,8 @@ class DropdownToolbar extends javax.swing.JPanel { private SearchSettingsChangeListener searchSettingsChangeListener; private boolean active = false; private DropdownSingleTermSearchPanel dropPanel = null; - + private DropdownListSearchPanel listsPanel = null; + private List dataSources = new ArrayList<>(); /** * Gets the singleton panel that provides a toolbar button for the dropdown * keyword list search panel and dropdown single keyword search panel. @@ -77,13 +84,13 @@ class DropdownToolbar extends javax.swing.JPanel { private void customizeComponents() { searchSettingsChangeListener = new SearchSettingsChangeListener(); KeywordSearch.getServer().addServerActionListener(searchSettingsChangeListener); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), searchSettingsChangeListener); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_ADDED), searchSettingsChangeListener); - DropdownListSearchPanel listsPanel = DropdownListSearchPanel.getDefault(); + listsPanel = DropdownListSearchPanel.getDefault(); listsPanel.addSearchButtonActionListener((ActionEvent e) -> { listsMenu.setVisible(false); }); - + listsPanel.addPropertyChangeListener(searchSettingsChangeListener); // Adding border of six to account for menu border listsMenu.setSize(listsPanel.getPreferredSize().width + 6, listsPanel.getPreferredSize().height + 6); listsMenu.add(listsPanel); @@ -105,6 +112,7 @@ class DropdownToolbar extends javax.swing.JPanel { }); dropPanel = DropdownSingleTermSearchPanel.getDefault(); + dropPanel.addPropertyChangeListener(searchSettingsChangeListener); dropPanel.addSearchButtonActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -139,6 +147,8 @@ class DropdownToolbar extends javax.swing.JPanel { if (evt != null && !SwingUtilities.isLeftMouseButton(evt)) { return; } + listsPanel.setDataSources(dataSources); + listsPanel.updateDataSourceListModel(); listsMenu.show(listsButton, listsButton.getWidth() - listsMenu.getWidth(), listsButton.getHeight() - 1); } @@ -149,6 +159,8 @@ class DropdownToolbar extends javax.swing.JPanel { if (evt != null && !SwingUtilities.isLeftMouseButton(evt)) { return; } + dropPanel.setDataSources(dataSources); + dropPanel.updateDataSourceListModel(); searchMenu.show(searchDropButton, searchDropButton.getWidth() - searchMenu.getWidth(), searchDropButton.getHeight() - 1); } @@ -198,6 +210,16 @@ class DropdownToolbar extends javax.swing.JPanel { disableSearch = true; } + //set the data source list + try { + dataSources = getDataSourceList(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting text index info", ex); //NON-NLS + disableSearch = true; + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting current case.", ex); //NON-NLS + disableSearch = true; + } if (disableSearch) { searchDropButton.setEnabled(false); listsButton.setEnabled(false); @@ -228,11 +250,24 @@ class DropdownToolbar extends javax.swing.JPanel { break; default: } + } else if (changed.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + DataSource newDataSource = (DataSource) evt.getNewValue(); + dataSources.add(newDataSource); } } } } - + + /** + * Get a list of DataSource from case database + * @return A list of DataSource + * @throws NoCurrentCaseException + * @throws TskCoreException + */ + private synchronized List getDataSourceList() throws NoCurrentCaseException, TskCoreException { + Case openCase = Case.getCurrentCaseThrows(); + return openCase.getSleuthkitCase().getDataSources(); + } /** * 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 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/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index da7cf3c459..cf990e9674 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.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"); @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** * GlobalEditListPanel widget to manage keywords in lists */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionListener, OptionsPanel { private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java index 2a362dc945..db3a1d3cf0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListSettingsPanel.java @@ -27,6 +27,10 @@ import javax.swing.JOptionPane; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +/** + * Configuration for keyword lists. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class GlobalListSettingsPanel extends javax.swing.JPanel implements OptionsPanel { private static final long serialVersionUID = 1L; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java index 1d0dce9189..56c1fcd4c5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalListsManagementPanel.java @@ -43,6 +43,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** * A panel to manage all keyword lists created/imported in Autopsy. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPanel { private static final long serialVersionUID = 1L; 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/KeywordSearchGlobalLanguageSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java index 8342587833..2cc6f6365b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalLanguageSettingsPanel.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; /** * Child panel of the global settings panel (Languages tab). */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class KeywordSearchGlobalLanguageSettingsPanel extends javax.swing.JPanel implements OptionsPanel { private final Map scripts = new HashMap<>(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSearchSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSearchSettingsPanel.java index e6c8a98662..d2a52a3e1c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSearchSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSearchSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2014 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.keywordsearch.KeywordSearchIngestModule.UpdateFrequ /** * General, not per list, keyword search configuration and status display widget */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class KeywordSearchGlobalSearchSettingsPanel extends javax.swing.JPanel implements OptionsPanel { private final Logger logger = Logger.getLogger(KeywordSearchGlobalSearchSettingsPanel.class.getName()); @@ -360,7 +361,7 @@ class KeywordSearchGlobalSearchSettingsPanel extends javax.swing.JPanel implemen Object newValue = evt.getNewValue(); if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { - int newFilesIndexed = ((Integer) newValue).intValue(); + int newFilesIndexed = ((Integer) newValue); filesIndexedValue.setText(Integer.toString(newFilesIndexed)); try { chunksValLabel.setText(Integer.toString(KeywordSearch.getServer().queryNumIndexedChunks())); 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/KeywordSearchJobSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchJobSettingsPanel.java index 5515b10115..c80e66d947 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchJobSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchJobSettingsPanel.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"); @@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** * Ingest job settings panel for keyword search file ingest modules. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class KeywordSearchJobSettingsPanel extends IngestModuleIngestJobSettingsPanel implements PropertyChangeListener { private final KeywordListsTableModel tableModel = new KeywordListsTableModel(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/NewKeywordPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/NewKeywordPanel.java index f96738be67..d62e4442af 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/NewKeywordPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/NewKeywordPanel.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"); @@ -24,6 +24,7 @@ package org.sleuthkit.autopsy.keywordsearch; * indicate whether they want the keyword to be an exact match, a substring, or * a regular expression. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class NewKeywordPanel extends javax.swing.JPanel { /** 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/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/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index b1adb5d40b..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 -#Mon, 19 Mar 2018 11:17:11 -0700 +#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.6.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 6cb9d4bdea..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, 09 Mar 2018 13:03:41 -0700 -CTL_MainWindow_Title=Autopsy 4.6.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.6.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..1ce749b501 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,10 +4,10 @@ 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 +build.type=RELEASE +#build.type=DEVELOPMENT project.org.netbeans.progress=org-netbeans-api-progress project.org.sleuthkit.autopsy.experimental=Experimental diff --git a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py index 14477f06df..6b43853f30 100644 --- a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py +++ b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py @@ -36,9 +36,11 @@ import jarray import inspect import os -import subprocess +import java.util.ArrayList as ArrayList from java.lang import Class from java.lang import System +from java.lang import ProcessBuilder +from java.io import File from java.util.logging import Level from org.sleuthkit.datamodel import SleuthkitCase from org.sleuthkit.datamodel import AbstractFile @@ -47,8 +49,10 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Image from org.sleuthkit.autopsy.ingest import IngestModule +from org.sleuthkit.autopsy.ingest import IngestJobContext from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException from org.sleuthkit.autopsy.ingest import DataSourceIngestModule +from org.sleuthkit.autopsy.ingest import DataSourceIngestModuleProcessTerminator from org.sleuthkit.autopsy.ingest import IngestModuleFactoryAdapter from org.sleuthkit.autopsy.ingest import IngestMessage from org.sleuthkit.autopsy.ingest import IngestServices @@ -58,6 +62,7 @@ from org.sleuthkit.autopsy.coreutils import PlatformUtil from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Services from org.sleuthkit.autopsy.datamodel import ContentUtils +from org.sleuthkit.autopsy.coreutils import ExecUtil # Factory that defines the name and details of the module and allows Autopsy @@ -102,10 +107,10 @@ class RunExeIngestModule(DataSourceIngestModule): # Get path to EXE based on where this script is run from. # Assumes EXE is in same folder as script # Verify it is there before any ingest starts - self.path_to_exe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe") - if not os.path.exists(self.path_to_exe): + exe_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe") + self.pathToEXE = File(exe_path) + if not self.pathToEXE.exists(): raise IngestModuleException("EXE was not found in module folder") - # Where the analysis is done. # The 'dataSource' object being passed in is of type org.sleuthkit.datamodel.Content. # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.4/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html @@ -115,7 +120,6 @@ class RunExeIngestModule(DataSourceIngestModule): # we don't know how much work there will be progressBar.switchToIndeterminate() - # Example has only a Windows EXE, so bail if we aren't on Windows if not PlatformUtil.isWindowsOS(): self.log(Level.INFO, "Ignoring data source. Not running on Windows") @@ -130,17 +134,27 @@ class RunExeIngestModule(DataSourceIngestModule): imagePaths = dataSource.getPaths() # We'll save our output to a file in the reports folder, named based on EXE and data source ID - reportPath = os.path.join(Case.getCurrentCase().getCaseDirectory(), "Reports", "img_stat-" + str(dataSource.getId()) + ".txt") - reportHandle = open(reportPath, 'w') - + reportFile = File(Case.getCurrentCase().getCaseDirectory() + "\\Reports" + "\\img_stat-" + str(dataSource.getId()) + ".txt") # Run the EXE, saving output to the report - # NOTE: we should really be checking for if the module has been - # cancelled and then killing the process. + # Check if the ingest is terminated and delete the incomplete report file + # Do not add report to the case tree if the ingest is cancelled before finish. + # This can be done by using IngestJobContext.dataSourceIngestIsCancelled + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.7.0/_ingest_job_context_8java.html self.log(Level.INFO, "Running program on data source") - subprocess.Popen([self.path_to_exe, imagePaths[0]], stdout=reportHandle).communicate()[0] - reportHandle.close() + cmd = ArrayList() + cmd.add(self.pathToEXE.toString()) + cmd.add(imagePaths[0]) + + processBuilder = ProcessBuilder(cmd); + processBuilder.redirectOutput(reportFile) + ExecUtil.execute(processBuilder,DataSourceIngestModuleProcessTerminator(self.context)) # Add the report to the case, so it shows up in the tree - Case.getCurrentCase().addReport(reportPath, "Run EXE", "img_stat output") - + if not self.context.dataSourceIngestIsCancelled(): + Case.getCurrentCase().addReport(reportFile.toString(), "Run EXE", "img_stat output") + else: + if reportFile.exists(): + if not reportFile.delete(): + self.log(LEVEL.warning,"Error deleting the incomplete report file") + return IngestModule.ProcessResult.OK 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-2413.jar b/thirdparty/opencv/ext/opencv-2413.jar new file mode 100644 index 0000000000..dccffb96a9 Binary files /dev/null and b/thirdparty/opencv/ext/opencv-2413.jar differ diff --git a/thirdparty/opencv/lib/amd64/opencv_ffmpeg2413_64.dll b/thirdparty/opencv/lib/amd64/opencv_ffmpeg2413_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/amd64/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/amd64/opencv_java2413.dll b/thirdparty/opencv/lib/amd64/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/amd64/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_ffmpeg2413.dll b/thirdparty/opencv/lib/i386/opencv_ffmpeg2413.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/i386/opencv_ffmpeg2413.dll differ diff --git a/thirdparty/opencv/lib/i386/opencv_java2413.dll b/thirdparty/opencv/lib/i386/opencv_java2413.dll new file mode 100644 index 0000000000..c25b6209f8 Binary files /dev/null and b/thirdparty/opencv/lib/i386/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i586/opencv_ffmpeg2413_64.dll b/thirdparty/opencv/lib/i586/opencv_ffmpeg2413_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/i586/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/i586/opencv_java2413.dll b/thirdparty/opencv/lib/i586/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/i586/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll b/thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/i686/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/i686/opencv_java2413.dll b/thirdparty/opencv/lib/i686/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/i686/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/x86/opencv_ffmpeg2413.dll b/thirdparty/opencv/lib/x86/opencv_ffmpeg2413.dll new file mode 100644 index 0000000000..b1e70df6a3 Binary files /dev/null and b/thirdparty/opencv/lib/x86/opencv_ffmpeg2413.dll differ diff --git a/thirdparty/opencv/lib/x86/opencv_java2413.dll b/thirdparty/opencv/lib/x86/opencv_java2413.dll new file mode 100644 index 0000000000..c25b6209f8 Binary files /dev/null and b/thirdparty/opencv/lib/x86/opencv_java2413.dll differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll new file mode 100644 index 0000000000..37236e5424 Binary files /dev/null and b/thirdparty/opencv/lib/x86_64/opencv_ffmpeg2413_64.dll differ diff --git a/thirdparty/opencv/lib/x86_64/opencv_java2413.dll b/thirdparty/opencv/lib/x86_64/opencv_java2413.dll new file mode 100644 index 0000000000..e1aeca4ba5 Binary files /dev/null and b/thirdparty/opencv/lib/x86_64/opencv_java2413.dll differ