diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 17b8248f06..caa2481a4c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -60,7 +60,6 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; 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; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus; import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool; @@ -79,45 +78,62 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; /** - * Node wrapping a blackboard artifact object. This is generated from several - * places in the tree. + * A BlackboardArtifactNode is an AbstractNode implementation that can be used + * to represent an artifact of any type. */ public class BlackboardArtifactNode extends AbstractContentNode { 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, + + /* + * Cache of Content objects used to avoid repeated trips to the case + * database to retrieve Content objects that are the source of multiple + * artifacts. + */ + private static final Cache contentCache = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(); + + /* + * Case events that indicate an update to the node's property sheet may be + * required. + */ + 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, Case.Events.CONTENT_TAG_DELETED, - Case.Events.CURRENT_CASE, - Case.Events.CR_COMMENT_CHANGED); - - private static Cache contentCache = CacheBuilder.newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES). - build(); - - private final BlackboardArtifact artifact; - private Content associated = null; - - private List> customProperties; + Case.Events.CR_COMMENT_CHANGED, + Case.Events.CURRENT_CASE); /* - * Artifact types which should have the full unique path of the associated - * content as a property. + * Artifact types for which the unique path of the artifact's source content + * should be displayed in the node's property sheet. */ private static final Integer[] SHOW_UNIQUE_PATH = new Integer[]{ BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(),}; + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + }; - // TODO (RC): This is an unattractive alternative to subclassing BlackboardArtifactNode, - // cut from the same cloth as the equally unattractive SHOW_UNIQUE_PATH array - // above. It should be removed when and if the subclassing is implemented. + /* + * Artifact types for which the file metadata of the artifact's source file + * should be displayed in the node's property sheet. + */ private static final Integer[] SHOW_FILE_METADATA = new Integer[]{ - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),}; + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() + }; - private final PropertyChangeListener pcl = new PropertyChangeListener() { + private final BlackboardArtifact artifact; + private Content srcContent; // May be null. + + /* + * A method has been provided to allow the injection of properties into this + * node for display in the node's property sheet, independent of the + * artifact the node represents. + */ + private List> customProperties; + + private final PropertyChangeListener appEventListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); @@ -132,24 +148,32 @@ 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 + private static Lookup createLookup(BlackboardArtifact artifact) { + final long objectID = artifact.getObjectID(); try { - if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) { - actionsList.add(new ViewArtifactInTimelineAction(artifact)); + Content content = contentCache.get(objectID, () -> artifact.getSleuthkitCase().getContentById(objectID)); + if (content == null) { + return Lookups.fixed(artifact); + } else { + return Lookups.fixed(artifact, content); } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS - MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_resultErrorMessage()); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting source content (artifact objID={0}", artifact.getId()), ex); //NON-NLS + return Lookups.fixed(artifact); } - - // if the artifact links to another file, add an action to go to that file - try { - AbstractFile c = findLinked(artifact); - if (c != null) { - actionsList.add(ViewFileInTimelineAction.createViewFileAction(c)); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS - MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_linkedFileMessage()); - } - - //if the artifact has associated content, add the action to view the content in the timeline - if (null != file) { - actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); - } - - return actionsList.toArray(new Action[actionsList.size()]); } - @NbBundle.Messages({"# {0} - artifactDisplayName", "BlackboardArtifactNode.displayName.artifact={0} Artifact"}) /** - * Set the filter node display name. The value will either be the file name - * or something along the lines of e.g. "Messages Artifact" for keyword hits - * on artifacts. + * Sets the display name for this node. */ + @NbBundle.Messages({"# {0} - artifactDisplayName", "BlackboardArtifactNode.displayName.artifact={0} Artifact"}) private void setDisplayName() { String displayName = ""; //NON-NLS - // If this is a node for a keyword hit on an artifact, we set the - // display name to be the artifact type name followed by " Artifact" - // e.g. "Messages Artifact". + /* + * If the artifact this node represents is a keyword hit artifact and + * the associated artifact can be retreived from the case database, make + * the display name the display name of the associated artifact. + * + * If the artifact this node represents is an interesting artifact hit + * and the associated artifact can be retreived from the case database, + * make the display name the display name of the artifact this node + * represents. + */ if (artifact != null && (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() || artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID())) { @@ -309,43 +292,111 @@ public class BlackboardArtifactNode extends AbstractContentNode actionsList = new ArrayList<>(); + actionsList.addAll(Arrays.asList(super.getActions(context))); + + /* + * If the artifact represented by this node has a timestamp, add an + * action to view it in the timeline. + */ + try { + if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) { + actionsList.add(new ViewArtifactInTimelineAction(artifact)); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting artifact timestamp (artifact objID={0})", artifact.getId()), ex); //NON-NLS + } + + /* + * If the artifact represented by this node is linked to a file via a + * TSK_PATH_ID attribute, add an action to view the file in the + * timeline. + */ + try { + AbstractFile linkedFile = findLinked(artifact); + if (linkedFile != null) { + actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedFile)); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file of artifact (artifact objID={0})", artifact.getId()), ex); //NON-NLS + } + + /* + * If the source content of the artifact represented by this node is a + * file, add an action to view the file in the data source tree. + */ + AbstractFile file = getLookup().lookup(AbstractFile.class); + if (null != file) { + actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); + } + + return actionsList.toArray(new Action[actionsList.size()]); + } + + /** + * Gets the name of the source content of the artifact represented by this + * node. + * + * @return The source content name. */ public String getSourceName() { - - String srcName = ""; - if (associated != null) { - srcName = associated.getName(); + String name = ""; + if (srcContent != null) { + name = srcContent.getName(); } - return srcName; + return name; } @NbBundle.Messages({ @@ -358,10 +409,13 @@ public class BlackboardArtifactNode extends AbstractContentNode map = new LinkedHashMap<>(); - fillPropertyMap(map, artifact); - + /* + * Add the name of the source content of the artifact represented by + * this node to the sheet. + */ sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), NO_DESCR, this.getSourceName())); - // Create place holders for S C O if (!UserPreferences.getHideSCOColumns()) { + /* + * Add S(core), C(omments), and O(ther occurences) columns to the + * sheet and start a background task to compute the value of these + * properties for the artifact represented by this node. The task + * will fire a PropertyChangeEvent when the computation is completed + * and this node's PropertyChangeListener will update the sheet. + */ sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, "")); sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, "")); if (CentralRepository.isEnabled()) { sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, "")); } - // Get the SCO columns data in a background task - backgroundTasksPool.submit(new GetSCOTask( - new WeakReference<>(this), weakPcl)); + backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakAppEventListener)); } + /* + * If the artifact represented by this node is an interesting artifact + * hit, add the type and description of the interesting artifact to the + * sheet. + */ if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); @@ -404,10 +468,16 @@ public class BlackboardArtifactNode extends AbstractContentNode map = new LinkedHashMap<>(); + fillPropertyMap(map, artifact); for (Map.Entry entry : map.entrySet()) { sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), @@ -415,23 +485,28 @@ public class BlackboardArtifactNode extends AbstractContentNode np : customProperties) { sheetSet.put(np); } } + /* + * If the artifact represented by this node is a file extension mismatch + * artifact, add the extension and type of the artifact's source file to + * the sheet. + */ final int artifactTypeId = artifact.getArtifactTypeID(); - - // If mismatch, add props for extension and file type if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()) { String ext = ""; //NON-NLS String actualMimeType = ""; //NON-NLS - if (associated instanceof AbstractFile) { - AbstractFile af = (AbstractFile) associated; - ext = af.getNameExtension(); - actualMimeType = af.getMIMEType(); + if (srcContent != null && srcContent instanceof AbstractFile) { + AbstractFile file = (AbstractFile) srcContent; + ext = file.getNameExtension(); + actualMimeType = file.getMIMEType(); if (actualMimeType == null) { actualMimeType = ""; //NON-NLS } @@ -447,12 +522,18 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", @@ -484,7 +570,7 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", - associated.getSize())); + file == null ? "" : file.getSize())); sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), Bundle.BlackboardArtifactNode_createSheet_artifactMD5_displayName(), "", @@ -492,15 +578,17 @@ public class BlackboardArtifactNode extends AbstractContentNode getAllTagsFromDatabase() { List tags = new ArrayList<>(); try { tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(associated)); + if (srcContent != null) { + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); + } } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex); + logger.log(Level.SEVERE, MessageFormat.format("Error getting tags for artifact and its source content (artifact objID={0})", artifact.getId()), ex); } return tags; } /** - * Used by (subclasses of) BlackboardArtifactNode to add the tags property - * to their sheets. + * Gets the correlation attribute for the MD5 hash of the source file of the + * artifact represented by this node. The correlation attribute instance can + * only be returned if the central repository is enabled and the source + * content is a file. * - * @param sheetSet the modifiable Sheet.Set returned by - * Sheet.get(Sheet.PROPERTIES) - */ - @NbBundle.Messages({ - "BlackboardArtifactNode.createSheet.tags.displayName=Tags"}) - @Deprecated - protected void addTagProperty(Sheet.Set sheetSet) throws MissingResourceException { - // add properties for tags - List tags = new ArrayList<>(); - try { - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(associated)); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex); - } - sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), - NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); - } - - /** - * Used by (subclasses of) BlackboardArtifactNode to add the tags property - * to their sheets. - * - * @param sheetSet the modifiable Sheet.Set returned by - * Sheet.get(Sheet.PROPERTIES) - * @param tags the list of tags which should appear as the value for the - * property - */ - @Deprecated - protected final void addTagProperty(Sheet.Set sheetSet, List tags) { - sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), - NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); - } - - /** - * Gets the correlation attribute for the associated file - * - * @return the correlation attribute for the file associated with this - * BlackboardArtifactNode + * @return The correlation attribute instance, may be null. */ @Override protected final CorrelationAttributeInstance getCorrelationAttributeInstance() { CorrelationAttributeInstance correlationAttribute = null; - if (CentralRepository.isEnabled() && associated instanceof AbstractFile) { - correlationAttribute = CorrelationAttributeUtil.getCorrAttrForFile((AbstractFile)associated); + if (srcContent != null && CentralRepository.isEnabled() && srcContent instanceof AbstractFile) { + correlationAttribute = CorrelationAttributeUtil.getCorrAttrForFile((AbstractFile) srcContent); } return correlationAttribute; } /** - * Used by (subclasses of) BlackboardArtifactNode to add the comment - * property to their sheets. + * Computes the value of the comment property ("C" in S, C, O) for the + * artifact represented by this node. * - * @param sheetSet the modifiable Sheet.Set to add the property to - * @param tags the list of tags associated with the file - * @param attribute the correlation attribute associated with this - * artifact's associated file, null if central repo is not - * enabled + * An icon is displayed in the property sheet if a commented tag has been + * applied to the artifact or its source content, or if there is a + * corresponding commented correlation attribute instance in the central + * repository. * - * @deprecated Use the GetSCOTask to get this data on a background - * thread..., and then update the property sheet asynchronously - */ - @NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C", - "BlackboardArtifactNode.createSheet.comment.displayName=C"}) - @Deprecated - protected final void addCommentProperty(Sheet.Set sheetSet, List tags, CorrelationAttributeInstance attribute) { - HasCommentStatus status = getCommentProperty(tags, attribute); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, - status)); - } - - /** - * Gets the comment property for the node + * @param tags The tags applied to the artifact and its source content. + * @param attribute A correlation attribute instance Ffor the central + * repository lookup. * - * @param tags the list of tags associated with the file - * @param attribute the correlation attribute associated with this - * artifact's associated file, null if central repo is not - * enabled - * - * @return comment property + * @return The value of the comment property. */ @Override protected DataResultViewerTable.HasCommentStatus getCommentProperty(List tags, CorrelationAttributeInstance attribute) { + /* + * Has a tag with a comment been applied to the artifact or its source + * content? + */ HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT; for (Tag tag : tags) { if (!StringUtils.isBlank(tag.getComment())) { - //if the tag is null or empty or contains just white space it will indicate there is not a comment status = HasCommentStatus.TAG_COMMENT; break; } } - //currently checks for a comment on the associated file in the central repo not the artifact itself - //what we want the column property to reflect should be revisted when we have added a way to comment - //on the artifact itself + + /* + * Does the given correlation attribute instance have a comment in the + * central repository? + */ if (attribute != null && !StringUtils.isBlank(attribute.getComment())) { if (status == HasCommentStatus.TAG_COMMENT) { status = HasCommentStatus.CR_AND_TAG_COMMENTS; @@ -665,53 +708,51 @@ public class BlackboardArtifactNode extends AbstractContentNode tags) { - Pair scoreAndDescription = getScorePropertyAndDescription(tags); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); - } - - /** - * Get the score property for the node. + * A red icon will be displayed in the property sheet if the hash of the + * source file has been found in a notable hash set or if either the + * artifact or its source content has been tagged with a notable tag. A + * yellow icon will be displayed if the source file belongs to an + * interesting file set or either the artifact or its source content has + * been tagged with a non-notable tag. * - * @param tags the list of tags associated with the file + * @param tags The tags that have been applied to the artifact and its + * source content. * - * @return score property and description + * @return The value of the score property as an enum element and a + * description string for dislpay in a tool tip. */ @Override protected Pair getScorePropertyAndDescription(List tags) { + /* + * Is the artifact's source content marked as notable? + */ Score score = Score.NO_SCORE; String description = Bundle.BlackboardArtifactNode_createSheet_noScore_description(); - if (associated instanceof AbstractFile) { - if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) { + if (srcContent != null && srcContent instanceof AbstractFile) { + if (((AbstractFile) srcContent).getKnown() == TskData.FileKnown.BAD) { score = Score.NOTABLE_SCORE; description = Bundle.BlackboardArtifactNode_createSheet_notableFile_description(); } } - //if the artifact being viewed is a hashhit check if the hashset is notable - if ((score == Score.NO_SCORE || score == Score.INTERESTING_SCORE) && content.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { + + /* + * If the artifact is a hash set hit, is the hash set a notable hashes + * hash set? + */ + if (score == Score.NO_SCORE && artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { try { - BlackboardAttribute attr = content.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME)); + BlackboardAttribute attr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SET_NAME)); List notableHashsets = HashDbManager.getInstance().getKnownBadFileHashSets(); for (HashDbManager.HashDb hashDb : notableHashsets) { if (hashDb.getHashSetName().equals(attr.getValueString())) { @@ -721,18 +762,29 @@ public class BlackboardArtifactNode extends AbstractContentNode 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) { score = Score.INTERESTING_SCORE; description = Bundle.BlackboardArtifactNode_createSheet_taggedItem_description(); @@ -749,124 +801,117 @@ public class BlackboardArtifactNode extends AbstractContentNode countAndDescription = getCountPropertyAndDescription(attribute.getCorrelationType(), attribute.getCorrelationValue(), Bundle.BlackboardArtifactNode_createSheet_count_noCorrelationAttributes_description()); - sheetSet.put( - new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft())); - } - - /** - * Gets the Occurrences property for the node. - * - * @param attributeType the type of the attribute to count - * @param attributeValue the value of the attribute to count - * @param defaultDescription a description to use when none is determined by - * the getCountPropertyAndDescription method - * - * @return count and description + * @return The value of the occurrences property as a data sources count and + * a description string. * */ @Override - protected Pair getCountPropertyAndDescription(Type attributeType, String attributeValue, String defaultDescription) { + protected Pair getCountPropertyAndDescription(Type corrAttrType, String attributeValue, String defaultDescription) { Long count = -1L; String description = defaultDescription; try { - //don't perform the query if there is no correlation value - if (attributeType != null && StringUtils.isNotBlank(attributeValue)) { - count = CentralRepository.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attributeType, attributeValue); - description = Bundle.BlackboardArtifactNode_createSheet_count_description(count, attributeType.getDisplayName()); - } else if (attributeType != null) { + if (corrAttrType != null && StringUtils.isNotBlank(attributeValue)) { + count = CentralRepository.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(corrAttrType, attributeValue); + description = Bundle.BlackboardArtifactNode_createSheet_count_description(count, corrAttrType.getDisplayName()); + } else if (corrAttrType != null) { description = Bundle.BlackboardArtifactNode_createSheet_count_noCorrelationValues_description(); } } catch (CentralRepoException ex) { - logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex); + logger.log(Level.SEVERE, MessageFormat.format("Error querying central repository for other occurences count (artifact objID={0}, corrAttrType={1}, corrAttrValue={2})", artifact.getId(), corrAttrType, attributeValue), ex); } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex); + logger.log(Level.SEVERE, MessageFormat.format("Error normalizing correlation attribute for central repository query (artifact objID={0}, corrAttrType={2}, corrAttrValue={3})", artifact.getId(), corrAttrType, attributeValue), ex); } return Pair.of(count, description); } + /** + * Refreshes this node's property sheet. + */ private void updateSheet() { this.setSheet(createSheet()); } - private String getRootParentName() { - String parentName = associated.getName(); - Content parent = associated; + /** + * Gets the name of the root ancestor of the source content for the artifact + * represented by this node. + * + * @return The root ancestor name or the empty string if an error occurs. + */ + private String getRootAncestorName() { + String parentName = srcContent.getName(); + Content parent = srcContent; try { while ((parent = parent.getParent()) != null) { parentName = parent.getName(); } } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get parent name from {0}", associated.getName()); //NON-NLS + logger.log(Level.SEVERE, MessageFormat.format("Error getting root ancestor name for source content (artifact objID={0})", artifact.getId())); //NON-NLS return ""; } return parentName; } /** - * Add an additional custom node property to that node before it is - * displayed + * Adds a "custom" property to the property sheet of this node, indepoendent + * of the artifact this node represents or its source content. * - * @param np NodeProperty to add + * @param property The custom property. */ - public void addNodeProperty(NodeProperty np) { - if (null == customProperties) { - //lazy create the list + public void addNodeProperty(NodeProperty property) { + if (customProperties == null) { customProperties = new ArrayList<>(); } - customProperties.add(np); + customProperties.add(property); } /** - * Fill map with Artifact properties + * Converts the attributes of the artifact this node represents to a map of + * name-value pairs, where the names are attribute type display names. * - * @param map map with preserved ordering, where property names/values - * are put - * @param artifact to extract properties from + * @param map The map to be populated with the artifact attribute + * name-value pairs. + * @param artifact The artifact. */ @SuppressWarnings("deprecation") private void fillPropertyMap(Map map, BlackboardArtifact artifact) { try { for (BlackboardAttribute attribute : artifact.getAttributes()) { final int attributeTypeID = attribute.getAttributeType().getTypeID(); - //skip some internal attributes that user shouldn't see if (attributeTypeID == ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() - || attributeTypeID == ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + || attributeTypeID == ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() || attribute.getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) { - continue; + /* + * Do nothing. + */ } else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) { addEmailMsgProperty(map, attribute); } else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated)); + map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), srcContent)); } else if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getTypeID() && attributeTypeID == ATTRIBUTE_TYPE.TSK_TEXT.getTypeID()) { /* - * This was added because the RegRipper output would often - * cause the UI to get a black line accross it and hang if - * you hovered over large output or selected it. This - * reduces the amount of data in the table. Could consider - * doing this for all fields in the UI. + * The truncation of text attributes appears to have been + * motivated by the statement that "RegRipper output would + * often cause the UI to get a black line accross it and + * hang if you hovered over large output or selected it. + * This reduces the amount of data in the table. Could + * consider doing this for all fields in the UI." */ String value = attribute.getDisplayString(); if (value.length() > 512) { @@ -878,43 +923,41 @@ public class BlackboardArtifactNode extends AbstractContentNode map, BlackboardAttribute attribute) { - final int attributeTypeID = attribute.getAttributeType().getTypeID(); - - // Skip certain Email msg attributes if (attributeTypeID == ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_EMAIL_BCC.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_EMAIL_CC.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_HEADERS.getTypeID()) { - - // do nothing + /* + * Do nothing. + */ } else if (attributeTypeID == ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN.getTypeID()) { - String value = attribute.getDisplayString(); if (value.length() > 160) { value = value.substring(0, 160) + "..."; } map.put(attribute.getAttributeType().getDisplayName(), value); } else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated)); + map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), srcContent)); } else { map.put(attribute.getAttributeType().getDisplayName(), attribute.getDisplayString()); } - } @Override @@ -922,30 +965,6 @@ public class BlackboardArtifactNode extends AbstractContentNode artifact.getSleuthkitCase().getContentById(objectID)); - if (content == null) { - return Lookups.fixed(artifact); - } else { - return Lookups.fixed(artifact, content); - } - } catch (ExecutionException ex) { - logger.log(Level.WARNING, "Getting associated content for artifact failed", ex); //NON-NLS - return Lookups.fixed(artifact); - } - } - @Override public boolean isLeafTypeNode() { return true; @@ -960,4 +979,117 @@ public class BlackboardArtifactNode extends AbstractContentNode T accept(ContentNodeVisitor visitor) { return visitor.visit(this); } + + /** + * Adds the score property for the artifact represented by this node to the + * node property sheet. + * + * @param sheetSet The property sheet. + * @param tags The tags that have been applied to the artifact and its + * source content. + * + * @deprecated Do not use. The score property is now computed in a + * background thread and added to the property sheet via property change + * event. + */ + @NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S", + "BlackboardArtifactNode.createSheet.score.displayName=S", + "BlackboardArtifactNode.createSheet.notableFile.description=Associated file recognized as notable.", + "BlackboardArtifactNode.createSheet.interestingResult.description=Result has an interesting result associated with it.", + "BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged.", + "BlackboardArtifactNode.createSheet.notableTaggedItem.description=Result or associated file tagged with notable tag.", + "BlackboardArtifactNode.createSheet.noScore.description=No score"}) + @Deprecated + protected final void addScorePropertyAndDescription(Sheet.Set sheetSet, List tags) { + Pair scoreAndDescription = getScorePropertyAndDescription(tags); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft())); + } + + /** + * Adds the tags property for the artifact represented by this node to the + * node property sheet. + * + * @param sheetSet The property sheet. + * + * @deprecated Do not use. The tags property is now computed in a background + * thread and added to the property sheet via property change event. + */ + @NbBundle.Messages({ + "BlackboardArtifactNode.createSheet.tags.displayName=Tags"} + ) + @Deprecated + protected void addTagProperty(Sheet.Set sheetSet) throws MissingResourceException { + List tags = new ArrayList<>(); + try { + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting tags for artifact and source content (artifact objID={0})", artifact.getId()), ex); + } + sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); + } + + /** + * Adds the tags property for the artifact represented by this node to the + * node property sheet. + * + * @param sheetSet The property sheet. + * @param tags The tags that have been applied to the artifact and its + * source content. + * + * @deprecated Do not use. The tags property is now computed in a background + * thread and added to the property sheet via property change event. + */ + @Deprecated + protected final void addTagProperty(Sheet.Set sheetSet, List tags) { + sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(), NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); + } + + /** + * Adds the count property for the artifact represented by this node to the + * node property sheet. + * + * @param sheetSet The property sheet. + * @param attribute The correlation attribute instance to use for the + * central repository lookup. + * + * @deprecated Do not use. The count property is now computed in a + * background thread and added to the property sheet via property change + * event. + */ + @NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O", + "BlackboardArtifactNode.createSheet.count.displayName=O", + "BlackboardArtifactNode.createSheet.count.noCorrelationAttributes.description=No correlation properties found", + "BlackboardArtifactNode.createSheet.count.noCorrelationValues.description=Unable to find other occurrences because no value exists for the available correlation property", + "# {0} - occurrenceCount", + "# {1} - attributeType", + "BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}"}) + @Deprecated + protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) { + Pair countAndDescription = getCountPropertyAndDescription(attribute.getCorrelationType(), attribute.getCorrelationValue(), Bundle.BlackboardArtifactNode_createSheet_count_noCorrelationAttributes_description()); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft())); + } + + /** + * Adds the other occurrences property for the artifact represented by this + * node to the node property sheet. + * + * @param sheetSet The property sheet. + * @param tags The tags that have been applied to the artifact and its + * source content. + * @param attribute The correlation attribute instance to use for the + * central repository lookup. + * + * @deprecated Do not use. The other occurrences property is now computed in a + * background thread and added to the property sheet via property change + * event. + */ + @NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C", + "BlackboardArtifactNode.createSheet.comment.displayName=C"}) + @Deprecated + protected final void addCommentProperty(Sheet.Set sheetSet, List tags, CorrelationAttributeInstance attribute) { + HasCommentStatus status = getCommentProperty(tags, attribute); + sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, status)); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 9ac85bd1e7..8289f947da 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -78,9 +78,6 @@ BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated f BlackboardArtifactNode.createSheet.tags.displayName=Tags # {0} - artifactDisplayName BlackboardArtifactNode.displayName.artifact={0} Artifact -BlackboardArtifactNode.getAction.errorTitle=Error getting actions -BlackboardArtifactNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. The 'View File in Timeline' action will not be available. -BlackboardArtifactNode.getAction.resultErrorMessage=There was a problem getting actions for the selected result. The 'View Result in Timeline' action will not be available. BlackboardArtifactTagNode.createSheet.userName.text=User Name BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result Category.five=CAT-5: Non-pertinent