mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
show hash hit pin on groups and add hash hit counts to tooltip
This commit is contained in:
parent
497ce91da4
commit
6537ebe92c
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,14 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.events;
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Sets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.openide.util.NbBundle;
|
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
@ -47,26 +44,19 @@ public class AggregateEvent {
|
|||||||
|
|
||||||
private final DescriptionLOD lod;
|
private final DescriptionLOD lod;
|
||||||
|
|
||||||
public AggregateEvent(Interval spanningInterval, EventType type, Set<Long> eventIDs, String description, DescriptionLOD lod) {
|
private final Set<Long> hashHits;
|
||||||
|
|
||||||
|
public AggregateEvent(Interval spanningInterval, EventType type, Set<Long> eventIDs, Set<Long> hashHits, String description, DescriptionLOD lod) {
|
||||||
|
|
||||||
this.span = spanningInterval;
|
this.span = spanningInterval;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.hashHits = hashHits;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
this.eventIDs = eventIDs;
|
this.eventIDs = eventIDs;
|
||||||
this.lod = lod;
|
this.lod = lod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AggregateEvent(Interval spanningInterval, EventType type, List<String> events, String description, DescriptionLOD lod) {
|
|
||||||
|
|
||||||
this.span = spanningInterval;
|
|
||||||
this.type = type;
|
|
||||||
this.description = description;
|
|
||||||
|
|
||||||
this.eventIDs = new HashSet<>(Collections2.transform(events, Long::valueOf));
|
|
||||||
this.lod = lod;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return the actual interval from the first event to the last event */
|
/** @return the actual interval from the first event to the last event */
|
||||||
public Interval getSpan() {
|
public Interval getSpan() {
|
||||||
return span;
|
return span;
|
||||||
@ -76,6 +66,10 @@ public class AggregateEvent {
|
|||||||
return Collections.unmodifiableSet(eventIDs);
|
return Collections.unmodifiableSet(eventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<Long> getEventIDsWithHashHits() {
|
||||||
|
return Collections.unmodifiableSet(hashHits);
|
||||||
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
@ -101,11 +95,10 @@ public class AggregateEvent {
|
|||||||
if (!ag1.getDescription().equals(ag2.getDescription())) {
|
if (!ag1.getDescription().equals(ag2.getDescription())) {
|
||||||
throw new IllegalArgumentException("aggregate events are not compatible they have different descriptions");
|
throw new IllegalArgumentException("aggregate events are not compatible they have different descriptions");
|
||||||
}
|
}
|
||||||
HashSet<Long> ids = new HashSet<>(ag1.getEventIDs());
|
Sets.SetView<Long> idsUnion = Sets.union(ag1.getEventIDs(), ag2.getEventIDs());
|
||||||
ids.addAll(ag2.getEventIDs());
|
Sets.SetView<Long> hashHitsUnion = Sets.union(ag1.getEventIDsWithHashHits(), ag2.getEventIDsWithHashHits());
|
||||||
|
|
||||||
//TODO: check that types/descriptions are actually the same -jm
|
return new AggregateEvent(IntervalUtils.span(ag1.span, ag2.span), ag1.getType(), idsUnion, hashHitsUnion, ag1.getDescription(), ag1.lod);
|
||||||
return new AggregateEvent(IntervalUtils.span(ag1.span, ag2.span), ag1.getType(), ids, ag1.getDescription(), ag1.lod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DescriptionLOD getLOD() {
|
public DescriptionLOD getLOD() {
|
||||||
|
@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
|||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.HashHitFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
@ -106,7 +107,7 @@ public final class FilteredEventsModel {
|
|||||||
dataSourceFilter.setSelected(Boolean.TRUE);
|
dataSourceFilter.setSelected(Boolean.TRUE);
|
||||||
dataSourcesFilter.addDataSourceFilter(dataSourceFilter);
|
dataSourcesFilter.addDataSourceFilter(dataSourceFilter);
|
||||||
});
|
});
|
||||||
return new RootFilter(new HideKnownFilter(), new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter);
|
return new RootFilter(new HideKnownFilter(), new HashHitFilter(), new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
||||||
@ -151,6 +152,9 @@ public final class FilteredEventsModel {
|
|||||||
public TimeLineEvent getEventById(Long eventID) {
|
public TimeLineEvent getEventById(Long eventID) {
|
||||||
return repo.getEventById(eventID);
|
return repo.getEventById(eventID);
|
||||||
}
|
}
|
||||||
|
public Set<TimeLineEvent> getEventsById(Collection<Long> eventIDs) {
|
||||||
|
return repo.getEventsById(eventIDs);
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||||
final Interval overlap;
|
final Interval overlap;
|
||||||
@ -206,7 +210,7 @@ public final class FilteredEventsModel {
|
|||||||
* @return the smallest interval spanning all the events from the
|
* @return the smallest interval spanning all the events from the
|
||||||
* repository, ignoring any filters or requested ranges
|
* repository, ignoring any filters or requested ranges
|
||||||
*/
|
*/
|
||||||
public Interval getSpanningInterval() {
|
public Interval getSpanningInterval() {
|
||||||
return new Interval(getMinTime() * 1000, 1000 + getMaxTime() * 1000, DateTimeZone.UTC);
|
return new Interval(getMinTime() * 1000, 1000 + getMaxTime() * 1000, DateTimeZone.UTC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,5 +283,4 @@ public final class FilteredEventsModel {
|
|||||||
return requestedLOD.get();
|
return requestedLOD.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2014 Basis Technology Corp.
|
* Copyright 2014-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -28,7 +28,9 @@ public class TimeLineEvent {
|
|||||||
|
|
||||||
private final Long eventID;
|
private final Long eventID;
|
||||||
|
|
||||||
private final Long fileID, time;
|
private final Long fileID;
|
||||||
|
|
||||||
|
private final Long time;
|
||||||
|
|
||||||
private final Long artifactID;
|
private final Long artifactID;
|
||||||
|
|
||||||
@ -38,6 +40,26 @@ public class TimeLineEvent {
|
|||||||
|
|
||||||
private final TskData.FileKnown known;
|
private final TskData.FileKnown known;
|
||||||
|
|
||||||
|
private final boolean hashHit;
|
||||||
|
|
||||||
|
public TimeLineEvent(Long eventID, Long objID, Long artifactID, Long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit) {
|
||||||
|
this.eventID = eventID;
|
||||||
|
this.fileID = objID;
|
||||||
|
this.artifactID = artifactID;
|
||||||
|
this.time = time;
|
||||||
|
this.subType = type;
|
||||||
|
|
||||||
|
this.fullDescription = fullDescription;
|
||||||
|
this.medDescription = medDescription;
|
||||||
|
this.shortDescription = shortDescription;
|
||||||
|
this.known = known;
|
||||||
|
this.hashHit = hashHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHashHit() {
|
||||||
|
return hashHit;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getArtifactID() {
|
public Long getArtifactID() {
|
||||||
return artifactID;
|
return artifactID;
|
||||||
}
|
}
|
||||||
@ -71,20 +93,6 @@ public class TimeLineEvent {
|
|||||||
return shortDescription;
|
return shortDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeLineEvent(Long eventID, Long objID, Long artifactID, Long time,
|
|
||||||
EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known) {
|
|
||||||
this.eventID = eventID;
|
|
||||||
this.fileID = objID;
|
|
||||||
this.artifactID = artifactID;
|
|
||||||
this.time = time;
|
|
||||||
this.subType = type;
|
|
||||||
|
|
||||||
this.fullDescription = fullDescription;
|
|
||||||
this.medDescription = medDescription;
|
|
||||||
this.shortDescription = shortDescription;
|
|
||||||
this.known = known;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TskData.FileKnown getKnown() {
|
public TskData.FileKnown getKnown() {
|
||||||
return known;
|
return known;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -44,6 +43,8 @@ import java.util.TimeZone;
|
|||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
@ -89,7 +90,8 @@ public class EventDB {
|
|||||||
FULL_DESCRIPTION("full_description"), // NON-NLS
|
FULL_DESCRIPTION("full_description"), // NON-NLS
|
||||||
MED_DESCRIPTION("med_description"), // NON-NLS
|
MED_DESCRIPTION("med_description"), // NON-NLS
|
||||||
SHORT_DESCRIPTION("short_description"), // NON-NLS
|
SHORT_DESCRIPTION("short_description"), // NON-NLS
|
||||||
TIME("time"); // NON-NLS
|
TIME("time"),
|
||||||
|
HASH_HIT("hash_hit"); // NON-NLS
|
||||||
|
|
||||||
private final String columnName;
|
private final String columnName;
|
||||||
|
|
||||||
@ -358,10 +360,10 @@ public class EventDB {
|
|||||||
return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1);
|
return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasDataSourceInfo() {
|
boolean hasNewColumns() {
|
||||||
/* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
/* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
||||||
* value for the datasource_id column in the events table. */
|
* value for the datasource_id column in the events table. */
|
||||||
return hasDataSourceIDColumn()
|
return hasHashHitColumn() && hasDataSourceIDColumn()
|
||||||
&& (getDataSourceIDs().isEmpty() == false);
|
&& (getDataSourceIDs().isEmpty() == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,26 +468,35 @@ public class EventDB {
|
|||||||
+ " full_description TEXT, " // NON-NLS
|
+ " full_description TEXT, " // NON-NLS
|
||||||
+ " med_description TEXT, " // NON-NLS
|
+ " med_description TEXT, " // NON-NLS
|
||||||
+ " short_description TEXT, " // NON-NLS
|
+ " short_description TEXT, " // NON-NLS
|
||||||
+ " known_state INTEGER)"; // NON-NLS
|
+ " known_state INTEGER,"
|
||||||
|
+ " hash_hit INTEGER)"; //boolean // NON-NLS
|
||||||
stmt.execute(sql);
|
stmt.execute(sql);
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasDSInfo = hasDataSourceIDColumn();
|
if (hasDataSourceIDColumn() == false) {
|
||||||
if (hasDSInfo == false) {
|
|
||||||
try (Statement stmt = con.createStatement()) {
|
try (Statement stmt = con.createStatement()) {
|
||||||
String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS
|
String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS
|
||||||
stmt.execute(sql);
|
stmt.execute(sql);
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem upgrading database table", ex); // NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasHashHitColumn() == false) {
|
||||||
|
try (Statement stmt = con.createStatement()) {
|
||||||
|
String sql = "ALTER TABLE events ADD COLUMN hash_hit INTEGER"; // NON-NLS
|
||||||
|
stmt.execute(sql);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "problem upgrading database table", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
insertRowStmt = prepareStatement(
|
insertRowStmt = prepareStatement(
|
||||||
"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state) " // NON-NLS
|
"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit) " // NON-NLS
|
||||||
+ "VALUES (?,?,?,?,?,?,?,?,?,?)"); // NON-NLS
|
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS
|
||||||
|
|
||||||
getDataSourceIDsStmt = prepareStatement("select distinct datasource_id from events"); // NON-NLS
|
getDataSourceIDsStmt = prepareStatement("select distinct datasource_id from events"); // NON-NLS
|
||||||
getMaxTimeStmt = prepareStatement("select Max(time) as max from events"); // NON-NLS
|
getMaxTimeStmt = prepareStatement("select Max(time) as max from events"); // NON-NLS
|
||||||
@ -496,14 +507,7 @@ public class EventDB {
|
|||||||
} catch (SQLException sQLException) {
|
} catch (SQLException sQLException) {
|
||||||
LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS
|
LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS
|
||||||
}
|
}
|
||||||
if (hasDataSourceInfo() == false) {
|
|
||||||
try (Statement stmt = con.createStatement()) {
|
|
||||||
String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS
|
|
||||||
stmt.execute(sql);
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Statement stmt = con.createStatement()) {
|
try (Statement stmt = con.createStatement()) {
|
||||||
String sql = "CREATE INDEX if not exists file_idx ON events(file_id)"; // NON-NLS
|
String sql = "CREATE INDEX if not exists file_idx ON events(file_id)"; // NON-NLS
|
||||||
@ -551,24 +555,41 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDataSourceIDColumn() {
|
/**
|
||||||
|
*
|
||||||
|
* @param dbColumn the value of dbColumn
|
||||||
|
*
|
||||||
|
* @return the boolean
|
||||||
|
*/
|
||||||
|
private boolean hasDBColumn(final EventTableColumn dbColumn) {
|
||||||
try (Statement stmt = con.createStatement()) {
|
try (Statement stmt = con.createStatement()) {
|
||||||
|
|
||||||
ResultSet executeQuery = stmt.executeQuery("PRAGMA table_info(events)");
|
ResultSet executeQuery = stmt.executeQuery("PRAGMA table_info(events)");
|
||||||
while (executeQuery.next()) {
|
while (executeQuery.next()) {
|
||||||
if (EventTableColumn.DATA_SOURCE_ID.toString().equals(executeQuery.getString("name"))) {
|
if (dbColumn.toString().equals(executeQuery.getString("name"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem executing pragma", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertEvent(long time, EventType type, long datasourceID, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known) {
|
private boolean hasDataSourceIDColumn() {
|
||||||
|
return hasDBColumn(EventTableColumn.DATA_SOURCE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasHashHitColumn() {
|
||||||
|
return hasDBColumn(EventTableColumn.HASH_HIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertEvent(long time, EventType type, long datasourceID, Long objID,
|
||||||
|
Long artifactID, String fullDescription, String medDescription,
|
||||||
|
String shortDescription, TskData.FileKnown known, boolean hashHit) {
|
||||||
|
|
||||||
EventTransaction trans = beginTransaction();
|
EventTransaction trans = beginTransaction();
|
||||||
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, trans);
|
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashHit, trans);
|
||||||
commitTransaction(trans, true);
|
commitTransaction(trans, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,10 +597,14 @@ public class EventDB {
|
|||||||
* use transactions to update files
|
* use transactions to update files
|
||||||
*
|
*
|
||||||
* @param f
|
* @param f
|
||||||
* @param tr
|
* @param transaction
|
||||||
*/
|
*/
|
||||||
void insertEvent(long time, EventType type, long datasourceID, Long objID, Long artifactID, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, EventTransaction tr) {
|
void insertEvent(long time, EventType type, long datasourceID, Long objID,
|
||||||
if (tr.isClosed()) {
|
Long artifactID, String fullDescription, String medDescription,
|
||||||
|
String shortDescription, TskData.FileKnown known, boolean hashHit,
|
||||||
|
EventTransaction transaction) {
|
||||||
|
|
||||||
|
if (transaction.isClosed()) {
|
||||||
throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS
|
throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS
|
||||||
}
|
}
|
||||||
int typeNum;
|
int typeNum;
|
||||||
@ -591,7 +616,7 @@ public class EventDB {
|
|||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try {
|
try {
|
||||||
|
|
||||||
//"INSERT INTO events (datasource_id, file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description) "
|
//"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hashHit) "
|
||||||
insertRowStmt.clearParameters();
|
insertRowStmt.clearParameters();
|
||||||
insertRowStmt.setLong(1, datasourceID);
|
insertRowStmt.setLong(1, datasourceID);
|
||||||
if (objID != null) {
|
if (objID != null) {
|
||||||
@ -618,6 +643,7 @@ public class EventDB {
|
|||||||
insertRowStmt.setString(9, shortDescription);
|
insertRowStmt.setString(9, shortDescription);
|
||||||
|
|
||||||
insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
|
insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
|
||||||
|
insertRowStmt.setInt(11, hashHit ? 1 : 0);
|
||||||
|
|
||||||
insertRowStmt.executeUpdate();
|
insertRowStmt.executeUpdate();
|
||||||
|
|
||||||
@ -693,16 +719,15 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException {
|
private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException {
|
||||||
EventType type = RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString()));
|
|
||||||
return new TimeLineEvent(rs.getLong(EventTableColumn.EVENT_ID.toString()),
|
return new TimeLineEvent(rs.getLong(EventTableColumn.EVENT_ID.toString()),
|
||||||
rs.getLong(EventTableColumn.FILE_ID.toString()),
|
rs.getLong(EventTableColumn.FILE_ID.toString()),
|
||||||
rs.getLong(EventTableColumn.ARTIFACT_ID.toString()),
|
rs.getLong(EventTableColumn.ARTIFACT_ID.toString()),
|
||||||
rs.getLong(EventTableColumn.TIME.toString()),
|
rs.getLong(EventTableColumn.TIME.toString()), RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())),
|
||||||
type,
|
|
||||||
rs.getString(EventTableColumn.FULL_DESCRIPTION.toString()),
|
rs.getString(EventTableColumn.FULL_DESCRIPTION.toString()),
|
||||||
rs.getString(EventTableColumn.MED_DESCRIPTION.toString()),
|
rs.getString(EventTableColumn.MED_DESCRIPTION.toString()),
|
||||||
rs.getString(EventTableColumn.SHORT_DESCRIPTION.toString()),
|
rs.getString(EventTableColumn.SHORT_DESCRIPTION.toString()),
|
||||||
TskData.FileKnown.valueOf(rs.getByte(EventTableColumn.KNOWN.toString())));
|
TskData.FileKnown.valueOf(rs.getByte(EventTableColumn.KNOWN.toString())),
|
||||||
|
rs.getInt(EventTableColumn.HASH_HIT.toString()) != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -815,7 +840,9 @@ public class EventDB {
|
|||||||
|
|
||||||
//get all agregate events in this time unit
|
//get all agregate events in this time unit
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval, group_concat(event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes)
|
String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval,"
|
||||||
|
+ " group_concat(event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes)
|
||||||
|
// + " , (select group_concat(event_id) as ids_with_hash_hits from events where event_id IN event_ids)"
|
||||||
+ " from events where time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
+ " from events where time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
||||||
+ " group by interval, " + useSubTypeHelper(useSubTypes) + " , " + descriptionColumn // NON-NLS
|
+ " group by interval, " + useSubTypeHelper(useSubTypes) + " , " + descriptionColumn // NON-NLS
|
||||||
+ " order by Min(time)"; // NON-NLS
|
+ " order by Min(time)"; // NON-NLS
|
||||||
@ -831,12 +858,23 @@ public class EventDB {
|
|||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
//System.out.println(stopwatch.elapsedMillis() / 1000.0 + " seconds");
|
//System.out.println(stopwatch.elapsedMillis() / 1000.0 + " seconds");
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
|
String eventIDS = rs.getString("event_ids");
|
||||||
|
HashSet<Long> hashHits = new HashSet<>();
|
||||||
|
try (Statement st2 = con.createStatement();) {
|
||||||
|
|
||||||
|
ResultSet executeQuery = st2.executeQuery("select event_id from events where event_id in (" + eventIDS + ") and hash_hit = 1");
|
||||||
|
while (executeQuery.next()) {
|
||||||
|
hashHits.add(executeQuery.getLong(EventTableColumn.EVENT_ID.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())) : BaseTypes.values()[rs.getInt(EventTableColumn.BASE_TYPE.toString())];
|
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt(EventTableColumn.SUB_TYPE.toString())) : BaseTypes.values()[rs.getInt(EventTableColumn.BASE_TYPE.toString())];
|
||||||
|
|
||||||
AggregateEvent aggregateEvent = new AggregateEvent(
|
AggregateEvent aggregateEvent = new AggregateEvent(
|
||||||
new Interval(rs.getLong("Min(time)") * 1000, rs.getLong("Max(time)") * 1000, TimeLineController.getJodaTimeZone()), // NON-NLS
|
new Interval(rs.getLong("Min(time)") * 1000, rs.getLong("Max(time)") * 1000, TimeLineController.getJodaTimeZone()), // NON-NLS
|
||||||
type,
|
type,
|
||||||
Arrays.asList(rs.getString("event_ids").split(",")), // NON-NLS
|
Stream.of(eventIDS.split(",")).map(Long::valueOf).collect(Collectors.toSet()), // NON-NLS
|
||||||
|
hashHits,
|
||||||
rs.getString(descriptionColumn), lod);
|
rs.getString(descriptionColumn), lod);
|
||||||
|
|
||||||
//put events in map from type/descrition -> event
|
//put events in map from type/descrition -> event
|
||||||
|
@ -33,6 +33,7 @@ import java.util.concurrent.CancellationException;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableMap;
|
import javafx.collections.ObservableMap;
|
||||||
@ -175,6 +176,13 @@ public class EventsRepository {
|
|||||||
return idToEventCache.getUnchecked(eventID);
|
return idToEventCache.getUnchecked(eventID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<TimeLineEvent> getEventsById(Collection<Long> eventIDs) {
|
||||||
|
return eventIDs.stream()
|
||||||
|
.map(idToEventCache::getUnchecked)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
|
public List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
|
||||||
|
|
||||||
return aggregateEventsCache.getUnchecked(params);
|
return aggregateEventsCache.getUnchecked(params);
|
||||||
@ -213,7 +221,7 @@ public class EventsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasDataSourceInfo() {
|
public boolean hasDataSourceInfo() {
|
||||||
return eventDB.hasDataSourceInfo();
|
return eventDB.hasNewColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DBPopulationWorker extends SwingWorker<Void, ProgressWindow.ProgressUpdate> {
|
private class DBPopulationWorker extends SwingWorker<Void, ProgressWindow.ProgressUpdate> {
|
||||||
@ -268,20 +276,21 @@ public class EventsRepository {
|
|||||||
String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, "");
|
String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, "");
|
||||||
String medD = datasourceName + parentPath;
|
String medD = datasourceName + parentPath;
|
||||||
final TskData.FileKnown known = f.getKnown();
|
final TskData.FileKnown known = f.getKnown();
|
||||||
|
boolean hashHit = f.getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT) > 0;
|
||||||
|
|
||||||
//insert it into the db if time is > 0 => time is legitimate (drops logical files)
|
//insert it into the db if time is > 0 => time is legitimate (drops logical files)
|
||||||
long time;
|
long time;
|
||||||
if (f.getAtime() > 0) {
|
if (f.getAtime() > 0) {
|
||||||
eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans);
|
eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans);
|
||||||
}
|
}
|
||||||
if (f.getMtime() > 0) {
|
if (f.getMtime() > 0) {
|
||||||
eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans);
|
eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans);
|
||||||
}
|
}
|
||||||
if (f.getCtime() > 0) {
|
if (f.getCtime() > 0) {
|
||||||
eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans);
|
eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans);
|
||||||
}
|
}
|
||||||
if (f.getCrtime() > 0) {
|
if (f.getCrtime() > 0) {
|
||||||
eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, trans);
|
eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashHit, trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(i, numFiles,
|
process(Arrays.asList(new ProgressWindow.ProgressUpdate(i, numFiles,
|
||||||
@ -381,7 +390,9 @@ public class EventsRepository {
|
|||||||
|
|
||||||
if (eventDescription != null && eventDescription.getTime() > 0L) { //insert it into the db if time is > 0 => time is legitimate
|
if (eventDescription != null && eventDescription.getTime() > 0L) { //insert it into the db if time is > 0 => time is legitimate
|
||||||
long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId();
|
long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId();
|
||||||
eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, trans);
|
|
||||||
|
boolean hashHit = skCase.getAbstractFileById(bbart.getObjectID()).getArtifactsCount(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT) > 0;
|
||||||
|
eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, hashHit, trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
|
@ -14,6 +14,7 @@ import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
|||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.HashHitFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
@ -59,6 +60,8 @@ public class SQLHelper {
|
|||||||
result = getSQLWhere((DataSourcesFilter) filter);
|
result = getSQLWhere((DataSourcesFilter) filter);
|
||||||
} else if (filter instanceof HideKnownFilter) {
|
} else if (filter instanceof HideKnownFilter) {
|
||||||
result = getSQLWhere((HideKnownFilter) filter);
|
result = getSQLWhere((HideKnownFilter) filter);
|
||||||
|
} else if (filter instanceof HashHitFilter) {
|
||||||
|
result = getSQLWhere((HashHitFilter) filter);
|
||||||
} else if (filter instanceof TextFilter) {
|
} else if (filter instanceof TextFilter) {
|
||||||
result = getSQLWhere((TextFilter) filter);
|
result = getSQLWhere((TextFilter) filter);
|
||||||
} else if (filter instanceof TypeFilter) {
|
} else if (filter instanceof TypeFilter) {
|
||||||
@ -79,6 +82,10 @@ public class SQLHelper {
|
|||||||
return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.KNOWN.toString() + " is not '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')" // NON-NLS
|
return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.KNOWN.toString() + " is not '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')" // NON-NLS
|
||||||
: "1";
|
: "1";
|
||||||
}
|
}
|
||||||
|
static String getSQLWhere(HashHitFilter filter) {
|
||||||
|
return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.HASH_HIT.toString() + " is not 0)" // NON-NLS
|
||||||
|
: "1";
|
||||||
|
}
|
||||||
|
|
||||||
static String getSQLWhere(DataSourceFilter filter) {
|
static String getSQLWhere(DataSourceFilter filter) {
|
||||||
return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.DATA_SOURCE_ID.toString() + " = '" + filter.getDataSourceID() + "')" : "1";
|
return (filter.isSelected()) ? "(" + EventDB.EventTableColumn.DATA_SOURCE_ID.toString() + " = '" + filter.getDataSourceID() + "')" : "1";
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.timeline.filters;
|
||||||
|
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class HashHitFilter extends AbstractFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages("hashHitFilter.displayName.text=Show only Hash Hits")
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Bundle.hashHitFilter_displayName_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashHitFilter() {
|
||||||
|
super();
|
||||||
|
getActiveProperty().set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HashHitFilter copyOf() {
|
||||||
|
HashHitFilter hashHitFilter = new HashHitFilter();
|
||||||
|
hashHitFilter.setSelected(isSelected());
|
||||||
|
hashHitFilter.setDisabled(isDisabled());
|
||||||
|
return hashHitFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHTMLReportString() {
|
||||||
|
return "only hash hits" + getStringCheckBox();// NON-NLS
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final HashHitFilter other = (HashHitFilter) obj;
|
||||||
|
|
||||||
|
return isSelected() == other.isSelected();
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ import javafx.collections.FXCollections;
|
|||||||
public class RootFilter extends IntersectionFilter<Filter> {
|
public class RootFilter extends IntersectionFilter<Filter> {
|
||||||
|
|
||||||
private final HideKnownFilter knwonFilter;
|
private final HideKnownFilter knwonFilter;
|
||||||
|
private final HashHitFilter hashFilter;
|
||||||
private final TextFilter textFilter;
|
private final TextFilter textFilter;
|
||||||
private final TypeFilter typeFilter;
|
private final TypeFilter typeFilter;
|
||||||
private final DataSourcesFilter dataSourcesFilter;
|
private final DataSourcesFilter dataSourcesFilter;
|
||||||
@ -35,9 +36,10 @@ public class RootFilter extends IntersectionFilter<Filter> {
|
|||||||
return dataSourcesFilter;
|
return dataSourcesFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RootFilter(HideKnownFilter knownFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
|
public RootFilter(HideKnownFilter knownFilter,HashHitFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
|
||||||
super(FXCollections.observableArrayList(knownFilter, textFilter, dataSourceFilter, typeFilter));
|
super(FXCollections.observableArrayList(knownFilter,hashFilter, textFilter, dataSourceFilter, typeFilter));
|
||||||
this.knwonFilter = knownFilter;
|
this.knwonFilter = knownFilter;
|
||||||
|
this.hashFilter = hashFilter;
|
||||||
this.textFilter = textFilter;
|
this.textFilter = textFilter;
|
||||||
this.typeFilter = typeFilter;
|
this.typeFilter = typeFilter;
|
||||||
this.dataSourcesFilter = dataSourceFilter;
|
this.dataSourcesFilter = dataSourceFilter;
|
||||||
@ -45,7 +47,7 @@ public class RootFilter extends IntersectionFilter<Filter> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RootFilter copyOf() {
|
public RootFilter copyOf() {
|
||||||
RootFilter filter = new RootFilter(knwonFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf());
|
RootFilter filter = new RootFilter(knwonFilter.copyOf(),hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf());
|
||||||
filter.setSelected(isSelected());
|
filter.setSelected(isSelected());
|
||||||
filter.setDisabled(isDisabled());
|
filter.setDisabled(isDisabled());
|
||||||
return filter;
|
return filter;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,8 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
@ -58,17 +63,25 @@ import org.openide.util.Exceptions;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/** Represents an {@link AggregateEvent} in a {@link EventDetailChart}. */
|
/** Represents an {@link AggregateEvent} in a {@link EventDetailChart}. */
|
||||||
public class AggregateEventNode extends StackPane {
|
public class AggregateEventNode extends StackPane {
|
||||||
|
|
||||||
|
private static final Image HASH_PIN = new Image(AggregateEventNode.class.getResourceAsStream("/org/sleuthkit/autopsy/images/hashset_hits.png"));
|
||||||
private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS
|
private final static Image PLUS = new Image("/org/sleuthkit/autopsy/timeline/images/plus-button.png"); // NON-NLS
|
||||||
private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS
|
private final static Image MINUS = new Image("/org/sleuthkit/autopsy/timeline/images/minus-button.png"); // NON-NLS
|
||||||
|
|
||||||
@ -130,15 +143,25 @@ public class AggregateEventNode extends StackPane {
|
|||||||
|
|
||||||
private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
|
private SimpleObjectProperty<DescriptionLOD> descLOD = new SimpleObjectProperty<>();
|
||||||
private DescriptionVisibility descrVis;
|
private DescriptionVisibility descrVis;
|
||||||
|
private final SleuthkitCase sleuthkitCase;
|
||||||
|
private final FilteredEventsModel eventsModel;
|
||||||
|
private Map<String, Long> hashSetCounts = null;
|
||||||
|
private Tooltip tooltip;
|
||||||
|
|
||||||
public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart) {
|
public AggregateEventNode(final AggregateEvent event, AggregateEventNode parentEventNode, EventDetailChart chart) {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
descLOD.set(event.getLOD());
|
descLOD.set(event.getLOD());
|
||||||
this.parentEventNode = parentEventNode;
|
this.parentEventNode = parentEventNode;
|
||||||
this.chart = chart;
|
this.chart = chart;
|
||||||
|
sleuthkitCase = chart.getController().getAutopsyCase().getSleuthkitCase();
|
||||||
|
eventsModel = chart.getController().getEventsModel();
|
||||||
final Region region = new Region();
|
final Region region = new Region();
|
||||||
HBox.setHgrow(region, Priority.ALWAYS);
|
HBox.setHgrow(region, Priority.ALWAYS);
|
||||||
final HBox hBox = new HBox(descrLabel, countLabel, region, minusButton, plusButton);
|
ImageView imageView = new ImageView(HASH_PIN);
|
||||||
|
final HBox hBox = new HBox(descrLabel, countLabel, region, imageView, minusButton, plusButton);
|
||||||
|
if (event.getEventIDsWithHashHits().isEmpty()) {
|
||||||
|
hBox.getChildren().remove(imageView);
|
||||||
|
}
|
||||||
hBox.setPrefWidth(USE_COMPUTED_SIZE);
|
hBox.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
hBox.setMinWidth(USE_PREF_SIZE);
|
hBox.setMinWidth(USE_PREF_SIZE);
|
||||||
hBox.setPadding(new Insets(2, 5, 2, 5));
|
hBox.setPadding(new Insets(2, 5, 2, 5));
|
||||||
@ -229,11 +252,41 @@ public class AggregateEventNode extends StackPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void installTooltip() {
|
private void installTooltip() {
|
||||||
Tooltip.install(AggregateEventNode.this, new Tooltip(
|
|
||||||
NbBundle.getMessage(this.getClass(), "AggregateEventNode.installTooltip.text",
|
if (tooltip == null) {
|
||||||
getEvent().getEventIDs().size(), getEvent().getType(), getEvent().getDescription(),
|
String collect = "";
|
||||||
getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter()),
|
if (!event.getEventIDsWithHashHits().isEmpty()) {
|
||||||
getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter()))));
|
if (Objects.isNull(hashSetCounts)) {
|
||||||
|
hashSetCounts = new HashMap<>();
|
||||||
|
try {
|
||||||
|
for (TimeLineEvent tle : eventsModel.getEventsById(event.getEventIDsWithHashHits())) {
|
||||||
|
ArrayList<BlackboardArtifact> blackboardArtifacts = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, tle.getFileID());
|
||||||
|
for (BlackboardArtifact artf : blackboardArtifacts) {
|
||||||
|
for (BlackboardAttribute attr : artf.getAttributes()) {
|
||||||
|
if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
|
||||||
|
hashSetCounts.merge(attr.getValueString(), 1L, Long::sum);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
Logger.getLogger(AggregateEventNode.class.getName()).log(Level.SEVERE, "Error getting hashset hit info for event.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collect = hashSetCounts.entrySet().stream()
|
||||||
|
.map((Map.Entry<String, Long> t) -> t.getKey() + " : " + t.getValue())
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
|
}
|
||||||
|
tooltip = new Tooltip(
|
||||||
|
NbBundle.getMessage(this.getClass(), "AggregateEventNode.installTooltip.text",
|
||||||
|
getEvent().getEventIDs().size(), getEvent().getType(), getEvent().getDescription(),
|
||||||
|
getEvent().getSpan().getStart().toString(TimeLineController.getZonedFormatter()),
|
||||||
|
getEvent().getSpan().getEnd().toString(TimeLineController.getZonedFormatter()))
|
||||||
|
+ (collect.isEmpty() ? "" : "\n\nHash Set Hits\n" + collect));
|
||||||
|
Tooltip.install(AggregateEventNode.this, tooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pane getSubNodePane() {
|
public Pane getSubNodePane() {
|
||||||
@ -371,36 +424,36 @@ public class AggregateEventNode extends StackPane {
|
|||||||
LoggedTask<List<AggregateEventNode>> loggedTask = new LoggedTask<List<AggregateEventNode>>(
|
LoggedTask<List<AggregateEventNode>> loggedTask = new LoggedTask<List<AggregateEventNode>>(
|
||||||
NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
|
NbBundle.getMessage(this.getClass(), "AggregateEventNode.loggedTask.name"), true) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<AggregateEventNode> call() throws Exception {
|
protected List<AggregateEventNode> call() throws Exception {
|
||||||
//query for the sub-clusters
|
//query for the sub-clusters
|
||||||
List<AggregateEvent> aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span,
|
List<AggregateEvent> aggregatedEvents = chart.getFilteredEvents().getAggregatedEvents(new ZoomParams(span,
|
||||||
chart.getFilteredEvents().eventTypeZoom().get(),
|
chart.getFilteredEvents().eventTypeZoom().get(),
|
||||||
combinedFilter,
|
combinedFilter,
|
||||||
newLOD));
|
newLOD));
|
||||||
//for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
|
//for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
|
||||||
return aggregatedEvents.stream().map((AggregateEvent t) -> {
|
return aggregatedEvents.stream().map((AggregateEvent t) -> {
|
||||||
AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart);
|
AggregateEventNode subNode = new AggregateEventNode(t, AggregateEventNode.this, chart);
|
||||||
subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation());
|
subNode.setLayoutX(chart.getXAxis().getDisplayPosition(new DateTime(t.getSpan().getStartMillis())) - getLayoutXCompensation());
|
||||||
return subNode;
|
return subNode;
|
||||||
}).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters
|
}).collect(Collectors.toList()); // return list of AggregateEventNodes representing subclusters
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void succeeded() {
|
protected void succeeded() {
|
||||||
try {
|
try {
|
||||||
chart.setCursor(Cursor.WAIT);
|
chart.setCursor(Cursor.WAIT);
|
||||||
//assign subNodes and request chart layout
|
//assign subNodes and request chart layout
|
||||||
getSubNodePane().getChildren().setAll(get());
|
getSubNodePane().getChildren().setAll(get());
|
||||||
setDescriptionVisibility(descrVis);
|
setDescriptionVisibility(descrVis);
|
||||||
chart.setRequiresLayout(true);
|
chart.setRequiresLayout(true);
|
||||||
chart.requestChartLayout();
|
chart.requestChartLayout();
|
||||||
chart.setCursor(null);
|
chart.setCursor(null);
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
Exceptions.printStackTrace(ex);
|
Exceptions.printStackTrace(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//start task
|
//start task
|
||||||
chart.getController().monitorTask(loggedTask);
|
chart.getController().monitorTask(loggedTask);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Timeline.ui.detailview.tooltip.text={0}\nRight-click to remove.\nRight-drag to reposition.
|
Timeline.ui.detailview.tooltip.text={0}\nRight-click to remove.\nRight-drag to reposition.
|
||||||
AggregateEventNode.installTooltip.text={0} {1} events\n{2}\nbetween {3}\nand {4}
|
AggregateEventNode.installTooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}
|
||||||
AggregateEventNode.loggedTask.name=Load sub events
|
AggregateEventNode.loggedTask.name=Load sub events
|
||||||
DetailViewPane.loggedTask.name=Update Details
|
DetailViewPane.loggedTask.name=Update Details
|
||||||
DetailViewPane.loggedTask.preparing=preparing
|
DetailViewPane.loggedTask.preparing=preparing
|
||||||
|
Loading…
x
Reference in New Issue
Block a user