changes to hide nodes with no results

This commit is contained in:
Greg DiCristofaro 2021-04-30 09:20:25 -04:00
parent 1d1904478c
commit e2f83a897a

View File

@ -24,8 +24,6 @@ import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -40,27 +38,26 @@ import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.RootContentChildren.CreateAutopsyNodeVisitor;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.guiutils.RefreshThrottler; import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
import org.sleuthkit.datamodel.BlackboardArtifact.Category; import org.sleuthkit.datamodel.BlackboardArtifact.Category;
import org.python.google.common.collect.Sets;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT;
/** /**
* Parent of the "extracted content" artifacts to be displayed in the tree. * Parent of the "extracted content" artifacts to be displayed in the tree.
@ -147,6 +144,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
} }
static class RootNode extends DisplayableItemNode { static class RootNode extends DisplayableItemNode {
RootNode(Children children, String icon, String name, String displayName) { RootNode(Children children, String icon, String name, String displayName) {
super(children, Lookups.singleton(name)); super(children, Lookups.singleton(name));
super.setName(name); super.setName(name);
@ -187,197 +185,51 @@ public class ExtractedContent implements AutopsyVisitableItem {
} }
private static class AnalysisResultsTypeFactory extends TypeFactory { private static class AnalysisResultsTypeFactory extends TypeFactory {
private static final Logger logger = Logger.getLogger(AnalysisResultsTypeFactory.class.getName());
private static final Set<BlackboardArtifact.Type> EXCLUDED_ANALYSIS_RESULTS = Stream.of(
// these are shown in other parts of the UI (and different node types)
new BlackboardArtifact.Type(TSK_HASHSET_HIT),
new BlackboardArtifact.Type(TSK_KEYWORD_HIT),
new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT),
new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT),
new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE)
).collect(Collectors.toSet());
private static Map<String, AutopsyVisitableItem> getVisitableItems(long filteringDSObjId) {
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return new HashMap<String, AutopsyVisitableItem>() {
{
put(KeywordHits.getDisplayName(), new KeywordHits(skCase, filteringDSObjId));
put(HashsetHits.getDisplayName(), new HashsetHits(skCase, filteringDSObjId));
put(InterestingHits.getDisplayName(), new InterestingHits(skCase, filteringDSObjId));
}
};
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Trying to create AnalysisResultsTypeFactory with no open case.", ex);
return Collections.emptyMap();
}
}
AnalysisResultsTypeFactory(long filteringDSObjId) { AnalysisResultsTypeFactory(long filteringDSObjId) {
super(getVisitableItems(filteringDSObjId), EXCLUDED_ANALYSIS_RESULTS, Category.ANALYSIS_RESULT, filteringDSObjId); super(Category.ANALYSIS_RESULT, filteringDSObjId);
} }
} }
private static class DataArtifactsTypeFactory extends TypeFactory { private static class DataArtifactsTypeFactory extends TypeFactory {
private static final Logger logger = Logger.getLogger(DataArtifactsTypeFactory.class.getName());
@SuppressWarnings("deprecation")
private static final Set<BlackboardArtifact.Type> EXCLUDED_DATA_ARTIFACTS = Stream.of(
// these are shown in other parts of the UI (and different node types)
new BlackboardArtifact.Type(TSK_GEN_INFO),
new BlackboardArtifact.Type(TSK_EMAIL_MSG),
new BlackboardArtifact.Type(TSK_ACCOUNT),
new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE),
new BlackboardArtifact.Type(TSK_TL_EVENT),
//This is not meant to be shown in the UI at all. It is more of a meta artifact.
new BlackboardArtifact.Type(TSK_ASSOCIATED_OBJECT)
).collect(Collectors.toSet());
private static Map<String, AutopsyVisitableItem> getVisitableItems(long filteringDSObjId) {
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return new HashMap<String, AutopsyVisitableItem>() {
{
put(EmailExtracted.getDisplayName(), new EmailExtracted(skCase, filteringDSObjId));
put(Accounts.getDisplayName(), new Accounts(skCase, filteringDSObjId));
}
};
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Trying to create DataArtifactsTypeFactory with no open case.", ex);
return Collections.emptyMap();
}
}
DataArtifactsTypeFactory(long filteringDSObjId) { DataArtifactsTypeFactory(long filteringDSObjId) {
super(getVisitableItems(filteringDSObjId), EXCLUDED_DATA_ARTIFACTS, Category.DATA_ARTIFACT, filteringDSObjId); super(Category.DATA_ARTIFACT, filteringDSObjId);
} }
} }
interface ArtifactKey { private static class TypeNodeRecord {
Node getNode(); private final Node node;
private final Runnable onUpdate;
private final Set<BlackboardArtifact.Type> applicableTypes;
String getName(); TypeNodeRecord(BlackboardArtifact.Type type, long dsObjId) {
this(new TypeNode(type, dsObjId), type);
void update();
}
private static class VisitableArtifactKey implements ArtifactKey {
private static final CreateAutopsyNodeVisitor visitor = new CreateAutopsyNodeVisitor();
private final AutopsyVisitableItem visitable;
private final String name;
private final long dsFilteringObjId;
VisitableArtifactKey(AutopsyVisitableItem visitable, String name, long dsFilteringObjId) {
this.visitable = visitable;
this.name = name;
this.dsFilteringObjId = dsFilteringObjId;
} }
@Override private TypeNodeRecord(TypeNode typeNode, BlackboardArtifact.Type type) {
public Node getNode() { this(typeNode, typeNode::updateDisplayName, Stream.of(type).collect(Collectors.toSet()));
return visitable.accept(visitor);
} }
@Override TypeNodeRecord(Node node, Runnable onUpdate, Set<BlackboardArtifact.Type> applicableTypes) {
public String getName() { this.node = node;
return this.name; this.onUpdate = onUpdate;
this.applicableTypes = applicableTypes;
} }
@Override Node getNode() {
public void update() { return node;
// no need to handle updates for a visitable item
} }
@Override void update() {
public int hashCode() { if (onUpdate != null) {
int hash = 7; onUpdate.run();
hash = 53 * hash + Objects.hashCode(this.name);
hash = 53 * hash + (int) (this.dsFilteringObjId ^ (this.dsFilteringObjId >>> 32));
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} }
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final VisitableArtifactKey other = (VisitableArtifactKey) obj;
if (this.dsFilteringObjId != other.dsFilteringObjId) {
return false;
}
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
}
private static class TypeArtifactKey implements ArtifactKey {
private final BlackboardArtifact.Type type;
private final Map<BlackboardArtifact.Type, TypeNode> typeMapping;
private final long dsFilteringObjId;
TypeArtifactKey(BlackboardArtifact.Type type, Map<BlackboardArtifact.Type, TypeNode> typeMapping, long dsFilteringObjId) {
this.type = type;
this.typeMapping = typeMapping;
this.dsFilteringObjId = dsFilteringObjId;
} }
@Override Set<BlackboardArtifact.Type> getApplicableTypes() {
public TypeNode getNode() { return applicableTypes;
return typeMapping.computeIfAbsent(type, (tp) -> new TypeNode(type, dsFilteringObjId));
} }
@Override
public String getName() {
return this.type.getDisplayName();
}
@Override
public void update() {
getNode().updateDisplayName();
}
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + Objects.hashCode(this.type);
hash = 53 * hash + (int) (this.dsFilteringObjId ^ (this.dsFilteringObjId >>> 32));
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TypeArtifactKey other = (TypeArtifactKey) obj;
if (this.dsFilteringObjId != other.dsFilteringObjId) {
return false;
}
if (!Objects.equals(this.type, other.type)) {
return false;
}
return true;
}
} }
/** /**
@ -385,12 +237,59 @@ public class ExtractedContent implements AutopsyVisitableItem {
* This area has all of the blackboard artifacts that are not displayed in a * This area has all of the blackboard artifacts that are not displayed in a
* more specific form elsewhere in the tree. * more specific form elsewhere in the tree.
*/ */
private static class TypeFactory extends ChildFactory.Detachable<ArtifactKey> implements RefreshThrottler.Refresher { private static class TypeFactory extends ChildFactory.Detachable<TypeNodeRecord> implements RefreshThrottler.Refresher {
private static final Logger logger = Logger.getLogger(TypeNode.class.getName()); private static final Logger logger = Logger.getLogger(TypeNode.class.getName());
private static final Set<BlackboardArtifact.Type> IGNORED_TYPES = Sets.newHashSet(
// these are shown in other parts of the UI (and different node types)
new BlackboardArtifact.Type(TSK_DATA_SOURCE_USAGE),
new BlackboardArtifact.Type(TSK_GEN_INFO),
new BlackboardArtifact.Type(TSK_DOWNLOAD_SOURCE),
new BlackboardArtifact.Type(TSK_TL_EVENT),
//This is not meant to be shown in the UI at all. It is more of a meta artifact.
new BlackboardArtifact.Type(TSK_ASSOCIATED_OBJECT)
);
private static TypeNodeRecord getRecord(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) {
int typeId = type.getTypeID();
if (TSK_EMAIL_MSG.getTypeID() == typeId) {
EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode();
return new TypeNodeRecord(
emailNode,
null,
Sets.newHashSet(new BlackboardArtifact.Type(TSK_EMAIL_MSG)));
} else if (TSK_ACCOUNT.getTypeID() == typeId) {
Accounts.AccountsRootNode accountsNode = new Accounts(skCase, dsObjId).new AccountsRootNode();
return new TypeNodeRecord(
accountsNode,
null,
Sets.newHashSet(new BlackboardArtifact.Type(TSK_ACCOUNT)));
} else if (TSK_KEYWORD_HIT.getTypeID() == typeId) {
KeywordHits.RootNode keywordsNode = new KeywordHits(skCase, dsObjId).new RootNode();
return new TypeNodeRecord(
keywordsNode,
null,
Sets.newHashSet(new BlackboardArtifact.Type(TSK_KEYWORD_HIT)));
} else if (TSK_EMAIL_MSG.getTypeID() == typeId) {
InterestingHits.RootNode interestingHitsNode = new InterestingHits(skCase, dsObjId).new RootNode();
return new TypeNodeRecord(
interestingHitsNode,
null,
Sets.newHashSet(
new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT),
new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT)));
} else {
return new TypeNodeRecord(type, dsObjId);
}
}
// maps the artifact type to its child node // maps the artifact type to its child node
private final HashMap<BlackboardArtifact.Type, TypeNode> typeNodeMap = new HashMap<>(); private final HashMap<BlackboardArtifact.Type, TypeNodeRecord> typeNodeMap = new HashMap<>();
private final long filteringDSObjId; private final long filteringDSObjId;
/** /**
@ -399,17 +298,12 @@ public class ExtractedContent implements AutopsyVisitableItem {
* received. * received.
*/ */
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this); private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
private final Map<String, AutopsyVisitableItem> visitableItems;
private final Category category; private final Category category;
private final Set<BlackboardArtifact.Type> excludeTypes;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
TypeFactory(Map<String, AutopsyVisitableItem> visitableItems, Set<BlackboardArtifact.Type> excludeTypes, TypeFactory(Category category, long filteringDSObjId) {
Category category, long filteringDSObjId) {
super(); super();
this.filteringDSObjId = filteringDSObjId; this.filteringDSObjId = filteringDSObjId;
this.excludeTypes = excludeTypes;
this.visitableItems = visitableItems;
this.category = category; this.category = category;
} }
@ -454,30 +348,41 @@ public class ExtractedContent implements AutopsyVisitableItem {
} }
@Override @Override
protected boolean createKeys(List<ArtifactKey> list) { protected boolean createKeys(List<TypeNodeRecord> list) {
try { try {
// Potentially can reuse // Potentially can reuse
List<BlackboardArtifact.Type> types = (this.filteringDSObjId > 0) List<BlackboardArtifact.Type> types = (this.filteringDSObjId > 0)
? Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getArtifactTypesInUse(this.filteringDSObjId) ? Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard().getArtifactTypesInUse(this.filteringDSObjId)
: Case.getCurrentCaseThrows().getSleuthkitCase().getArtifactTypesInUse(); : Case.getCurrentCaseThrows().getSleuthkitCase().getArtifactTypesInUse();
Stream<ArtifactKey> typeArtifactKeys = types.stream() SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
.filter(tp -> category.equals(tp.getCategory()) && !excludeTypes.contains(tp))
.map(tp -> new TypeArtifactKey(tp, this.typeNodeMap, this.filteringDSObjId));
Stream<ArtifactKey> visitableKeys = visitableItems.entrySet().stream() List<TypeNodeRecord> allKeysSorted = types.stream()
.map(entry -> new VisitableArtifactKey(entry.getValue(), entry.getKey(), filteringDSObjId)); .filter(tp -> category.equals(tp.getCategory()) && !IGNORED_TYPES.contains(tp))
.map(tp -> {
List<ArtifactKey> allKeysSorted = Stream.concat(typeArtifactKeys, visitableKeys) if (typeNodeMap.containsKey(tp)) {
.filter(item -> item != null) TypeNodeRecord record = typeNodeMap.get(tp);
record.update();
return record;
} else {
TypeNodeRecord newRecord = getRecord(tp, skCase, filteringDSObjId);
for (BlackboardArtifact.Type recordType : newRecord.getApplicableTypes()) {
typeNodeMap.put(recordType, newRecord);
}
return newRecord;
}
})
.filter(record -> record != null)
.distinct()
.sorted((a, b) -> { .sorted((a, b) -> {
String aSafe = (a.getName() == null) ? "" : a.getName(); String aSafe = (a.getNode() == null || a.getNode().getDisplayName() == null) ? "" : a.getNode().getDisplayName();
String bSafe = (b.getName() == null) ? "" : b.getName(); String bSafe = (b.getNode() == null || b.getNode().getDisplayName() == null) ? "" : b.getNode().getDisplayName();
return aSafe.compareToIgnoreCase(bSafe); return aSafe.compareToIgnoreCase(bSafe);
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
allKeysSorted.forEach(ArtifactKey::update); allKeysSorted.forEach(record -> record.update());
list.addAll(allKeysSorted); list.addAll(allKeysSorted);
@ -490,7 +395,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
} }
@Override @Override
protected Node createNodeForKey(ArtifactKey key) { protected Node createNodeForKey(TypeNodeRecord key) {
return key.getNode(); return key.getNode();
} }
@ -517,7 +422,7 @@ public class ExtractedContent implements AutopsyVisitableItem {
*/ */
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
if (null != event && category.equals(event.getBlackboardArtifactType().getCategory()) if (null != event && category.equals(event.getBlackboardArtifactType().getCategory())
&& !(excludeTypes.contains(event.getBlackboardArtifactType()))) { && !(IGNORED_TYPES.contains(event.getBlackboardArtifactType()))) {
return true; return true;
} }
} catch (NoCurrentCaseException notUsed) { } catch (NoCurrentCaseException notUsed) {