diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java index 99bc2484c3..e849590dd2 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CaseDBCommonAttributeInstanceNode.java @@ -83,8 +83,8 @@ public class CaseDBCommonAttributeInstanceNode extends FileNode { final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText(); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName())); + this.addScoreProperty(sheetSet, tags); this.addCommentProperty(sheetSet, tags); - sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath())); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsCsvList(this.getContent()))); sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource())); diff --git a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java index bd864938c0..538ca0948b 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/RelationshipNode.java @@ -67,8 +67,8 @@ final class RelationshipNode extends BlackboardArtifactNode { } sheetSet.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName())); + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); - final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID()); if (null != fromID) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index d2754a6ddd..81297b6305 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -728,6 +728,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont AbstractFile file = getContent(); sheetSet.put(new NodeProperty<>("Name", "Name", "Name", file.getName())); + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); sheetSet.put(new NodeProperty<>("Size", "Size", "Size", file.getSize())); sheetSet.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType()))); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 0276591386..3bf2773e10 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -26,6 +26,7 @@ import java.awt.Graphics; import java.awt.dnd.DnDConstants; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.beans.FeatureDescriptor; import java.beans.PropertyVetoException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -86,8 +87,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName()); + private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png"; + private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png"; + private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png"; private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false)); + private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false)); + private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false)); @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195); @@ -109,7 +115,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { public DataResultViewerTable() { this(null, Bundle.DataResultViewerTable_title()); } - /** * Constructs a tabular result viewer that displays the children of a given @@ -658,35 +663,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ private class IconRendererTableListener implements TableColumnModelListener { - @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C"}) + @NbBundle.Messages({"DataResultViewerTable.commentRender.name=C", + "DataResultViewerTable.scoreRender.name=S"}) @Override public void columnAdded(TableColumnModelEvent e) { - if (e.getSource() instanceof ETableColumnModel) { - TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex()); + if (e.getSource() instanceof ETableColumnModel) { + TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex()); + if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) { //if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer - if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) { - column.setCellRenderer(new HasCommentCellRenderer()); - } + column.setCellRenderer(new HasCommentCellRenderer()); + } else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) { + //if the current column is a score column set the cell renderer to be the ScoreCellRenderer + column.setCellRenderer(new ScoreCellRenderer()); } + } } @Override - public void columnRemoved(TableColumnModelEvent e) { + public void columnRemoved(TableColumnModelEvent e + ) { //Don't do anything when column removed } @Override - public void columnMoved(TableColumnModelEvent e) { + public void columnMoved(TableColumnModelEvent e + ) { //Don't do anything when column moved } @Override - public void columnMarginChanged(ChangeEvent e) { + public void columnMarginChanged(ChangeEvent e + ) { //Don't do anything when column margin changed } @Override - public void columnSelectionChanged(ListSelectionEvent e) { + public void columnSelectionChanged(ListSelectionEvent e + ) { //Don't do anything when column selection changed } @@ -929,18 +942,76 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } + /* + * A renderer which based on the contents of the cell will display an icon + * to indicate the score associated with the item. + */ + private final class ScoreCellRenderer extends ColorTagCustomRenderer { + + private static final long serialVersionUID = 1L; + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBackground(component.getBackground()); //inherit highlighting + setHorizontalAlignment(CENTER); + Object switchValue = null; + if ((value instanceof NodeProperty)) { + //The Outline view has properties in the cell, the value contained in the property is what we want + try { + switchValue = ((Node.Property) value).getValue(); + setToolTipText(((FeatureDescriptor) value).getShortDescription()); + } catch (IllegalAccessException | InvocationTargetException ex) { + //Unable to get the value from the NodeProperty no Icon will be displayed + } + + } else { + //JTables contain the value we want directly in the cell + switchValue = value; + } + setText(""); + if ((switchValue instanceof Score)) { + + switch ((Score) switchValue) { + case INTERESTING_SCORE: + setIcon(INTERESTING_SCORE_ICON); + break; + case NOTABLE_SCORE: + setIcon(NOTABLE_ICON_SCORE); + break; + case NO_SCORE: + default: + setIcon(null); + } + } else { + setIcon(null); + } + return this; + } + + } + /** * Enum to denote the presence of a comment associated with the content or * artifacts generated from it. */ public enum HasCommentStatus { NO_COMMENT, - TAG_NO_COMMENT, + TAG_NO_COMMENT, CR_COMMENT, TAG_COMMENT, CR_AND_TAG_COMMENTS } + /** + * Enum to denote the score given to an item to draw the users attention + */ + public enum Score { + NO_SCORE, + INTERESTING_SCORE, + NOTABLE_SCORE + } + /** * 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/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 2aa8e70af2..3b7410e0e0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*; import static org.sleuthkit.autopsy.datamodel.Bundle.*; @@ -47,9 +48,11 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStat import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; /** * An abstract node that encapsulates AbstractFile data @@ -313,6 +316,49 @@ public abstract class AbstractAbstractFileNode extends A status)); } + /** + * Used by subclasses of AbstractAbstractFileNode to add the Score property + * to their sheets. + * + * @param sheetSet the modifiable Sheet.Set returned by + * Sheet.get(Sheet.PROPERTIES) + * @param tags the list of tags associated with the file + */ + @NbBundle.Messages({"AbstractAbstractFileNode.createSheet.score.name=S", + "AbstractAbstractFileNode.createSheet.score.displayName=S", + "AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.", + "AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.", + "AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.", + "AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag."}) + protected void addScoreProperty(Sheet.Set sheetSet, List tags) { + Score score = Score.NO_SCORE; + String description = NO_DESCR; + if (content.getKnown() == TskData.FileKnown.BAD) { + score = Score.NOTABLE_SCORE; + description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description(); + } + try { + if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) { + score = Score.INTERESTING_SCORE; + description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description(); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex); + } + if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) { + score = Score.INTERESTING_SCORE; + description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description(); + for (ContentTag tag : tags) { + if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) { + score = Score.NOTABLE_SCORE; + description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description(); + break; + } + } + } + sheetSet.put(new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_score_name(), Bundle.AbstractAbstractFileNode_createSheet_score_displayName(), description, score)); + } + /** * Used by subclasses of AbstractAbstractFileNode to add the tags property * to their sheets. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java index 1ef7ec091a..d28eb4a67e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java @@ -81,9 +81,10 @@ public abstract class AbstractFsContentNode extends Abst AbstractFilePropertyType.NAME.toString(), NO_DESCR, getName())); + //add the cr status property before the propertyMap to ensure it is early in column order + addScoreProperty(sheetSet, tags); //add the comment property before the propertyMap to ensure it is early in column order addCommentProperty(sheetSet, tags); - for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) { final String propString = propType.toString(); sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString))); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 66e727e570..7555271af4 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -52,6 +52,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; @@ -66,6 +67,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; /** * Node wrapping a blackboard artifact object. This is generated from several @@ -345,6 +347,7 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { + Score score = Score.NO_SCORE; + String description = ""; + if (associated instanceof AbstractFile) { + if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) { + score = Score.NOTABLE_SCORE; + description = Bundle.BlackboardArtifactNode_createSheet_notableFile_description(); + } + } + try { + if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT).isEmpty()) { + score = Score.INTERESTING_SCORE; + description = Bundle.BlackboardArtifactNode_createSheet_interestingResult_description(); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting artifacts for artifact: " + content.getName(), ex); + } + if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) { + score = Score.INTERESTING_SCORE; + description = Bundle.BlackboardArtifactNode_createSheet_taggedItem_description(); + for (Tag tag : tags) { + if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) { + score = Score.NOTABLE_SCORE; + description = Bundle.BlackboardArtifactNode_createSheet_notableTaggedItem_description(); + break; + } + } + } + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score)); + } + private void updateSheet() { this.setSheet(createSheet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index c070d6087f..806442cb5c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -89,6 +89,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode { NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.desc"), getName())); + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.noDescr.text"); for (Map.Entry entry : map.entrySet()) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java index cad26038c2..a6f1312fa5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalDirectoryNode.java @@ -62,6 +62,7 @@ public class LocalDirectoryNode extends SpecialDirectoryNode { Bundle.LocalDirectoryNode_createSheet_name_displayName(), Bundle.LocalDirectoryNode_createSheet_name_desc(), getName())); + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); // At present, a LocalDirectory will never be a datasource - the top level of a logical // file set is a VirtualDirectory diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 6f6062d7c6..b3a30a86f3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -81,8 +81,8 @@ public class LocalFileNode extends AbstractAbstractFileNode { NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.desc"), getName())); + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); - final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.noDescr.text"); for (Map.Entry entry : map.entrySet()) { sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 85350ea143..dae57a50e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -89,6 +89,7 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode { NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"), getName())); if (!this.content.isDataSource()) { + addScoreProperty(sheetSet, tags); addCommentProperty(sheetSet, tags); Map map = new LinkedHashMap<>(); fillPropertyMap(map, getContent()); diff --git a/Core/src/org/sleuthkit/autopsy/images/red-circle-exclamation.png b/Core/src/org/sleuthkit/autopsy/images/red-circle-exclamation.png new file mode 100644 index 0000000000..26b61e6f06 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/red-circle-exclamation.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/yellow-circle-yield.png b/Core/src/org/sleuthkit/autopsy/images/yellow-circle-yield.png new file mode 100644 index 0000000000..85c873f33f Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/yellow-circle-yield.png differ