Autopsy  4.21.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CaseEventListener.java
Go to the documentation of this file.
1 /*
2  * Central Repository
3  *
4  * Copyright 2017-2021 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.centralrepository.eventlisteners;
20 
21 import com.google.common.util.concurrent.ThreadFactoryBuilder;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.util.ArrayList;
25 import java.util.EnumSet;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.logging.Level;
31 import org.apache.commons.lang.StringUtils;
32 import org.openide.util.NbBundle.Messages;
58 import org.sleuthkit.datamodel.Tag;
62 
67 @Messages({"caseeventlistener.evidencetag=Evidence"})
68 public final class CaseEventListener implements PropertyChangeListener {
69 
70  private static final Logger LOGGER = Logger.getLogger(CaseEventListener.class.getName());
71  private static final String CASE_EVENT_THREAD_NAME = "CR-Case-Event-Listener-%d";
72  private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
80  private final ExecutorService jobProcessingExecutor;
81 
86  public CaseEventListener() {
87  jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build());
88  }
89 
93  public void startUp() {
94  Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
95  }
96 
100  public void shutdown() {
101  Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this);
102  ThreadUtils.shutDownTaskExecutor(jobProcessingExecutor);
103  }
104 
105  @Override
106  public void propertyChange(PropertyChangeEvent evt) {
107  if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) {
108  return;
109  }
110 
111  if (!CentralRepository.isEnabled()) {
112  return;
113  }
114 
115  CentralRepository centralRepo;
116  try {
117  centralRepo = CentralRepository.getInstance();
118  } catch (CentralRepoException ex) {
119  LOGGER.log(Level.SEVERE, "Failed to access central repository", ex);
120  return;
121  }
122 
123  /*
124  * IMPORTANT: If any changes are made to which event types are handled,
125  * the change must also be made to the contents of the
126  * CASE_EVENTS_OF_INTEREST set.
127  */
128  switch (Case.Events.valueOf(evt.getPropertyName())) {
129  case CONTENT_TAG_ADDED:
130  case CONTENT_TAG_DELETED:
131  jobProcessingExecutor.submit(new ContentTagTask(centralRepo, evt));
132  break;
133  case BLACKBOARD_ARTIFACT_TAG_DELETED:
134  case BLACKBOARD_ARTIFACT_TAG_ADDED:
135  jobProcessingExecutor.submit(new ArtifactTagTask(centralRepo, evt));
136  break;
137  case DATA_SOURCE_ADDED:
138  jobProcessingExecutor.submit(new DataSourceAddedTask(centralRepo, evt));
139  break;
140  case TAG_DEFINITION_CHANGED:
141  jobProcessingExecutor.submit(new TagDefinitionChangeTask(evt));
142  break;
143  case CURRENT_CASE:
144  jobProcessingExecutor.submit(new CurrentCaseTask(centralRepo, evt));
145  break;
146  case DATA_SOURCE_NAME_CHANGED:
147  jobProcessingExecutor.submit(new DataSourceNameChangedTask(centralRepo, evt));
148  break;
149  default:
150  break;
151  }
152  }
153 
161  private static boolean isNotableTag(Tag tag) {
162  return (tag != null && isNotableTagDefinition(tag.getName()));
163  }
164 
172  private static boolean isNotableTagDefinition(TagName tagDef) {
173  return (tagDef != null && TagsManager.getNotableTagDisplayNames().contains(tagDef.getDisplayName()));
174  }
175 
183  private static boolean hasNotableTag(List<? extends Tag> tags) {
184  if (tags == null) {
185  return false;
186  }
187  return tags.stream()
189  .findFirst()
190  .isPresent();
191  }
192 
201  private static void setArtifactKnownStatus(CentralRepository centralRepo, BlackboardArtifact artifact, TskData.FileKnown notableStatus) {
202  List<CorrelationAttributeInstance> corrAttrInstances = new ArrayList<>();
203  if (artifact instanceof DataArtifact) {
204  corrAttrInstances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) artifact));
205  } else if (artifact instanceof AnalysisResult) {
206  corrAttrInstances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) artifact));
207  }
208  for (CorrelationAttributeInstance corrAttrInstance : corrAttrInstances) {
209  try {
210  centralRepo.setAttributeInstanceKnownStatus(corrAttrInstance, notableStatus);
211  } catch (CentralRepoException ex) {
212  LOGGER.log(Level.SEVERE, String.format("Error setting correlation attribute instance known status", corrAttrInstance), ex); //NON-NLS
213  }
214  }
215  }
216 
217  private final class ContentTagTask implements Runnable {
218 
220  private final PropertyChangeEvent event;
221 
222  private ContentTagTask(CentralRepository db, PropertyChangeEvent evt) {
223  dbManager = db;
224  event = evt;
225  }
226 
227  @Override
228  public void run() {
229  if (!CentralRepository.isEnabled()) {
230  return;
231  }
232 
233  Case.Events curEventType = Case.Events.valueOf(event.getPropertyName());
234  if (curEventType == Case.Events.CONTENT_TAG_ADDED && event instanceof ContentTagAddedEvent) {
235  handleTagAdded((ContentTagAddedEvent) event);
236  } else if (curEventType == Case.Events.CONTENT_TAG_DELETED && event instanceof ContentTagDeletedEvent) {
237  handleTagDeleted((ContentTagDeletedEvent) event);
238  } else {
239  LOGGER.log(Level.SEVERE,
240  String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.",
241  event, curEventType));
242  }
243  }
244 
246  // ensure tag deleted event has a valid content id
247  if (evt.getDeletedTagInfo() == null) {
248  LOGGER.log(Level.SEVERE, "ContentTagDeletedEvent did not have valid content to provide a content id.");
249  return;
250  }
251 
252  try {
253  // obtain content
255  if (content == null) {
256  LOGGER.log(Level.WARNING,
257  String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID()));
258  return;
259  }
260 
261  // then handle the event
262  handleTagChange(content);
264  LOGGER.log(Level.WARNING, "Error updating non-file object: " + evt.getDeletedTagInfo().getContentID(), ex);
265  }
266  }
267 
269  // ensure tag added event has a valid content id
270  if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null) {
271  LOGGER.log(Level.SEVERE, "ContentTagAddedEvent did not have valid content to provide a content id.");
272  return;
273  }
274 
275  // then handle the event
276  handleTagChange(evt.getAddedTag().getContent());
277  }
278 
286  private void handleTagChange(Content content) {
287  AbstractFile af = null;
288  try {
291  Long contentID = (content != null) ? content.getId() : null;
292  LOGGER.log(Level.WARNING, "Error updating non-file object: " + contentID, ex);
293  }
294 
295  if (af == null) {
296  return;
297  }
298 
299  try {
300  // Get the tags on the content object
302 
303  if (hasNotableTag(tagsManager.getContentTagsByContent(content))) {
304  // if there is a notable tag on the object, set content known status to bad
305  setContentKnownStatus(af, TskData.FileKnown.BAD);
306  } else {
307  // otherwise, set to unknown
308  setContentKnownStatus(af, TskData.FileKnown.UNKNOWN);
309  }
311  LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex);
312  }
313  }
314 
324  private void setContentKnownStatus(AbstractFile af, TskData.FileKnown knownStatus) {
325  final List<CorrelationAttributeInstance> md5CorrelationAttr = CorrelationAttributeUtil.makeCorrAttrsForSearch(af);
326  if (!md5CorrelationAttr.isEmpty()) {
327  //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852
328  // send update to Central Repository db
329  try {
330  dbManager.setAttributeInstanceKnownStatus(md5CorrelationAttr.get(0), knownStatus);
331  } catch (CentralRepoException ex) {
332  LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS
333  }
334  }
335  }
336  }
337 
338  private final class ArtifactTagTask implements Runnable {
339 
341  private final PropertyChangeEvent event;
342 
343  private ArtifactTagTask(CentralRepository db, PropertyChangeEvent evt) {
344  dbManager = db;
345  event = evt;
346  }
347 
348  @Override
349  public void run() {
350  if (!CentralRepository.isEnabled()) {
351  return;
352  }
353 
354  Case.Events curEventType = Case.Events.valueOf(event.getPropertyName());
355  if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED && event instanceof BlackBoardArtifactTagAddedEvent) {
356  handleTagAdded((BlackBoardArtifactTagAddedEvent) event);
357  } else if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED && event instanceof BlackBoardArtifactTagDeletedEvent) {
358  handleTagDeleted((BlackBoardArtifactTagDeletedEvent) event);
359  } else {
360  LOGGER.log(Level.WARNING,
361  String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.",
362  event, curEventType));
363  }
364  }
365 
367  // ensure tag deleted event has a valid content id
368  if (evt.getDeletedTagInfo() == null) {
369  LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagDeletedEvent did not have valid content to provide a content id.");
370  return;
371  }
372 
373  try {
374  Case openCase = Case.getCurrentCaseThrows();
375 
376  // obtain content
378  if (content == null) {
379  LOGGER.log(Level.WARNING,
380  String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID()));
381  return;
382  }
383 
384  // obtain blackboard artifact
386  if (bbArtifact == null) {
387  LOGGER.log(Level.WARNING,
388  String.format("Unable to get blackboard artifact for item with artifact id: %d.", evt.getDeletedTagInfo().getArtifactID()));
389  return;
390  }
391 
392  // then handle the event
393  handleTagChange(content, bbArtifact);
395  LOGGER.log(Level.WARNING, "Error updating non-file object.", ex);
396  }
397  }
398 
400  // ensure tag added event has a valid content id
401  if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null || evt.getAddedTag().getArtifact() == null) {
402  LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagAddedEvent did not have valid content to provide a content id.");
403  return;
404  }
405 
406  // then handle the event
407  handleTagChange(evt.getAddedTag().getContent(), evt.getAddedTag().getArtifact());
408  }
409 
418  private void handleTagChange(Content content, BlackboardArtifact bbArtifact) {
419  Case openCase;
420  try {
421  openCase = Case.getCurrentCaseThrows();
422  } catch (NoCurrentCaseException ex) {
423  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
424  return;
425  }
426 
427  try {
428  if (isKnownFile(content)) {
429  return;
430  }
431 
432  TagsManager tagsManager = openCase.getServices().getTagsManager();
433  List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact);
434  if (hasNotableTag(tags)) {
435  setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.BAD);
436  } else {
437  setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.UNKNOWN);
438  }
439  } catch (TskCoreException ex) {
440  LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex);
441  }
442  }
443 
451  private boolean isKnownFile(Content content) {
452  return ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN));
453  }
454 
455  }
456 
457  private final class TagDefinitionChangeTask implements Runnable {
458 
459  private final PropertyChangeEvent event;
460 
461  private TagDefinitionChangeTask(PropertyChangeEvent evt) {
462  event = evt;
463  }
464 
465  @Override
466  public void run() {
467  if (!CentralRepository.isEnabled()) {
468  return;
469  }
470  //get the display name of the tag that has had it's definition modified
471  String modifiedTagName = (String) event.getOldValue();
472 
473  /*
474  * Set knownBad status for all files/artifacts in the given case
475  * that are tagged with the given tag name.
476  */
477  try {
479  //First update the artifacts
480  //Get all BlackboardArtifactTags with this tag name
481  List<BlackboardArtifactTag> artifactTags = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifactTagsByTagName(tagName);
482  for (BlackboardArtifactTag bbTag : artifactTags) {
483  //start with assumption that none of the other tags applied to this Correlation Attribute will prevent it's status from being changed
484  boolean hasTagWithConflictingKnownStatus = false;
485  // if the status of the tag has been changed to TskData.TagType.UNKNOWN or TskData.TagType.SUSPICIOUS
486  // we need to check the status of all other tags on this correlation attribute before changing
487  // the status of the correlation attribute in the central repository
488  if (tagName.getTagType() == TskData.TagType.UNKNOWN
489  || tagName.getTagType() == TskData.TagType.SUSPICIOUS) {
490  Content content = bbTag.getContent();
491  // If the content which this Blackboard Artifact Tag is linked to is an AbstractFile with KNOWN status then
492  // it's status in the central reporsitory should not be changed to UNKNOWN
493  if ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)) {
494  continue;
495  }
496  //Get the BlackboardArtifact which this BlackboardArtifactTag has been applied to.
497  BlackboardArtifact bbArtifact = bbTag.getArtifact();
499  List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact);
500  //get all tags which are on this blackboard artifact
501  for (BlackboardArtifactTag t : tags) {
502  //All instances of the modified tag name will be changed, they can not conflict with each other
503  if (t.getName().equals(tagName)) {
504  continue;
505  }
506  //if any other tags on this artifact are Notable in status then this artifact can not have its status changed
507  if (TskData.TagType.BAD == t.getName().getTagType()) {
508  //a tag with a conflicting status has been found, the status of this correlation attribute can not be modified
509  hasTagWithConflictingKnownStatus = true;
510  break;
511  }
512  }
513  }
514  //if the Correlation Attribute will have no tags with a status which would prevent the current status from being changed
515  if (!hasTagWithConflictingKnownStatus) {
516  setArtifactKnownStatus(CentralRepository.getInstance(), bbTag.getArtifact(), tagName.getKnownStatus());
517  }
518  }
519  // Next update the files
520 
521  List<ContentTag> fileTags = Case.getCurrentCaseThrows().getSleuthkitCase().getContentTagsByTagName(tagName);
522  //Get all ContentTags with this tag name
523  for (ContentTag contentTag : fileTags) {
524  //start with assumption that none of the other tags applied to this ContentTag will prevent it's status from being changed
525  boolean hasTagWithConflictingKnownStatus = false;
526  // if the status of the tag has been changed to TskData.TagType.UNKNOWN or TskData.TagType.SUSPICIOUS
527  // we need to check the status of all other tags on this file before changing
528  // the status of the file in the central repository
529  if (tagName.getTagType() == TskData.TagType.UNKNOWN
530  || tagName.getTagType() == TskData.TagType.SUSPICIOUS) {
531  Content content = contentTag.getContent();
533  List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
534  //get all tags which are on this file
535  for (ContentTag t : tags) {
536  //All instances of the modified tag name will be changed, they can not conflict with each other
537  if (t.getName().equals(tagName)) {
538  continue;
539  }
540  //if any other tags on this file are Notable in status then this file can not have its status changed
541  if (TskData.TagType.BAD == t.getName().getTagType()) {
542  //a tag with a conflicting status has been found, the status of this file can not be modified
543  hasTagWithConflictingKnownStatus = true;
544  break;
545  }
546  }
547  }
548  //if the file will have no tags with a status which would prevent the current status from being changed
549  if (!hasTagWithConflictingKnownStatus) {
550  Content taggedContent = contentTag.getContent();
551  if (taggedContent instanceof AbstractFile) {
552  final List<CorrelationAttributeInstance> eamArtifact = CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) taggedContent);
553  if (!eamArtifact.isEmpty()) {
554  //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852
556  }
557  }
558  }
559  }
560  } catch (TskCoreException ex) {
561  LOGGER.log(Level.SEVERE, "Cannot update tag type in central repository for tag: " + modifiedTagName, ex); //NON-NLS
562  } catch (CentralRepoException ex) {
563  LOGGER.log(Level.SEVERE, "Cannot get central repository for tag: " + modifiedTagName, ex); //NON-NLS
564  } catch (NoCurrentCaseException ex) {
565  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
566  }
567  } //TAG_STATUS_CHANGED
568  }
569 
570  private final class DataSourceAddedTask implements Runnable {
571 
573  private final PropertyChangeEvent event;
574 
575  private DataSourceAddedTask(CentralRepository db, PropertyChangeEvent evt) {
576  dbManager = db;
577  event = evt;
578  }
579 
580  @Override
581  public void run() {
582  if (!CentralRepository.isEnabled()) {
583  return;
584  }
585  Case openCase;
586  try {
587  openCase = Case.getCurrentCaseThrows();
588  } catch (NoCurrentCaseException ex) {
589  LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
590  return;
591  }
592 
593  final DataSourceAddedEvent dataSourceAddedEvent = (DataSourceAddedEvent) event;
594  Content newDataSource = dataSourceAddedEvent.getDataSource();
595 
596  try {
597  CorrelationCase correlationCase = dbManager.getCase(openCase);
598  if (null == dbManager.getDataSource(correlationCase, newDataSource.getId())) {
599  CorrelationDataSource.fromTSKDataSource(correlationCase, newDataSource);
600  }
601  } catch (CentralRepoException ex) {
602  LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS
603  }
604  } // DATA_SOURCE_ADDED
605  }
606 
607  private final class CurrentCaseTask implements Runnable {
608 
610  private final PropertyChangeEvent event;
611 
612  private CurrentCaseTask(CentralRepository db, PropertyChangeEvent evt) {
613  dbManager = db;
614  event = evt;
615  }
616 
617  @Override
618  public void run() {
619  /*
620  * A case has been opened if evt.getOldValue() is null and
621  * evt.getNewValue() is a valid Case.
622  */
623  if ((null == event.getOldValue()) && (event.getNewValue() instanceof Case)) {
624  Case curCase = (Case) event.getNewValue();
625 
626  if (!CentralRepository.isEnabled()) {
627  return;
628  }
629 
630  try {
631  // NOTE: Cannot determine if the opened case is a new case or a reopened case,
632  // so check for existing name in DB and insert if missing.
633  if (dbManager.getCase(curCase) == null) {
634  dbManager.newCase(curCase);
635  }
636  } catch (CentralRepoException ex) {
637  LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); //NON-NLS
638  }
639  }
640  } // CURRENT_CASE
641  }
642 
643  private final class DataSourceNameChangedTask implements Runnable {
644 
646  private final PropertyChangeEvent event;
647 
648  private DataSourceNameChangedTask(CentralRepository db, PropertyChangeEvent evt) {
649  dbManager = db;
650  event = evt;
651  }
652 
653  @Override
654  public void run() {
655 
656  final DataSourceNameChangedEvent dataSourceNameChangedEvent = (DataSourceNameChangedEvent) event;
657  Content dataSource = dataSourceNameChangedEvent.getDataSource();
658  String newName = (String) event.getNewValue();
659 
660  if (!StringUtils.isEmpty(newName)) {
661 
662  if (!CentralRepository.isEnabled()) {
663  return;
664  }
665 
666  try {
667  CorrelationCase correlationCase = dbManager.getCase(Case.getCurrentCaseThrows());
668  CorrelationDataSource existingEamDataSource = dbManager.getDataSource(correlationCase, dataSource.getId());
669  dbManager.updateDataSourceName(existingEamDataSource, newName);
670  } catch (CentralRepoException ex) {
671  LOGGER.log(Level.SEVERE, "Error updating data source with ID " + dataSource.getId() + " to " + newName, ex); //NON-NLS
672  } catch (NoCurrentCaseException ex) {
673  LOGGER.log(Level.SEVERE, "No open case", ex);
674  }
675  }
676  }
677  }
678 
679 }
static CorrelationDataSource fromTSKDataSource(CorrelationCase correlationCase, Content dataSource)
TskData.FileKnown getKnownStatus()
List< ContentTag > getContentTagsByContent(Content content)
void setAttributeInstanceKnownStatus(CorrelationAttributeInstance eamArtifact, TskData.FileKnown knownStatus)
BlackboardArtifact getBlackboardArtifact(long artifactID)
AbstractFile getAbstractFileById(long id)
static void shutDownTaskExecutor(ExecutorService executor)
static List< CorrelationAttributeInstance > makeCorrAttrsForSearch(AnalysisResult analysisResult)
List< ContentTag > getContentTagsByTagName(TagName tagName)
static void setArtifactKnownStatus(CentralRepository centralRepo, BlackboardArtifact artifact, TskData.FileKnown notableStatus)
List< BlackboardArtifactTag > getBlackboardArtifactTagsByTagName(TagName tagName)
void updateDataSourceName(CorrelationDataSource eamDataSource, String newName)
CorrelationDataSource getDataSource(CorrelationCase correlationCase, Long caseDbDataSourceId)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:712
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:757
List< BlackboardArtifactTag > getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on: Mon Mar 17 2025
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.