fixes and events

This commit is contained in:
Greg DiCristofaro 2023-06-05 11:40:18 -04:00
parent 7225135935
commit 40683b19a0
7 changed files with 583 additions and 521 deletions

View File

@ -49,9 +49,9 @@ public interface AutopsyItemVisitor<T> {
T visit(DeletedContent.DeletedContentFilter dcf);
T visit(SuspectContent sc);
T visit(ScoreContent sc);
T visit(SuspectContent.SuspectContentFilter scf);
T visit(ScoreContent.ScoreContentFilter scf);
T visit(FileSize fs);
@ -130,12 +130,12 @@ public interface AutopsyItemVisitor<T> {
}
@Override
public T visit(SuspectContent dc) {
public T visit(ScoreContent dc) {
return defaultVisit(dc);
}
@Override
public T visit(SuspectContent.SuspectContentFilter dcf) {
public T visit(ScoreContent.ScoreContentFilter dcf) {
return defaultVisit(dcf);
}

View File

@ -412,6 +412,13 @@ ReportNode.reportNameProperty.name=Report Name
ReportNode.reportNameProperty.displayName=Report Name
ReportNode.reportNameProperty.desc=Name of the report
ReportsListNode.displayName=Reports
ScoreContent_badFilter_text=Bad Items
ScoreContent_createSheet_filterType_desc=no description
ScoreContent_createSheet_filterType_displayName=Type
ScoreContent_createSheet_name_desc=no description
ScoreContent_createSheet_name_displayName=Name
ScoreContent_ScoreContentNode_name=Score
ScoreContent_susFilter_text=Suspicious Items
SlackFileNode.getActions.viewInNewWin.text=View in New Window
SlackFileNode.getActions.viewFileInDir.text=View File in Directory
SpecialDirectoryNode.getActions.viewInNewWin.text=View in New Window

View File

@ -32,8 +32,8 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode;
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.autopsy.allcasessearch.CorrelationAttributeInstanceNode;
import org.sleuthkit.autopsy.datamodel.SuspectContent.SuspectContentsNode;
import org.sleuthkit.autopsy.datamodel.SuspectContent.SuspectContentsChildren.SuspectContentNode;
import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsNode;
import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren.ScoreContentNode;
/**
* Visitor pattern that goes over all nodes in the directory tree. This includes
@ -80,9 +80,9 @@ public interface DisplayableItemNodeVisitor<T> {
T visit(DeletedContentsNode dcn);
T visit(SuspectContentNode scn);
T visit(ScoreContentNode scn);
T visit(SuspectContentsNode scn);
T visit(ScoreContentsNode scn);
T visit(FileSizeRootNode fsrn);
@ -342,12 +342,12 @@ public interface DisplayableItemNodeVisitor<T> {
}
@Override
public T visit(SuspectContentNode scn) {
public T visit(ScoreContentNode scn) {
return defaultVisit(scn);
}
@Override
public T visit(SuspectContentsNode scn) {
public T visit(ScoreContentsNode scn) {
return defaultVisit(scn);
}

View File

@ -24,8 +24,8 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.datamodel.SuspectContent.SuspectContentsChildren;
import org.sleuthkit.autopsy.datamodel.SuspectContent.SuspectContentsChildren.SuspectContentNode;
import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren;
import org.sleuthkit.autopsy.datamodel.ScoreContent.ScoreContentsChildren.ScoreContentNode;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
@ -101,8 +101,8 @@ public class RootContentChildren extends Children.Keys<Object> {
}
@Override
public AbstractNode visit(SuspectContent sc) {
return new SuspectContent.SuspectContentsNode(sc.getSleuthkitCase(), sc.filteringDataSourceObjId());
public AbstractNode visit(ScoreContent sc) {
return new ScoreContent.ScoreContentsNode(sc.getSleuthkitCase(), sc.filteringDataSourceObjId());
}
@Override

View File

@ -0,0 +1,561 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact.Category;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.Score.Priority;
import org.sleuthkit.datamodel.Score.Significance;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
/**
* Score content view nodes.
*/
public class ScoreContent implements AutopsyVisitableItem {
private SleuthkitCase skCase;
private final long filteringDSObjId; // 0 if not filtering/grouping by data source
@NbBundle.Messages({"ScoreContent_badFilter_text=Bad Items",
"ScoreContent_susFilter_text=Suspicious Items"})
public enum ScoreContentFilter implements AutopsyVisitableItem {
BAD_ITEM_FILTER(0, "BAD_ITEM_FILTER", //NON-NLS
Bundle.ScoreContent_badFilter_text()),
SUS_ITEM_FILTER(1, "SUS_ITEM_FILTER", //NON-NLS
Bundle.ScoreContent_susFilter_text());
private int id;
private String name;
private String displayName;
private ScoreContentFilter(int id, String name, String displayName) {
this.id = id;
this.name = name;
this.displayName = displayName;
}
public String getName() {
return this.name;
}
public int getId() {
return this.id;
}
public String getDisplayName() {
return this.displayName;
}
@Override
public <T> T accept(AutopsyItemVisitor<T> visitor) {
return visitor.visit(this);
}
}
public ScoreContent(SleuthkitCase skCase) {
this(skCase, 0);
}
public ScoreContent(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
this.filteringDSObjId = dsObjId;
}
long filteringDataSourceObjId() {
return this.filteringDSObjId;
}
@Override
public <T> T accept(AutopsyItemVisitor<T> visitor) {
return visitor.visit(this);
}
public SleuthkitCase getSleuthkitCase() {
return this.skCase;
}
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
Case.Events.DATA_SOURCE_ADDED,
Case.Events.CURRENT_CASE,
Case.Events.CONTENT_TAG_ADDED,
Case.Events.CONTENT_TAG_DELETED,
Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED,
Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED
);
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestModuleEvent.CONTENT_CHANGED);
private static PropertyChangeListener getPcl(final Runnable onRefresh, final Runnable onRemove) {
return (PropertyChangeEvent evt) -> {
String eventType = evt.getPropertyName();
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
/**
* + // @@@ COULD CHECK If the new file is deleted before
* notifying... Checking for a current case is a stop gap
* measure + update(); until a different way of handling the
* closing of cases is worked out. Currently, remote events may
* be received for a case that is already closed.
*/
try {
Case.getCurrentCaseThrows();
// new file was added
// @@@ COULD CHECK If the new file is deleted before notifying...
if (onRefresh != null) {
onRefresh.run();
}
} catch (NoCurrentCaseException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null && onRemove != null) {
onRemove.run();
}
} else if (CASE_EVENTS_OF_INTEREST.contains(eventType)) {
/**
* Checking for a current case is a stop gap measure until a
* different way of handling the closing of cases is worked out.
* Currently, remote events may be received for a case that is
* already closed.
*/
try {
Case.getCurrentCaseThrows();
if (onRefresh != null) {
onRefresh.run();
}
} catch (NoCurrentCaseException notUsed) {
/**
* Case is closed, do nothing.
*/
}
}
};
}
static private String getFileFilter(ScoreContent.ScoreContentFilter filter, long filteringDSObjId) throws IllegalArgumentException {
String aggregateScoreFilter = "";
switch (filter) {
case SUS_ITEM_FILTER:
aggregateScoreFilter = " tsk_aggregate_score.significance = " + Significance.LIKELY_NOTABLE.getId() + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )";
break;
case BAD_ITEM_FILTER:
aggregateScoreFilter = " tsk_aggregate_score.significance = " + Significance.NOTABLE.getId() + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )";
break;
default:
throw new IllegalArgumentException(MessageFormat.format("Unsupported filter type to get suspect content: {0}", filter));
}
String query = " obj_id IN (SELECT tsk_aggregate_score.obj_id FROM tsk_aggregate_score WHERE " + aggregateScoreFilter + ") ";
if (filteringDSObjId > 0) {
query += " AND data_source_obj_id = " + filteringDSObjId;
}
return query;
}
/**
* Checks for analysis results added to the case that could affect the
* aggregate score of the file.
*
* @param evt The event.
* @return True if has an analysis result.
*/
private static boolean isRefreshRequired(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
/**
* This is a stop gap measure until a different way of handling the
* closing of cases is worked out. Currently, remote events may be
* received for a case that is already closed.
*/
try {
Case.getCurrentCaseThrows();
/**
* Due to some unresolved issues with how cases are closed, it
* is possible for the event to have a null oldValue if the
* event is a remote event.
*/
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
if (null != event && Category.ANALYSIS_RESULT.equals(event.getBlackboardArtifactType().getCategory())) {
return true;
}
} catch (NoCurrentCaseException notUsed) {
/**
* Case is closed, do nothing.
*/
}
}
return false;
}
public static class ScoreContentsNode extends DisplayableItemNode {
@NbBundle.Messages("ScoreContent_ScoreContentNode_name=Score")
private static final String NAME = Bundle.ScoreContent_ScoreContentNode_name();
ScoreContentsNode(SleuthkitCase skCase, long datasourceObjId) {
super(Children.create(new ScoreContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME));
super.setName(NAME);
super.setDisplayName(NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
}
@Override
public boolean isLeafTypeNode() {
return false;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
@NbBundle.Messages({
"ScoreContent_createSheet_name_displayName=Name",
"ScoreContent_createSheet_name_desc=no description"})
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>("Name", //NON-NLS
Bundle.ScoreContent_createSheet_name_displayName(),
Bundle.ScoreContent_createSheet_name_desc(),
NAME));
return sheet;
}
@Override
public String getItemType() {
return getClass().getName();
}
}
public static class ScoreContentsChildren extends ChildFactory.Detachable<ScoreContent.ScoreContentFilter> implements RefreshThrottler.Refresher {
private SleuthkitCase skCase;
private final long datasourceObjId;
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
private final PropertyChangeListener pcl = getPcl(
() -> ScoreContentsChildren.this.refresh(false),
() -> ScoreContentsChildren.this.removeNotify());
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
private final Map<ScoreContentFilter, ScoreContentsChildren.ScoreContentNode> typeNodeMap = new HashMap<>();
public ScoreContentsChildren(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
this.datasourceObjId = dsObjId;
}
@Override
protected void addNotify() {
super.addNotify();
refreshThrottler.registerForIngestModuleEvents();
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
@Override
protected void removeNotify() {
refreshThrottler.unregisterEventListener();
IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
IngestManager.getInstance().removeIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
typeNodeMap.clear();
}
@Override
public void refresh() {
refresh(false);
}
@Override
public boolean isRefreshRequired(PropertyChangeEvent evt) {
return ScoreContent.isRefreshRequired(evt);
}
@Override
protected boolean createKeys(List<ScoreContent.ScoreContentFilter> list) {
list.addAll(Arrays.asList(ScoreContent.ScoreContentFilter.values()));
typeNodeMap.values().forEach(nd -> nd.updateDisplayName());
return true;
}
@Override
protected Node createNodeForKey(ScoreContent.ScoreContentFilter key) {
ScoreContentsChildren.ScoreContentNode nd = new ScoreContentsChildren.ScoreContentNode(skCase, key, datasourceObjId);
typeNodeMap.put(key, nd);
return nd;
}
public class ScoreContentNode extends DisplayableItemNode {
private static final Logger logger = Logger.getLogger(ScoreContentNode.class.getName());
private final ScoreContent.ScoreContentFilter filter;
private final long datasourceObjId;
ScoreContentNode(SleuthkitCase skCase, ScoreContent.ScoreContentFilter filter, long dsObjId) {
super(Children.create(new ScoreContentChildren(filter, skCase, dsObjId), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
this.datasourceObjId = dsObjId;
init();
}
private void init() {
super.setName(filter.getName());
String tooltip = filter.getDisplayName();
this.setShortDescription(tooltip);
switch (this.filter) {
case SUS_ITEM_FILTER:
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/yellow-circle-yield.png"); //NON-NLS
break;
default:
case BAD_ITEM_FILTER:
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
break;
}
updateDisplayName();
}
void updateDisplayName() {
//get count of children without preloading all children nodes
long count = 0;
try {
count = calculateItems(skCase, filter, datasourceObjId);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "An error occurred while fetching file counts", ex);
}
//final long count = getChildren().getNodesCount(true);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
}
/**
* Get children count without actually loading all nodes
*
* @param sleuthkitCase
* @param filter
*
* @return
*/
private static long calculateItems(SleuthkitCase sleuthkitCase, ScoreContent.ScoreContentFilter filter, long datasourceObjId) throws TskCoreException {
return sleuthkitCase.countFilesWhere(getFileFilter(filter, datasourceObjId));
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
@NbBundle.Messages({
"ScoreContent_createSheet_filterType_displayName=Type",
"ScoreContent_createSheet_filterType_desc=no description"})
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>("Type", //NON_NLS
Bundle.ScoreContent_createSheet_filterType_displayName(),
Bundle.ScoreContent_createSheet_filterType_desc(),
filter.getDisplayName()));
return sheet;
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public String getItemType() {
/**
* Return getClass().getName() + filter.getName() if custom
* settings are desired for different filters.
*/
return DisplayableItemNode.FILE_PARENT_NODE_KEY;
}
}
static class ScoreContentChildren extends BaseChildFactory<AbstractFile> implements RefreshThrottler.Refresher {
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
private final PropertyChangeListener pcl = getPcl(
() -> ScoreContentChildren.this.refresh(false),
() -> ScoreContentChildren.this.removeNotify());
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
private final SleuthkitCase skCase;
private final ScoreContent.ScoreContentFilter filter;
private static final Logger logger = Logger.getLogger(ScoreContentChildren.class.getName());
private final long datasourceObjId;
ScoreContentChildren(ScoreContent.ScoreContentFilter filter, SleuthkitCase skCase, long datasourceObjId) {
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
this.skCase = skCase;
this.filter = filter;
this.datasourceObjId = datasourceObjId;
}
@Override
protected void onAdd() {
refreshThrottler.registerForIngestModuleEvents();
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
@Override
protected void onRemove() {
refreshThrottler.unregisterEventListener();
IngestManager.getInstance().removeIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
IngestManager.getInstance().removeIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
@Override
public void refresh() {
refresh(false);
}
@Override
public boolean isRefreshRequired(PropertyChangeEvent evt) {
return ScoreContent.isRefreshRequired(evt);
}
private List<AbstractFile> runFsQuery() {
List<AbstractFile> ret = new ArrayList<>();
String query = null;
try {
query = getFileFilter(filter, datasourceObjId);
ret = skCase.findAllFilesWhere(query);
} catch (TskCoreException | IllegalArgumentException e) {
logger.log(Level.SEVERE, "Error getting files for the deleted content view using: " + StringUtils.defaultString(query, "<null>"), e); //NON-NLS
}
return ret;
}
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
@Override
protected Node createNodeForKey(AbstractFile key) {
return key.accept(new ContentVisitor.Default<AbstractNode>() {
public FileNode visit(AbstractFile f) {
return new FileNode(f, false);
}
public FileNode visit(FsContent f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(LayoutFile f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(File f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(Directory f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(VirtualDirectory f) {
return new FileNode(f, false);
}
@Override
protected AbstractNode defaultVisit(Content di) {
throw new UnsupportedOperationException("Not supported for this type of Displayable Item: " + di.toString());
}
});
}
}
}
}

View File

@ -1,506 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.logging.Level;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.CONTENT_CHANGED;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.Score.Priority;
import org.sleuthkit.datamodel.Score.Significance;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
/**
* Suspect content view nodes.
*/
public class SuspectContent implements AutopsyVisitableItem {
private SleuthkitCase skCase;
private final long filteringDSObjId; // 0 if not filtering/grouping by data source
@NbBundle.Messages({"SuspectContent_badFilter_text=Bad Items",
"SuspectContent_susFilter_text=Suspicious Items"})
public enum SuspectContentFilter implements AutopsyVisitableItem {
BAD_ITEM_FILTER(0, "BAD_ITEM_FILTER", //NON-NLS
Bundle.SuspectContent_badFilter_text()),
SUS_ITEM_FILTER(1, "SUS_ITEM_FILTER", //NON-NLS
Bundle.SuspectContent_susFilter_text());
private int id;
private String name;
private String displayName;
private SuspectContentFilter(int id, String name, String displayName) {
this.id = id;
this.name = name;
this.displayName = displayName;
}
public String getName() {
return this.name;
}
public int getId() {
return this.id;
}
public String getDisplayName() {
return this.displayName;
}
@Override
public <T> T accept(AutopsyItemVisitor<T> visitor) {
return visitor.visit(this);
}
}
public SuspectContent(SleuthkitCase skCase) {
this(skCase, 0);
}
public SuspectContent(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
this.filteringDSObjId = dsObjId;
}
long filteringDataSourceObjId() {
return this.filteringDSObjId;
}
@Override
public <T> T accept(AutopsyItemVisitor<T> visitor) {
return visitor.visit(this);
}
public SleuthkitCase getSleuthkitCase() {
return this.skCase;
}
public static class SuspectContentsNode extends DisplayableItemNode {
@NbBundle.Messages("SuspectContent_SuspectContentNode_name=Score")
private static final String NAME = Bundle.SuspectContent_SuspectContentNode_name();
SuspectContentsNode(SleuthkitCase skCase, long datasourceObjId) {
super(Children.create(new SuspectContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME));
super.setName(NAME);
super.setDisplayName(NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
}
@Override
public boolean isLeafTypeNode() {
return false;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
@NbBundle.Messages({
"SuspectContent_createSheet_name_displayName=Name",
"SuspectContent_createSheet_name_desc=no description"})
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>("Name", //NON-NLS
Bundle.SuspectContent_createSheet_name_displayName(),
Bundle.SuspectContent_createSheet_name_desc(),
NAME));
return sheet;
}
@Override
public String getItemType() {
return getClass().getName();
}
}
public static class SuspectContentsChildren extends ChildFactory<SuspectContent.SuspectContentFilter> {
private SleuthkitCase skCase;
private Observable notifier;
private final long datasourceObjId;
// true if we have already told user that not all files will be shown
private static volatile boolean maxFilesDialogShown = false;
public SuspectContentsChildren(SleuthkitCase skCase, long dsObjId) {
this.skCase = skCase;
this.datasourceObjId = dsObjId;
this.notifier = new SuspectContentsChildrenObservable();
}
/**
* Listens for case and ingest invest. Updates observers when events are
* fired. Other nodes are listening to this for changes.
*/
private static final class SuspectContentsChildrenObservable extends Observable {
// GVDTODO are these all events of interest
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
Case.Events.DATA_SOURCE_ADDED,
Case.Events.CURRENT_CASE
);
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(CONTENT_CHANGED);
SuspectContentsChildrenObservable() {
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl);
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
}
private void removeListeners() {
deleteObservers();
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
}
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
String eventType = evt.getPropertyName();
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
/**
* + // @@@ COULD CHECK If the new file is deleted before
* notifying... Checking for a current case is a stop gap
* measure + update(); until a different way of handling the
* closing of cases is worked out. Currently, remote events
* may be received for a case that is already closed.
*/
try {
Case.getCurrentCaseThrows();
// new file was added
// @@@ COULD CHECK If the new file is deleted before notifying...
update();
} catch (NoCurrentCaseException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())
|| eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
/**
* Checking for a current case is a stop gap measure until a
* different way of handling the closing of cases is worked
* out. Currently, remote events may be received for a case
* that is already closed.
*/
try {
Case.getCurrentCaseThrows();
update();
} catch (NoCurrentCaseException notUsed) {
/**
* Case is closed, do nothing.
*/
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null) {
removeListeners();
}
maxFilesDialogShown = false;
}
};
private void update() {
setChanged();
notifyObservers();
}
}
@Override
protected boolean createKeys(List<SuspectContent.SuspectContentFilter> list) {
list.addAll(Arrays.asList(SuspectContent.SuspectContentFilter.values()));
return true;
}
@Override
protected Node createNodeForKey(SuspectContent.SuspectContentFilter key) {
return new SuspectContentsChildren.SuspectContentNode(skCase, key, notifier, datasourceObjId);
}
// GVDTODO remove observable
public class SuspectContentNode extends DisplayableItemNode {
private final SuspectContent.SuspectContentFilter filter;
private final long datasourceObjId;
SuspectContentNode(SleuthkitCase skCase, SuspectContent.SuspectContentFilter filter, Observable o, long dsObjId) {
super(Children.create(new SuspectContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
this.datasourceObjId = dsObjId;
init();
o.addObserver(new SuspectContentNodeObserver());
}
private void init() {
super.setName(filter.getName());
String tooltip = filter.getDisplayName();
this.setShortDescription(tooltip);
switch (this.filter) {
case SUS_ITEM_FILTER:
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/yellow-circle-yield.png"); //NON-NLS
break;
default:
case BAD_ITEM_FILTER:
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
break;
}
updateDisplayName();
}
// update the display name when new events are fired
private class SuspectContentNodeObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
}
private void updateDisplayName() {
//get count of children without preloading all children nodes
final long count = SuspectContentChildren.calculateItems(skCase, filter, datasourceObjId);
//final long count = getChildren().getNodesCount(true);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
return visitor.visit(this);
}
@Override
@NbBundle.Messages({
"SuspectContent_createSheet_filterType_displayName=Type",
"SuspectContent_createSheet_filterType_desc=no description"})
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
sheetSet.put(new NodeProperty<>("Type", //NON_NLS
Bundle.SuspectContent_createSheet_filterType_displayName(),
Bundle.SuspectContent_createSheet_filterType_desc(),
filter.getDisplayName()));
return sheet;
}
@Override
public boolean isLeafTypeNode() {
return true;
}
@Override
public String getItemType() {
/**
* Return getClass().getName() + filter.getName() if custom
* settings are desired for different filters.
*/
return DisplayableItemNode.FILE_PARENT_NODE_KEY;
}
}
static class SuspectContentChildren extends BaseChildFactory<AbstractFile> {
private final SleuthkitCase skCase;
private final SuspectContent.SuspectContentFilter filter;
private static final Logger logger = Logger.getLogger(SuspectContentChildren.class.getName());
private final Observable notifier;
private final long datasourceObjId;
// GVDTODO remove observable
SuspectContentChildren(SuspectContent.SuspectContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) {
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
this.skCase = skCase;
this.filter = filter;
this.notifier = o;
this.datasourceObjId = datasourceObjId;
}
private final Observer observer = new DeletedContentChildrenObserver();
@Override
protected List<AbstractFile> makeKeys() {
return runFsQuery();
}
// Cause refresh of children if there are changes
private class DeletedContentChildrenObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
@Override
protected void onAdd() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void onRemove() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
}
static private String makeQuery(SuspectContent.SuspectContentFilter filter, long filteringDSObjId) {
String aggregateScoreFilter = "";
switch (filter) {
case SUS_ITEM_FILTER:
aggregateScoreFilter = " tsk_aggregate_score.significance = " + Significance.LIKELY_NOTABLE.getId() + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )";
break;
case BAD_ITEM_FILTER:
aggregateScoreFilter = " tsk_aggregate_score.significance = " + Significance.NOTABLE.getId() + " AND (tsk_aggregate_score.priority = " + Priority.NORMAL.getId() + " OR tsk_aggregate_score.priority = " + Priority.OVERRIDE.getId() + " )";
break;
default:
logger.log(Level.SEVERE, "Unsupported filter type to get suspect content: {0}", filter); //NON-NLS
}
String query = " obj_id IN (SELECT tsk_aggregate_score.obj_id FROM tsk_aggregate_score WHERE " + aggregateScoreFilter + ") ";
if (filteringDSObjId > 0) {
query += " AND data_source_obj_id = " + filteringDSObjId;
}
return query;
}
private List<AbstractFile> runFsQuery() {
List<AbstractFile> ret = new ArrayList<>();
String query = makeQuery(filter, datasourceObjId);
try {
ret = skCase.findAllFilesWhere(query);
} catch (TskCoreException e) {
logger.log(Level.SEVERE, "Error getting files for the deleted content view using: " + query, e); //NON-NLS
}
return ret;
}
/**
* Get children count without actually loading all nodes
*
* @param sleuthkitCase
* @param filter
*
* @return
*/
static long calculateItems(SleuthkitCase sleuthkitCase, SuspectContent.SuspectContentFilter filter, long datasourceObjId) {
try {
return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS
return 0;
}
}
@Override
protected Node createNodeForKey(AbstractFile key) {
return key.accept(new ContentVisitor.Default<AbstractNode>() {
public FileNode visit(AbstractFile f) {
return new FileNode(f, false);
}
public FileNode visit(FsContent f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(LayoutFile f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(File f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(Directory f) {
return new FileNode(f, false);
}
@Override
public FileNode visit(VirtualDirectory f) {
return new FileNode(f, false);
}
@Override
protected AbstractNode defaultVisit(Content di) {
throw new UnsupportedOperationException("Not supported for this type of Displayable Item: " + di.toString());
}
});
}
}
}
}

View File

@ -47,7 +47,7 @@ public class ViewsNode extends DisplayableItemNode {
// new RecentFiles(sleuthkitCase),
new DeletedContent(sleuthkitCase, dsObjId),
new FileSize(sleuthkitCase, dsObjId),
new SuspectContent(sleuthkitCase, dsObjId))
new ScoreContent(sleuthkitCase, dsObjId))
),
Lookups.singleton(NAME)
);