Merge pull request #7813 from gdicristofaro/7072ntl2

7072 new_table_load score changes
This commit is contained in:
eugene7646 2023-07-11 15:03:02 -04:00 committed by GitHub
commit 73366ec442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 923 additions and 383 deletions

View File

@ -106,7 +106,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.ReportsDAO.ReportsFetcher;
import org.sleuthkit.autopsy.mainui.datamodel.ReportsSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.ScoreFileFetcher;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreDAO.ScoreContentFetcher;
import org.sleuthkit.autopsy.mainui.datamodel.events.CacheClearEvent;
import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo;
import org.sleuthkit.autopsy.mainui.nodes.SearchManager;
@ -1373,7 +1373,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
*/
void displayScoreContent(ScoreViewSearchParams scoreSearchParams) {
try {
this.searchResultManager = new SearchManager(new ScoreFileFetcher(scoreSearchParams), getPageSize());
this.searchResultManager = new SearchManager(new ScoreContentFetcher(scoreSearchParams), getPageSize());
SearchResultsDTO results = searchResultManager.getResults();
displaySearchResults(results, true);
} catch (ExecutionException ex) {

View File

@ -150,15 +150,16 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
return IGNORED_TYPES;
}
TableData createTableData(BlackboardArtifact.Type artType, List<BlackboardArtifact> arts) throws TskCoreException, NoCurrentCaseException {
TableData createTableData(BlackboardArtifact.Type artType, List<? extends BlackboardArtifact> arts) throws TskCoreException, NoCurrentCaseException {
// A linked hashmap is being used for artifactAttributes to ensure that artifact order
// as well as attribute orders within those artifacts are preserved. This is to maintain
// a consistent ordering of attribute columns as received from BlackboardArtifact.getAttributes
Map<Long, Map<BlackboardAttribute.Type, Object>> artifactAttributes = new LinkedHashMap<>();
for (BlackboardArtifact art : arts) {
BlackboardArtifact.Type thisArtType = artType != null ? artType : art.getType();
Map<BlackboardAttribute.Type, Object> attrs = art.getAttributes().stream()
.filter(attr -> isRenderedAttr(artType, attr.getAttributeType()))
.collect(Collectors.toMap(attr -> attr.getAttributeType(), attr -> getAttrValue(artType, attr), (attr1, attr2) -> attr1, LinkedHashMap::new));
.filter(attr -> isRenderedAttr(thisArtType, attr.getAttributeType()))
.collect(Collectors.toMap(attr -> attr.getAttributeType(), attr -> getAttrValue(thisArtType, attr), (attr1, attr2) -> attr1, LinkedHashMap::new));
artifactAttributes.put(art.getId(), attrs);
}
@ -205,7 +206,8 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
cellValues.add(dataSourceName);
AbstractFile linkedFile = null;
if (artType.getCategory().equals(BlackboardArtifact.Category.DATA_ARTIFACT)) {
BlackboardArtifact.Type thisArtType = artType != null ? artType : artifact.getType();
if (thisArtType.getCategory().equals(BlackboardArtifact.Category.DATA_ARTIFACT)) {
// Note that we need to get the attribute from the original artifact since it is not displayed.
if (artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID) != null) {
long linkedId = artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID).getValueLong();
@ -469,7 +471,7 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
return pagedArtsStream.collect(Collectors.toList());
}
class TableData {
static class TableData {
final List<ColumnKey> columnKeys;
final List<RowDTO> rows;

View File

@ -105,6 +105,13 @@ ReportsRowDTO_reportFilePath_displayName=Report File Path
ReportsRowDTO_reportName_displayName=Report Name
ReportsRowDTO_sourceModuleName_displayName=Source Module Name
ResultTag.name.text=Result Tag
ScoreDAO_columns_createdDateLbl=Created Date
ScoreDAO_columns_noDescription=No Description
ScoreDAO_columns_pathLbl=Path
ScoreDAO_columns_sourceLbl=Source
ScoreDAO_columns_typeLbl=Type
ScoreDAO_mainNode_displayName=Score
ScoreDAO_types_filelbl=File
ScoreViewFilter_bad_name=Bad Items
ScoreViewFilter_suspicious_name=Suspicious Items
TagsDAO.fileColumns.accessTimeColLbl=Accessed Time

View File

@ -173,6 +173,7 @@ public class MainDAO extends AbstractDAO {
private final EmailsDAO emailsDAO = EmailsDAO.getInstance();
private final HostPersonDAO hostPersonDAO = HostPersonDAO.getInstance();
private final ReportsDAO reportsDAO = ReportsDAO.getInstance();
private final ScoreDAO scoreDAO = ScoreDAO.getInstance();
// NOTE: whenever adding a new sub-dao, it should be added to this list for event updates.
private final List<AbstractDAO> allDAOs = ImmutableList.of(dataArtifactDAO,
@ -185,7 +186,8 @@ public class MainDAO extends AbstractDAO {
creditCardDAO,
emailsDAO,
hostPersonDAO,
reportsDAO);
reportsDAO,
scoreDAO);
/**
* Registers listeners with autopsy event publishers and starts internal
@ -265,6 +267,10 @@ public class MainDAO extends AbstractDAO {
return reportsDAO;
}
public ScoreDAO getScoreDAO() {
return scoreDAO;
}
public PropertyChangeManager getResultEventsManager() {
return this.resultEventsManager;
}
@ -316,10 +322,9 @@ public class MainDAO extends AbstractDAO {
/**
* Processes and handles an autopsy event.
*
* @param evt The event.
* @param evt The event.
* @param immediateResultAction If true, result events are immediately
* fired. Otherwise, the result events are
* batched.
* fired. Otherwise, the result events are batched.
*/
private void handleEvent(PropertyChangeEvent evt, boolean immediateResultAction) {
Collection<DAOEvent> daoEvts = processEvent(evt);

View File

@ -0,0 +1,632 @@
/*
* 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.mainui.datamodel;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import java.beans.PropertyChangeEvent;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE;
import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactDAO.TableData;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils;
import org.sleuthkit.autopsy.mainui.datamodel.events.ScoreContentEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.Category;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.Score;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
/**
* Provides information to populate the results viewer for data in the views
* section.
*/
@Messages({
"ScoreDAO_columns_sourceLbl=Source",
"ScoreDAO_columns_typeLbl=Type",
"ScoreDAO_columns_pathLbl=Path",
"ScoreDAO_columns_createdDateLbl=Created Date",
"ScoreDAO_columns_noDescription=No Description",
"ScoreDAO_types_filelbl=File",
"ScoreDAO_mainNode_displayName=Score"
})
public class ScoreDAO extends AbstractDAO {
private static final Logger logger = Logger.getLogger(ScoreDAO.class.getName());
private static final List<BlackboardAttribute.Type> TIME_ATTRS = Arrays.asList(
BlackboardAttribute.Type.TSK_DATETIME,
BlackboardAttribute.Type.TSK_DATETIME_ACCESSED,
BlackboardAttribute.Type.TSK_DATETIME_RCVD,
BlackboardAttribute.Type.TSK_DATETIME_SENT,
BlackboardAttribute.Type.TSK_DATETIME_CREATED,
BlackboardAttribute.Type.TSK_DATETIME_MODIFIED,
BlackboardAttribute.Type.TSK_DATETIME_START,
BlackboardAttribute.Type.TSK_DATETIME_END,
BlackboardAttribute.Type.TSK_DATETIME_DELETED,
BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_RESET,
BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_FAIL
);
private static final Map<Integer, Integer> TIME_ATTR_IMPORTANCE = IntStream.range(0, TIME_ATTRS.size())
.mapToObj(idx -> Pair.of(TIME_ATTRS.get(idx).getTypeID(), idx))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1));
private static final List<ColumnKey> RESULT_SCORE_COLUMNS = Arrays.asList(
getFileColumnKey(Bundle.ScoreDAO_columns_sourceLbl()),
getFileColumnKey(Bundle.ScoreDAO_columns_typeLbl()),
getFileColumnKey(Bundle.ScoreDAO_columns_pathLbl()),
getFileColumnKey(Bundle.ScoreDAO_columns_createdDateLbl())
);
private static final String SCORE_TYPE_ID = ScoreDAO.class.getName() + "_SIGNATURE_ID";
private static final String BASE_AGGR_SCORE_QUERY
= "FROM tsk_aggregate_score aggr_score\n"
+ "INNER JOIN (\n"
+ " SELECT obj_id, data_source_obj_id, 'f' AS type FROM tsk_files\n"
+ " UNION SELECT artifact_obj_id AS obj_id, data_source_obj_id, 'a' AS type FROM blackboard_artifacts\n"
+ " WHERE blackboard_artifacts.artifact_type_id IN\n"
+ " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + Category.DATA_ARTIFACT.getID() + ")\n"
+ ") art_files ON aggr_score.obj_id = art_files.obj_id\n";
private static ScoreDAO instance = null;
synchronized static ScoreDAO getInstance() {
if (instance == null) {
instance = new ScoreDAO();
}
return instance;
}
private final Cache<SearchParams<Object>, SearchResultsDTO> searchParamsCache
= CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>();
private static ColumnKey getFileColumnKey(String name) {
return new ColumnKey(name, name, Bundle.ScoreDAO_columns_noDescription());
}
private SleuthkitCase getCase() throws NoCurrentCaseException {
return Case.getCurrentCaseThrows().getSleuthkitCase();
}
public SearchResultsDTO getFilesByScore(ScoreViewSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) {
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
}
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchScoreSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
}
private boolean isScoreContentInvalidating(ScoreViewSearchParams params, DAOEvent eventData) {
if (!(eventData instanceof ScoreContentEvent)) {
return false;
}
ScoreContentEvent scoreContentEvt = (ScoreContentEvent) eventData;
ScoreViewFilter evtFilter = scoreContentEvt.getFilter();
ScoreViewFilter paramsFilter = params.getFilter();
Long evtDsId = scoreContentEvt.getDataSourceId();
Long paramsDsId = params.getDataSourceId();
return (evtFilter == null || evtFilter.equals(paramsFilter))
&& (paramsDsId == null || evtDsId == null
|| Objects.equals(paramsDsId, evtDsId));
}
/**
* Returns counts for deleted content categories.
*
* @param dataSourceId The data source object id or null if no data source
* filtering should occur.
*
* @return The results.
*
* @throws IllegalArgumentException
* @throws ExecutionException
*/
public TreeResultsDTO<ScoreViewSearchParams> getScoreContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
Set<ScoreViewFilter> indeterminateFilters = new HashSet<>();
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof ScoreContentEvent) {
ScoreContentEvent scoreEvt = (ScoreContentEvent) evt;
if (dataSourceId == null || scoreEvt.getDataSourceId() == null || Objects.equals(scoreEvt.getDataSourceId(), dataSourceId)) {
if (scoreEvt.getFilter() == null) {
// if null filter, indicates full refresh and all file sizes need refresh.
indeterminateFilters.addAll(Arrays.asList(ScoreViewFilter.values()));
break;
} else {
indeterminateFilters.add(scoreEvt.getFilter());
}
}
}
}
String dsClause = getDsFilter(dataSourceId);
String queryStrSelects = Stream.of(ScoreViewFilter.values())
.map((filter) -> Pair.of(filter.name(), getScoreFilter(filter.getScores())))
.map((filterSqlPair) -> {
String filterSql = Stream.of(filterSqlPair.getRight(), dsClause)
.filter(StringUtils::isNotBlank)
.collect(Collectors.joining("\nAND "));
if (StringUtils.isNotBlank(filterSql)) {
filterSql = "\n WHERE " + filterSql;
}
return "(SELECT COUNT(aggr_score.obj_id) "
+ BASE_AGGR_SCORE_QUERY
+ filterSql
+ ") AS "
+ filterSqlPair.getLeft();
})
.collect(Collectors.joining(",\n"));
String queryStr = "\n" + queryStrSelects;
try {
SleuthkitCase skCase = getCase();
List<TreeItemDTO<ScoreViewSearchParams>> treeList = new ArrayList<>();
skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> {
try {
if (resultSet.next()) {
for (ScoreViewFilter filter : ScoreViewFilter.values()) {
long count = resultSet.getLong(filter.name());
TreeDisplayCount displayCount = indeterminateFilters.contains(filter)
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(count);
treeList.add(createScoreContentTreeItem(filter, dataSourceId, displayCount));
}
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex);
}
});
return new TreeResultsDTO<>(treeList);
} catch (NoCurrentCaseException | TskCoreException ex) {
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
}
}
private static TreeItemDTO<ScoreViewSearchParams> createScoreContentTreeItem(ScoreViewFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
"SCORE_CONTENT",
new ScoreViewSearchParams(filter, dataSourceId),
filter,
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
private static String getScoreFilter(Collection<Score> scores) {
if (CollectionUtils.isEmpty(scores)) {
return null;
} else {
return "("
+ scores.stream()
.map(s -> MessageFormat.format(
" (aggr_score.significance = {0} AND aggr_score.priority = {1}) ",
s.getSignificance().getId(),
s.getPriority().getId()))
.collect(Collectors.joining(" OR "))
+ ")";
}
}
private static String getDsFilter(Long dataSourceId) {
return (dataSourceId == null || dataSourceId <= 0)
? null
: "aggr_score.data_source_obj_id = " + dataSourceId;
}
private SearchResultsDTO fetchScoreSearchResultsDTOs(ScoreViewFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
String scoreClause = getScoreFilter(filter.getScores());
String dsClause = getDsFilter(dataSourceId);
String filterSql = Stream.of(scoreClause, dsClause)
.filter(str -> str != null)
.collect(Collectors.joining(" AND "));
filterSql = StringUtils.isNotEmpty(filterSql) ? " WHERE " + filterSql + "\n" : "";
String baseQuery = BASE_AGGR_SCORE_QUERY
+ filterSql
+ "ORDER BY art_files.obj_id";
String countQuery = " COUNT(art_files.obj_id) AS count\n" + baseQuery;
AtomicLong totalCountRef = new AtomicLong(0);
AtomicReference<SQLException> countException = new AtomicReference<>(null);
getCase().getCaseDbAccessManager()
.select(countQuery, (rs) -> {
try {
if (rs.next()) {
totalCountRef.set(rs.getLong("count"));
}
} catch (SQLException ex) {
countException.set(ex);
}
}
);
SQLException sqlEx = countException.get();
if (sqlEx != null) {
throw new TskCoreException(
MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", countQuery),
sqlEx);
}
String objIdQuery = " art_files.obj_id, art_files.type\n" + baseQuery + "\n"
+ (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "")
+ (startItem > 0 ? " OFFSET " + startItem : "");;
List<Long> fileIds = new ArrayList<>();
List<Long> artifactIds = new ArrayList<>();
AtomicReference<SQLException> objIdException = new AtomicReference<>(null);
getCase().getCaseDbAccessManager()
.select(objIdQuery, (rs) -> {
try {
while (rs.next()) {
String type = rs.getString("type");
if ("f".equalsIgnoreCase(type)) {
fileIds.add(rs.getLong("obj_id"));
} else {
artifactIds.add(rs.getLong("obj_id"));
}
}
} catch (SQLException ex) {
objIdException.set(ex);
}
}
);
sqlEx = objIdException.get();
if (sqlEx != null) {
throw new TskCoreException(
MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", objIdQuery),
sqlEx);
}
List<RowDTO> dataRows = new ArrayList<>();
if (!fileIds.isEmpty()) {
String joinedFileIds = fileIds.stream()
.map(l -> Long.toString(l))
.collect(Collectors.joining(", "));
List<AbstractFile> files = getCase().findAllFilesWhere("obj_id IN (" + joinedFileIds + ")");
for (AbstractFile file : files) {
List<Object> cellValues = Arrays.asList(
file.getName(),
Bundle.ScoreDAO_types_filelbl(),
file.getUniquePath(),
file.getCtime() <= 0
? null
: TimeZoneUtils.getFormattedTime(file.getCtime())
);
dataRows.add(new ScoreResultRowDTO(
new FileRowDTO(
file,
file.getId(),
file.getName(),
file.getNameExtension(),
MediaTypeUtils.getExtensionMediaType(file.getNameExtension()),
file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.ALLOC),
file.getType(),
cellValues),
// the modified column types: source name, type, path, created time
cellValues,
file.getId()));
}
}
if (!artifactIds.isEmpty()) {
String joinedArtifactIds = artifactIds.stream()
.map(l -> Long.toString(l))
.collect(Collectors.joining(", "));
List<DataArtifact> dataArtifacts = getCase().getBlackboard().getDataArtifactsWhere("artifacts.artifact_obj_id IN (" + joinedArtifactIds + ")");
TableData artTableData = MainDAO.getInstance().getDataArtifactsDAO().createTableData(null, dataArtifacts);
// all rows should be data artifact rows, and can be appended accordingly
for (RowDTO rowDTO : artTableData.rows) {
if (rowDTO instanceof DataArtifactRowDTO dataArtRow) {
BlackboardArtifact.Type artifactType = dataArtRow.getArtifact().getType();
List<Object> cellValues = Arrays.asList(
dataArtRow.getSrcContent().getName(),
artifactType.getDisplayName(),
dataArtRow.getArtifact().getUniquePath(),
getTimeStamp(dataArtRow.getArtifact())
);
dataRows.add(new ScoreResultRowDTO(
new DataArtifactRowDTO(
dataArtRow.getArtifact(),
dataArtRow.getSrcContent(),
dataArtRow.getLinkedFile(),
dataArtRow.isTimelineSupported(),
cellValues,
dataArtRow.getId()),
artifactType,
cellValues,
dataArtRow.getId()));
}
}
}
return new BaseSearchResultsDTO(
SCORE_TYPE_ID,
Bundle.ScoreDAO_mainNode_displayName(),
RESULT_SCORE_COLUMNS,
dataRows,
SCORE_TYPE_ID,
startItem,
totalCountRef.get());
}
private String getTimeStamp(BlackboardArtifact artifact) {
Long time = getTime(artifact);
if (time == null || time <= 0) {
return null;
} else {
return TimeZoneUtils.getFormattedTime(time);
}
}
private Long getTime(BlackboardArtifact artifact) {
try {
BlackboardAttribute timeAttr = artifact.getAttributes().stream()
.filter((attr) -> TIME_ATTR_IMPORTANCE.keySet().contains(attr.getAttributeType().getTypeID()))
.sorted(Comparator.comparing(attr -> TIME_ATTR_IMPORTANCE.get(attr.getAttributeType().getTypeID())))
.findFirst()
.orElse(null);
if (timeAttr != null) {
return timeAttr.getValueLong();
} else {
return (artifact.getParent() instanceof AbstractFile) ? ((AbstractFile) artifact.getParent()).getCtime() : null;
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "An exception occurred while fetching time for artifact", ex);
return null;
}
}
private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) {
if (daoEvent instanceof ScoreContentEvent) {
ScoreContentEvent scoreEvt = (ScoreContentEvent) daoEvent;
return createScoreContentTreeItem(scoreEvt.getFilter(), scoreEvt.getDataSourceId(), count);
} else {
return null;
}
}
@Override
void clearCaches() {
this.searchParamsCache.invalidateAll();
handleIngestComplete();
}
@Override
Set<? extends DAOEvent> handleIngestComplete() {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(true, null, true, searchParams));
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null);
List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream()
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toList());
return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents)
.flatMap(c -> c.stream())
.collect(Collectors.toSet());
}
@Override
Set<TreeEvent> shouldRefreshTree() {
return SubDAOUtils.getRefreshEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
}
@Override
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
AbstractFile af;
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
Long dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
return invalidateScoreParamsAndReturnEvents(dsId);
} else if ((af = DAOEventUtils.getFileFromFileEvent(evt)) != null) {
return invalidateScoreParamsAndReturnEvents(af.getDataSourceObjectId());
}
ModuleDataEvent dataEvt;
if (Case.Events.CONTENT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof ContentTagAddedEvent) && ((ContentTagAddedEvent) evt).getAddedTag().getContent() instanceof AbstractFile) {
ContentTagAddedEvent tagAddedEvt = (ContentTagAddedEvent) evt;
return invalidateScoreParamsAndReturnEvents(((AbstractFile) tagAddedEvt.getAddedTag().getContent()).getDataSourceObjectId());
} else if (Case.Events.CONTENT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
return invalidateScoreParamsAndReturnEvents(null);
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof BlackBoardArtifactTagAddedEvent)) {
BlackBoardArtifactTagAddedEvent artifactAddedEvt = (BlackBoardArtifactTagAddedEvent) evt;
return invalidateScoreParamsAndReturnEvents(artifactAddedEvt.getAddedTag().getArtifact().getDataSourceObjectID());
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
return invalidateScoreParamsAndReturnEvents(null);
} else if ((dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt)) != null
&& Category.ANALYSIS_RESULT.equals(dataEvt.getBlackboardArtifactType().getCategory())) {
Set<Long> dsIds = dataEvt.getArtifacts().stream().map(ar -> ar.getDataSourceObjectID()).distinct().collect(Collectors.toSet());
return invalidateScoreParamsAndReturnEventsFromSet(dsIds);
} else {
return Collections.emptySet();
}
}
private Set<DAOEvent> invalidateScoreParamsAndReturnEvents(Long dataSourceId) {
return invalidateScoreParamsAndReturnEventsFromSet(dataSourceId != null ? Collections.singleton(dataSourceId) : null);
}
private Set<DAOEvent> invalidateScoreParamsAndReturnEventsFromSet(Set<Long> dataSourceIds) {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> {
if (searchParams instanceof ScoreViewSearchParams) {
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
return (CollectionUtils.isEmpty(dataSourceIds) || scoreParams.getDataSourceId() == null || dataSourceIds.contains(scoreParams.getDataSourceId()));
} else {
return false;
}
});
Set<DAOEvent> dataEvents = CollectionUtils.isEmpty(dataSourceIds)
? Collections.singleton(new ScoreContentEvent(null, null))
: dataSourceIds.stream().map(dsId -> new ScoreContentEvent(null, dsId)).collect(Collectors.toSet());
Set<DAOEvent> treeEvents = CollectionUtils.isEmpty(dataSourceIds)
? Collections.singleton(new TreeEvent(createScoreContentTreeItem(
null,
null,
TreeDisplayCount.UNSPECIFIED),
true))
: dataSourceIds.stream().map(dsId -> new TreeEvent(
createScoreContentTreeItem(
null,
dsId,
TreeDisplayCount.UNSPECIFIED), true))
.collect(Collectors.toSet());
return Stream.of(dataEvents, treeEvents).flatMap(s -> s.stream()).collect(Collectors.toSet());
}
private boolean searchParamsMatchEvent(
boolean invalidatesScore,
Long dsId,
boolean dataSourceAdded,
Object searchParams) {
if (searchParams instanceof ScoreViewSearchParams) {
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
return (dataSourceAdded || (invalidatesScore && (scoreParams.getDataSourceId() == null || dsId == null || Objects.equals(scoreParams.getDataSourceId(), dsId))));
} else {
return false;
}
}
/**
* Returns events for when a full refresh is required because module content
* events will not necessarily provide events for files (i.e. data source
* added, ingest cancelled/completed).
*
* @param dataSourceId The data source id or null if not applicable.
*
* @return The set of events that apply in this situation.
*/
private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) {
return ImmutableSet.of(
new ScoreContentEvent(null, dataSourceId)
);
}
/**
* Handles fetching and paging of data for deleted content.
*/
public static class ScoreContentFetcher extends DAOFetcher<ScoreViewSearchParams> {
/**
* Main constructor.
*
* @param params Parameters to handle fetching of data.
*/
public ScoreContentFetcher(ScoreViewSearchParams params) {
super(params);
}
protected ScoreDAO getDAO() {
return MainDAO.getInstance().getScoreDAO();
}
@Override
public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException {
return getDAO().getFilesByScore(this.getParameters(), pageIdx * pageSize, (long) pageSize);
}
@Override
public boolean isRefreshRequired(DAOEvent evt) {
return getDAO().isScoreContentInvalidating(this.getParameters(), evt);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.mainui.datamodel;
import java.util.List;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Score results dto.
*/
public class ScoreResultRowDTO extends BaseRowDTO {
private static final String TYPE_ID = "SCORE_RESULT_ROW";
public static String getTypeIdForClass() {
return TYPE_ID;
}
private final FileRowDTO fileDTO;
private final DataArtifactRowDTO artifactDTO;
private final BlackboardArtifact.Type artifactType;
public ScoreResultRowDTO(FileRowDTO fileDTO, List<Object> cellValues, long id) {
super(cellValues, TYPE_ID, id);
this.fileDTO = fileDTO;
this.artifactDTO = null;
this.artifactType = null;
}
public ScoreResultRowDTO(DataArtifactRowDTO artifactDTO, BlackboardArtifact.Type artifactType, List<Object> cellValues, long id) {
super(cellValues, TYPE_ID, id);
this.fileDTO = null;
this.artifactDTO = artifactDTO;
this.artifactType = artifactType;
}
public FileRowDTO getFileDTO() {
return fileDTO;
}
public DataArtifactRowDTO getArtifactDTO() {
return artifactDTO;
}
public BlackboardArtifact.Type getArtifactType() {
return artifactType;
}
}

View File

@ -42,19 +42,13 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree;
import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS;
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE;
@ -65,12 +59,10 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DeletedContentEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeExtensionsEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeMimeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeSizeEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.ScoreContentEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact.Category;
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@ -154,15 +146,6 @@ public class ViewsDAO extends AbstractDAO {
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchSizeSearchResultsDTOs(key.getSizeFilter(), key.getDataSourceId(), startItem, maxCount));
}
public SearchResultsDTO getFilesByScore(ScoreViewSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) {
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
}
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
return searchParamsCache.get(searchParams, () -> fetchScoreSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
}
/**
* Returns search results for the given deleted content search params.
@ -227,24 +210,6 @@ public class ViewsDAO extends AbstractDAO {
&& (params.getDataSourceId() == null || deletedContentEvt.getDataSourceId() == null
|| Objects.equals(params.getDataSourceId(), deletedContentEvt.getDataSourceId()));
}
private boolean isScoreContentInvalidating(ScoreViewSearchParams params, DAOEvent eventData) {
if (!(eventData instanceof ScoreContentEvent)) {
return false;
}
ScoreContentEvent scoreContentEvt = (ScoreContentEvent) eventData;
ScoreViewFilter evtFilter = scoreContentEvt.getFilter();
ScoreViewFilter paramsFilter = params.getFilter();
Long evtDsId = scoreContentEvt.getDataSourceId();
Long paramsDsId = params.getDataSourceId();
return (evtFilter == null || evtFilter.equals(paramsFilter))
&& (paramsDsId == null || evtDsId == null
|| Objects.equals(paramsDsId, evtDsId));
}
/**
* Returns a sql 'and' clause to filter by data source id if one is present.
@ -457,31 +422,6 @@ public class ViewsDAO extends AbstractDAO {
return query;
}
/**
* Creates a clause to be proceeded with 'where' or 'and' that will show
* files specified by the filter and the specified data source.
*
* @param filter The scores filter.
* @param dataSourceId The id of the data source or null if no data source
* filtering.
*
* @return The clause to be proceeded with 'where' or 'and'.
*/
private String getFileScoreWhereStatement(ScoreViewFilter filter, Long dataSourceId) {
String scoreWhereClause = filter.getScores().stream()
.map(s -> MessageFormat.format(
" (tsk_aggregate_score.significance = {0} AND tsk_aggregate_score.priority = {1}) ",
s.getSignificance().getId(),
s.getPriority().getId()))
.collect(Collectors.joining(" OR "));
String query = MessageFormat.format(
" obj_id IN (SELECT tsk_aggregate_score.obj_id FROM tsk_aggregate_score WHERE {0}) {1}",
scoreWhereClause,
getDataSourceAndClause(dataSourceId));
return query;
}
/**
* Returns counts for a collection of file extension search filters.
@ -694,70 +634,6 @@ public class ViewsDAO extends AbstractDAO {
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
}
}
/**
* Returns counts for deleted content categories.
*
* @param dataSourceId The data source object id or null if no data source
* filtering should occur.
*
* @return The results.
*
* @throws IllegalArgumentException
* @throws ExecutionException
*/
public TreeResultsDTO<ScoreViewSearchParams> getScoreContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
Set<ScoreViewFilter> indeterminateFilters = new HashSet<>();
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
if (evt instanceof ScoreContentEvent) {
ScoreContentEvent scoreEvt = (ScoreContentEvent) evt;
if (dataSourceId == null || scoreEvt.getDataSourceId() == null || Objects.equals(scoreEvt.getDataSourceId(), dataSourceId)) {
if (scoreEvt.getFilter() == null) {
// if null filter, indicates full refresh and all file sizes need refresh.
indeterminateFilters.addAll(Arrays.asList(ScoreViewFilter.values()));
break;
} else {
indeterminateFilters.add(scoreEvt.getFilter());
}
}
}
}
String queryStr = Stream.of(ScoreViewFilter.values())
.map((filter) -> {
String clause = getFileScoreWhereStatement(filter, dataSourceId);
return MessageFormat.format(" (SELECT COUNT(*) FROM tsk_files WHERE {0}) AS {1}", clause, filter.name());
})
.collect(Collectors.joining(", \n"));
try {
SleuthkitCase skCase = getCase();
List<TreeItemDTO<ScoreViewSearchParams>> treeList = new ArrayList<>();
skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> {
try {
if (resultSet.next()) {
for (ScoreViewFilter filter : ScoreViewFilter.values()) {
long count = resultSet.getLong(filter.name());
TreeDisplayCount displayCount = indeterminateFilters.contains(filter)
? TreeDisplayCount.INDETERMINATE
: TreeDisplayCount.getDeterminate(count);
treeList.add(createScoreContentTreeItem(filter, dataSourceId, displayCount));
}
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex);
}
});
return new TreeResultsDTO<>(treeList);
} catch (NoCurrentCaseException | TskCoreException ex) {
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
}
}
private static TreeItemDTO<DeletedContentSearchParams> createDeletedContentTreeItem(DeletedContentFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
@ -767,16 +643,6 @@ public class ViewsDAO extends AbstractDAO {
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
private static TreeItemDTO<ScoreViewSearchParams> createScoreContentTreeItem(ScoreViewFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
return new TreeItemDTO<>(
"SCORE_CONTENT",
new ScoreViewSearchParams(filter, dataSourceId),
filter,
filter == null ? "" : filter.getDisplayName(),
displayCount);
}
/**
* Creates a size tree item.
@ -1088,11 +954,6 @@ public class ViewsDAO extends AbstractDAO {
String whereStatement = getFileSizesWhereStatement(filter, dataSourceId);
return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount);
}
private SearchResultsDTO fetchScoreSearchResultsDTOs(ScoreViewFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
String whereStatement = getFileScoreWhereStatement(filter, dataSourceId);
return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount);
}
private SearchResultsDTO fetchFileViewFiles(String originalWhereStatement, String displayName, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
@ -1156,9 +1017,6 @@ public class ViewsDAO extends AbstractDAO {
} else if (daoEvent instanceof DeletedContentEvent) {
DeletedContentEvent sizeEvt = (DeletedContentEvent) daoEvent;
return createDeletedContentTreeItem(sizeEvt.getFilter(), sizeEvt.getDataSourceId(), count);
} else if (daoEvent instanceof ScoreContentEvent) {
ScoreContentEvent scoreEvt = (ScoreContentEvent) daoEvent;
return createScoreContentTreeItem(scoreEvt.getFilter(), scoreEvt.getDataSourceId(), count);
} else {
return null;
}
@ -1174,7 +1032,7 @@ public class ViewsDAO extends AbstractDAO {
@Override
Set<? extends DAOEvent> handleIngestComplete() {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(null, null, null, null, true, null, true, searchParams));
(searchParams) -> searchParamsMatchEvent(null, null, null, null, null, true, searchParams));
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
(daoEvt, count) -> createTreeItem(daoEvt, count));
@ -1204,16 +1062,12 @@ public class ViewsDAO extends AbstractDAO {
Set<DeletedContentFilter> deletedContentFilters = null;
String evtMimeType = null;
FileSizeFilter evtFileSize = null;
boolean scoreInvalidating = false;
AbstractFile af;
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
dataSourceAdded = true;
scoreInvalidating = true;
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, scoreInvalidating, dsId, dataSourceAdded);
} else if ((af = DAOEventUtils.getFileFromFileEvent(evt)) != null) {
} else {
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
if (af == null) {
return Collections.emptySet();
} else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) {
@ -1245,31 +1099,9 @@ public class ViewsDAO extends AbstractDAO {
if (evtExtFilters == null || evtExtFilters.isEmpty() && deletedContentFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
return Collections.emptySet();
}
// a file is being added, so invalidate all score items where data source id is in scope
scoreInvalidating = true;
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, scoreInvalidating, dsId, dataSourceAdded);
}
ModuleDataEvent dataEvt;
if (Case.Events.CONTENT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof ContentTagAddedEvent) && ((ContentTagAddedEvent) evt).getAddedTag().getContent() instanceof AbstractFile) {
ContentTagAddedEvent tagAddedEvt = (ContentTagAddedEvent) evt;
return invalidateScoreParamsAndReturnEvents(((AbstractFile) tagAddedEvt.getAddedTag().getContent()).getDataSourceObjectId());
} else if (Case.Events.CONTENT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
return invalidateScoreParamsAndReturnEvents(null);
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof BlackBoardArtifactTagAddedEvent)) {
BlackBoardArtifactTagAddedEvent artifactAddedEvt = (BlackBoardArtifactTagAddedEvent) evt;
return invalidateScoreParamsAndReturnEvents(artifactAddedEvt.getAddedTag().getArtifact().getDataSourceObjectID());
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
return invalidateScoreParamsAndReturnEvents(null);
} else if ((dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt)) != null
&& Category.ANALYSIS_RESULT.equals(dataEvt.getBlackboardArtifactType().getCategory())) {
Set<Long> dsIds = dataEvt.getArtifacts().stream().map(ar -> ar.getDataSourceObjectID()).distinct().collect(Collectors.toSet());
return invalidateScoreParamsAndReturnEventsFromSet(dsIds);
} else {
return Collections.emptySet();
}
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, dsId, dataSourceAdded);
}
/**
@ -1279,7 +1111,6 @@ public class ViewsDAO extends AbstractDAO {
* @param evtMimeType The mime type or null.
* @param evtFileSize The file size filter or null.
* @param deletedContentFilters The set of affected deleted content filters.
* @param invalidateScore All score views should be invalidated if data source id is in scope.
* @param dsId The data source id or null.
* @param dataSourceAdded Whether or not this is a data source added
* event.
@ -1287,42 +1118,19 @@ public class ViewsDAO extends AbstractDAO {
* @return The set of dao events to be fired.
*/
private Set<DAOEvent> invalidateAndReturnEvents(Set<FileExtSearchFilter> evtExtFilters, String evtMimeType,
FileSizeFilter evtFileSize, Set<DeletedContentFilter> deletedContentFilters, boolean invalidateScore, Long dsId, boolean dataSourceAdded) {
FileSizeFilter evtFileSize, Set<DeletedContentFilter> deletedContentFilters, Long dsId, boolean dataSourceAdded) {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> searchParamsMatchEvent(evtExtFilters, deletedContentFilters,
evtMimeType, evtFileSize, invalidateScore, dsId, dataSourceAdded, searchParams));
evtMimeType, evtFileSize, dsId, dataSourceAdded, searchParams));
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, invalidateScore, dsId, dataSourceAdded);
}
private Set<DAOEvent> invalidateScoreParamsAndReturnEvents(Long dataSourceId) {
return invalidateScoreParamsAndReturnEventsFromSet(dataSourceId != null ? Collections.singleton(dataSourceId) : null);
}
private Set<DAOEvent> invalidateScoreParamsAndReturnEventsFromSet(Set<Long> dataSourceIds) {
SubDAOUtils.invalidateKeys(this.searchParamsCache,
(searchParams) -> {
if (searchParams instanceof ScoreViewSearchParams) {
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
return (CollectionUtils.isEmpty(dataSourceIds) || scoreParams.getDataSourceId() == null || dataSourceIds.contains(scoreParams.getDataSourceId()));
} else {
return false;
}
});
return CollectionUtils.isEmpty(dataSourceIds)
? Collections.singleton(new ScoreContentEvent(null, null))
: dataSourceIds.stream().map(dsId -> new ScoreContentEvent(null, dsId)).collect(Collectors.toSet());
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
}
private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters,
Set<DeletedContentFilter> deletedContentFilters,
String evtMimeType,
FileSizeFilter evtFileSize,
boolean invalidatesScore,
Long dsId,
boolean dataSourceAdded,
Object searchParams) {
@ -1349,9 +1157,6 @@ public class ViewsDAO extends AbstractDAO {
DeletedContentSearchParams deletedParams = (DeletedContentSearchParams) searchParams;
return (dataSourceAdded || (deletedContentFilters != null && deletedContentFilters.contains(deletedParams.getFilter())))
&& (deletedParams.getDataSourceId() == null || dsId == null || Objects.equals(deletedParams.getDataSourceId(), dsId));
} else if (searchParams instanceof ScoreViewSearchParams) {
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
return (dataSourceAdded || (invalidatesScore && (scoreParams.getDataSourceId() == null || dsId == null || Objects.equals(scoreParams.getDataSourceId(), dsId))));
} else {
return false;
}
@ -1374,7 +1179,6 @@ public class ViewsDAO extends AbstractDAO {
Set<DeletedContentFilter> deletedContentFilters,
String mimeType,
FileSizeFilter sizeFilter,
boolean invalidateScore,
Long dsId,
boolean dataSourceAdded) {
@ -1398,10 +1202,6 @@ public class ViewsDAO extends AbstractDAO {
if (sizeFilter != null) {
daoEvents.add(new FileTypeSizeEvent(sizeFilter, dsId));
}
if (invalidateScore) {
daoEvents.add(new ScoreContentEvent(null, dsId));
}
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
.map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false))
@ -1456,13 +1256,10 @@ public class ViewsDAO extends AbstractDAO {
return ImmutableSet.of(
new DeletedContentEvent(null, dataSourceId),
new FileTypeSizeEvent(null, dataSourceId),
new FileTypeExtensionsEvent(null, dataSourceId),
new ScoreContentEvent(null, dataSourceId)
new FileTypeExtensionsEvent(null, dataSourceId)
);
}
/**
* Handles fetching and paging of data for file types by extension.
*/
@ -1579,35 +1376,4 @@ public class ViewsDAO extends AbstractDAO {
}
}
/**
* Handles fetching and paging of data for deleted content.
*/
public static class ScoreFileFetcher extends DAOFetcher<ScoreViewSearchParams> {
/**
* Main constructor.
*
* @param params Parameters to handle fetching of data.
*/
public ScoreFileFetcher(ScoreViewSearchParams params) {
super(params);
}
protected ViewsDAO getDAO() {
return MainDAO.getInstance().getViewsDAO();
}
@Override
public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException {
return getDAO().getFilesByScore(this.getParameters(), pageIdx * pageSize, (long) pageSize);
}
@Override
public boolean isRefreshRequired(DAOEvent evt) {
return getDAO().isScoreContentInvalidating(this.getParameters(), evt);
}
}
}

View File

@ -22,6 +22,7 @@ RootFactory_OsAccountsRootNode_displayName=OS Accounts
RootFactory_ReportsRootNode_displayName=Reports
RootFactory_TagsRootNode_displayName=Tags
RootFactory_ViewsRootNode_displayName=Views
ScoreTypeFactory_ScoreParentNode_displayName=Score
SearchResultRootNode_createSheet_childCount_displayName=Child Count
SearchResultRootNode_createSheet_childCount_name=Child Count
SearchResultRootNode_createSheet_type_displayName=Name
@ -31,6 +32,5 @@ ViewsTypeFactory_DeletedParentNode_displayName=Deleted Files
ViewsTypeFactory_ExtensionParentNode_displayName=By Extension
ViewsTypeFactory_FileTypesParentNode_displayName=File Types
ViewsTypeFactory_MimeParentNode_displayName=By MIME Type
ViewsTypeFactory_ScoreParentNode_displayName=Score
ViewsTypeFactory_SizeParentNode_displayName=File Size
VolumnNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files

View File

@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO.CommAccoutTableSearchResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
import org.sleuthkit.datamodel.DataArtifact;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
@ -57,10 +58,10 @@ public class DataArtifactNode extends ArtifactNode<DataArtifact, DataArtifactRow
this(tableData, artifactRow, getIconFilePath(tableData), backgroundTasksPool);
}
public DataArtifactNode(DataArtifactTableSearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath, ExecutorService backgroundTasksPool) {
public DataArtifactNode(SearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath, ExecutorService backgroundTasksPool) {
super(tableData, artifactRow, tableData.getColumns(), createLookup(artifactRow), iconPath, backgroundTasksPool);
}
@Override
public Optional<List<Tag>> getAllTagsFromDatabase() {
try {

View File

@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.HostPersonEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.ScoreTypeFactory.ScoreParentNode;
import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Host;
@ -194,6 +195,7 @@ public class RootFactory {
new AnalysisResultsRootNode(null),
new OsAccountsRootNode(null),
new TagsRootNode(null),
new ScoreParentNode(null),
new ReportsRootNode()
));
}
@ -505,6 +507,7 @@ public class RootFactory {
new DataArtifactsRootNode(dataSourceObjId),
new AnalysisResultsRootNode(dataSourceObjId),
new OsAccountsRootNode(dataSourceObjId),
new ScoreParentNode(dataSourceObjId),
new TagsRootNode(dataSourceObjId)
));
}
@ -721,7 +724,7 @@ public class RootFactory {
new TagNameFactory(dataSourceObjId));
}
}
/**
* Root node for reports in the tree.
*/

View File

@ -0,0 +1,174 @@
/*
* 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.mainui.nodes;
import com.google.common.collect.ImmutableList;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import org.openide.nodes.Children;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode;
/**
*
* Factories for displaying views.
*/
public class ScoreTypeFactory {
private static final String SCORE_ICON = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
/**
* Children of 'Score' in the tree.
*/
public static class ScoreChildren extends Children.Array {
public ScoreChildren(Long dataSourceId) {
super(ImmutableList.of(
new ScoreParentNode(dataSourceId)
));
}
}
/**
* Parent of score nodes in the tree.
*/
@Messages({"ScoreTypeFactory_ScoreParentNode_displayName=Score"})
public static class ScoreParentNode extends StaticTreeNode {
ScoreParentNode(Long dataSourceId) {
super(
"FILE_VIEW_SCORE_PARENT",
Bundle.ScoreTypeFactory_ScoreParentNode_displayName(),
SCORE_ICON,
new ScoreContentFactory(dataSourceId)
);
}
}
/**
* The factory for creating deleted content tree nodes.
*/
public static class ScoreContentFactory extends TreeChildFactory<ScoreViewSearchParams> {
private final Long dataSourceId;
/**
* Main constructor.
*
* @param dataSourceId The data source to filter files to or null.
*/
public ScoreContentFactory(Long dataSourceId) {
this.dataSourceId = dataSourceId;
}
@Override
protected TreeNode<ScoreViewSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> rowData) {
return new ScoreContentTypeNode(rowData);
}
@Override
protected TreeResultsDTO<? extends ScoreViewSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException {
return MainDAO.getInstance().getScoreDAO().getScoreContentCounts(dataSourceId);
}
@Override
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, ScoreViewSearchParams.class);
// if search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, ScoreViewSearchParams.class);
if (originalTreeItem != null
// only create child if size filter is present (if null, update should be triggered separately)
&& originalTreeItem.getSearchParams().getFilter() != null
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
ScoreViewSearchParams searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
ScoreViewSearchParams.getTypeId(),
new ScoreViewSearchParams(searchParam.getFilter(), this.dataSourceId),
searchParam.getFilter(),
searchParam.getFilter().getDisplayName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(TreeItemDTO<? extends ScoreViewSearchParams> o1, TreeItemDTO<? extends ScoreViewSearchParams> o2) {
return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId());
}
/**
* Shows a deleted content tree node.
*/
static class ScoreContentTypeNode extends TreeNode<ScoreViewSearchParams> {
private static final String BAD_SCORE_ICON = SCORE_ICON;
private static final String SUS_SCORE_ICON = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
private static String getIcon(ScoreViewFilter filter) {
switch (filter) {
case SUSPICIOUS:
return SUS_SCORE_ICON;
case BAD:
default:
return BAD_SCORE_ICON;
}
}
/**
* Main constructor.
*
* @param itemData The data for the node.
*/
ScoreContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> itemData) {
super("SCORE_CONTENT_" + itemData.getSearchParams().getFilter().name(), getIcon(itemData.getSearchParams().getFilter()), itemData);
}
@Override
public void respondSelection(DataResultTopComponent dataResultPanel) {
dataResultPanel.displayScoreContent(this.getItemData().getSearchParams());
}
}
}
}

View File

@ -27,6 +27,7 @@ import java.util.stream.Collectors;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultTableSearchResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactTagsRowDTO;
@ -49,6 +50,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.RowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.CreditCardByFileRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ReportsRowDTO;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreResultRowDTO;
import org.sleuthkit.autopsy.mainui.nodes.FileNode.LayoutFileNode;
import org.sleuthkit.autopsy.mainui.nodes.FileNode.SlackFileNode;
import org.sleuthkit.autopsy.mainui.nodes.SpecialDirectoryNode.LocalDirectoryNode;
@ -89,7 +91,14 @@ public class SearchResultChildFactory extends ChildFactory<ChildKey> {
protected Node createNodeForKey(ChildKey key) {
String typeId = key.getRow().getTypeId();
try {
if (DataArtifactRowDTO.getTypeIdForClass().equals(typeId)) {
if (ScoreResultRowDTO.getTypeIdForClass().equals(typeId) && key.getRow() instanceof ScoreResultRowDTO scoreRow) {
if (scoreRow.getArtifactDTO() != null && scoreRow.getArtifactType() != null) {
String iconPath = IconsUtil.getIconFilePath(scoreRow.getArtifactType().getTypeID());
return new DataArtifactNode(key.getSearchResults(), scoreRow.getArtifactDTO(), iconPath, nodeThreadPool);
} else if (scoreRow.getFileDTO() != null) {
return new FileNode(key.getSearchResults(), scoreRow.getFileDTO(), true, nodeThreadPool);
}
} else if (DataArtifactRowDTO.getTypeIdForClass().equals(typeId)) {
return new DataArtifactNode((DataArtifactTableSearchResultsDTO) key.getSearchResults(), (DataArtifactRowDTO) key.getRow(), nodeThreadPool);
} else if (FileRowDTO.getTypeIdForClass().equals(typeId)) {
return new FileNode(key.getSearchResults(), (FileRowDTO) key.getRow(), true, nodeThreadPool);

View File

@ -37,8 +37,6 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter;
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
@ -56,7 +54,6 @@ public class ViewsTypeFactory {
private static final String FILE_TYPES_ICON = "org/sleuthkit/autopsy/images/file_types.png";
private static final String SIZE_ICON = "org/sleuthkit/autopsy/images/file-size-16.png";
private static final String SCORE_ICON = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
/**
* Node for file extensions parent in the tree.
@ -122,22 +119,6 @@ public class ViewsTypeFactory {
}
}
/**
* Parent of file size nodes in the tree.
*/
@Messages({"ViewsTypeFactory_ScoreParentNode_displayName=Score"})
private static class ScoreParentNode extends StaticTreeNode {
ScoreParentNode(Long dataSourceId) {
super(
"FILE_VIEW_SCORE_PARENT",
Bundle.ViewsTypeFactory_ScoreParentNode_displayName(),
SCORE_ICON,
new ScoreContentFactory(dataSourceId)
);
}
}
/**
* 'File Types' children in the tree.
*/
@ -177,8 +158,7 @@ public class ViewsTypeFactory {
super(ImmutableList.of(
new FileTypesParentNode(dataSourceId),
new DeletedParentNode(dataSourceId),
new SizeParentNode(dataSourceId),
new ScoreParentNode(dataSourceId)
new SizeParentNode(dataSourceId)
));
}
}
@ -651,108 +631,4 @@ public class ViewsTypeFactory {
}
}
/**
* The factory for creating deleted content tree nodes.
*/
public static class ScoreContentFactory extends TreeChildFactory<ScoreViewSearchParams> {
private final Long dataSourceId;
/**
* Main constructor.
*
* @param dataSourceId The data source to filter files to or null.
*/
public ScoreContentFactory(Long dataSourceId) {
this.dataSourceId = dataSourceId;
}
@Override
protected TreeNode<ScoreViewSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> rowData) {
return new ScoreContentTypeNode(rowData);
}
@Override
protected TreeResultsDTO<? extends ScoreViewSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException {
return MainDAO.getInstance().getViewsDAO().getScoreContentCounts(dataSourceId);
}
@Override
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
for (DAOEvent evt : aggEvt.getEvents()) {
if (evt instanceof TreeEvent) {
TreeResultsDTO.TreeItemDTO<DeletedContentSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, DeletedContentSearchParams.class);
// if search params has null filter, trigger full refresh
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
super.update();
return;
}
}
}
super.handleDAOAggregateEvent(aggEvt);
}
@Override
protected TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, ScoreViewSearchParams.class);
if (originalTreeItem != null
// only create child if size filter is present (if null, update should be triggered separately)
&& originalTreeItem.getSearchParams().getFilter() != null
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
ScoreViewSearchParams searchParam = originalTreeItem.getSearchParams();
return new TreeResultsDTO.TreeItemDTO<>(
ScoreViewSearchParams.getTypeId(),
new ScoreViewSearchParams(searchParam.getFilter(), this.dataSourceId),
searchParam.getFilter(),
searchParam.getFilter().getDisplayName(),
originalTreeItem.getDisplayCount());
}
return null;
}
@Override
public int compare(TreeItemDTO<? extends ScoreViewSearchParams> o1, TreeItemDTO<? extends ScoreViewSearchParams> o2) {
return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId());
}
/**
* Shows a deleted content tree node.
*/
static class ScoreContentTypeNode extends TreeNode<ScoreViewSearchParams> {
private static final String BAD_SCORE_ICON = SCORE_ICON;
private static final String SUS_SCORE_ICON = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
private static String getIcon(ScoreViewFilter filter) {
switch (filter) {
case SUSPICIOUS:
return SUS_SCORE_ICON;
case BAD:
default:
return BAD_SCORE_ICON;
}
}
/**
* Main constructor.
*
* @param itemData The data for the node.
*/
ScoreContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> itemData) {
super("SCORE_CONTENT_" + itemData.getSearchParams().getFilter().name(), getIcon(itemData.getSearchParams().getFilter()), itemData);
}
@Override
public void respondSelection(DataResultTopComponent dataResultPanel) {
dataResultPanel.displayScoreContent(this.getItemData().getSearchParams());
}
}
}
}