Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
BlackboardArtifactNode.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2016 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datamodel;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.text.MessageFormat;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.logging.Level;
30 import java.util.stream.Collectors;
31 import javax.swing.Action;
32 import org.apache.commons.lang3.StringUtils;
33 import org.openide.nodes.Children;
34 import org.openide.nodes.Sheet;
35 import org.openide.util.Lookup;
36 import org.openide.util.NbBundle;
37 import org.openide.util.lookup.Lookups;
47 import org.sleuthkit.datamodel.AbstractFile;
48 import org.sleuthkit.datamodel.BlackboardArtifact;
49 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
50 import org.sleuthkit.datamodel.BlackboardAttribute;
51 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
52 import org.sleuthkit.datamodel.Content;
53 import org.sleuthkit.datamodel.Tag;
54 import org.sleuthkit.datamodel.TskCoreException;
55 
61 
62  private final BlackboardArtifact artifact;
63  private final Content associated;
64  private List<NodeProperty<? extends Object>> customProperties;
65  private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName());
66  /*
67  * Artifact types which should have the full unique path of the associated
68  * content as a property.
69  */
70  private static final Integer[] SHOW_UNIQUE_PATH = new Integer[]{
71  BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID(),
72  BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(),
73  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),};
74 
75  // TODO (RC): This is an unattractive alternative to subclassing BlackboardArtifactNode,
76  // cut from the same cloth as the equally unattractive SHOW_UNIQUE_PATH array
77  // above. It should be removed when and if the subclassing is implemented.
78  private static final Integer[] SHOW_FILE_METADATA = new Integer[]{
79  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),};
80 
81  private final PropertyChangeListener pcl = new PropertyChangeListener() {
82  @Override
83  public void propertyChange(PropertyChangeEvent evt) {
84  String eventType = evt.getPropertyName();
85  if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString())) {
87  if (event.getAddedTag().getArtifact().equals(artifact)) {
88  updateSheet();
89  }
90  } else if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())) {
92  if (event.getDeletedTagInfo().getArtifactID() == artifact.getArtifactID()) {
93  updateSheet();
94  }
95  } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
97  if (event.getAddedTag().getContent().equals(associated)) {
98  updateSheet();
99  }
100  } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
102  if (event.getDeletedTagInfo().getContentID()== associated.getId()) {
103  updateSheet();
104  }
105  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
106  if (evt.getNewValue() == null) {
107  // case was closed. Remove listeners so that we don't get called with a stale case handle
108  removeListeners();
109  }
110  }
111  }
112  };
113 
121  public BlackboardArtifactNode(BlackboardArtifact artifact, String iconPath) {
122  super(Children.LEAF, createLookup(artifact));
123 
124  this.artifact = artifact;
125  //this.associated = getAssociatedContent(artifact);
126  this.associated = this.getLookup().lookup(Content.class);
127  this.setName(Long.toString(artifact.getArtifactID()));
128  this.setDisplayName();
129  this.setIconBaseWithExtension(iconPath);
131  }
132 
139  public BlackboardArtifactNode(BlackboardArtifact artifact) {
140  super(Children.LEAF, createLookup(artifact));
141 
142  this.artifact = artifact;
143  //this.associated = getAssociatedContent(artifact);
144  this.associated = this.getLookup().lookup(Content.class);
145  this.setName(Long.toString(artifact.getArtifactID()));
146  this.setDisplayName();
147  this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); //NON-NLS
149  }
150 
151  private void removeListeners() {
153  }
154 
155  @Override
156  @NbBundle.Messages({
157  "BlackboardArtifactNode.getAction.errorTitle=Error getting actions",
158  "BlackboardArtifactNode.getAction.resultErrorMessage=There was a problem getting actions for the selected result."
159  + " The 'View Result in Timeline' action will not be available.",
160  "BlackboardArtifactNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. "
161  + " The 'View File in Timeline' action will not be available."})
162  public Action[] getActions(boolean context) {
163  List<Action> actionsList = new ArrayList<>();
164  actionsList.addAll(Arrays.asList(super.getActions(context)));
165 
166  //if this artifact has a time stamp add the action to view it in the timeline
167  try {
169  actionsList.add(new ViewArtifactInTimelineAction(artifact));
170  }
171  } catch (TskCoreException ex) {
172  LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
173  MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_resultErrorMessage());
174  }
175 
176  // if the artifact links to another file, add an action to go to that file
177  try {
178  AbstractFile c = findLinked(artifact);
179  if (c != null) {
181  }
182  } catch (TskCoreException ex) {
183  LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
184  MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_linkedFileMessage());
185  }
186 
187  //if this artifact has associated content, add the action to view the content in the timeline
188  AbstractFile file = getLookup().lookup(AbstractFile.class);
189  if (null != file) {
191  }
192 
193  return actionsList.toArray(new Action[actionsList.size()]);
194  }
195 
201  private void setDisplayName() {
202  String displayName = ""; //NON-NLS
203  if (associated != null) {
204  displayName = associated.getName();
205  }
206 
207  // If this is a node for a keyword hit on an artifact, we set the
208  // display name to be the artifact type name followed by " Artifact"
209  // e.g. "Messages Artifact".
210  if (artifact != null && artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
211  try {
212  for (BlackboardAttribute attribute : artifact.getAttributes()) {
213  if (attribute.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
214  BlackboardArtifact associatedArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong());
215  if (associatedArtifact != null) {
216  displayName = associatedArtifact.getDisplayName() + " Artifact";
217  }
218  }
219  }
220  } catch (TskCoreException ex) {
221  // Do nothing since the display name will be set to the file name.
222  }
223  }
224  this.setDisplayName(displayName);
225  }
226 
227  @Override
228  protected Sheet createSheet() {
229  Sheet s = super.createSheet();
230  Sheet.Set ss = s.get(Sheet.PROPERTIES);
231  if (ss == null) {
232  ss = Sheet.createPropertiesSet();
233  s.put(ss);
234  }
235  final String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text");
236 
237  Map<String, Object> map = new LinkedHashMap<>();
238  fillPropertyMap(map, artifact);
239 
240  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"),
241  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"),
242  NO_DESCR,
243  this.getDisplayName()));
244 
245  for (Map.Entry<String, Object> entry : map.entrySet()) {
246  ss.put(new NodeProperty<>(entry.getKey(),
247  entry.getKey(),
248  NO_DESCR,
249  entry.getValue()));
250  }
251 
252  //append custom node properties
253  if (customProperties != null) {
254  for (NodeProperty<? extends Object> np : customProperties) {
255  ss.put(np);
256  }
257  }
258 
259  final int artifactTypeId = artifact.getArtifactTypeID();
260 
261  // If mismatch, add props for extension and file type
262  if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()) {
263  String ext = ""; //NON-NLS
264  String actualMimeType = ""; //NON-NLS
265  if (associated instanceof AbstractFile) {
266  AbstractFile af = (AbstractFile) associated;
267  ext = af.getNameExtension();
268  actualMimeType = af.getMIMEType();
269  if (actualMimeType == null) {
270  actualMimeType = ""; //NON-NLS
271  }
272  }
273  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"),
274  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"),
275  NO_DESCR,
276  ext));
277  ss.put(new NodeProperty<>(
278  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"),
279  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"),
280  NO_DESCR,
281  actualMimeType));
282  }
283 
284  if (Arrays.asList(SHOW_UNIQUE_PATH).contains(artifactTypeId)) {
285  String sourcePath = ""; //NON-NLS
286  try {
287  sourcePath = associated.getUniquePath();
288  } catch (TskCoreException ex) {
289  LOGGER.log(Level.WARNING, "Failed to get unique path from: {0}", associated.getName()); //NON-NLS
290  }
291 
292  if (sourcePath.isEmpty() == false) {
293  ss.put(new NodeProperty<>(
294  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"),
295  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"),
296  NO_DESCR,
297  sourcePath));
298  }
299 
300  if (Arrays.asList(SHOW_FILE_METADATA).contains(artifactTypeId)) {
301  AbstractFile file = associated instanceof AbstractFile ? (AbstractFile) associated : null;
302  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"),
303  NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"),
304  "",
305  file != null ? ContentUtils.getStringTime(file.getMtime(), file) : ""));
306  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"),
307  NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"),
308  "",
309  file != null ? ContentUtils.getStringTime(file.getCtime(), file) : ""));
310  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"),
311  NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"),
312  "",
313  file != null ? ContentUtils.getStringTime(file.getAtime(), file) : ""));
314  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"),
315  NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"),
316  "",
317  file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : ""));
318  ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"),
319  NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"),
320  "",
321  associated.getSize()));
322  }
323  } else {
324  String dataSourceStr = "";
325  try {
326  Content dataSource = associated.getDataSource();
327  if (dataSource != null) {
328  dataSourceStr = dataSource.getName();
329  } else {
330  dataSourceStr = getRootParentName();
331  }
332  } catch (TskCoreException ex) {
333  LOGGER.log(Level.WARNING, "Failed to get image name from {0}", associated.getName()); //NON-NLS
334  }
335 
336  if (dataSourceStr.isEmpty() == false) {
337  ss.put(new NodeProperty<>(
338  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"),
339  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"),
340  NO_DESCR,
341  dataSourceStr));
342  }
343  }
344 
345  // add properties for tags
346  List<Tag> tags = new ArrayList<>();
347  try {
350  } catch (TskCoreException ex) {
351  LOGGER.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex);
352  }
353  ss.put(new NodeProperty<>("Tags", NbBundle.getMessage(AbstractAbstractFileNode.class, "BlackboardArtifactNode.createSheet.tags.displayName"),
354  NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", "))));
355 
356  return s;
357  }
358 
359  private void updateSheet() {
360  this.setSheet(createSheet());
361  }
362 
363  private String getRootParentName() {
364  String parentName = associated.getName();
365  Content parent = associated;
366  try {
367  while ((parent = parent.getParent()) != null) {
368  parentName = parent.getName();
369  }
370  } catch (TskCoreException ex) {
371  LOGGER.log(Level.WARNING, "Failed to get parent name from {0}", associated.getName()); //NON-NLS
372  return "";
373  }
374  return parentName;
375  }
376 
384  if (null == customProperties) {
385  //lazy create the list
386  customProperties = new ArrayList<>();
387  }
388  customProperties.add(np);
389  }
390 
398  @SuppressWarnings("deprecation")
399  private void fillPropertyMap(Map<String, Object> map, BlackboardArtifact artifact) {
400  try {
401  for (BlackboardAttribute attribute : artifact.getAttributes()) {
402  final int attributeTypeID = attribute.getAttributeType().getTypeID();
403  //skip some internal attributes that user shouldn't see
404  if (attributeTypeID == ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()
405  || attributeTypeID == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()
406  || attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()
407  || attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
408  } else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) {
409  map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated));
410  } else if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getTypeID()
411  && attributeTypeID == ATTRIBUTE_TYPE.TSK_TEXT.getTypeID()) {
412  /*
413  * This was added because the RegRipper output would often
414  * cause the UI to get a black line accross it and hang if
415  * you hovered over large output or selected it. This
416  * reduces the amount of data in the table. Could consider
417  * doing this for all fields in the UI.
418  */
419  String value = attribute.getDisplayString();
420  if (value.length() > 512) {
421  value = value.substring(0, 512);
422  }
423  map.put(attribute.getAttributeType().getDisplayName(), value);
424  } else {
425  map.put(attribute.getAttributeType().getDisplayName(), attribute.getDisplayString());
426  }
427  }
428  } catch (TskCoreException ex) {
429  LOGGER.log(Level.SEVERE, "Getting attributes failed", ex); //NON-NLS
430  }
431  }
432 
433  @Override
434  public <T> T accept(DisplayableItemNodeVisitor<T> v) {
435  return v.visit(this);
436  }
437 
445  private static Lookup createLookup(BlackboardArtifact artifact) {
446  List<Object> forLookup = new ArrayList<>();
447  forLookup.add(artifact);
448 
449  // Add the content the artifact is associated with
450  Content content = getAssociatedContent(artifact);
451  if (content != null) {
452  forLookup.add(content);
453  }
454 
455  // if there is a text highlighted version, of the content, add it too
456  // currently happens from keyword search module
457  TextMarkupLookup highlight = getHighlightLookup(artifact, content);
458  if (highlight != null) {
459  forLookup.add(highlight);
460  }
461 
462  return Lookups.fixed(forLookup.toArray(new Object[forLookup.size()]));
463  }
464 
465  private static Content getAssociatedContent(BlackboardArtifact artifact) {
466  try {
467  return artifact.getSleuthkitCase().getContentById(artifact.getObjectID());
468  } catch (TskCoreException ex) {
469  LOGGER.log(Level.WARNING, "Getting file failed", ex); //NON-NLS
470  }
471  throw new IllegalArgumentException(
472  NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.getAssocCont.exception.msg"));
473  }
474 
475  private static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact, Content content) {
476  if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
477  return null;
478  }
479 
480  long objectId = content.getId();
481 
482  Lookup lookup = Lookup.getDefault();
483  TextMarkupLookup highlightFactory = lookup.lookup(TextMarkupLookup.class);
484  try {
485  List<BlackboardAttribute> attributes = artifact.getAttributes();
486  String keyword = null;
487  String regexp = null;
488  for (BlackboardAttribute att : attributes) {
489  final int attributeTypeID = att.getAttributeType().getTypeID();
490  if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) {
491  keyword = att.getValueString();
492  } else if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()) {
493  regexp = att.getValueString();
494  } else if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
495  objectId = att.getValueLong();
496  }
497  }
498  if (keyword != null) {
499  boolean isRegexp = StringUtils.isNotBlank(regexp);
500  String origQuery = isRegexp ? regexp : keyword;
501  return highlightFactory.createInstance(objectId, keyword, isRegexp, origQuery);
502  }
503  } catch (TskCoreException ex) {
504  LOGGER.log(Level.WARNING, "Failed to retrieve Blackboard Attributes", ex); //NON-NLS
505  }
506  return null;
507  }
508 
509  @Override
510  public boolean isLeafTypeNode() {
511  return true;
512  }
513 
514  @Override
515  public String getItemType() {
516  return getClass().getName();
517  }
518 }
void fillPropertyMap(Map< String, Object > map, BlackboardArtifact artifact)
static String getStringTime(long epochSeconds, TimeZone tzone)
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:318
BlackboardArtifactNode(BlackboardArtifact artifact, String iconPath)
static Lookup createLookup(BlackboardArtifact artifact)
synchronized List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)
TextMarkupLookup createInstance(long objectId, String keyword, boolean isRegex, String originalQuery)
static Content getAssociatedContent(BlackboardArtifact artifact)
static ViewFileInTimelineAction createViewSourceFileAction(AbstractFile file)
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:306
static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact, Content content)
static void error(String title, String message)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161
synchronized List< ContentTag > getContentTagsByContent(Content content)
static ViewFileInTimelineAction createViewFileAction(AbstractFile file)

Copyright © 2012-2016 Basis Technology. Generated on: Mon Jan 2 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.