First pass

This commit is contained in:
Kelly Kelly
2021-11-17 10:13:29 -05:00
parent 381cba69db
commit ce21d60ab1
11 changed files with 677 additions and 21 deletions
@@ -30,7 +30,7 @@ public class NodeProperty<T> extends PropertySupport.ReadOnly<T> {
private T value;
@SuppressWarnings("unchecked")
public NodeProperty(String name, String displayName, String desc, T value) {
public NodeProperty(String name, String displayName, String desc, T value) {
super(name, (Class<T>) value.getClass(), displayName, desc);
setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS
this.value = value;
@@ -18,9 +18,13 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AnalysisResultItem;
import org.sleuthkit.autopsy.datamodel.FileTypeExtensions;
@@ -30,7 +34,10 @@ import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultTableSearchResultsDT
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.AnalysisResult;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
/**
@@ -58,7 +65,7 @@ public class AnalysisResultNode extends ArtifactNode<AnalysisResult, AnalysisRes
* @param iconPath The path for the node icon.
*/
AnalysisResultNode(AnalysisResultTableSearchResultsDTO tableData, AnalysisResultRowDTO resultRow, String iconPath) {
super(resultRow, tableData.getColumns(), tableData.getArtifactType(), createLookup(resultRow), iconPath);
super(tableData, resultRow, tableData.getColumns(), createLookup(resultRow), iconPath);
}
/**
@@ -104,4 +111,32 @@ public class AnalysisResultNode extends ArtifactNode<AnalysisResult, AnalysisRes
}
return Optional.empty();
}
@Override
public Optional<List<Tag>> getAllTagsFromDatabase() {
List<Tag> tags = new ArrayList<>();
try {
List<BlackboardArtifactTag> artifactTags = ContentNodeUtil.getArtifactTagsFromDatabase(getRowDTO().getArtifact());
if(!artifactTags.isEmpty()) {
tags.addAll(artifactTags);
}
List<ContentTag> contentTags = ContentNodeUtil.getContentTagsFromDatabase(getRowDTO().getSrcContent());
if(!contentTags.isEmpty()) {
tags.addAll(contentTags);
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get content tags from database for Artifact id=" + getRowDTO().getArtifact().getId(), ex);
}
if(!tags.isEmpty()) {
return Optional.of(tags);
}
return Optional.empty();
}
@Override
public Logger getLogger() {
return logger;
}
}
@@ -18,24 +18,40 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode;
import org.sleuthkit.autopsy.datamodel.LocalFileNode;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.SlackFileNode;
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
import org.sleuthkit.autopsy.mainui.datamodel.ArtifactRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import static org.sleuthkit.autopsy.mainui.nodes.BaseNode.backgroundTasksPool;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory;
import org.sleuthkit.autopsy.mainui.nodes.sco.SCOFetcher;
import org.sleuthkit.autopsy.mainui.nodes.sco.SCOSupporter;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
@@ -48,23 +64,29 @@ import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.LocalFile;
import org.sleuthkit.datamodel.OsAccount;
import org.sleuthkit.datamodel.SlackFile;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.VirtualDirectory;
public abstract class ArtifactNode<T extends BlackboardArtifact, R extends ArtifactRowDTO<T>> extends AbstractNode implements ActionContext {
public abstract class ArtifactNode<T extends BlackboardArtifact, R extends ArtifactRowDTO<T>> extends BaseNode<SearchResultsDTO, ArtifactRowDTO> implements ActionContext, SCOSupporter {
private final R rowData;
private final BlackboardArtifact.Type artifactType;
private final List<ColumnKey> columns;
private Node parentFileNode;
ArtifactNode(R rowData, List<ColumnKey> columns, BlackboardArtifact.Type artifactType, Lookup lookup, String iconPath) {
super(Children.LEAF, lookup);
ArtifactNode(SearchResultsDTO searchResults, R rowData, List<ColumnKey> columns, Lookup lookup, String iconPath) {
super(Children.LEAF, lookup, searchResults, rowData);
this.rowData = rowData;
this.artifactType = artifactType;
this.columns = columns;
setupNodeDisplay(iconPath);
}
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
backgroundTasksPool.submit(new SCOFetcher<>(new WeakReference<>(this)));
return sheet;
}
@Override
public Optional<Content> getSourceContent() {
return Optional.ofNullable(rowData.getSrcContent());
@@ -133,7 +155,7 @@ public abstract class ArtifactNode<T extends BlackboardArtifact, R extends Artif
public boolean supportsArtifactTagAction() {
return true;
}
private Node getParentFileNode() {
if (parentFileNode == null) {
parentFileNode = getParentFileNode(rowData.getSrcContent());
@@ -154,13 +176,80 @@ public abstract class ArtifactNode<T extends BlackboardArtifact, R extends Artif
}
@Override
protected Sheet createSheet() {
return ContentNodeUtil.setSheet(super.createSheet(), columns, rowData.getCellValues());
public Action[] getActions(boolean context) {
return ActionsFactory.getActions(this);
}
@Override
public Action[] getActions(boolean context) {
return ActionsFactory.getActions( this);
public Optional<Content> getContent() {
return Optional.of(rowData.getArtifact());
}
@Override
public void updateSheet(List<NodeProperty<?>> newProps) {
super.updateSheet(newProps);
}
@Messages({
"# {0} - occurrenceCount",
"# {1} - attributeType",
"ArtifactNode_createSheet_count_description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}",
"ArtifactNode_createSheet_count_noCorrelationValues_description=Unable to find other occurrences because no value exists for the available correlation property"
})
@Override
public Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) {
Long count = -1L;
String description = defaultDescription;
try {
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
count = CentralRepository.getInstance().getCountCasesWithOtherInstances(attribute);
description = Bundle.ArtifactNode_createSheet_count_description(count, attribute.getCorrelationType().getDisplayName());
} else if (attribute != null) {
description = Bundle.ArtifactNode_createSheet_count_noCorrelationValues_description();
}
} catch (CentralRepoException ex) {
getLogger().log(Level.SEVERE, MessageFormat.format("Error querying central repository for other occurences count (artifact objID={0}, corrAttrType={1}, corrAttrValue={2})",
getRowDTO().getArtifact().getId(),
attribute.getCorrelationType(),
attribute.getCorrelationValue()), ex);
} catch (CorrelationAttributeNormalizationException ex) {
getLogger().log(Level.SEVERE, MessageFormat.format("Error normalizing correlation attribute for central repository query (artifact objID={0}, corrAttrType={2}, corrAttrValue={3})",
getRowDTO().getArtifact().getId(),
attribute.getCorrelationType(),
attribute.getCorrelationValue()), ex);
}
return Pair.of(count, description);
}
@Override
public DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) {
/*
* Has a tag with a comment been applied to the artifact or its source
* content?
*/
DataResultViewerTable.HasCommentStatus status = tags.size() > 0 ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT;
for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT;
break;
}
}
/*
* Is there a comment in the CR for anything that matches the value and
* type of the specified attributes.
*/
try {
if (CentralRepoDbUtil.commentExistsOnAttributes(attributes)) {
if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) {
status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS;
} else {
status = DataResultViewerTable.HasCommentStatus.CR_COMMENT;
}
}
} catch (CentralRepoException ex) {
getLogger().log(Level.SEVERE, "Attempted to Query CR for presence of comments in a Blackboard Artifact node and was unable to perform query, comment column will only reflect caseDB", ex);
}
return status;
}
/**
@@ -18,11 +18,18 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.mainui.datamodel.BaseRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext;
@@ -31,10 +38,24 @@ import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory;
/**
* A a simple starting point for nodes.
*/
abstract class BaseNode<S extends SearchResultsDTO, R extends BaseRowDTO> extends AbstractNode implements ActionContext {
public abstract class BaseNode<S extends SearchResultsDTO, R extends BaseRowDTO> extends AbstractNode implements ActionContext {
private final S results;
private final R rowData;
/**
* A pool of background tasks to run any long computation needed to populate
* this node.
*/
static final ExecutorService backgroundTasksPool;
private static final Integer MAX_POOL_SIZE = 10;
static {
//Initialize this pool only once! This will be used by every instance BaseNode
//to do their heavy duty SCO column and translation updates.
backgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
new ThreadFactoryBuilder().setNameFormat("BaseNode-background-task-%d").build());
}
BaseNode(Children children, Lookup lookup, S results, R rowData) {
super(children, lookup);
@@ -69,4 +90,41 @@ abstract class BaseNode<S extends SearchResultsDTO, R extends BaseRowDTO> extend
public Action[] getActions(boolean context) {
return ActionsFactory.getActions(this);
}
/**
* Updates the values of the properties in the current property sheet with
* the new properties being passed in. Only if that property exists in the
* current sheet will it be applied. That way, we allow for subclasses to
* add their own (or omit some!) properties and we will not accidentally
* disrupt their UI.
*
* Race condition if not synchronized. Only one update should be applied at
* a time.
*
* @param newProps New file property instances to be updated in the current
* sheet.
*/
protected synchronized void updateSheet(List<NodeProperty<?>> newProps) {
SwingUtilities.invokeLater(() -> {
/*
* Refresh ONLY those properties in the sheet currently. Subclasses
* may have only added a subset of our properties or their own
* properties.
*/
Sheet visibleSheet = this.getSheet();
Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
Property<?>[] visibleProps = visibleSheetSet.getProperties();
for (NodeProperty<?> newProp : newProps) {
for (int i = 0; i < visibleProps.length; i++) {
if (visibleProps[i].getName().equals(newProp.getName())) {
visibleProps[i] = newProp;
}
}
}
visibleSheetSet.put(visibleProps);
visibleSheet.put(visibleSheetSet);
//setSheet() will notify Netbeans to update this node in the UI.
this.setSheet(visibleSheet);
});
}
}
@@ -1,4 +1,8 @@
AnalysisResultTypeFactory_adHocName=Adhoc Results
# {0} - occurrenceCount
# {1} - attributeType
ArtifactNode_createSheet_count_description=There were {0} datasource(s) found with occurrences of the correlation value of type {1}
ArtifactNode_createSheet_count_noCorrelationValues_description=Unable to find other occurrences because no value exists for the available correlation property
ImageNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files
SearchResultRootNode_createSheet_childCount_displayName=Child Count
SearchResultRootNode_createSheet_childCount_name=Child Count
@@ -18,17 +18,24 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.openide.nodes.Sheet;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.TskContentItem;
import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utilities for setting up nodes that handle content.
@@ -68,6 +75,12 @@ public class ContentNodeUtil {
Object cellValue = values.get(i);
if (cellValue == null) {
sheetSet.put(new NodeProperty<>(
columnKey.getFieldName(),
columnKey.getDisplayName(),
columnKey.getDescription(),
""
));
continue;
}
@@ -85,4 +98,22 @@ public class ContentNodeUtil {
return sheet;
}
/**
* Get all tags from the case database that are associated with the file
*
* @return a list of tags that are associated with the file
*/
public static List<ContentTag> getContentTagsFromDatabase(Content content) throws TskCoreException, NoCurrentCaseException{
List<ContentTag> tags = new ArrayList<>();
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
return tags;
}
public static List<BlackboardArtifactTag> getArtifactTagsFromDatabase(BlackboardArtifact artifact) throws TskCoreException, NoCurrentCaseException{
List<BlackboardArtifactTag> tags = new ArrayList<>();
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact));
return tags;
}
}
@@ -18,14 +18,23 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
import org.sleuthkit.autopsy.datamodel.DataArtifactItem;
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* node to display a data artifact.
@@ -48,6 +57,27 @@ public class DataArtifactNode extends ArtifactNode<DataArtifact, DataArtifactRow
}
public DataArtifactNode(DataArtifactTableSearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath) {
super(artifactRow, tableData.getColumns(), tableData.getArtifactType(), createLookup(artifactRow), iconPath);
super(tableData, artifactRow, tableData.getColumns(), createLookup(artifactRow), iconPath);
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public Optional<List<Tag>> getAllTagsFromDatabase() {
try {
List<BlackboardArtifactTag> artifactTags = ContentNodeUtil.getArtifactTagsFromDatabase(getRowDTO().getArtifact());
if(!artifactTags.isEmpty()) {
List<Tag> tags = new ArrayList<>();
tags.addAll(artifactTags);
return Optional.of(tags);
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get content tags from database for Artifact id=" + getRowDTO().getArtifact().getId(), ex);
}
return Optional.empty();
}
}
@@ -18,25 +18,34 @@
*/
package org.sleuthkit.autopsy.mainui.nodes;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.datamodel.FileTypeExtensions;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ColumnKey;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.ExtensionMediaType;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionContext;
import org.sleuthkit.autopsy.mainui.nodes.actions.ActionsFactory;
import org.sleuthkit.autopsy.mainui.nodes.sco.SCOFetcher;
import org.sleuthkit.autopsy.mainui.nodes.sco.SCOSupporter;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
@@ -45,7 +54,9 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
/**
* A node for representing an AbstractFile.
*/
public class FileNode extends AbstractNode implements ActionContext {
public class FileNode extends BaseNode<SearchResultsDTO, FileRowDTO> implements SCOSupporter {
private static final Logger logger = Logger.getLogger(FileNode.class.getName());
/**
* Gets the path to the icon file that should be used to visually represent
@@ -95,7 +106,7 @@ public class FileNode extends AbstractNode implements ActionContext {
public FileNode(SearchResultsDTO results, FileRowDTO file, boolean directoryBrowseMode) {
// GVDTODO: at some point, this leaf will need to allow for children
super(Children.LEAF, ContentNodeUtil.getLookup(file.getAbstractFile()));
super(Children.LEAF, ContentNodeUtil.getLookup(file.getAbstractFile()), results, file);
setIcon(file);
setDisplayName(ContentNodeUtil.getContentDisplayName(file.getFileName()));
setName(ContentNodeUtil.getContentName(file.getId()));
@@ -187,7 +198,40 @@ public class FileNode extends AbstractNode implements ActionContext {
@Override
protected Sheet createSheet() {
return ContentNodeUtil.setSheet(super.createSheet(), this.columns, this.fileData.getCellValues());
Sheet sheet = super.createSheet();
backgroundTasksPool.submit(new SCOFetcher<>(new WeakReference<>(this)));
return sheet;
}
@Override
public Logger getLogger() {
return logger;
}
@Override
public Optional<Content> getContent() {
return Optional.ofNullable(fileData.getAbstractFile());
}
@Override
public void updateSheet(List<NodeProperty<?>> newProps) {
super.updateSheet(newProps);
}
@Override
public Optional<List<Tag>> getAllTagsFromDatabase() {
try {
List<ContentTag> contentTags = ContentNodeUtil.getContentTagsFromDatabase(fileData.getAbstractFile());
if(!contentTags.isEmpty()) {
List<Tag> tags = new ArrayList<>();
tags.addAll(contentTags);
return Optional.of(tags);
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get content tags from database for AbstractFile id=" + fileData.getAbstractFile().getId(), ex);
}
return Optional.empty();
}
/**
@@ -0,0 +1,9 @@
SCOFetcher_comment_display_name=C
SCOFetcher_count_display_name=O
SCOFetcher_occurrences_defaultDescription=No correlation properties found
SCOFetcher_occurrences_multipleProperties=Multiple different correlation properties exist for this result
SCOFetcher_score_display_name=S
SCOSupporter.valueLoading=value loading
# {0} - significanceDisplayName
SCOSupporter_getScorePropertyAndDescription_description=Has an {0} analysis result score
SCOSupporter_nodescription_text=no description
@@ -0,0 +1,239 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.sleuthkit.autopsy.mainui.nodes.sco;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.mainui.nodes.sco.SCOFetcher.SCOData;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.AnalysisResult;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.OsAccount;
import org.sleuthkit.datamodel.OsAccountInstance;
import org.sleuthkit.datamodel.Score;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
* @author kelly
*/
public class SCOFetcher<T extends Content> extends SwingWorker<SCOData, Void> {
private final WeakReference<SCOSupporter> weakSupporterRef;
private static final Logger logger = Logger.getLogger(SCOFetcher.class.getName());
public SCOFetcher(WeakReference<SCOSupporter> weakSupporterRef) {
this.weakSupporterRef = weakSupporterRef;
}
@NbBundle.Messages({"SCOFetcher_occurrences_defaultDescription=No correlation properties found",
"SCOFetcher_occurrences_multipleProperties=Multiple different correlation properties exist for this result"})
@Override
protected SCOData doInBackground() throws Exception {
SCOSupporter scoSupporter = weakSupporterRef.get();
Content content = scoSupporter.getContent().get();
//Check for stale reference or if columns are disabled
if (content == null || UserPreferences.getHideSCOColumns()) {
return null;
}
// get the SCO column values
Pair<Score, String> scoreAndDescription;
Pair<Long, String> countAndDescription = null;
scoreAndDescription = scoSupporter.getScorePropertyAndDescription();
String description = Bundle.SCOFetcher_occurrences_defaultDescription();
List<CorrelationAttributeInstance> listOfPossibleAttributes = new ArrayList<>();
//the lists returned will be empty if the CR is not enabled
if (content instanceof AbstractFile) {
listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) content));
} else if (content instanceof AnalysisResult) {
listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) content));
} else if (content instanceof DataArtifact) {
listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) content));
} else if (content instanceof OsAccount) {
try {
List<OsAccountInstance> osAccountInstances = ((OsAccount) content).getOsAccountInstances();
/*
* In the most common use cases it will not matter which
* OsAccountInstance is selected, so choosing the first one is
* the most efficient solution.
*/
OsAccountInstance osAccountInstance = osAccountInstances.isEmpty() ? null : osAccountInstances.get(0);
/*
* If we have a Case whith both data sources in the CR and data
* sources not in the CR, some of the OsAccountInstances for
* this OsAccount have not been processed into the CR. In this
* situation the counts may not always be accurate or
* consistent.
*
* In order to ensure conistency in all use cases we would need
* to ensure we always had an OsAccountInstance whose data
* source was in the CR when such an OsAccountInstance was
* available.
*
* The following block of code has been commented out because it
* reduces efficiency in what are believed to be the most common
* use cases. It would serve the purpose of providing
* consistency in edge cases where users are putting some but
* not all the data concerning OS Accounts, which is present in
* a single Case, into the CR. See TODO-JIRA-8031 for a similar
* issue in the OO viewer.
*/
// if (CentralRepository.isEnabled() && !osAccountInstances.isEmpty()) {
// try {
// CentralRepository centralRepo = CentralRepository.getInstance();
// //Correlation Cases are cached when we get them so this shouldn't involve a round trip for every node.
// CorrelationCase crCase = centralRepo.getCase(Case.getCurrentCaseThrows());
// for (OsAccountInstance caseOsAccountInstance : osAccountInstances) {
// //correlation data sources are also cached so once should not involve round trips every time.
// CorrelationDataSource correlationDataSource = centralRepo.getDataSource(crCase, caseOsAccountInstance.getDataSource().getId());
// if (correlationDataSource != null) {
// //we have found a data source which exists in the CR we will use it instead of the arbitrary first instance
// osAccountInstance = caseOsAccountInstance;
// break;
// }
// }
// } catch (CentralRepoException ex) {
// logger.log(Level.SEVERE, "Error checking CR for data sources which exist in it", ex);
// } catch (NoCurrentCaseException ex) {
// logger.log(Level.WARNING, "The current case was closed while attempting to find a data source in the central repository", ex);
// }
// }
listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(osAccountInstance));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Unable to get the DataSource or OsAccountInstances from an OsAccount with ID: " + content.getId(), ex);
}
}
Optional<List<Tag>> optionalList = scoSupporter.getAllTagsFromDatabase();
DataResultViewerTable.HasCommentStatus commentStatus = DataResultViewerTable.HasCommentStatus.NO_COMMENT;
if(optionalList.isPresent()) {
commentStatus = scoSupporter.getCommentProperty(optionalList.get(), listOfPossibleAttributes);
}
CorrelationAttributeInstance corInstance = null;
if (CentralRepository.isEnabled()) {
if (listOfPossibleAttributes.size() > 1) {
//Don't display anything if there is more than 1 correlation property for an artifact but let the user know
description = Bundle.SCOFetcher_occurrences_multipleProperties();
} else if (!listOfPossibleAttributes.isEmpty()) {
//there should only be one item in the list
corInstance = listOfPossibleAttributes.get(0);
}
countAndDescription = scoSupporter.getCountPropertyAndDescription(corInstance, description);
}
if (isCancelled()) {
return null;
}
return new SCOData(scoreAndDescription, commentStatus, countAndDescription);
}
@Messages({
"SCOFetcher_score_display_name=S",
"SCOFetcher_comment_display_name=C",
"SCOFetcher_count_display_name=O"
})
@Override
public void done() {
if (isCancelled() || UserPreferences.getHideSCOColumns()) {
return;
}
try {
SCOData data = get();
if(data == null) {
return;
}
List<NodeProperty<?>> props = new ArrayList<>();
if(data.getScoreAndDescription() != null) {
props.add(new NodeProperty<>(
Bundle.SCOFetcher_score_display_name(),
Bundle.SCOFetcher_score_display_name(),
data.getScoreAndDescription().getRight(),
data.getScoreAndDescription().getLeft()));
}
if(data.getComment() != null) {
props.add(new NodeProperty<>(
Bundle.SCOFetcher_comment_display_name(),
Bundle.SCOFetcher_comment_display_name(),
"",
data.getComment()));
}
if(data.getCountAndDescription() != null) {
props.add(new NodeProperty<>(
Bundle.SCOFetcher_count_display_name(),
Bundle.SCOFetcher_count_display_name(),
data.getCountAndDescription().getRight(),
data.getCountAndDescription().getLeft()));
}
SCOSupporter scoSupporter = weakSupporterRef.get();
if(!props.isEmpty() && scoSupporter != null) {
scoSupporter.updateSheet(props);
}
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
}
public static class SCOData {
private final Pair<Score, String> scoreAndDescription;
private final DataResultViewerTable.HasCommentStatus comment;
private final Pair<Long, String> countAndDescription;
SCOData(Pair<Score, String> scoreAndDescription, DataResultViewerTable.HasCommentStatus comment, Pair<Long, String> countAndDescription) {
this.scoreAndDescription = scoreAndDescription;
this.comment = comment;
this.countAndDescription = countAndDescription;
}
Pair<Score, String> getScoreAndDescription() {
return scoreAndDescription;
}
DataResultViewerTable.HasCommentStatus getComment() {
return comment;
}
Pair<Long, String> getCountAndDescription() {
return countAndDescription;
}
}
}
@@ -0,0 +1,117 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.mainui.nodes.sco;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Score;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.Tag;
/**
*
*/
public interface SCOSupporter {
@NbBundle.Messages({"SCOSupporter_nodescription_text=no description",
"SCOSupporter.valueLoading=value loading"})
static final String NO_DESCR = Bundle.SCOSupporter_nodescription_text();
default Optional<Content> getContent() {
return Optional.empty();
}
default Optional<List<Tag>> getAllTagsFromDatabase() {
return Optional.empty();
}
default void updateSheet(List<NodeProperty<?>> newProps) {
}
/**
*
* @return
*/
Logger getLogger();
/**
* Returns Score property for the content.
*
* @return
*/
@NbBundle.Messages({
"# {0} - significanceDisplayName",
"SCOSupporter_getScorePropertyAndDescription_description=Has an {0} analysis result score"
})
default Pair<Score, String> getScorePropertyAndDescription() {
Score score = Score.SCORE_UNKNOWN;
Optional<Content> optional = getContent();
if (optional.isPresent()) {
Content content = optional.get();
try {
score = content.getAggregateScore();
} catch (TskCoreException ex) {
getLogger().log(Level.WARNING, "Unable to get aggregate score for content with id: " + content.getId(), ex);
}
}
String significanceDisplay = score.getSignificance().getDisplayName();
String description = Bundle.SCOSupporter_getScorePropertyAndDescription_description(significanceDisplay);
return Pair.of(score, description);
}
/**
* Returns comment property for the node.
*
* Default implementation is a null implementation.
*
* @param tags The list of tags.
* @param attributes The list of correlation attribute instances.
*
* @return Comment property for the underlying content of the node.
*/
default DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, List<CorrelationAttributeInstance> attributes) {
return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
}
/**
* Returns occurrences/count property for the node.
*
* Default implementation is a null implementation.
*
* @param attribute The correlation attribute for which data will
* be retrieved.
* @param defaultDescription A description to use when none is determined by
* the getCountPropertyAndDescription method.
*
* @return count property for the underlying content of the node.
*/
default Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute, String defaultDescription) {
return Pair.of(-1L, NO_DESCR);
}
}