Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 6985-MiniTimelineDiscovery

This commit is contained in:
William Schaefer 2020-12-07 09:47:55 -05:00
commit 6a70439b8f
50 changed files with 15861 additions and 391 deletions

View File

@ -6,6 +6,7 @@ jobs:
- os: linux - os: linux
dist: bionic dist: bionic
- os: osx - os: osx
osx_image: xcode12.2
env: env:
global: global:

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.sleuthkit.autopsy.contentviewers.contextviewer.ContextViewer.DateTimePanel;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT;
@ -29,27 +30,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
* usage, if known. * usage, if known.
* *
*/ */
public final class ContextSourcePanel extends javax.swing.JPanel { public final class ContextSourcePanel extends javax.swing.JPanel implements DateTimePanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// defines a list of artifacts that provide context for a file // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
static { static {
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
} }
private final BlackboardArtifact sourceContextArtifact; private final BlackboardArtifact sourceContextArtifact;
private final Long dateTime;
/** /**
* Creates new form ContextViewer * Creates new form ContextViewer
*/ */
public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
initComponents(); initComponents();
sourceContextArtifact = associatedArtifact; sourceContextArtifact = associatedArtifact;
setSourceName(sourceName); setSourceName(sourceName);
setSourceText(sourceText); setSourceText(sourceText);
this.dateTime = dateTime;
}
@Override
public Long getDateTime() {
return dateTime;
} }
/** /**
@ -140,10 +150,10 @@ public final class ContextSourcePanel extends javax.swing.JPanel {
private void showSourceText(boolean show) { private void showSourceText(boolean show) {
jSourceTextLabel.setVisible(show); jSourceTextLabel.setVisible(show);
} }
private void showSourceButton(boolean show) { private void showSourceButton(boolean show) {
jSourceGoToResultButton.setVisible(show); jSourceGoToResultButton.setVisible(show);
jSourceGoToResultButton.setEnabled(show); jSourceGoToResultButton.setEnabled(show);
} }
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -29,27 +29,36 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOC
* usage, if known. * usage, if known.
* *
*/ */
public final class ContextUsagePanel extends javax.swing.JPanel { public final class ContextUsagePanel extends javax.swing.JPanel implements ContextViewer.DateTimePanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// defines a list of artifacts that provide context for a file // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>();
static { static {
SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
} }
private final BlackboardArtifact sourceContextArtifact; private final BlackboardArtifact sourceContextArtifact;
private final Long dateTime;
/** /**
* Creates new form ContextViewer * Creates new form ContextViewer
*/ */
public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact, Long dateTime) {
initComponents(); initComponents();
sourceContextArtifact = associatedArtifact; sourceContextArtifact = associatedArtifact;
setUsageName(sourceName); setUsageName(sourceName);
setUsageText(sourceText); setUsageText(sourceText);
this.dateTime = dateTime;
}
@Override
public Long getDateTime() {
return dateTime;
} }
/** /**
@ -138,10 +147,10 @@ public final class ContextUsagePanel extends javax.swing.JPanel {
private void showUsageText(boolean show) { private void showUsageText(boolean show) {
jUsageTextLabel.setVisible(show); jUsageTextLabel.setVisible(show);
} }
private void showUsageButton(boolean show) { private void showUsageButton(boolean show) {
jUsageGoToResultButton.setVisible(show); jUsageGoToResultButton.setVisible(show);
jUsageGoToResultButton.setEnabled(show); jUsageGoToResultButton.setEnabled(show);
} }
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.contentviewers.contextviewer;
import java.awt.Component; import java.awt.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -56,8 +58,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
// defines a list of artifacts that provide context for a file // defines a list of artifacts that provide context for a file
private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>(); private static final List<BlackboardArtifact.ARTIFACT_TYPE> CONTEXT_ARTIFACTS = new ArrayList<>();
private final List<javax.swing.JPanel> contextSourcePanels = new ArrayList<>(); private final List<ContextSourcePanel> contextSourcePanels = new ArrayList<>();
private final List<javax.swing.JPanel> contextUsagePanels = new ArrayList<>(); private final List<ContextUsagePanel> contextUsagePanels = new ArrayList<>();
static { static {
CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT);
@ -338,32 +340,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
"ContextViewer.programExecution=Program Execution: " "ContextViewer.programExecution=Program Execution: "
}) })
private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException { private void addArtifactToPanels(BlackboardArtifact associatedArtifact) throws TskCoreException {
Long dateTime = getArtifactDateTime(associatedArtifact);
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID() if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) { || BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_attachmentSource(); String sourceName = Bundle.ContextViewer_attachmentSource();
String sourceText = msgArtifactToAbbreviatedString(associatedArtifact); String sourceText = msgArtifactToAbbreviatedString(associatedArtifact);
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextSourcePanels.add(sourcePanel); contextSourcePanels.add(sourcePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID() } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) { || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_downloadSource(); String sourceName = Bundle.ContextViewer_downloadSource();
String sourceText = webDownloadArtifactToString(associatedArtifact); String sourceText = webDownloadArtifactToString(associatedArtifact);
javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); ContextSourcePanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextSourcePanels.add(sourcePanel); contextSourcePanels.add(sourcePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) { } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_recentDocs(); String sourceName = Bundle.ContextViewer_recentDocs();
String sourceText = recentDocArtifactToString(associatedArtifact); String sourceText = recentDocArtifactToString(associatedArtifact);
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact); ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextUsagePanels.add(usagePanel); contextUsagePanels.add(usagePanel);
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) { } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID() == associatedArtifact.getArtifactTypeID()) {
String sourceName = Bundle.ContextViewer_programExecution(); String sourceName = Bundle.ContextViewer_programExecution();
String sourceText = programExecArtifactToString(associatedArtifact); String sourceText = programExecArtifactToString(associatedArtifact);
javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact); ContextUsagePanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact, dateTime);
contextUsagePanels.add(usagePanel); contextUsagePanels.add(usagePanel);
} }
Collections.sort(contextSourcePanels, new SortByDateTime());
Collections.sort(contextUsagePanels, new SortByDateTime());
} }
/** /**
@ -532,6 +538,59 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
return attributeMap; return attributeMap;
} }
interface DateTimePanel {
/**
* Return the date time value for this panel.
*
* @return Date time value or null of one is not available.
*/
Long getDateTime();
}
/**
* Return the dateTime value for the given message artifact.
*
* @param artifact
*
* @return Long dateTime value or null if the attribute was not found.
*
* @throws TskCoreException
*/
private Long getArtifactDateTime(BlackboardArtifact artifact) throws TskCoreException {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME));
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == artifact.getArtifactTypeID()) {
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_SENT));
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == artifact.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == artifact.getArtifactTypeID()) {
attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED));
}
return (attribute != null ? attribute.getValueLong() : null);
}
/**
* Class for sorting lists of DateTimePanels.
*/
class SortByDateTime implements Comparator<DateTimePanel> {
@Override
public int compare(DateTimePanel panel1, DateTimePanel panel2) {
Long dateTime1 = panel1.getDateTime();
Long dateTime2 = panel2.getDateTime();
if(dateTime1 == null && dateTime2 == null) {
return 0;
} else if(dateTime1 == null) {
return -1;
} else if(dateTime2 == null) {
return 1;
}
return dateTime1.compareTo(dateTime2);
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables

View File

@ -89,6 +89,7 @@ public final class UserPreferences {
private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath"; private static final String GEO_OSM_TILE_ZIP_PATH = "GeolocationOsmZipPath";
private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress"; private static final String GEO_OSM_SERVER_ADDRESS = "GeolocationOsmServerAddress";
private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath"; private static final String GEO_MBTILES_FILE_PATH = "GeolcoationMBTilesFilePath";
private static final String HEALTH_MONITOR_REPORT_PATH = "HealthMonitorReportPath";
// Prevent instantiation. // Prevent instantiation.
private UserPreferences() { private UserPreferences() {
@ -668,4 +669,22 @@ public final class UserPreferences {
return Paths.get(UserMachinePreferences.getBaseTempDirectory(), getAppName()) return Paths.get(UserMachinePreferences.getBaseTempDirectory(), getAppName())
.toAbsolutePath().toString(); .toAbsolutePath().toString();
} }
/**
* Set the last used health monitor report path.
*
* @param reportPath Last used health monitor report path.
*/
public static void setHealthMonitorReportPath(String reportPath) {
preferences.put(HEALTH_MONITOR_REPORT_PATH, reportPath);
}
/**
* Gets the last used health monitor report path.
*
* @return Last used health monitor report path. Empty string if no value has been recorded.
*/
public static String getHealthMonitorReportPath() {
return preferences.get(HEALTH_MONITOR_REPORT_PATH, "");
}
} }

View File

@ -0,0 +1,197 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.coreutils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
/**
* Attempts to get the domain from a url/domain provided removing the
* subdomain(s).
*/
class DomainTokenizer {
/**
* This is a node in the trie. Children in the hashmap are identified by
* token. So for example, for a domain google.co.uk, the top level category
* would have an entry for "uk" linking to a domain category with "co".
*/
private static class DomainCategory extends HashMap<String, DomainCategory> {
private DomainCategory getOrAddChild(String childKey) {
DomainCategory cat = this.get(childKey);
if (cat == null) {
cat = new DomainCategory();
this.put(childKey, cat);
}
return cat;
}
}
// Character for joining domain segments.
private static final String JOINER = ".";
// delimiter when used with regex
private static final String DELIMITER = "\\" + JOINER;
private static final String WILDCARD = "*";
private static final String EXCEPTION_PREFIX = "!";
// taken from https://publicsuffix.org/list/public_suffix_list.dat
// file containing line seperated suffixes
// rules for parsing can be found here: https://publicsuffix.org/list/
private static final String DOMAIN_LIST = "public_suffix_list.dat";
// token for comments
private static final String COMMENT_TOKEN = "//";
// singleton instance of this class.
private static DomainTokenizer categorizer = null;
/**
* Returns the singleton instance of this class.
*
* @return The DomainCategorizer instance.
* @throws IOException
*/
static DomainTokenizer getInstance() throws IOException {
if (categorizer == null) {
categorizer = load();
}
return categorizer;
}
/**
* Loads a DomainCategorizer instance using the public suffix list.
*
* @return The DomainCategorizer instance.
* @throws IOException If there is an error reading the file.
*/
private static DomainTokenizer load() throws IOException {
try (InputStream is = DomainTokenizer.class.getResourceAsStream(DOMAIN_LIST);
InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isReader)) {
DomainTokenizer categorizer = new DomainTokenizer();
while (reader.ready()) {
String line = reader.readLine();
String trimmed = line.trim();
if (!StringUtils.isBlank(trimmed) && !trimmed.startsWith(COMMENT_TOKEN)) {
categorizer.addDomainSuffix(trimmed);
}
}
return categorizer;
}
}
private DomainTokenizer() {
}
// The top-level trie node.
private final DomainCategory trie = new DomainCategory();
/**
* Parses a domain suffix and adds components to the trie in reverse order
* (i.e. ".co.uk" becomes "uk" -> "co").
*
* @param domainSuffix The domain suffix.
*/
private void addDomainSuffix(String domainSuffix) {
if (StringUtils.isBlank(domainSuffix)) {
return;
}
String[] tokens = domainSuffix.toLowerCase().trim().split(DELIMITER);
DomainCategory cat = trie;
for (int i = tokens.length - 1; i >= 0; i--) {
String token = tokens[i];
if (StringUtils.isBlank(token)) {
continue;
}
cat = cat.getOrAddChild(tokens[i]);
}
}
/**
* Gets the domain by attempting to identify the host without the subdomain.
* If no domain can be determined, the domain is returned.
*
* @param domain The domain to query for.
* @return If provided argument is blank, null is returned. If no domain
* suffixes can be identified, the full host is returned. If a host and
* suffixes are identified, the domain (all suffixes with a prefix of the
* next token) are returned.
*/
String getDomain(String domain) {
if (StringUtils.isBlank(domain)) {
return "";
}
List<String> tokens = Stream.of(domain.toLowerCase().split(DELIMITER))
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
int idx = tokens.size() - 1;
DomainCategory cat = trie;
for (; idx >= 0; idx--) {
// an exception rule must be at the beginning of a suffix, and, in
// practice, indicates a domain that would otherwise be a further
// suffix with a wildcard rule per: https://publicsuffix.org/list/
if (cat.get(EXCEPTION_PREFIX + tokens.get(idx)) != null) {
break;
}
DomainCategory newCat = cat.get(tokens.get(idx));
// if no matching token can be found, look for wildcard token
if (newCat == null) {
// if no wildcard token can be found, the portion found
// so far is the suffix.
newCat = cat.get(WILDCARD);
if (newCat == null) {
break;
}
}
cat = newCat;
}
// if first suffix cannot be found, return the whole domain
if (idx == tokens.size() - 1) {
return domain;
} else {
int minIndex = Math.max(0, idx);
List<String> subList = tokens.subList(minIndex, tokens.size());
return String.join(JOINER, subList);
}
}
}

View File

@ -18,13 +18,17 @@
*/ */
package org.sleuthkit.autopsy.coreutils; package org.sleuthkit.autopsy.coreutils;
import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.StringTokenizer; import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
public class NetworkUtils { public class NetworkUtils {
private static final Logger logger = Logger.getLogger(NetworkUtils.class.getName());
private NetworkUtils() { private NetworkUtils() {
} }
@ -32,7 +36,7 @@ public class NetworkUtils {
* Set the host name variable. Sometimes the network can be finicky, so the * Set the host name variable. Sometimes the network can be finicky, so the
* answer returned by getHostName() could throw an exception or be null. * answer returned by getHostName() could throw an exception or be null.
* Have it read the environment variable if getHostName() is unsuccessful. * Have it read the environment variable if getHostName() is unsuccessful.
* *
* @return the local host name * @return the local host name
*/ */
public static String getLocalHostName() { public static String getLocalHostName() {
@ -49,16 +53,16 @@ public class NetworkUtils {
} }
return hostName; return hostName;
} }
/** /**
* Attempt to manually extract the domain from a URL. * Attempt to manually extract the domain from a URL.
* *
* @param url * @param url
* @return empty string if no domain could be found * @return empty string if no domain could be found
*/ */
private static String getBaseDomain(String url) { private static String getBaseDomain(String url) {
String host = null; String host = null;
//strip protocol //strip protocol
String cleanUrl = url.replaceFirst(".*:\\/\\/", ""); String cleanUrl = url.replaceFirst(".*:\\/\\/", "");
@ -70,42 +74,30 @@ public class NetworkUtils {
host = cleanUrl; host = cleanUrl;
} }
//get the domain part from host (last 2) String base = host;
StringTokenizer tok = new StringTokenizer(host, "."); try {
StringBuilder hostB = new StringBuilder(); base = DomainTokenizer.getInstance().getDomain(host);
int toks = tok.countTokens(); } catch (IOException ex) {
logger.log(Level.WARNING, "Unable to load resources for domain categorization.", ex);
for (int count = 0; count < toks; ++count) {
String part = tok.nextToken();
int diff = toks - count;
if (diff < 3) {
hostB.append(part);
}
if (diff == 2) {
hostB.append(".");
}
} }
String base = hostB.toString();
// verify there are no special characters in there // verify there are no special characters in there
if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) { if (base.matches(".*[~`!@#$%^&\\*\\(\\)\\+={}\\[\\];:\\?<>,/ ].*")) {
return ""; return "";
} }
//verify that the base domain actually has a '.', details JIRA-4609 //verify that the base domain actually has a '.', details JIRA-4609
if(!base.contains(".")) { if (!base.contains(".")) {
return ""; return "";
} }
return base; return base;
} }
/** /**
* Attempt to extract the domain from a URL. * Attempt to extract the domain from a URL. Will start by using the
* Will start by using the built-in URL class, and if that fails will * built-in URL class, and if that fails will try to extract it manually.
* try to extract it manually. *
*
* @param urlString The URL to extract the domain from * @param urlString The URL to extract the domain from
* @return empty string if no domain name was found * @return empty string if no domain name was found
*/ */
@ -113,20 +105,22 @@ public class NetworkUtils {
if (urlString == null) { if (urlString == null) {
return ""; return "";
} }
String result = ""; String urlHost = null;
try { try {
URL url = new URL(urlString); URL url = new URL(urlString);
result = url.getHost(); urlHost = url.getHost();
} catch (MalformedURLException ex) { } catch (MalformedURLException ex) {
//do not log if not a valid URL - we will try to extract it ourselves //do not log if not a valid URL - we will try to extract it ourselves
} }
//was not a valid URL, try a less picky method // if there is a valid url host, get base domain from that host
if (result == null || result.trim().isEmpty()) { // otherwise use urlString and parse the domain
return getBaseDomain(urlString); String result = (StringUtils.isNotBlank(urlHost))
} ? getBaseDomain(urlHost)
: getBaseDomain(urlString);
return result; return result;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -121,6 +121,8 @@ public final class IconsUtil {
imageFile = "web-account-type.png"; //NON-NLS imageFile = "web-account-type.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) { } else if (typeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) {
imageFile = "web-form-address.png"; //NON-NLS imageFile = "web-form-address.png"; //NON-NLS
} else if (typeID == ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()) {
imageFile = "gps-area.png"; //NON-NLS
} else { } else {
imageFile = "artifact-icon.png"; //NON-NLS imageFile = "artifact-icon.png"; //NON-NLS
} }

View File

@ -130,7 +130,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) { if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) {
return null; return null;
} else { } else {
return new RecentFileDetails(path, lastOpened); return new RecentFileDetails(artifact, path, lastOpened);
} }
} }
@ -179,7 +179,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) { if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) {
return null; return null;
} else { } else {
return new RecentDownloadDetails(path, accessedTime, domain); return new RecentDownloadDetails(artifact, path, accessedTime, domain);
} }
} }
@ -298,7 +298,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
if (date == null || date == 0 || StringUtils.isBlank(path)) { if (date == null || date == 0 || StringUtils.isBlank(path)) {
return null; return null;
} else { } else {
return new RecentAttachmentDetails(path, date, sender); return new RecentAttachmentDetails(messageArtifact, path, date, sender);
} }
} }
@ -324,14 +324,17 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
private final String path; private final String path;
private final long date; private final long date;
private final BlackboardArtifact artifact;
/** /**
* Constructor for files with just a path and date. * Constructor for files with just a path and date.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File access date\time in seconds with java epoch * @param date File access date\time in seconds with java epoch
*/ */
RecentFileDetails(String path, long date) { RecentFileDetails(BlackboardArtifact artifact, String path, long date) {
this.artifact = artifact;
this.path = path; this.path = path;
this.date = date; this.date = date;
} }
@ -364,6 +367,12 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return path; return path;
} }
/**
* @return The pertinent artifact for this recent file hit.
*/
public BlackboardArtifact getArtifact() {
return artifact;
}
} }
/** /**
@ -376,12 +385,13 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
/** /**
* Constructor for files with just a path and date. * Constructor for files with just a path and date.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File access date\time in seconds with java epoch. * @param date File access date\time in seconds with java epoch.
* @param webDomain The webdomain from which the file was downloaded. * @param webDomain The webdomain from which the file was downloaded.
*/ */
RecentDownloadDetails(String path, long date, String webDomain) { RecentDownloadDetails(BlackboardArtifact artifact, String path, long date, String webDomain) {
super(path, date); super(artifact, path, date);
this.webDomain = webDomain; this.webDomain = webDomain;
} }
@ -407,13 +417,14 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
* Constructor for recent download files which have a path, date and * Constructor for recent download files which have a path, date and
* domain value. * domain value.
* *
* @param artifact The relevant artifact.
* @param path File path. * @param path File path.
* @param date File crtime. * @param date File crtime.
* @param sender The sender of the message from which the file was * @param sender The sender of the message from which the file was
* attached. * attached.
*/ */
RecentAttachmentDetails(String path, long date, String sender) { RecentAttachmentDetails(BlackboardArtifact artifact, String path, long date, String sender) {
super(path, date); super(artifact, path, date);
this.sender = sender; this.sender = sender;
} }

View File

@ -0,0 +1,95 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineModule;
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.FilterState;
import org.sleuthkit.autopsy.timeline.ui.filtering.datamodel.RootFilterState;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TimelineFilter;
import org.sleuthkit.datamodel.TimelineFilter.DataSourceFilter;
import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utilities for interacting with Timeline in relation to data sources.
*/
public class TimelineDataSourceUtils {
private static TimelineDataSourceUtils instance = null;
/**
* @return Singleton instance of this class.
*/
public static TimelineDataSourceUtils getInstance() {
if (instance == null) {
instance = new TimelineDataSourceUtils();
}
return instance;
}
/**
* Main constructor. Should be instantiated through getInstance().
*/
private TimelineDataSourceUtils() {
}
/**
* Retrieves a RootFilter based on the default filter state but only the
* specified dataSource is selected.
*
* @param dataSource The data source.
* @return The root filter representing a default filter with only this data
* source selected.
* @throws NoCurrentCaseException
* @throws TskCoreException
*/
public RootFilter getDataSourceFilter(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
RootFilterState filterState = getDataSourceFilterState(dataSource);
return filterState == null ? null : filterState.getActiveFilter();
}
/**
* Retrieves a TimeLineController based on the default filter state but only
* the specified dataSource is selected.
*
* @param dataSource The data source.
* @return The root filter state representing a default filter with only
* this data source selected.
* @throws NoCurrentCaseException
* @throws TskCoreException
*/
public RootFilterState getDataSourceFilterState(DataSource dataSource) throws NoCurrentCaseException, TskCoreException {
TimeLineController controller = TimeLineModule.getController();
RootFilterState dataSourceState = controller.getEventsModel().getDefaultEventFilterState().copyOf();
for (FilterState<? extends TimelineFilter.DataSourceFilter> filterState : dataSourceState.getDataSourcesFilterState().getSubFilterStates()) {
DataSourceFilter dsFilter = filterState.getFilter();
if (dsFilter != null) {
filterState.setSelected(dsFilter.getDataSourceID() == dataSource.getId());
}
}
return dataSourceState;
}
}

View File

@ -38,12 +38,11 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TimelineEvent; import org.sleuthkit.datamodel.TimelineEvent;
import org.sleuthkit.datamodel.TimelineEventType; import org.sleuthkit.datamodel.TimelineEventType;
import org.sleuthkit.datamodel.TimelineFilter;
import org.sleuthkit.datamodel.TimelineFilter.DataSourcesFilter;
import org.sleuthkit.datamodel.TimelineFilter.RootFilter; import org.sleuthkit.datamodel.TimelineFilter.RootFilter;
import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.TimelineManager;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferences;
/** /**
@ -51,6 +50,23 @@ import org.sleuthkit.autopsy.core.UserPreferences;
*/ */
public class TimelineSummary implements DefaultUpdateGovernor { public class TimelineSummary implements DefaultUpdateGovernor {
/**
* A function for obtaining a Timeline RootFilter filtered to the specific
* data source.
*/
public interface DataSourceFilterFunction {
/**
* Obtains a Timeline RootFilter filtered to the specific data source.
*
* @param dataSource The data source.
* @return The timeline root filter.
* @throws NoCurrentCaseException
* @throws TskCoreException
*/
RootFilter apply(DataSource dataSource) throws NoCurrentCaseException, TskCoreException;
}
private static final long DAY_SECS = 24 * 60 * 60; private static final long DAY_SECS = 24 * 60 * 60;
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>( private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS = new HashSet<>(
Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED)); Arrays.asList(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED));
@ -64,23 +80,29 @@ public class TimelineSummary implements DefaultUpdateGovernor {
private final SleuthkitCaseProvider caseProvider; private final SleuthkitCaseProvider caseProvider;
private final Supplier<TimeZone> timeZoneProvider; private final Supplier<TimeZone> timeZoneProvider;
private final DataSourceFilterFunction filterFunction;
/** /**
* Default constructor. * Default constructor.
*/ */
public TimelineSummary() { public TimelineSummary() {
this(SleuthkitCaseProvider.DEFAULT, () -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays())); this(SleuthkitCaseProvider.DEFAULT,
() -> TimeZone.getTimeZone(UserPreferences.getTimeZoneForDisplays()),
(ds) -> TimelineDataSourceUtils.getInstance().getDataSourceFilter(ds));
} }
/** /**
* Construct object with given SleuthkitCaseProvider * Construct object with given SleuthkitCaseProvider
* *
* @param caseProvider SleuthkitCaseProvider provider, cannot be null. * @param caseProvider SleuthkitCaseProvider provider; cannot be null.
* @param timeZoneProvider The timezone provider, cannot be null. * @param timeZoneProvider The timezone provider; cannot be null.
* @param filterFunction Provides the default root filter function filtered
* to the data source; cannot be null.
*/ */
public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider) { public TimelineSummary(SleuthkitCaseProvider caseProvider, Supplier<TimeZone> timeZoneProvider, DataSourceFilterFunction filterFunction) {
this.caseProvider = caseProvider; this.caseProvider = caseProvider;
this.timeZoneProvider = timeZoneProvider; this.timeZoneProvider = timeZoneProvider;
this.filterFunction = filterFunction;
} }
@Override @Override
@ -113,8 +135,9 @@ public class TimelineSummary implements DefaultUpdateGovernor {
* @return The retrieved data. * @return The retrieved data.
* @throws SleuthkitCaseProviderException * @throws SleuthkitCaseProviderException
* @throws TskCoreException * @throws TskCoreException
* @throws NoCurrentCaseException
*/ */
public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException { public TimelineSummaryData getData(DataSource dataSource, int recentDaysNum) throws SleuthkitCaseProviderException, TskCoreException, NoCurrentCaseException {
TimeZone timeZone = this.timeZoneProvider.get(); TimeZone timeZone = this.timeZoneProvider.get();
TimelineManager timelineManager = this.caseProvider.get().getTimelineManager(); TimelineManager timelineManager = this.caseProvider.get().getTimelineManager();
@ -144,7 +167,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
// get most recent days activity // get most recent days activity
List<DailyActivityAmount> mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay); List<DailyActivityAmount> mostRecentActivityAmt = getMostRecentActivityAmounts(dateCounts, minRecentDay, maxDay);
return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt); return new TimelineSummaryData(minDate, maxDate, mostRecentActivityAmt, dataSource);
} }
/** /**
@ -181,25 +204,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
* belongs. * belongs.
* @return A Map mapping days from epoch to the activity for that day. * @return A Map mapping days from epoch to the activity for that day.
* @throws TskCoreException * @throws TskCoreException
* @throws NoCurrentCaseException
*/ */
private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone) throws TskCoreException { private Map<Long, DailyActivityAmount> getTimelineEventsByDay(DataSource dataSource, TimelineManager timelineManager, TimeZone timeZone)
throws TskCoreException, NoCurrentCaseException {
DataSourcesFilter dataSourceFilter = new DataSourcesFilter(); RootFilter rootFilter = this.filterFunction.apply(dataSource);
dataSourceFilter.addSubFilter(new TimelineFilter.DataSourceFilter(dataSource.getName(), dataSource.getId()));
RootFilter dataSourceRootFilter = new RootFilter(
null,
null,
null,
null,
null,
dataSourceFilter,
null,
Collections.emptySet());
// get events for data source // get events for data source
long curRunTime = System.currentTimeMillis(); long curRunTime = System.currentTimeMillis();
List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), dataSourceRootFilter); List<TimelineEvent> events = timelineManager.getEvents(new Interval(1, curRunTime), rootFilter);
// get counts of events per day (left is file system events, right is everything else) // get counts of events per day (left is file system events, right is everything else)
Map<Long, DailyActivityAmount> dateCounts = new HashMap<>(); Map<Long, DailyActivityAmount> dateCounts = new HashMap<>();
@ -233,6 +246,7 @@ public class TimelineSummary implements DefaultUpdateGovernor {
private final Date minDate; private final Date minDate;
private final Date maxDate; private final Date maxDate;
private final List<DailyActivityAmount> histogramActivity; private final List<DailyActivityAmount> histogramActivity;
private final DataSource dataSource;
/** /**
* Main constructor. * Main constructor.
@ -240,12 +254,15 @@ public class TimelineSummary implements DefaultUpdateGovernor {
* @param minDate Earliest usage date recorded for the data source. * @param minDate Earliest usage date recorded for the data source.
* @param maxDate Latest usage date recorded for the data source. * @param maxDate Latest usage date recorded for the data source.
* @param recentDaysActivity A list of activity prior to and including * @param recentDaysActivity A list of activity prior to and including
* the latest usage date by day. * max date sorted by min to max date.
* @param dataSource The data source for which this data applies. the
* latest usage date by day.
*/ */
TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity) { TimelineSummaryData(Date minDate, Date maxDate, List<DailyActivityAmount> recentDaysActivity, DataSource dataSource) {
this.minDate = minDate; this.minDate = minDate;
this.maxDate = maxDate; this.maxDate = maxDate;
this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity); this.histogramActivity = (recentDaysActivity == null) ? Collections.emptyList() : Collections.unmodifiableList(recentDaysActivity);
this.dataSource = dataSource;
} }
/** /**
@ -264,11 +281,18 @@ public class TimelineSummary implements DefaultUpdateGovernor {
/** /**
* @return A list of activity prior to and including the latest usage * @return A list of activity prior to and including the latest usage
* date by day. * date by day sorted min to max date.
*/ */
public List<DailyActivityAmount> getMostRecentDaysActivity() { public List<DailyActivityAmount> getMostRecentDaysActivity() {
return histogramActivity; return histogramActivity;
} }
/**
* @return The data source that this data applies to.
*/
public DataSource getDataSource() {
return dataSource;
}
} }
/** /**

View File

@ -108,8 +108,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT"; private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
private static final String WINDOWS_PREFIX = "/WINDOWS"; private static final String WINDOWS_PREFIX = "/WINDOWS";
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess()); private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed()); private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccessed().compareTo(b.getLastAccessed());
/** /**
* Sorts TopProgramsResults pushing highest run time count then most recent * Sorts TopProgramsResults pushing highest run time count then most recent
@ -126,8 +126,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
// second priority for sorting is the last run date // second priority for sorting is the last run date
// if non-0, this is the return value for the comparator // if non-0, this is the return value for the comparator
int lastRunCompare = nullableCompare( int lastRunCompare = nullableCompare(
a.getLastRun() == null ? null : a.getLastRun().getTime(), a.getLastAccessed() == null ? null : a.getLastAccessed().getTime(),
b.getLastRun() == null ? null : b.getLastRun().getTime()); b.getLastAccessed() == null ? null : b.getLastAccessed().getTime());
if (lastRunCompare != 0) { if (lastRunCompare != 0) {
return -lastRunCompare; return -lastRunCompare;
@ -219,14 +219,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); return Collections.emptyList();
} }
Pair<Long, Map<String, List<Long>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource); Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> mostRecentAndGroups = getDomainGroupsAndMostRecent(dataSource);
// if no recent domains, return accordingly // if no recent domains, return accordingly
if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) { if (mostRecentAndGroups.getKey() == null || mostRecentAndGroups.getValue().size() == 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
final long mostRecentMs = mostRecentAndGroups.getLeft(); final long mostRecentMs = mostRecentAndGroups.getLeft();
Map<String, List<Long>> groups = mostRecentAndGroups.getRight(); Map<String, List<Pair<BlackboardArtifact, Long>>> groups = mostRecentAndGroups.getRight();
return groups.entrySet().stream() return groups.entrySet().stream()
.map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs)) .map(entry -> getDomainsResult(entry.getKey(), entry.getValue(), mostRecentMs))
@ -243,24 +243,32 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* within DOMAIN_WINDOW_MS of mostRecentMs. * within DOMAIN_WINDOW_MS of mostRecentMs.
* *
* @param domain The domain. * @param domain The domain.
* @param visits The number of visits. * @param visits The list of the artifact and its associated time in
* milliseconds.
* @param mostRecentMs The most recent visit of any domain. * @param mostRecentMs The most recent visit of any domain.
* *
* @return The TopDomainsResult or null if no visits to this domain within * @return The TopDomainsResult or null if no visits to this domain within
* 30 days of mostRecentMs. * 30 days of mostRecentMs.
*/ */
private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long mostRecentMs) { private TopDomainsResult getDomainsResult(String domain, List<Pair<BlackboardArtifact, Long>> visits, long mostRecentMs) {
long visitCount = 0; long visitCount = 0;
Long thisMostRecentMs = null; Long thisMostRecentMs = null;
BlackboardArtifact thisMostRecentArtifact = null;
for (Long visitMs : visits) { for (Pair<BlackboardArtifact, Long> visitInstance : visits) {
BlackboardArtifact artifact = visitInstance.getLeft();
Long visitMs = visitInstance.getRight();
// make sure that visit is within window of mostRecentMS; otherwise skip it. // make sure that visit is within window of mostRecentMS; otherwise skip it.
if (visitMs + DOMAIN_WINDOW_MS < mostRecentMs) { if (visitMs == null || visitMs + DOMAIN_WINDOW_MS < mostRecentMs) {
continue; continue;
} }
// if visit is within window, increment the count and get most recent // if visit is within window, increment the count and get most recent
visitCount++; visitCount++;
if (thisMostRecentMs == null || visitMs > thisMostRecentMs) {
thisMostRecentMs = visitMs;
thisMostRecentArtifact = artifact;
}
thisMostRecentMs = getMax(thisMostRecentMs, visitMs); thisMostRecentMs = getMax(thisMostRecentMs, visitMs);
} }
@ -269,7 +277,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return null; return null;
} else { } else {
// create a top domain result with the domain, count, and most recent visit date // create a top domain result with the domain, count, and most recent visit date
return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs)); return new TopDomainsResult(domain, visitCount, new Date(thisMostRecentMs), thisMostRecentArtifact);
} }
} }
@ -281,17 +289,18 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* *
* @return A tuple where the first value is the latest web history accessed * @return A tuple where the first value is the latest web history accessed
* date in milliseconds and the second value maps normalized (lowercase; * date in milliseconds and the second value maps normalized (lowercase;
* trimmed) domain names to when those domains were visited. * trimmed) domain names to when those domains were visited and the relevant
* artifact.
* *
* @throws TskCoreException * @throws TskCoreException
* @throws SleuthkitCaseProviderException * @throws SleuthkitCaseProviderException
*/ */
private Pair<Long, Map<String, List<Long>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException { private Pair<Long, Map<String, List<Pair<BlackboardArtifact, Long>>>> getDomainGroupsAndMostRecent(DataSource dataSource) throws TskCoreException, SleuthkitCaseProviderException {
List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY, List<BlackboardArtifact> artifacts = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_WEB_HISTORY,
dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0); dataSource, TYPE_DATETIME_ACCESSED, DataSourceInfoUtilities.SortOrder.DESCENDING, 0);
Long mostRecentMs = null; Long mostRecentMs = null;
Map<String, List<Long>> domainVisits = new HashMap<>(); Map<String, List<Pair<BlackboardArtifact, Long>>> domainVisits = new HashMap<>();
for (BlackboardArtifact art : artifacts) { for (BlackboardArtifact art : artifacts) {
Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED); Long artifactDateSecs = DataSourceInfoUtilities.getLongOrNull(art, TYPE_DATETIME_ACCESSED);
@ -312,13 +321,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
domain = domain.toLowerCase().trim(); domain = domain.toLowerCase().trim();
// add this visit date to the list of dates for the domain // add this visit date to the list of dates for the domain
List<Long> domainVisitList = domainVisits.get(domain); List<Pair<BlackboardArtifact, Long>> domainVisitList = domainVisits.get(domain);
if (domainVisitList == null) { if (domainVisitList == null) {
domainVisitList = new ArrayList<>(); domainVisitList = new ArrayList<>();
domainVisits.put(domain, domainVisitList); domainVisits.put(domain, domainVisitList);
} }
domainVisitList.add(artifactDateMs); domainVisitList.add(Pair.of(art, artifactDateMs));
} }
return Pair.of(mostRecentMs, domainVisits); return Pair.of(mostRecentMs, domainVisits);
@ -354,7 +363,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT); String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT);
Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED); Date dateAccessed = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME_ACCESSED);
return (StringUtils.isNotBlank(searchString) && dateAccessed != null) return (StringUtils.isNotBlank(searchString) && dateAccessed != null)
? new TopWebSearchResult(searchString, dateAccessed) ? new TopWebSearchResult(searchString, dateAccessed, artifact)
: null; : null;
} }
@ -456,15 +465,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @return The most recent one with a non-null date. * @return The most recent one with a non-null date.
*/ */
private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) { private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) {
if (r2.getDateAccessed() == null) { if (r2.getLastAccessed()== null) {
return r1; return r1;
} }
if (r1.getDateAccessed() == null) { if (r1.getLastAccessed() == null) {
return r2; return r2;
} }
return r1.getDateAccessed().compareTo(r2.getDateAccessed()) >= 0 ? r1 : r2; return r1.getLastAccessed().compareTo(r2.getLastAccessed()) >= 0 ? r1 : r2;
} }
/** /**
@ -495,7 +504,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID), DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_ID),
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME), DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE), DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MAKE),
DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL) DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_DEVICE_MODEL),
artifact
); );
}) })
// remove Root Hub identifier // remove Root Hub identifier
@ -524,7 +534,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE); String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE);
Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME); Date date = DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME);
return (StringUtils.isNotBlank(type) && date != null) return (StringUtils.isNotBlank(type) && date != null)
? new TopAccountResult(type, date) ? new TopAccountResult(type, date, artifact)
: null; : null;
} }
@ -552,7 +562,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
} }
return (StringUtils.isNotBlank(type) && latestDate != null) return (StringUtils.isNotBlank(type) && latestDate != null)
? new TopAccountResult(type, latestDate) ? new TopAccountResult(type, latestDate, artifact)
: null; : null;
} }
@ -690,7 +700,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
programName, programName,
path, path,
longCount, longCount,
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME) DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME),
artifact
); );
} }
@ -786,11 +797,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()), res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()),
res -> res, res -> res,
(res1, res2) -> { (res1, res2) -> {
Long maxRunTimes = getMax(res1.getRunTimes(), res2.getRunTimes());
Date maxDate = getMax(res1.getLastAccessed(), res2.getLastAccessed());
TopProgramsResult maxResult = TOP_PROGRAMS_RESULT_COMPARE.compare(res1, res2) >= 0 ? res1 : res2;
return new TopProgramsResult( return new TopProgramsResult(
res1.getProgramName(), maxResult.getProgramName(),
res1.getProgramPath(), maxResult.getProgramPath(),
getMax(res1.getRunTimes(), res2.getRunTimes()), maxRunTimes,
getMax(res1.getLastRun(), res2.getLastRun())); maxDate,
maxResult.getArtifact());
})).values(); })).values();
List<TopProgramsResult> orderedResults = results.stream() List<TopProgramsResult> orderedResults = results.stream()
@ -803,7 +818,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
// if run times / last run information is available, the first item should have some value, // if run times / last run information is available, the first item should have some value,
// and then the items should be limited accordingly. // and then the items should be limited accordingly.
if (isPositiveNum(topResult.getRunTimes()) if (isPositiveNum(topResult.getRunTimes())
|| (topResult.getLastRun() != null && isPositiveNum(topResult.getLastRun().getTime()))) { || (topResult.getLastAccessed() != null && isPositiveNum(topResult.getLastAccessed().getTime()))) {
return orderedResults.stream().limit(count).collect(Collectors.toList()); return orderedResults.stream().limit(count).collect(Collectors.toList());
} }
} }
@ -812,13 +827,47 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return orderedResults; return orderedResults;
} }
/**
* Base class including date of last access and the relevant blackboard
* artifact.
*/
public static class LastAccessedArtifact {
private final Date lastAccessed;
private final BlackboardArtifact artifact;
/**
* Main constructor.
*
* @param lastAccessed The date of last access.
* @param artifact The relevant blackboard artifact.
*/
public LastAccessedArtifact(Date lastAccessed, BlackboardArtifact artifact) {
this.lastAccessed = lastAccessed;
this.artifact = artifact;
}
/**
* @return The date of last access.
*/
public Date getLastAccessed() {
return lastAccessed;
}
/**
* @return The associated artifact.
*/
public BlackboardArtifact getArtifact() {
return artifact;
}
}
/** /**
* Object containing information about a web search artifact. * Object containing information about a web search artifact.
*/ */
public static class TopWebSearchResult { public static class TopWebSearchResult extends LastAccessedArtifact {
private final String searchString; private final String searchString;
private final Date dateAccessed;
private String translatedResult; private String translatedResult;
/** /**
@ -826,10 +875,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* *
* @param searchString The search string. * @param searchString The search string.
* @param dateAccessed The latest date searched. * @param dateAccessed The latest date searched.
* @param artifact The relevant blackboard artifact.
*/ */
public TopWebSearchResult(String searchString, Date dateAccessed) { public TopWebSearchResult(String searchString, Date dateAccessed, BlackboardArtifact artifact) {
super(dateAccessed, artifact);
this.searchString = searchString; this.searchString = searchString;
this.dateAccessed = dateAccessed;
} }
/** /**
@ -854,22 +904,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public String getSearchString() { public String getSearchString() {
return searchString; return searchString;
} }
/**
* @return The date for the search.
*/
public Date getDateAccessed() {
return dateAccessed;
}
} }
/** /**
* A record of a device attached. * A record of a device attached.
*/ */
public static class TopDeviceAttachedResult { public static class TopDeviceAttachedResult extends LastAccessedArtifact {
private final String deviceId; private final String deviceId;
private final Date dateAccessed;
private final String deviceMake; private final String deviceMake;
private final String deviceModel; private final String deviceModel;
@ -880,10 +922,11 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param dateAccessed The date last attached. * @param dateAccessed The date last attached.
* @param deviceMake The device make. * @param deviceMake The device make.
* @param deviceModel The device model. * @param deviceModel The device model.
* @param artifact The relevant blackboard artifact.
*/ */
public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) { public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel, BlackboardArtifact artifact) {
super(dateAccessed, artifact);
this.deviceId = deviceId; this.deviceId = deviceId;
this.dateAccessed = dateAccessed;
this.deviceMake = deviceMake; this.deviceMake = deviceMake;
this.deviceModel = deviceModel; this.deviceModel = deviceModel;
} }
@ -895,13 +938,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return deviceId; return deviceId;
} }
/**
* @return The date last attached.
*/
public Date getDateAccessed() {
return dateAccessed;
}
/** /**
* @return The device make. * @return The device make.
*/ */
@ -921,20 +957,20 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* A record of an account and the last time it was used determined by * A record of an account and the last time it was used determined by
* messages. * messages.
*/ */
public static class TopAccountResult { public static class TopAccountResult extends LastAccessedArtifact {
private final String accountType; private final String accountType;
private final Date lastAccess;
/** /**
* Main constructor. * Main constructor.
* *
* @param accountType The account type. * @param accountType The account type.
* @param lastAccess The date the account was last accessed. * @param lastAccess The date the account was last accessed.
* @param artifact The artifact indicating last access.
*/ */
public TopAccountResult(String accountType, Date lastAccess) { public TopAccountResult(String accountType, Date lastAccess, BlackboardArtifact artifact) {
super(lastAccess, artifact);
this.accountType = accountType; this.accountType = accountType;
this.lastAccess = lastAccess;
} }
/** /**
@ -943,23 +979,15 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public String getAccountType() { public String getAccountType() {
return accountType; return accountType;
} }
/**
* @return The date the account was last accessed.
*/
public Date getLastAccess() {
return lastAccess;
}
} }
/** /**
* Describes a result of a program run on a datasource. * Describes a result of a program run on a datasource.
*/ */
public static class TopDomainsResult { public static class TopDomainsResult extends LastAccessedArtifact {
private final String domain; private final String domain;
private final Long visitTimes; private final Long visitTimes;
private final Date lastVisit;
/** /**
* Describes a top domain result. * Describes a top domain result.
@ -967,11 +995,12 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param domain The domain. * @param domain The domain.
* @param visitTimes The number of times it was visited. * @param visitTimes The number of times it was visited.
* @param lastVisit The date of the last visit. * @param lastVisit The date of the last visit.
* @param artifact The relevant blackboard artifact.
*/ */
public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) { public TopDomainsResult(String domain, Long visitTimes, Date lastVisit, BlackboardArtifact artifact) {
super(lastVisit, artifact);
this.domain = domain; this.domain = domain;
this.visitTimes = visitTimes; this.visitTimes = visitTimes;
this.lastVisit = lastVisit;
} }
/** /**
@ -987,24 +1016,16 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public Long getVisitTimes() { public Long getVisitTimes() {
return visitTimes; return visitTimes;
} }
/**
* @return The date of the last visit.
*/
public Date getLastVisit() {
return lastVisit;
}
} }
/** /**
* Describes a result of a program run on a datasource. * Describes a result of a program run on a datasource.
*/ */
public static class TopProgramsResult { public static class TopProgramsResult extends LastAccessedArtifact {
private final String programName; private final String programName;
private final String programPath; private final String programPath;
private final Long runTimes; private final Long runTimes;
private final Date lastRun;
/** /**
* Main constructor. * Main constructor.
@ -1012,12 +1033,13 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param programName The name of the program. * @param programName The name of the program.
* @param programPath The path of the program. * @param programPath The path of the program.
* @param runTimes The number of runs. * @param runTimes The number of runs.
* @param artifact The relevant blackboard artifact.
*/ */
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) { TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun, BlackboardArtifact artifact) {
super(lastRun, artifact);
this.programName = programName; this.programName = programName;
this.programPath = programPath; this.programPath = programPath;
this.runTimes = runTimes; this.runTimes = runTimes;
this.lastRun = lastRun;
} }
/** /**
@ -1040,12 +1062,5 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
public Long getRunTimes() { public Long getRunTimes() {
return runTimes; return runTimes;
} }
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
} }
} }

View File

@ -19,20 +19,29 @@
package org.sleuthkit.autopsy.datasourcesummary.ui; package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
@ -41,6 +50,8 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.SwingWorkerSequentialExecutor;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor;
import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent; import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -53,6 +64,8 @@ import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Base class from which other tabs in data source summary derive. * Base class from which other tabs in data source summary derive.
*/ */
@Messages({"BaseDataSourceSummaryPanel_goToArtifact=View Source Result",
"BaseDataSourceSummaryPanel_goToFile=View Source File in Directory"})
abstract class BaseDataSourceSummaryPanel extends JPanel { abstract class BaseDataSourceSummaryPanel extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -64,6 +77,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
private final EventUpdateHandler updateHandler; private final EventUpdateHandler updateHandler;
private final List<UpdateGovernor> governors; private final List<UpdateGovernor> governors;
private Runnable notifyParentClose = null;
private DataSource dataSource; private DataSource dataSource;
/** /**
@ -77,7 +92,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* Checks to see if artifact is from a datasource. * Checks to see if artifact is from a datasource.
* *
* @param art The artifact. * @param art The artifact.
* @param ds The datasource. * @param ds The datasource.
* *
* @return True if in datasource; false if not or exception. * @return True if in datasource; false if not or exception.
*/ */
@ -219,7 +234,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* Main constructor. * Main constructor.
* *
* @param governors The items governing when this panel should receive * @param governors The items governing when this panel should receive
* updates. * updates.
*/ */
protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) { protected BaseDataSourceSummaryPanel(UpdateGovernor... governors) {
this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors); this.governors = (governors == null) ? Collections.emptyList() : Arrays.asList(governors);
@ -227,6 +242,104 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
this.updateHandler.register(); this.updateHandler.register();
} }
/**
* Given the relevant artifact, navigates to the artifact in the tree and
* closes data source summary dialog if open.
*
* @param artifact The artifact.
* @return The menu item for a go to artifact menu item.
*/
protected CellModelTableCellRenderer.MenuItem getArtifactNavigateItem(BlackboardArtifact artifact) {
if (artifact == null) {
return null;
}
return new CellModelTableCellRenderer.DefaultMenuItem(
Bundle.BaseDataSourceSummaryPanel_goToArtifact(),
() -> {
final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
// Navigate to the source context artifact.
if (dtc != null && artifact != null) {
dtc.viewArtifact(artifact);
}
notifyParentClose();
});
}
private static final Pattern windowsDrivePattern = Pattern.compile("^[A-Za-z]\\:(.*)$");
/**
* Normalizes the path for lookup in the sleuthkit database (unix endings;
* remove C:\).
*
* @param path The path to normalize.
* @return The normalized path.
*/
private String normalizePath(String path) {
if (path == null) {
return null;
}
String trimmed = path.trim();
Matcher match = windowsDrivePattern.matcher(trimmed);
if (match.find()) {
return FilenameUtils.normalize(match.group(1), true);
} else {
return FilenameUtils.normalize(trimmed, true);
}
}
/**
* Creates a menu item to navigate to a file.
*
* @param path The path to the file.
* @return The menu item or null if file cannot be found in data source.
*/
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(String path) {
if (StringUtils.isNotBlank(path)) {
Path p = Paths.get(path);
String fileName = normalizePath(p.getFileName().toString());
String directory = normalizePath(p.getParent().toString());
if (fileName != null && directory != null) {
try {
List<AbstractFile> files = Case.getCurrentCaseThrows().getSleuthkitCase().findFiles(getDataSource(), fileName, directory);
if (CollectionUtils.isNotEmpty(files)) {
return getFileNavigateItem(files.get(0));
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "There was an error fetching file for path: " + path, ex);
}
}
}
return null;
}
/**
* Given the relevant artifact, navigates to the artifact's content in the
* tree and closes data source summary dialog if open.
*
* @param artifact The artifact.
* @return The menu item list for a go to artifact menu item.
*/
protected CellModelTableCellRenderer.MenuItem getFileNavigateItem(AbstractFile file) {
if (file == null) {
return null;
}
return new CellModelTableCellRenderer.DefaultMenuItem(
Bundle.BaseDataSourceSummaryPanel_goToFile(),
() -> {
new ViewContextAction(Bundle.BaseDataSourceSummaryPanel_goToFile(), file)
.actionPerformed(null);
notifyParentClose();
});
}
/** /**
* Closes listeners and resources. * Closes listeners and resources.
*/ */
@ -246,6 +359,24 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
onNewDataSource(this.dataSource); onNewDataSource(this.dataSource);
} }
/**
* Sends event that parent should close.
*/
protected void notifyParentClose() {
if (notifyParentClose != null) {
notifyParentClose.run();
}
}
/**
* Sets the listener for parent close events.
*
* @param action The action to run when parent is to close.
*/
void setParentCloseListener(Runnable action) {
notifyParentClose = action;
}
/** /**
* @return The current data source. * @return The current data source.
*/ */
@ -287,7 +418,7 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* argument and data fetch components and then submits them to run. * argument and data fetch components and then submits them to run.
* *
* @param dataFetchComponents The components to be run. * @param dataFetchComponents The components to be run.
* @param dataSource The data source argument. * @param dataSource The data source argument.
*/ */
protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) { protected void fetchInformation(List<DataFetchComponents<DataSource, ?>> dataFetchComponents, DataSource dataSource) {
if (dataSource == null || !Case.isCaseOpen()) { if (dataSource == null || !Case.isCaseOpen()) {
@ -320,8 +451,8 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* argument and submits them to be executed. * argument and submits them to be executed.
* *
* @param dataFetchComponents The components to register. * @param dataFetchComponents The components to register.
* @param loadableComponents The components to set to a loading screen. * @param loadableComponents The components to set to a loading screen.
* @param dataSource The data source argument. * @param dataSource The data source argument.
*/ */
protected void onNewDataSource( protected void onNewDataSource(
List<DataFetchComponents<DataSource, ?>> dataFetchComponents, List<DataFetchComponents<DataSource, ?>> dataFetchComponents,
@ -371,12 +502,11 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* message indicating the unrun ingest module will be shown. Otherwise, the * message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used. * default LoadableComponent.showDataFetchResult behavior will be used.
* *
* @param component The component. * @param component The component.
* @param result The data result. * @param result The data result.
* @param factoryClass The fully qualified class name of the relevant * @param factoryClass The fully qualified class name of the relevant
* factory. * factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword * @param moduleName The name of the ingest module (i.e. 'Keyword Search').
* Search').
*/ */
protected <T> void showResultWithModuleCheck(LoadableComponent<List<T>> component, DataFetchResult<List<T>> result, String factoryClass, String moduleName) { protected <T> void showResultWithModuleCheck(LoadableComponent<List<T>> component, DataFetchResult<List<T>> result, String factoryClass, String moduleName) {
Predicate<List<T>> hasResults = (lst) -> lst != null && !lst.isEmpty(); Predicate<List<T>> hasResults = (lst) -> lst != null && !lst.isEmpty();
@ -389,14 +519,13 @@ abstract class BaseDataSourceSummaryPanel extends JPanel {
* message indicating the unrun ingest module will be shown. Otherwise, the * message indicating the unrun ingest module will be shown. Otherwise, the
* default LoadableComponent.showDataFetchResult behavior will be used. * default LoadableComponent.showDataFetchResult behavior will be used.
* *
* @param component The component. * @param component The component.
* @param result The data result. * @param result The data result.
* @param hasResults Given the data type, will provide whether or not the * @param hasResults Given the data type, will provide whether or not the
* data contains any actual results. * data contains any actual results.
* @param factoryClass The fully qualified class name of the relevant * @param factoryClass The fully qualified class name of the relevant
* factory. * factory.
* @param moduleName The name of the ingest module (i.e. 'Keyword * @param moduleName The name of the ingest module (i.e. 'Keyword Search').
* Search').
*/ */
protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result, protected <T> void showResultWithModuleCheck(LoadableComponent<T> component, DataFetchResult<T> result,
Predicate<T> hasResults, String factoryClass, String moduleName) { Predicate<T> hasResults, String factoryClass, String moduleName) {

View File

@ -43,3 +43,12 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
TimelinePanel.activityRangeLabel.text=Activity Range TimelinePanel.activityRangeLabel.text=Activity Range
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
TimelinePanel.viewInTimelineBtn.text=View in Timeline

View File

@ -3,6 +3,8 @@ AnalysisPanel_keyColumn_title=Name
AnalysisPanel_keywordSearchModuleName=Keyword Search AnalysisPanel_keywordSearchModuleName=Keyword Search
# {0} - module name # {0} - module name
BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source. BaseDataSourceSummaryPanel_defaultNotIngestMessage=The {0} ingest module has not been run on this data source.
BaseDataSourceSummaryPanel_goToArtifact=View Source Result
BaseDataSourceSummaryPanel_goToFile=View Source File in Directory
ContainerPanel_setFieldsForNonImageDataSource_na=N/A ContainerPanel_setFieldsForNonImageDataSource_na=N/A
CTL_DataSourceSummaryAction=Data Source Summary CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryDialog.closeButton.text=Close DataSourceSummaryDialog.closeButton.text=Close
@ -103,6 +105,15 @@ PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected. DataSourceSummaryTabbedPane.noDataSourceLabel.text=No data source has been selected.
TimelinePanel.activityRangeLabel.text=Activity Range TimelinePanel.activityRangeLabel.text=Activity Range
RecentFilesPanel.rightClickForMoreOptions1.text=Right click on row for more options
RecentFilesPanel.rightClickForMoreOptions2.text=Right click on row for more options
RecentFilesPanel.rightClickForMoreOptions3.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions1.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions2.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions3.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions4.text=Right click on row for more options
UserActivityPanel.rightClickForMoreOptions5.text=Right click on row for more options
TimelinePanel.viewInTimelineBtn.text=View in Timeline
UserActivityPanel_noDataExists=No communication data exists UserActivityPanel_noDataExists=No communication data exists
UserActivityPanel_tab_title=User Activity UserActivityPanel_tab_title=User Activity
UserActivityPanel_TopAccountTableModel_accountType_header=Account Type UserActivityPanel_TopAccountTableModel_accountType_header=Account Type

View File

@ -61,6 +61,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles(); Map<Long, Long> fileCountsMap = CaseDataSourcesSummary.getCountsOfFiles();
dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap); dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane(); dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
dataSourceSummaryTabbedPane.setParentCloseListener(() -> DataSourceSummaryDialog.this.dispose());
initComponents(); initComponents();
dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel); dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> { dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
@ -169,7 +170,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
* source matches the dataSourceID it will select the first datasource. * source matches the dataSourceID it will select the first datasource.
* *
* @param dataSourceID the ID of the datasource to select, null will cause * @param dataSourceID the ID of the datasource to select, null will cause
* the first datasource to be selected * the first datasource to be selected
*/ */
void selectDataSource(Long dataSourceId) { void selectDataSource(Long dataSourceId) {
dataSourcesPanel.selectDataSource(dataSourceId); dataSourcesPanel.selectDataSource(dataSourceId);

View File

@ -47,7 +47,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* Records of tab information (i.e. title, component, function to call on * Records of tab information (i.e. title, component, function to call on
* new data source). * new data source).
*/ */
private static class DataSourceTab { private class DataSourceTab {
private final String tabTitle; private final String tabTitle;
private final Component component; private final Component component;
@ -74,8 +74,12 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
* *
* @param tabTitle The title of the tab. * @param tabTitle The title of the tab.
* @param panel The component to be displayed in the tab. * @param panel The component to be displayed in the tab.
* @param notifyParentClose Notifies parent to trigger a close.
*/ */
DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) { DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) {
panel.setParentCloseListener(() -> notifyParentClose());
this.tabTitle = tabTitle; this.tabTitle = tabTitle;
this.component = panel; this.component = panel;
this.onDataSource = panel::setDataSource; this.onDataSource = panel::setDataSource;
@ -116,6 +120,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
private static final String TABBED_PANE = "tabbedPane"; private static final String TABBED_PANE = "tabbedPane";
private static final String NO_DATASOURCE_PANE = "noDataSourcePane"; private static final String NO_DATASOURCE_PANE = "noDataSourcePane";
private Runnable notifyParentClose = null;
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel(); private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
private final List<DataSourceTab> tabs = Arrays.asList( private final List<DataSourceTab> tabs = Arrays.asList(
@ -142,6 +147,24 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel {
postInit(); postInit();
} }
/**
* Sends event that parent should close.
*/
private void notifyParentClose() {
if (notifyParentClose != null) {
notifyParentClose.run();
}
}
/**
* Sets the listener for parent close events.
*
* @param parentCloseAction The observer.
*/
void setParentCloseListener(Runnable parentCloseAction) {
notifyParentClose = parentCloseAction;
}
/** /**
* Method called right after initComponents during initialization. * Method called right after initComponents during initialization.
*/ */

View File

@ -94,7 +94,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/> <GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
@ -106,7 +106,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/> <GridBagConstraints gridX="0" gridY="8" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="2" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="1.0" weightY="1.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
@ -140,7 +140,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/> <GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
</Component> </Component>
@ -156,7 +156,55 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="5" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/> <GridBagConstraints gridX="0" gridY="7" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="20" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="6" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="RecentFilesPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="9" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
</Component> </Component>

View File

@ -21,13 +21,16 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails; import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
@ -77,6 +80,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
initalizeTables(); initalizeTables();
} }
/**
* Takes a base class of RecentFileDetails and provides the pertinent menu
* items.
*
* @param record The RecentFileDetails instance.
* @return The menu items list containing one action or navigating to the
* appropriate artifact/file and closing the data source summary dialog if
* open.
*/
private Supplier<List<MenuItem>> getPopupFunct(RecentFileDetails record) {
return () -> {
if (record == null) {
return null;
}
List<MenuItem> toRet = new ArrayList<>();
MenuItem fileNav = getFileNavigateItem(record.getPath());
if (fileNav != null) {
toRet.add(fileNav);
}
if (record.getArtifact() != null) {
toRet.add(getArtifactNavigateItem(record.getArtifact()));
}
return (toRet.size() > 0) ? toRet : null;
};
}
@Override @Override
protected void fetchInformation(DataSource dataSource) { protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource); fetchInformation(dataFetchComponents, dataSource);
@ -113,11 +146,13 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentFileDetails>> list = Arrays.asList( List<ColumnModel<RecentFileDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80)); }, 80));
ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentFileDetails> tableModel = JTablePanel.getTableModel(list);
@ -126,6 +161,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setKeyFunction((recentFile) -> recentFile.getPath()); pane.setKeyFunction((recentFile) -> recentFile.getPath());
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker DataFetchWorker.DataFetchComponents<DataSource, List<RecentFileDetails>> worker
@ -148,15 +184,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList( List<ColumnModel<RecentDownloadDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_domain(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getWebDomain()); return new DefaultCellModel(prog.getWebDomain())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 100), }, 100),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80)); }, 80));
ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentDownloadDetails> tableModel = JTablePanel.getTableModel(list);
@ -165,6 +204,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setKeyFunction((download) -> download.getPath()); pane.setKeyFunction((download) -> download.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker DataFetchWorker.DataFetchComponents<DataSource, List<RecentDownloadDetails>> worker
@ -187,15 +227,18 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList( List<ColumnModel<RecentAttachmentDetails>> list = Arrays.asList(
new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_path(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getPath()); return new DefaultCellModel(prog.getPath())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 250), }, 250),
new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(), new ColumnModel<>(Bundle.RecentFilesPanel_col_head_date(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getDateAsString()); return new DefaultCellModel(prog.getDateAsString())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 80), }, 80),
new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(), new ColumnModel<>(Bundle.RecentFilePanel_col_header_sender(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getSender()); return new DefaultCellModel(prog.getSender())
.setPopupMenuRetriever(getPopupFunct(prog));
}, 150)); }, 150));
ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list); ListTableModel<RecentAttachmentDetails> tableModel = JTablePanel.getTableModel(list);
@ -204,6 +247,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
pane.setModel(tableModel); pane.setModel(tableModel);
pane.setKeyFunction((attachment) -> attachment.getPath()); pane.setKeyFunction((attachment) -> attachment.getPath());
pane.setColumnModel(JTablePanel.getTableColumnModel(list)); pane.setColumnModel(JTablePanel.getTableColumnModel(list));
pane.setCellListener(CellModelTableCellRenderer.getMouseListener());
tablePanelList.add(pane); tablePanelList.add(pane);
DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker DataFetchWorker.DataFetchComponents<DataSource, List<RecentAttachmentDetails>> worker
@ -234,6 +278,9 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
javax.swing.JLabel openDocsLabel = new javax.swing.JLabel(); javax.swing.JLabel openDocsLabel = new javax.swing.JLabel();
javax.swing.JLabel downloadLabel = new javax.swing.JLabel(); javax.swing.JLabel downloadLabel = new javax.swing.JLabel();
javax.swing.JLabel attachmentLabel = new javax.swing.JLabel(); javax.swing.JLabel attachmentLabel = new javax.swing.JLabel();
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
setLayout(new java.awt.BorderLayout()); setLayout(new java.awt.BorderLayout());
@ -263,7 +310,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
tablePanel.add(openedDocPane, gridBagConstraints); tablePanel.add(openedDocPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4; gridBagConstraints.gridy = 5;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0; gridBagConstraints.weightx = 1.0;
@ -272,7 +319,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
tablePanel.add(downloadsPane, gridBagConstraints); tablePanel.add(downloadsPane, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 6; gridBagConstraints.gridy = 8;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.weightx = 1.0; gridBagConstraints.weightx = 1.0;
@ -291,7 +338,7 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(downloadLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.downloadLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3; gridBagConstraints.gridy = 4;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0); gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(downloadLabel, gridBagConstraints); tablePanel.add(downloadLabel, gridBagConstraints);
@ -299,12 +346,36 @@ public final class RecentFilesPanel extends BaseDataSourceSummaryPanel {
org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(attachmentLabel, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.attachmentLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 5; gridBagConstraints.gridy = 7;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0); gridBagConstraints.insets = new java.awt.Insets(20, 0, 0, 0);
tablePanel.add(attachmentLabel, gridBagConstraints); tablePanel.add(attachmentLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions1.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
tablePanel.add(rightClickForMoreOptions1, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions2.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 6;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
tablePanel.add(rightClickForMoreOptions2, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(RecentFilesPanel.class, "RecentFilesPanel.rightClickForMoreOptions3.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 9;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
tablePanel.add(rightClickForMoreOptions3, gridBagConstraints);
scrollPane.setViewportView(tablePanel); scrollPane.setViewportView(tablePanel);
add(scrollPane, java.awt.BorderLayout.CENTER); add(scrollPane, java.awt.BorderLayout.CENTER);

View File

@ -171,7 +171,7 @@
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/> <AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalStrut"/>
</AuxValues> </AuxValues>
</Component> </Component>
<Container class="javax.swing.JPanel" name="sameIdPanel"> <Container class="javax.swing.JPanel" name="last30DaysPanel">
<Properties> <Properties>
<Property name="alignmentX" type="float" value="0.0"/> <Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -193,6 +193,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JButton" name="viewInTimelineBtn">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="TimelinePanel.viewInTimelineBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="viewInTimelineBtnActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.Box$Filler" name="filler5"> <Component class="javax.swing.Box$Filler" name="filler5">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">

View File

@ -26,8 +26,15 @@ import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.logging.Level;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineDataSourceUtils;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.DailyActivityAmount;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TimelineSummary.TimelineSummaryData;
@ -41,7 +48,11 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetch
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel;
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineModule;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* A tab shown in data source summary displaying information about a data * A tab shown in data source summary displaying information about a data
@ -54,7 +65,8 @@ import org.sleuthkit.datamodel.DataSource;
"TimlinePanel_last30DaysChart_fileEvts_title=File Events", "TimlinePanel_last30DaysChart_fileEvts_title=File Events",
"TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",}) "TimlinePanel_last30DaysChart_artifactEvts_title=Artifact Events",})
public class TimelinePanel extends BaseDataSourceSummaryPanel { public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static final Logger logger = Logger.getLogger(TimelinePanel.class.getName());
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy"); private static final DateFormat EARLIEST_LATEST_FORMAT = getUtcFormat("MMM d, yyyy");
private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d"); private static final DateFormat CHART_FORMAT = getUtcFormat("MMM d");
@ -75,13 +87,14 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title()); private final LoadableLabel earliestLabel = new LoadableLabel(Bundle.TimelinePanel_earliestLabel_title());
private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title()); private final LoadableLabel latestLabel = new LoadableLabel(Bundle.TimelinePanel_latestLabel_title());
private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", ""); private final BarChartPanel last30DaysChart = new BarChartPanel(Bundle.TimlinePanel_last30DaysChart_title(), "", "");
private final TimelineDataSourceUtils timelineUtils = TimelineDataSourceUtils.getInstance();
// all loadable components on this tab // all loadable components on this tab
private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart); private final List<LoadableComponent<?>> loadableComponents = Arrays.asList(earliestLabel, latestLabel, last30DaysChart);
// actions to load data for this tab // actions to load data for this tab
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents; private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
public TimelinePanel() { public TimelinePanel() {
this(new TimelineSummary()); this(new TimelineSummary());
} }
@ -96,7 +109,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
(dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT), (dataSource) -> timelineData.getData(dataSource, MOST_RECENT_DAYS_COUNT),
(result) -> handleResult(result)) (result) -> handleResult(result))
); );
initComponents(); initComponents();
} }
@ -112,7 +125,7 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
private static String formatDate(Date date, DateFormat formatter) { private static String formatDate(Date date, DateFormat formatter) {
return date == null ? null : formatter.format(date); return date == null ? null : formatter.format(date);
} }
private static final Color FILE_EVT_COLOR = new Color(228, 22, 28); private static final Color FILE_EVT_COLOR = new Color(228, 22, 28);
private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100); private static final Color ARTIFACT_EVT_COLOR = new Color(21, 227, 100);
@ -132,24 +145,27 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
// Create a bar chart item for each recent days activity item // Create a bar chart item for each recent days activity item
List<BarChartItem> fileEvtCounts = new ArrayList<>(); List<BarChartItem> fileEvtCounts = new ArrayList<>();
List<BarChartItem> artifactEvtCounts = new ArrayList<>(); List<BarChartItem> artifactEvtCounts = new ArrayList<>();
for (int i = 0; i < recentDaysActivity.size(); i++) { for (int i = 0; i < recentDaysActivity.size(); i++) {
DailyActivityAmount curItem = recentDaysActivity.get(i); DailyActivityAmount curItem = recentDaysActivity.get(i);
long fileAmt = curItem.getFileActivityCount(); long fileAmt = curItem.getFileActivityCount();
long artifactAmt = curItem.getArtifactActivityCount() * 100; long artifactAmt = curItem.getArtifactActivityCount() * 100;
String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1) String formattedDate = (i == 0 || i == recentDaysActivity.size() - 1)
? formatDate(curItem.getDay(), CHART_FORMAT) : ""; ? formatDate(curItem.getDay(), CHART_FORMAT) : "";
OrderedKey thisKey = new OrderedKey(formattedDate, i); OrderedKey thisKey = new OrderedKey(formattedDate, i);
fileEvtCounts.add(new BarChartItem(thisKey, fileAmt)); fileEvtCounts.add(new BarChartItem(thisKey, fileAmt));
artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt)); artifactEvtCounts.add(new BarChartItem(thisKey, artifactAmt));
} }
return Arrays.asList( return Arrays.asList(
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts), new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_fileEvts_title(), FILE_EVT_COLOR, fileEvtCounts),
new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts)); new BarChartSeries(Bundle.TimlinePanel_last30DaysChart_artifactEvts_title(), ARTIFACT_EVT_COLOR, artifactEvtCounts));
} }
private final Object timelineBtnLock = new Object();
private TimelineSummaryData curTimelineData = null;
/** /**
* Handles displaying the result for each displayable item in the * Handles displaying the result for each displayable item in the
@ -163,18 +179,99 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT))); earliestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMinDate(), EARLIEST_LATEST_FORMAT)));
latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT))); latestLabel.showDataFetchResult(DataFetchResult.getSubResult(result, r -> formatDate(r.getMaxDate(), EARLIEST_LATEST_FORMAT)));
last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity()))); last30DaysChart.showDataFetchResult(DataFetchResult.getSubResult(result, r -> parseChartData(r.getMostRecentDaysActivity())));
if (result != null
&& result.getResultType() == DataFetchResult.ResultType.SUCCESS
&& result.getData() != null) {
synchronized (this.timelineBtnLock) {
this.curTimelineData = result.getData();
this.viewInTimelineBtn.setEnabled(true);
}
} else {
synchronized (this.timelineBtnLock) {
this.viewInTimelineBtn.setEnabled(false);
}
}
} }
/**
* Action that occurs when 'View in Timeline' button is pressed.
*/
private void openFilteredChart() {
DataSource dataSource = null;
Date minDate = null;
Date maxDate = null;
// get date from current timelineData if that data exists.
synchronized (this.timelineBtnLock) {
if (curTimelineData == null) {
return;
}
dataSource = curTimelineData.getDataSource();
if (CollectionUtils.isNotEmpty(curTimelineData.getMostRecentDaysActivity())) {
minDate = curTimelineData.getMostRecentDaysActivity().get(0).getDay();
maxDate = curTimelineData.getMostRecentDaysActivity().get(curTimelineData.getMostRecentDaysActivity().size() - 1).getDay();
// set outer bound to end of day instead of beginning
if (maxDate != null) {
maxDate = new Date(maxDate.getTime() + 1000 * 60 * 60 * 24);
}
}
}
openFilteredChart(dataSource, minDate, maxDate);
}
/**
* Action that occurs when 'View in Timeline' button is pressed.
*
* @param dataSource The data source to filter to.
* @param minDate The min date for the zoom of the window.
* @param maxDate The max date for the zoom of the window.
*/
private void openFilteredChart(DataSource dataSource, Date minDate, Date maxDate) {
OpenTimelineAction openTimelineAction = CallableSystemAction.get(OpenTimelineAction.class);
if (openTimelineAction == null) {
logger.log(Level.WARNING, "No OpenTimelineAction provided by CallableSystemAction; taking no redirect action.");
}
// notify dialog (if in dialog) should close.
TimelinePanel.this.notifyParentClose();
Interval timeSpan = null;
try {
final TimeLineController controller = TimeLineModule.getController();
if (dataSource != null) {
controller.pushFilters(timelineUtils.getDataSourceFilterState(dataSource));
}
if (minDate != null && maxDate != null) {
timeSpan = new Interval(new DateTime(minDate), new DateTime(maxDate));
}
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "Unable to view time range in Timeline view", ex);
}
try {
openTimelineAction.showTimeline(timeSpan);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "An unexpected exception occurred while opening the timeline.", ex);
}
}
@Override @Override
protected void fetchInformation(DataSource dataSource) { protected void fetchInformation(DataSource dataSource) {
fetchInformation(dataFetchComponents, dataSource); fetchInformation(dataFetchComponents, dataSource);
} }
@Override @Override
protected void onNewDataSource(DataSource dataSource) { protected void onNewDataSource(DataSource dataSource) {
onNewDataSource(dataFetchComponents, loadableComponents, dataSource); onNewDataSource(dataFetchComponents, loadableComponents, dataSource);
} }
@Override @Override
public void close() { public void close() {
ingestRunningLabel.unregister(); ingestRunningLabel.unregister();
@ -198,7 +295,8 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
javax.swing.JPanel earliestLabelPanel = earliestLabel; javax.swing.JPanel earliestLabelPanel = earliestLabel;
javax.swing.JPanel latestLabelPanel = latestLabel; javax.swing.JPanel latestLabelPanel = latestLabel;
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JPanel sameIdPanel = last30DaysChart; javax.swing.JPanel last30DaysPanel = last30DaysChart;
viewInTimelineBtn = new javax.swing.JButton();
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
@ -233,12 +331,21 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
filler2.setAlignmentX(0.0F); filler2.setAlignmentX(0.0F);
mainContentPanel.add(filler2); mainContentPanel.add(filler2);
sameIdPanel.setAlignmentX(0.0F); last30DaysPanel.setAlignmentX(0.0F);
sameIdPanel.setMaximumSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setMaximumSize(new java.awt.Dimension(600, 300));
sameIdPanel.setMinimumSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setMinimumSize(new java.awt.Dimension(600, 300));
sameIdPanel.setPreferredSize(new java.awt.Dimension(600, 300)); last30DaysPanel.setPreferredSize(new java.awt.Dimension(600, 300));
sameIdPanel.setVerifyInputWhenFocusTarget(false); last30DaysPanel.setVerifyInputWhenFocusTarget(false);
mainContentPanel.add(sameIdPanel); mainContentPanel.add(last30DaysPanel);
org.openide.awt.Mnemonics.setLocalizedText(viewInTimelineBtn, org.openide.util.NbBundle.getMessage(TimelinePanel.class, "TimelinePanel.viewInTimelineBtn.text")); // NOI18N
viewInTimelineBtn.setEnabled(false);
viewInTimelineBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
viewInTimelineBtnActionPerformed(evt);
}
});
mainContentPanel.add(viewInTimelineBtn);
filler5.setAlignmentX(0.0F); filler5.setAlignmentX(0.0F);
mainContentPanel.add(filler5); mainContentPanel.add(filler5);
@ -257,7 +364,11 @@ public class TimelinePanel extends BaseDataSourceSummaryPanel {
); );
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
private void viewInTimelineBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInTimelineBtnActionPerformed
openFilteredChart();
}//GEN-LAST:event_viewInTimelineBtnActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton viewInTimelineBtn;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -135,6 +135,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler3"> <Component class="javax.swing.Box$Filler" name="filler3">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -204,6 +215,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions2">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler4"> <Component class="javax.swing.Box$Filler" name="filler4">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -273,6 +295,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions3">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions3.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler6"> <Component class="javax.swing.Box$Filler" name="filler6">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -342,6 +375,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions4">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions4.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler8"> <Component class="javax.swing.Box$Filler" name="filler8">
<Properties> <Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -411,6 +455,17 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container> </Container>
<Component class="javax.swing.JLabel" name="rightClickForMoreOptions5">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="UserActivityPanel.rightClickForMoreOptions5.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
</SubComponents> </SubComponents>
</Container> </Container>
</SubComponents> </SubComponents>

View File

@ -29,12 +29,14 @@ import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil; import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.LastAccessedArtifact;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.MenuItem;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
@ -92,7 +94,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
Bundle.UserActivityPanel_TopProgramsTableModel_name_header(), Bundle.UserActivityPanel_TopProgramsTableModel_name_header(),
(prog) -> { (prog) -> {
return new DefaultCellModel(prog.getProgramName()) return new DefaultCellModel(prog.getProgramName())
.setTooltip(prog.getProgramPath()); .setTooltip(prog.getProgramPath())
.setPopupMenu(getPopup(prog));
}, },
250), 250),
// program folder column // program folder column
@ -103,7 +106,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
getShortFolderName( getShortFolderName(
prog.getProgramPath(), prog.getProgramPath(),
prog.getProgramName())) prog.getProgramName()))
.setTooltip(prog.getProgramPath()); .setTooltip(prog.getProgramPath())
.setPopupMenu(getPopup(prog));
}, },
150), 150),
// run count column // run count column
@ -111,13 +115,17 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
Bundle.UserActivityPanel_TopProgramsTableModel_count_header(), Bundle.UserActivityPanel_TopProgramsTableModel_count_header(),
(prog) -> { (prog) -> {
String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()); String runTimes = prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes());
return new DefaultCellModel(runTimes); return new DefaultCellModel(runTimes)
.setPopupMenu(getPopup(prog));
}, },
80), 80),
// last run date column // last run date column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(), Bundle.UserActivityPanel_TopProgramsTableModel_lastrun_header(),
(prog) -> new DefaultCellModel(getFormatted(prog.getLastRun())), (prog) -> {
return new DefaultCellModel(getFormatted(prog.getLastAccessed()))
.setPopupMenu(getPopup(prog));
},
150) 150)
)) ))
.setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName()); .setKeyFunction((prog) -> prog.getProgramPath() + ":" + prog.getProgramName());
@ -127,20 +135,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// domain column // domain column
new ColumnModel<TopDomainsResult>( new ColumnModel<TopDomainsResult>(
Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(), Bundle.UserActivityPanel_TopDomainsTableModel_domain_header(),
(recentDomain) -> new DefaultCellModel(recentDomain.getDomain()), (recentDomain) -> {
return new DefaultCellModel(recentDomain.getDomain())
.setPopupMenu(getPopup(recentDomain));
},
250), 250),
// count column // count column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_count_header(), Bundle.UserActivityPanel_TopDomainsTableModel_count_header(),
(recentDomain) -> { (recentDomain) -> {
String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes()); String visitTimes = recentDomain.getVisitTimes() == null ? "" : Long.toString(recentDomain.getVisitTimes());
return new DefaultCellModel(visitTimes); return new DefaultCellModel(visitTimes)
.setPopupMenu(getPopup(recentDomain));
}, },
100), 100),
// last accessed column // last accessed column
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(), Bundle.UserActivityPanel_TopDomainsTableModel_lastAccess_header(),
(recentDomain) -> new DefaultCellModel(getFormatted(recentDomain.getLastVisit())), (recentDomain) -> {
return new DefaultCellModel(getFormatted(recentDomain.getLastAccessed()))
.setPopupMenu(getPopup(recentDomain));
},
150) 150)
)) ))
.setKeyFunction((domain) -> domain.getDomain()); .setKeyFunction((domain) -> domain.getDomain());
@ -150,19 +165,28 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// search string column // search string column
new ColumnModel<TopWebSearchResult>( new ColumnModel<TopWebSearchResult>(
Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_searchString_header(),
(webSearch) -> new DefaultCellModel(webSearch.getSearchString()), (webSearch) -> {
return new DefaultCellModel(webSearch.getSearchString())
.setPopupMenu(getPopup(webSearch));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_dateAccessed_header(),
(webSearch) -> new DefaultCellModel(getFormatted(webSearch.getDateAccessed())), (webSearch) -> {
return new DefaultCellModel(getFormatted(webSearch.getLastAccessed()))
.setPopupMenu(getPopup(webSearch));
},
150 150
), ),
// translated value // translated value
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(), Bundle.UserActivityPanel_TopWebSearchTableModel_translatedResult_header(),
(webSearch) -> new DefaultCellModel(webSearch.getTranslatedResult()), (webSearch) -> {
return new DefaultCellModel(webSearch.getTranslatedResult())
.setPopupMenu(getPopup(webSearch));
},
250 250
) )
)) ))
@ -173,13 +197,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// device id column // device id column
new ColumnModel<TopDeviceAttachedResult>( new ColumnModel<TopDeviceAttachedResult>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(), Bundle.UserActivityPanel_TopDeviceAttachedTableModel_deviceId_header(),
(device) -> new DefaultCellModel(device.getDeviceId()), (device) -> {
return new DefaultCellModel(device.getDeviceId())
.setPopupMenu(getPopup(device));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(), Bundle.UserActivityPanel_TopDeviceAttachedTableModel_dateAccessed_header(),
(device) -> new DefaultCellModel(getFormatted(device.getDateAccessed())), (device) -> {
return new DefaultCellModel(getFormatted(device.getLastAccessed()))
.setPopupMenu(getPopup(device));
},
150 150
), ),
// make and model // make and model
@ -191,7 +221,8 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
String makeModelString = (make.isEmpty() || model.isEmpty()) String makeModelString = (make.isEmpty() || model.isEmpty())
? make + model ? make + model
: String.format("%s - %s", make, model); : String.format("%s - %s", make, model);
return new DefaultCellModel(makeModelString); return new DefaultCellModel(makeModelString)
.setPopupMenu(getPopup(device));
}, },
250 250
) )
@ -203,13 +234,19 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
// account type column // account type column
new ColumnModel<TopAccountResult>( new ColumnModel<TopAccountResult>(
Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(), Bundle.UserActivityPanel_TopAccountTableModel_accountType_header(),
(account) -> new DefaultCellModel(account.getAccountType()), (account) -> {
return new DefaultCellModel(account.getAccountType())
.setPopupMenu(getPopup(account));
},
250 250
), ),
// last accessed // last accessed
new ColumnModel<>( new ColumnModel<>(
Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(), Bundle.UserActivityPanel_TopAccountTableModel_lastAccess_header(),
(account) -> new DefaultCellModel(getFormatted(account.getLastAccess())), (account) -> {
return new DefaultCellModel(getFormatted(account.getLastAccessed()))
.setPopupMenu(getPopup(account));
},
150 150
) )
)) ))
@ -227,7 +264,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents; private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final UserActivitySummary userActivityData; private final UserActivitySummary userActivityData;
/** /**
* Creates a new UserActivityPanel. * Creates a new UserActivityPanel.
*/ */
@ -239,7 +276,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
* Creates a new UserActivityPanel. * Creates a new UserActivityPanel.
* *
* @param userActivityData Class from which to obtain remaining user * @param userActivityData Class from which to obtain remaining user
* activity data. * activity data.
*/ */
public UserActivityPanel(UserActivitySummary userActivityData) { public UserActivityPanel(UserActivitySummary userActivityData) {
super(userActivityData); super(userActivityData);
@ -292,10 +329,23 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
initComponents(); initComponents();
} }
/**
* Takes a base class of LastAccessedArtifact and provides the pertinent
* menu items. going to artifact.
*
* @param record The LastAccessedArtifact instance.
* @param navigateToArtifact Navigate right tot the artifact.
* @return The menu items list containing one action or navigating to the
* appropriate artifact and closing the data source summary dialog if open.
*/
private List<MenuItem> getPopup(LastAccessedArtifact record) {
return record == null ? null : Arrays.asList(getArtifactNavigateItem(record.getArtifact()));
}
/** /**
* Queries DataSourceTopProgramsSummary instance for short folder name. * Queries DataSourceTopProgramsSummary instance for short folder name.
* *
* @param path The path for the application. * @param path The path for the application.
* @param appName The application name. * @param appName The application name.
* *
* @return The underlying short folder name if one exists. * @return The underlying short folder name if one exists.
@ -335,22 +385,27 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel(); javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topProgramsTablePanel = topProgramsTable; javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
javax.swing.JLabel rightClickForMoreOptions1 = new javax.swing.JLabel();
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel(); javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable; javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable;
javax.swing.JLabel rightClickForMoreOptions2 = new javax.swing.JLabel();
javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel topWebSearchLabel = new javax.swing.JLabel(); javax.swing.JLabel topWebSearchLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topWebSearches = topWebSearchesTable; javax.swing.JPanel topWebSearches = topWebSearchesTable;
javax.swing.JLabel rightClickForMoreOptions3 = new javax.swing.JLabel();
javax.swing.Box.Filler filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); javax.swing.Box.Filler filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel topDevicesAttachedLabel = new javax.swing.JLabel(); javax.swing.JLabel topDevicesAttachedLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.Box.Filler filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel recentDevicesAttached = topDevicesAttachedTable; javax.swing.JPanel recentDevicesAttached = topDevicesAttachedTable;
javax.swing.JLabel rightClickForMoreOptions4 = new javax.swing.JLabel();
javax.swing.Box.Filler filler8 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); javax.swing.Box.Filler filler8 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel recentAccountsLabel = new javax.swing.JLabel(); javax.swing.JLabel recentAccountsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler9 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); javax.swing.Box.Filler filler9 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topAccounts = topAccountsTable; javax.swing.JPanel topAccounts = topAccountsTable;
javax.swing.JLabel rightClickForMoreOptions5 = new javax.swing.JLabel();
setLayout(new java.awt.BorderLayout()); setLayout(new java.awt.BorderLayout());
@ -379,6 +434,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topProgramsTablePanel); contentPanel.add(topProgramsTablePanel);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions1, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions1.text")); // NOI18N
contentPanel.add(rightClickForMoreOptions1);
contentPanel.add(filler3); contentPanel.add(filler3);
recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -391,6 +449,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106)); recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(10, 106));
recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106)); recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(recentDomainsTablePanel); contentPanel.add(recentDomainsTablePanel);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions2, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions2.text")); // NOI18N
contentPanel.add(rightClickForMoreOptions2);
contentPanel.add(filler4); contentPanel.add(filler4);
topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); topWebSearchLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -403,6 +464,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106)); topWebSearches.setMinimumSize(new java.awt.Dimension(10, 106));
topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106)); topWebSearches.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topWebSearches); contentPanel.add(topWebSearches);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions3, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions3.text")); // NOI18N
contentPanel.add(rightClickForMoreOptions3);
contentPanel.add(filler6); contentPanel.add(filler6);
topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); topDevicesAttachedLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -415,6 +479,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106)); recentDevicesAttached.setMinimumSize(new java.awt.Dimension(10, 106));
recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106)); recentDevicesAttached.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(recentDevicesAttached); contentPanel.add(recentDevicesAttached);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions4, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions4.text")); // NOI18N
contentPanel.add(rightClickForMoreOptions4);
contentPanel.add(filler8); contentPanel.add(filler8);
recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); recentAccountsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
@ -428,6 +495,9 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
topAccounts.setPreferredSize(new java.awt.Dimension(10, 106)); topAccounts.setPreferredSize(new java.awt.Dimension(10, 106));
contentPanel.add(topAccounts); contentPanel.add(topAccounts);
org.openide.awt.Mnemonics.setLocalizedText(rightClickForMoreOptions5, org.openide.util.NbBundle.getMessage(UserActivityPanel.class, "UserActivityPanel.rightClickForMoreOptions5.text")); // NOI18N
contentPanel.add(rightClickForMoreOptions5);
contentScrollPane.setViewportView(contentPanel); contentScrollPane.setViewportView(contentPanel);
add(contentScrollPane, java.awt.BorderLayout.CENTER); add(contentScrollPane, java.awt.BorderLayout.CENTER);

View File

@ -304,4 +304,5 @@ public class BarChartPanel extends AbstractLoadableComponent<List<BarChartPanel.
} }
} }
} }
} }

View File

@ -20,12 +20,22 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.Component; import java.awt.Component;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.border.Border; import javax.swing.border.Border;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseEvent;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.CellMouseListener;
/** /**
* A Table cell renderer that renders a cell of a table based off of the * A Table cell renderer that renders a cell of a table based off of the
@ -49,7 +59,7 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* Constructor for a HorizontalAlign enum. * Constructor for a HorizontalAlign enum.
* *
* @param jlabelAlignment The corresponding JLabel horizontal alignment * @param jlabelAlignment The corresponding JLabel horizontal alignment
* number. * number.
*/ */
HorizontalAlign(int jlabelAlignment) { HorizontalAlign(int jlabelAlignment) {
this.jlabelAlignment = jlabelAlignment; this.jlabelAlignment = jlabelAlignment;
@ -57,13 +67,60 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
/** /**
* @return The corresponding JLabel horizontal alignment (i.e. * @return The corresponding JLabel horizontal alignment (i.e.
* JLabel.LEFT). * JLabel.LEFT).
*/ */
int getJLabelAlignment() { int getJLabelAlignment() {
return this.jlabelAlignment; return this.jlabelAlignment;
} }
} }
/**
* A menu item to be used within a popup menu.
*/
public interface MenuItem {
/**
* @return The title for that popup menu item.
*/
String getTitle();
/**
* @return The action if that popup menu item is clicked.
*/
Runnable getAction();
}
/**
* Default implementation of a menu item.
*/
public static class DefaultMenuItem implements MenuItem {
private final String title;
private final Runnable action;
/**
* Main constructor.
*
* @param title The title for the menu item.
* @param action The action should the menu item be clicked.
*/
public DefaultMenuItem(String title, Runnable action) {
this.title = title;
this.action = action;
}
@Override
public String getTitle() {
return title;
}
@Override
public Runnable getAction() {
return action;
}
}
/** /**
* Basic interface for a cell model. * Basic interface for a cell model.
*/ */
@ -88,6 +145,12 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* @return The insets for the cell text. * @return The insets for the cell text.
*/ */
Insets getInsets(); Insets getInsets();
/**
* @return The popup menu associated with this cell or null if no popup
* menu should be shown for this cell.
*/
List<MenuItem> getPopupMenu();
} }
/** /**
@ -99,6 +162,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
private String tooltip; private String tooltip;
private HorizontalAlign horizontalAlignment; private HorizontalAlign horizontalAlignment;
private Insets insets; private Insets insets;
private List<MenuItem> popupMenu;
private Supplier<List<MenuItem>> menuItemSupplier;
/** /**
* Main constructor. * Main constructor.
@ -166,6 +231,41 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
return this; return this;
} }
@Override
public List<MenuItem> getPopupMenu() {
if (popupMenu != null) {
return Collections.unmodifiableList(popupMenu);
}
if (menuItemSupplier != null) {
return this.menuItemSupplier.get();
}
return null;
}
/**
* Sets a function to lazy load the popup menu items.
*
* @param menuItemSupplier The lazy load function for popup items.
* @return
*/
public DefaultCellModel setPopupMenuRetriever(Supplier<List<MenuItem>> menuItemSupplier) {
this.menuItemSupplier = menuItemSupplier;
return this;
}
/**
* Sets the list of items for a popup menu
*
* @param popupMenu
* @return As a utility, returns this.
*/
public DefaultCellModel setPopupMenu(List<MenuItem> popupMenu) {
this.popupMenu = popupMenu == null ? null : new ArrayList<>(popupMenu);
return this;
}
@Override @Override
public String toString() { public String toString() {
return getText(); return getText();
@ -192,8 +292,8 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
* Customizes the jlabel to match the column model and cell model provided. * Customizes the jlabel to match the column model and cell model provided.
* *
* @param defaultCell The cell to customize that will be displayed in the * @param defaultCell The cell to customize that will be displayed in the
* jtable. * jtable.
* @param cellModel The cell model for this cell. * @param cellModel The cell model for this cell.
* *
* @return The provided defaultCell. * @return The provided defaultCell.
*/ */
@ -231,4 +331,42 @@ public class CellModelTableCellRenderer extends DefaultTableCellRenderer {
return defaultCell; return defaultCell;
} }
/**
* The default cell mouse listener that triggers popups for non-primary
* button events.
*/
private static final CellMouseListener DEFAULT_CELL_MOUSE_LISTENER = new CellMouseListener() {
@Override
public void mouseClicked(CellMouseEvent cellEvent) {
if (cellEvent.getCellValue() instanceof CellModel && cellEvent.getMouseEvent().getButton() != MouseEvent.BUTTON1) {
cellEvent.getTable().setRowSelectionInterval(cellEvent.getRow(), cellEvent.getRow());
CellModel cellModel = (CellModel) cellEvent.getCellValue();
List<MenuItem> menuItems = cellModel.getPopupMenu();
// if there are menu items, show a popup menu for
// this item with all the menu items.
if (CollectionUtils.isNotEmpty(menuItems)) {
final JPopupMenu popupMenu = new JPopupMenu();
for (MenuItem mItem : menuItems) {
JMenuItem jMenuItem = new JMenuItem(mItem.getTitle());
if (mItem.getAction() != null) {
jMenuItem.addActionListener((evt) -> mItem.getAction().run());
}
popupMenu.add(jMenuItem);
}
popupMenu.show(cellEvent.getTable(), cellEvent.getMouseEvent().getX(), cellEvent.getMouseEvent().getY());
}
}
}
};
/**
* @return The default cell mouse listener that triggers popups for
* non-primary button events.
*/
public static CellMouseListener getMouseListener() {
return DEFAULT_CELL_MOUSE_LISTENER;
}
} }

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@ -40,6 +42,85 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRendere
*/ */
public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> { public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/**
* An event that wraps a swing MouseEvent also providing context within the
* table cell.
*/
public static class CellMouseEvent {
private final MouseEvent e;
private final JTable table;
private final int row;
private final int col;
private final Object cellValue;
/**
* Main constructor.
*
* @param e The underlying mouse event.
* @param table The table that was the target of the mouse event.
* @param row The row within the table that the event occurs.
* @param col The column within the table that the event occurs.
* @param cellValue The value within the cell.
*/
public CellMouseEvent(MouseEvent e, JTable table, int row, int col, Object cellValue) {
this.e = e;
this.table = table;
this.row = row;
this.col = col;
this.cellValue = cellValue;
}
/**
* @return The underlying mouse event.
*/
public MouseEvent getMouseEvent() {
return e;
}
/**
* @return The table that was the target of the mouse event.
*/
public JTable getTable() {
return table;
}
/**
* @return The row within the table that the event occurs.
*/
public int getRow() {
return row;
}
/**
* @return The column within the table that the event occurs.
*/
public int getCol() {
return col;
}
/**
* @return The value within the cell.
*/
public Object getCellValue() {
return cellValue;
}
}
/**
* Handles mouse events for cells in the table.
*/
public interface CellMouseListener {
/**
* Handles mouse events at a cell level for the table.
*
* @param e The event containing information about the cell, the mouse
* event, and the table.
*/
void mouseClicked(CellMouseEvent e);
}
/** /**
* JTables don't allow displaying messages. So this LayerUI is used to * JTables don't allow displaying messages. So this LayerUI is used to
* display the contents of a child JLabel. Inspired by TableWaitLayerTest * display the contents of a child JLabel. Inspired by TableWaitLayerTest
@ -91,9 +172,9 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* Constructor for a DataResultColumnModel. * Constructor for a DataResultColumnModel.
* *
* @param headerTitle The title for the column. * @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the * @param cellRenderer The method that generates a CellModel for the
* column based on the data. * column based on the data.
*/ */
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) { public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer) {
this(headerTitle, cellRenderer, null); this(headerTitle, cellRenderer, null);
@ -102,10 +183,10 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* Constructor for a DataResultColumnModel. * Constructor for a DataResultColumnModel.
* *
* @param headerTitle The title for the column. * @param headerTitle The title for the column.
* @param cellRenderer The method that generates a CellModel for the * @param cellRenderer The method that generates a CellModel for the
* column based on the data. * column based on the data.
* @param width The preferred width of the column. * @param width The preferred width of the column.
*/ */
public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) { public ColumnModel(String headerTitle, Function<T, CellModelTableCellRenderer.CellModel> cellRenderer, Integer width) {
this.headerTitle = headerTitle; this.headerTitle = headerTitle;
@ -122,7 +203,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* @return The method that generates a CellModel for the column based on * @return The method that generates a CellModel for the column based on
* the data. * the data.
*/ */
public Function<T, CellModel> getCellRenderer() { public Function<T, CellModel> getCellRenderer() {
return cellRenderer; return cellRenderer;
@ -197,14 +278,18 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*/ */
public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) { public static <T> JTablePanel<T> getJTablePanel(List<ColumnModel<T>> columns) {
ListTableModel<T> tableModel = getTableModel(columns); ListTableModel<T> tableModel = getTableModel(columns);
JTablePanel<T> resultTable = new JTablePanel<>(tableModel); JTablePanel<T> resultTable = new JTablePanel<>(tableModel)
return resultTable.setColumnModel(getTableColumnModel(columns)); .setColumnModel(getTableColumnModel(columns))
.setCellListener(CellModelTableCellRenderer.getMouseListener());
return resultTable;
} }
private JScrollPane tableScrollPane; private JScrollPane tableScrollPane;
private Overlay overlayLayer; private Overlay overlayLayer;
private ListTableModel<T> tableModel; private ListTableModel<T> tableModel;
private JTable table; private JTable table;
private CellMouseListener cellListener = null;
private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem; private Function<T, ? extends Object> keyFunction = (rowItem) -> rowItem;
/** /**
@ -222,6 +307,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
*/ */
public JTablePanel() { public JTablePanel() {
initComponents(); initComponents();
this.table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// make sure click event isn't primary button and table is present
if (cellListener != null) {
int row = table.rowAtPoint(e.getPoint());
int col = table.columnAtPoint(e.getPoint());
// make sure there is a value at the row,col of click event.
if (tableModel != null
&& row >= 0 && row < tableModel.getRowCount()
&& col >= 0 && col < tableModel.getColumnCount()) {
Object cellValue = tableModel.getValueAt(row, col);
cellListener.mouseClicked(new CellMouseEvent(e, table, row, col, cellValue));
}
}
}
});
} }
/** /**
@ -242,6 +347,26 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
return this; return this;
} }
/**
* @return The current listener for mouse events. The events provided to
* this listener will have cell and table context.
*/
public CellMouseListener getCellListener() {
return cellListener;
}
/**
* Sets the current listener for mouse events.
*
* @param cellListener The event listener that will receive these events
* with cell and table context.
* @return
*/
public JTablePanel<T> setCellListener(CellMouseListener cellListener) {
this.cellListener = cellListener;
return this;
}
/** /**
* @return The underlying JTable's column model. * @return The underlying JTable's column model.
*/ */
@ -263,8 +388,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
/** /**
* @return The function for determining the key for a data row. This key is * @return The function for determining the key for a data row. This key is
* used to maintain current selection in the table despite changing * used to maintain current selection in the table despite changing rows.
* rows.
*/ */
public Function<T, ? extends Object> getKeyFunction() { public Function<T, ? extends Object> getKeyFunction() {
return keyFunction; return keyFunction;
@ -295,7 +419,7 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
T prevValue = (tableRows != null && prevSelectedRow >= 0 && prevSelectedRow < tableRows.size()) T prevValue = (tableRows != null && prevSelectedRow >= 0 && prevSelectedRow < tableRows.size())
? this.tableModel.getDataRows().get(prevSelectedRow) ? this.tableModel.getDataRows().get(prevSelectedRow)
: null; : null;
Object prevKeyValue = (prevValue == null) ? null : this.keyFunction.apply(prevValue); Object prevKeyValue = (prevValue == null) ? null : this.keyFunction.apply(prevValue);
// set the list of data to be shown as either the data or an empty list // set the list of data to be shown as either the data or an empty list
@ -330,7 +454,6 @@ public class JTablePanel<T> extends AbstractLoadableComponent<List<T>> {
private void initComponents() { private void initComponents() {
table = new JTable(); table = new JTable();
table.getTableHeader().setReorderingAllowed(false); table.getTableHeader().setReorderingAllowed(false);
overlayLayer = new Overlay(); overlayLayer = new Overlay();
tableScrollPane = new JScrollPane(table); tableScrollPane = new JScrollPane(table);
JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer); JLayer<JComponent> dualLayer = new JLayer<>(tableScrollPane, overlayLayer);

View File

@ -22,6 +22,7 @@ import com.google.common.cache.CacheLoader;
import java.awt.Image; import java.awt.Image;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -38,17 +39,22 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.openide.util.ImageUtilities; import org.openide.util.ImageUtilities;
/** /**
* Loads a thumbnail for the given request. Thumbnail candidates are JPEG files * Loads a thumbnail for the given request. Thumbnail candidates types are defined below.
* that have either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts that match the * These candidates types must be the source of either TSK_WEB_DOWNLOAD or
* domain name (see the DomainSearch getArtifacts() API). JPEG files are sorted * TSK_WEB_CACHE artifacts that match the
* domain name (see the DomainSearch getArtifacts() API). Candidate files are sorted
* by most recent if sourced from TSK_WEB_DOWNLOADs and by size if sourced from * by most recent if sourced from TSK_WEB_DOWNLOADs and by size if sourced from
* TSK_WEB_CACHE artifacts. The first suitable thumbnail is selected. * TSK_WEB_CACHE artifacts. The first suitable thumbnail is selected.
*/ */
public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbnailRequest, Image> { public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbnailRequest, Image> {
private static final String UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png"; private static final String UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
private static final String JPG_EXTENSION = "jpg"; private static final List<String> SUPPORTED_EXTENSIONS = Arrays.asList("jpg", "svg", "png", "webp", "ico", "gif");
private static final String JPG_MIME_TYPE = "image/jpeg"; private static final List<String> SUPPORTED_MIMETYPES = Arrays.asList(
"image/gif", "image/jpeg", "image/png", "image/webp",
"image/svg+xml", "image/vnd.microsoft.icon", "image/x-icon");
private static final String ICO_EXTENSION = "ico";
private static final List<String> ICO_MIMETYPES = Arrays.asList("image/vnd.microsoft.icon", "image/x-icon");
private final DomainSearchArtifactsCache artifactsCache; private final DomainSearchArtifactsCache artifactsCache;
/** /**
@ -75,8 +81,21 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest( final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD); caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD);
final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest); final List<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest);
final List<AbstractFile> webDownloadPictures = getJpegsFromWebDownload(caseDb, webDownloads); final List<AbstractFile> webDownloadPictures = getCandidatesFromWebDownloads(caseDb, webDownloads);
Collections.sort(webDownloadPictures, (file1, file2) -> Long.compare(file1.getCrtime(), file2.getCrtime())); Collections.sort(webDownloadPictures, (file1, file2) -> {
// Push ICO to the back of the sorted collection, so that ICO
// is a last resort matching type.
if (isIco(file1) && isIco(file2)) {
return Long.compare(file1.getCrtime(), file2.getCrtime());
} else if (isIco(file1)) {
return 1;
} else if (isIco(file2)) {
return -1;
} else {
return Long.compare(file1.getCrtime(), file2.getCrtime());
}
});
for (int i = webDownloadPictures.size() - 1; i >= 0; i--) { for (int i = webDownloadPictures.size() - 1; i >= 0; i--) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@ -92,8 +111,20 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest( final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest(
caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE); caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE);
final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest); final List<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest);
final List<AbstractFile> webCachePictures = getJpegsFromWebCache(caseDb, webCacheArtifacts); final List<AbstractFile> webCachePictures = getCandidatesFromWebCache(caseDb, webCacheArtifacts);
Collections.sort(webCachePictures, (file1, file2) -> Long.compare(file1.getSize(), file2.getSize())); Collections.sort(webCachePictures, (file1, file2) -> {
// Push ICO to the back of the sorted collection, so that ICO
// is a last resort matching type.
if (isIco(file1) && isIco(file2)) {
return Long.compare(file1.getSize(), file2.getSize());
} else if (isIco(file1)) {
return 1;
} else if (isIco(file2)) {
return -1;
} else {
return Long.compare(file1.getSize(), file2.getSize());
}
});
for (int i = webCachePictures.size() - 1; i >= 0; i--) { for (int i = webCachePictures.size() - 1; i >= 0; i--) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@ -109,40 +140,45 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
} }
/** /**
* Finds all JPEG source files from TSK_WEB_DOWNLOAD instances. * Finds all supported image files from TSK_WEB_DOWNLOAD instances.
* *
* @param caseDb The case database being searched. * @param caseDb The case database being searched.
* @param artifacts The list of artifacts to get jpegs from. * @param artifacts The list of artifacts to search.
* *
* @return The list of AbstractFiles representing jpegs which were * @return The list of AbstractFiles representing supported images which were
* associated with the artifacts. * associated with the artifacts.
* *
* @throws TskCoreException * @throws TskCoreException
*/ */
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException { private List<AbstractFile> getCandidatesFromWebDownloads(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
final List<AbstractFile> jpegs = new ArrayList<>(); final List<AbstractFile> candidates = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) { for (BlackboardArtifact artifact : artifacts) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
} }
final Content sourceContent = caseDb.getContentById(artifact.getObjectID()); final Content sourceContent = caseDb.getContentById(artifact.getObjectID());
addIfJpeg(jpegs, sourceContent); addIfSupported(candidates, sourceContent);
} }
return jpegs; return candidates;
}
private boolean isIco(AbstractFile file) {
return ICO_EXTENSION.equals(file.getNameExtension())
|| ICO_MIMETYPES.contains(file.getMIMEType());
} }
/** /**
* Finds all JPEG source files from TSK_WEB_CACHE instances. * Finds all supported image files from TSK_WEB_CACHE instances.
* *
* @param caseDb The case database being searched. * @param caseDb The case database being searched.
* @param artifacts The list of artifacts to get jpegs from. * @param artifacts The list of artifacts to get images from.
* *
* @return The list of AbstractFiles representing jpegs which were * @return The list of AbstractFiles representing supported images which were
* associated with the artifacts. * associated with the artifacts.
*/ */
private List<AbstractFile> getJpegsFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException { private List<AbstractFile> getCandidatesFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException, InterruptedException {
final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID); final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID);
final List<AbstractFile> jpegs = new ArrayList<>(); final List<AbstractFile> candidates = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) { for (BlackboardArtifact artifact : artifacts) {
if(Thread.currentThread().isInterrupted()) { if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException(); throw new InterruptedException();
@ -150,24 +186,23 @@ public class DomainSearchThumbnailLoader extends CacheLoader<DomainSearchThumbna
final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID); final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID);
if (tskPathId != null) { if (tskPathId != null) {
final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong()); final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong());
addIfJpeg(jpegs, sourceContent); addIfSupported(candidates, sourceContent);
} }
} }
return jpegs; return candidates;
} }
/** /**
* Checks if the candidate source content is indeed a JPEG file. * Checks if the candidate source content is indeed a supported type.
* *
* @param files The list of source content files which are jpegs to * @param files The list of source content files which are supported.
* add to.
* @param sourceContent The source content to check and possibly add. * @param sourceContent The source content to check and possibly add.
*/ */
private void addIfJpeg(List<AbstractFile> files, Content sourceContent) { private void addIfSupported(List<AbstractFile> files, Content sourceContent) {
if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) { if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) {
final AbstractFile file = (AbstractFile) sourceContent; final AbstractFile file = (AbstractFile) sourceContent;
if (JPG_EXTENSION.equals(file.getNameExtension()) if (SUPPORTED_EXTENSIONS.contains(file.getNameExtension())
|| JPG_MIME_TYPE.equals(file.getMIMEType())) { || SUPPORTED_MIMETYPES.contains(file.getMIMEType())) {
files.add(file); files.add(file);
} }
} }

View File

@ -22,11 +22,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.util.Pair;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
import org.sleuthkit.autopsy.geolocation.datamodel.Area;
import org.sleuthkit.autopsy.geolocation.datamodel.Track; import org.sleuthkit.autopsy.geolocation.datamodel.Track;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
@ -81,7 +81,7 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
* @param wasEntirelySuccessful True if no errors occurred while processing. * @param wasEntirelySuccessful True if no errors occurred while processing.
*/ */
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
boolean wasEntirelySuccessful); List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful);
@Override @Override
public void process(GeoLocationParseResult<Waypoint> waypointResults) { public void process(GeoLocationParseResult<Waypoint> waypointResults) {
@ -93,36 +93,54 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex); logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex);
} }
} }
Pair<List<Waypoint>, List<List<Waypoint>>> waypointsAndTracks = createWaypointList( GeoLocationParseResult<Area> areaResults = null;
if (filters.getArtifactTypes().contains(ARTIFACT_TYPE.TSK_GPS_AREA)) {
try {
areaResults = Area.getAreas(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources());
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, "Exception thrown while retrieving list of Areas", ex);
}
}
GeoDataSet geoDataSet = createWaypointList(
waypointResults.getItems(), waypointResults.getItems(),
(trackResults == null) ? new ArrayList<Track>() : trackResults.getItems()); (trackResults == null) ? new ArrayList<>() : trackResults.getItems(),
(areaResults == null) ? new ArrayList<>() : areaResults.getItems());
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(waypointsAndTracks.getKey()); final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(geoDataSet.getWaypoints());
final List<Set<MapWaypoint>> trackSets = new ArrayList<>(); final List<Set<MapWaypoint>> trackSets = new ArrayList<>();
for (List<Waypoint> t : waypointsAndTracks.getValue()) { for (List<Waypoint> t : geoDataSet.getTracks()) {
trackSets.add(MapWaypoint.getWaypoints(t)); trackSets.add(MapWaypoint.getWaypoints(t));
} }
final List<Set<MapWaypoint>> areaSets = new ArrayList<>();
for (List<Waypoint> t : geoDataSet.getAreas()) {
areaSets.add(MapWaypoint.getWaypoints(t));
}
handleFilteredWaypointSet( handleFilteredWaypointSet(
pointSet, trackSets, pointSet, trackSets, areaSets,
(trackResults == null || trackResults.isSuccessfullyParsed()) && waypointResults.isSuccessfullyParsed()); (trackResults == null || trackResults.isSuccessfullyParsed())
&& (areaResults == null || areaResults.isSuccessfullyParsed())
&& waypointResults.isSuccessfullyParsed());
} }
/** /**
* Returns a complete list of waypoints including the tracks. Takes into * Returns a complete list of waypoints including the tracks and areas. Takes into
* account the current filters and includes waypoints as approprate. * account the current filters and includes waypoints as approprate.
* *
* @param waypoints List of waypoints * @param waypoints List of waypoints
* @param tracks List of tracks * @param tracks List of tracks
* @param areas List of areas
* *
* @return A list of waypoints including the tracks based on the current * @return A GeoDataSet object containing a list of waypoints including the tracks and areas based on the current
* filters. filters.
*/ */
private Pair<List<Waypoint>, List<List<Waypoint>>> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) { private GeoDataSet createWaypointList(List<Waypoint> waypoints, List<Track> tracks, List<Area> areas) {
final List<Waypoint> completeList = new ArrayList<>(); final List<Waypoint> completeList = new ArrayList<>();
List<List<Waypoint>> filteredTracks = new ArrayList<>(); List<List<Waypoint>> filteredTracks = new ArrayList<>();
List<List<Waypoint>> filteredAreas = new ArrayList<>();
if (tracks != null) { if (tracks != null) {
Long timeRangeEnd; Long timeRangeEnd;
@ -149,7 +167,14 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
} else { } else {
completeList.addAll(waypoints); completeList.addAll(waypoints);
} }
return new Pair<>(completeList, filteredTracks);
// Areas don't have timestamps so add all of them
for (Area area : areas) {
completeList.addAll(area.getPath());
filteredAreas.add(area.getPath());
}
return new GeoDataSet(completeList, filteredTracks, filteredAreas);
} }
/** /**
@ -270,4 +295,31 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
return -1L; return -1L;
} }
/**
* Utility class to collect filtered GPS objects.
*/
static class GeoDataSet {
private final List<Waypoint> waypoints;
private final List<List<Waypoint>> tracks;
private final List<List<Waypoint>> areas;
GeoDataSet(List<Waypoint> waypoints, List<List<Waypoint>> tracks, List<List<Waypoint>> areas) {
this.waypoints = waypoints;
this.tracks = tracks;
this.areas = areas;
}
List<Waypoint> getWaypoints() {
return waypoints;
}
List<List<Waypoint>> getTracks() {
return tracks;
}
List<List<Waypoint>> getAreas() {
return areas;
}
}
} }

View File

@ -71,7 +71,8 @@ class GeoFilterPanel extends javax.swing.JPanel {
ARTIFACT_TYPE.TSK_GPS_SEARCH, ARTIFACT_TYPE.TSK_GPS_SEARCH,
ARTIFACT_TYPE.TSK_GPS_TRACK, ARTIFACT_TYPE.TSK_GPS_TRACK,
ARTIFACT_TYPE.TSK_GPS_TRACKPOINT, ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
ARTIFACT_TYPE.TSK_METADATA_EXIF ARTIFACT_TYPE.TSK_METADATA_EXIF,
ARTIFACT_TYPE.TSK_GPS_AREA
}; };
/** /**
@ -523,6 +524,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID() + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID() + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID() + " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS.getTypeID()
+ " )" + " )"
+ " ) as innerTable"; + " ) as innerTable";
try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr); try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr);

View File

@ -115,7 +115,8 @@ public final class GeolocationTopComponent extends TopComponent {
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID())) { || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID())) {
showRefreshPanel(true); showRefreshPanel(true);
} }
@ -330,7 +331,7 @@ public final class GeolocationTopComponent extends TopComponent {
* *
* @param waypointList * @param waypointList
*/ */
void addWaypointsToMap(Set<MapWaypoint> waypointList, List<Set<MapWaypoint>> tracks) { void addWaypointsToMap(Set<MapWaypoint> waypointList, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas) {
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -348,6 +349,7 @@ public final class GeolocationTopComponent extends TopComponent {
mapPanel.clearWaypoints(); mapPanel.clearWaypoints();
mapPanel.setWaypoints(waypointList); mapPanel.setWaypoints(waypointList);
mapPanel.setTracks(tracks); mapPanel.setTracks(tracks);
mapPanel.setAreas(areas);
mapPanel.initializePainter(); mapPanel.initializePainter();
setWaypointLoading(false); setWaypointLoading(false);
geoFilterPanel.setEnabled(true); geoFilterPanel.setEnabled(true);
@ -505,8 +507,9 @@ public final class GeolocationTopComponent extends TopComponent {
} }
@Override @Override
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, boolean wasEntirelySuccessful) { void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks,
addWaypointsToMap(mapWaypoints, tracks); List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
addWaypointsToMap(mapWaypoints, tracks, areas);
// if there is an error, present to the user. // if there is an error, present to the user.
if (!wasEntirelySuccessful) { if (!wasEntirelySuccessful) {

View File

@ -23,6 +23,7 @@ import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point; import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
@ -30,6 +31,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
@ -91,11 +93,14 @@ final public class MapPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Set<Integer> DOT_WAYPOINT_TYPES = new HashSet<>(); private static final Set<Integer> DOT_WAYPOINT_TYPES = new HashSet<>();
private static final int DOT_SIZE = 12; private static final int DOT_SIZE = 12;
private static final Set<Integer> VERY_SMALL_DOT_WAYPOINT_TYPES = new HashSet<>();
private static final int VERY_SMALL_DOT_SIZE = 6;
private boolean zoomChanging; private boolean zoomChanging;
private KdTree<MapWaypoint> waypointTree; private KdTree<MapWaypoint> waypointTree;
private Set<MapWaypoint> waypointSet; private Set<MapWaypoint> waypointSet;
private List<Set<MapWaypoint>> tracks = new ArrayList<>(); private List<Set<MapWaypoint>> tracks = new ArrayList<>();
private List<Set<MapWaypoint>> areas = new ArrayList<>();
private Popup currentPopup; private Popup currentPopup;
private final PopupFactory popupFactory; private final PopupFactory popupFactory;
@ -108,12 +113,13 @@ final public class MapPanel extends javax.swing.JPanel {
private BufferedImage transparentWaypointImage; private BufferedImage transparentWaypointImage;
private MapWaypoint currentlySelectedWaypoint; private MapWaypoint currentlySelectedWaypoint;
private Set<MapWaypoint> currentlySelectedTrack; private Set<MapWaypoint> currentlySelectedSet;
static { static {
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()); DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID());
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID()); DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID());
DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()); DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID());
VERY_SMALL_DOT_WAYPOINT_TYPES.add(ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID());
} }
/** /**
@ -241,6 +247,7 @@ final public class MapPanel extends javax.swing.JPanel {
waypointPainter.setRenderer(new MapWaypointRenderer()); waypointPainter.setRenderer(new MapWaypointRenderer());
ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>(); ArrayList<Painter<JXMapViewer>> painters = new ArrayList<>();
painters.add(new MapAreaRenderer(areas));
painters.add(new MapTrackRenderer(tracks)); painters.add(new MapTrackRenderer(tracks));
painters.add(waypointPainter); painters.add(waypointPainter);
@ -342,6 +349,15 @@ final public class MapPanel extends javax.swing.JPanel {
void setTracks(List<Set<MapWaypoint>> tracks) { void setTracks(List<Set<MapWaypoint>> tracks) {
this.tracks = tracks; this.tracks = tracks;
} }
/**
* Stores the given list of areas from which to draw paths later
*
* @param areas
*/
void setAreas(List<Set<MapWaypoint>> areas) {
this.areas = areas;
}
/** /**
* Set the current zoom level. * Set the current zoom level.
@ -361,7 +377,7 @@ final public class MapPanel extends javax.swing.JPanel {
void clearWaypoints() { void clearWaypoints() {
waypointTree = null; waypointTree = null;
currentlySelectedWaypoint = null; currentlySelectedWaypoint = null;
currentlySelectedTrack = null; currentlySelectedSet = null;
if (currentPopup != null) { if (currentPopup != null) {
currentPopup.hide(); currentPopup.hide();
} }
@ -514,6 +530,13 @@ final public class MapPanel extends javax.swing.JPanel {
DOT_SIZE, DOT_SIZE,
DOT_SIZE DOT_SIZE
); );
} else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(nextWaypoint.getArtifactTypeID())) {
rect = new Rectangle(
pointX - (VERY_SMALL_DOT_SIZE / 2),
pointY - (VERY_SMALL_DOT_SIZE / 2),
VERY_SMALL_DOT_SIZE,
VERY_SMALL_DOT_SIZE
);
} else { } else {
rect = new Rectangle( rect = new Rectangle(
pointX - (whiteWaypointImage.getWidth() / 2), pointX - (whiteWaypointImage.getWidth() / 2),
@ -717,16 +740,24 @@ final public class MapPanel extends javax.swing.JPanel {
if (waypoints.size() > 0) { if (waypoints.size() > 0) {
MapWaypoint selection = waypoints.get(0); MapWaypoint selection = waypoints.get(0);
currentlySelectedWaypoint = selection; currentlySelectedWaypoint = selection;
currentlySelectedTrack = null; currentlySelectedSet = null;
for (Set<MapWaypoint> track : tracks) { for (Set<MapWaypoint> track : tracks) {
if (track.contains(selection)) { if (track.contains(selection)) {
currentlySelectedTrack = track; currentlySelectedSet = track;
break; break;
} }
} }
if (currentlySelectedSet == null) {
for (Set<MapWaypoint> area : areas) {
if (area.contains(selection)) {
currentlySelectedSet = area;
break;
}
}
}
} else { } else {
currentlySelectedWaypoint = null; currentlySelectedWaypoint = null;
currentlySelectedTrack = null; currentlySelectedSet = null;
} }
showDetailsPopup(); showDetailsPopup();
} }
@ -755,6 +786,7 @@ final public class MapPanel extends javax.swing.JPanel {
private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> { private class MapWaypointRenderer implements WaypointRenderer<MapWaypoint> {
private final Map<Color, BufferedImage> dotImageCache = new HashMap<>(); private final Map<Color, BufferedImage> dotImageCache = new HashMap<>();
private final Map<Color, BufferedImage> verySmallDotImageCache = new HashMap<>();
private final Map<Color, BufferedImage> waypointImageCache = new HashMap<>(); private final Map<Color, BufferedImage> waypointImageCache = new HashMap<>();
/** /**
@ -766,7 +798,7 @@ final public class MapPanel extends javax.swing.JPanel {
private Color getColor(MapWaypoint waypoint) { private Color getColor(MapWaypoint waypoint) {
Color baseColor = waypoint.getColor(); Color baseColor = waypoint.getColor();
if (waypoint.equals(currentlySelectedWaypoint) if (waypoint.equals(currentlySelectedWaypoint)
|| (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) { || (currentlySelectedSet != null && currentlySelectedSet.contains(waypoint))) {
// Highlight this waypoint since it is selected // Highlight this waypoint since it is selected
return Color.YELLOW; return Color.YELLOW;
} else { } else {
@ -778,11 +810,11 @@ final public class MapPanel extends javax.swing.JPanel {
* Creates a dot image with the specified color * Creates a dot image with the specified color
* *
* @param color the color of the new image * @param color the color of the new image
* @param s the size of the dot
* *
* @return the new dot image * @return the new dot image
*/ */
private BufferedImage createTrackDotImage(Color color) { private BufferedImage createTrackDotImage(Color color, int s) {
int s = DOT_SIZE;
BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB); BufferedImage ret = new BufferedImage(s, s, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = ret.createGraphics(); Graphics2D g = ret.createGraphics();
@ -831,7 +863,13 @@ final public class MapPanel extends javax.swing.JPanel {
if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) { if (DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
image = dotImageCache.computeIfAbsent(color, k -> { image = dotImageCache.computeIfAbsent(color, k -> {
return createTrackDotImage(color); return createTrackDotImage(color, DOT_SIZE);
});
// Center the dot on the GPS coordinate
y -= image.getHeight() / 2;
} else if (VERY_SMALL_DOT_WAYPOINT_TYPES.contains(waypoint.getArtifactTypeID())) {
image = verySmallDotImageCache.computeIfAbsent(color, k -> {
return createTrackDotImage(color, VERY_SMALL_DOT_SIZE);
}); });
// Center the dot on the GPS coordinate // Center the dot on the GPS coordinate
y -= image.getHeight() / 2; y -= image.getHeight() / 2;
@ -903,4 +941,74 @@ final public class MapPanel extends javax.swing.JPanel {
g2d.dispose(); g2d.dispose();
} }
} }
/**
* Renderer for map areas
*/
private class MapAreaRenderer implements Painter<JXMapViewer> {
private final List<Set<MapWaypoint>> areas;
MapAreaRenderer(List<Set<MapWaypoint>> areas) {
this.areas = areas;
}
/**
* Shade in the area on the map.
*
* @param area The waypoints defining the outline of the area.
* @param g Graphics2D
* @param map JXMapViewer
*/
private void drawArea(Set<MapWaypoint> area, Graphics2D g, JXMapViewer map) {
if (area.isEmpty()) {
return;
}
boolean first = true;
GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, area.size());
for (MapWaypoint wp : area) {
Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom());
int thisX = (int) p.getX();
int thisY = (int) p.getY();
if (first) {
polygon.moveTo(thisX, thisY);
first = false;
} else {
polygon.lineTo(thisX, thisY);
}
}
polygon.closePath();
Color areaColor = area.iterator().next().getColor();
final double maxColorValue = 255.0;
g.setPaint(new Color((float)(areaColor.getRed() / maxColorValue),
(float)(areaColor.getGreen() / maxColorValue),
(float)(areaColor.getBlue() / maxColorValue),
.2f));
g.fill(polygon);
g.draw(polygon);
}
@Override
public void paint(Graphics2D g, JXMapViewer map, int w, int h) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = map.getViewportBounds();
g2d.translate(-bounds.x, -bounds.y);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
for (Set<MapWaypoint> area : areas) {
drawArea(area, g2d, map);
}
g2d.dispose();
}
}
} }

View File

@ -79,6 +79,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID(), Color.ORANGE); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID(), Color.ORANGE);
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID(), Color.ORANGE); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID(), Color.ORANGE);
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.MAGENTA); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.MAGENTA);
artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID(), new Color(0x8a2be2)); // Blue violet
} }
private final Waypoint dataModelWaypoint; private final Waypoint dataModelWaypoint;

View File

@ -0,0 +1,171 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.geolocation.datamodel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoAreaPoints;
/**
* A GPS track with which wraps the TSK_GPS_AREA artifact.
*/
public final class Area extends GeoPath {
/**
* Construct a new Area for the given artifact.
*
* @param artifact
*
* @throws GeoLocationDataException
*/
public Area(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, Waypoint.getAttributesFromArtifactAsMap(artifact));
}
/**
* Construct an Area for the given artifact and attributeMap.
*
* @param artifact TSK_GPD_TRACK artifact
* @param attributeMap Map of the artifact attributes
*
* @throws GeoLocationDataException
*/
private Area(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact, getAreaName(attributeMap));
GeoAreaPoints points = getPointsList(attributeMap);
buildPath(points, artifact);
}
/**
* Return the name of the area from the attributeMap. Track name is stored
* in the attribute TSK_NAME
*
* @param attributeMap
*
* @return Area name or empty string if none was available.
*/
private static String getAreaName(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
return attribute != null ? attribute.getValueString() : "";
}
/**
* Create the list of AreaWaypoints from the GeoTrackPoint list.
*
* @param points GeoAreaPoints object.
* @param artifact The artifact to which these points belong
*
* @throws GeoLocationDataException
*/
@Messages({
"# {0} - area name",
"GEOArea_point_label_header=Area outline point for area: {0}"
})
private void buildPath(GeoAreaPoints points, BlackboardArtifact artifact) throws GeoLocationDataException {
for (GeoAreaPoints.AreaPoint point : points) {
addToPath(new AreaWaypoint(artifact, Bundle.GEOArea_point_label_header(getLabel()), point));
}
}
/**
* Returns the list of GeoAreaPoints from the attributeMap. Creates the
* GeoAreaPoint list from the TSK_GEO_AREAPOINTS attribute.
*
* @param attributeMap Map of artifact attributes.
*
* @return GeoTrackPoint list empty list if the attribute was not found.
*
* @throws GeoLocationDataException
*/
private GeoAreaPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_AREAPOINTS);
if (attribute == null) {
throw new GeoLocationDataException("No TSK_GEO_AREAPOINTS attribute present in attribute map to parse.");
}
try {
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoAreaPoints.class);
} catch (InvalidJsonException ex) {
throw new GeoLocationDataException("Unable to parse area points in TSK_GEO_AREAPOINTS attribute", ex);
}
}
/**
* A Waypoint subclass for the points of an area outline.
*/
final class AreaWaypoint extends Waypoint {
private final List<Waypoint.Property> propertyList;
/**
* Construct a AreaWaypoint.
*
* @param artifact the artifact to which this waypoint belongs
*
* @param pointLabel the label for the waypoint
*
* @param point GeoAreaPoint
*
* @throws GeoLocationDataException
*/
AreaWaypoint(BlackboardArtifact artifact, String pointLabel, GeoAreaPoints.AreaPoint point) throws GeoLocationDataException {
super(artifact, pointLabel,
null,
point.getLatitude(),
point.getLongitude(),
null,
null,
null,
Area.this);
propertyList = createPropertyList(point);
}
/**
* Overloaded to return a property list that is generated from the
* GeoTrackPoint instead of an artifact.
*
* @return unmodifiable list of Waypoint.Property
*/
@Override
public List<Waypoint.Property> getOtherProperties() {
return Collections.unmodifiableList(propertyList);
}
/**
* Create a propertyList specific to GeoAreaPoints.
*
* @param point GeoAreaPoint to get values from.
*
* @return A list of Waypoint.properies.
*/
private List<Waypoint.Property> createPropertyList(GeoAreaPoints.AreaPoint point) {
List<Waypoint.Property> list = new ArrayList<>();
return list;
}
}
}

View File

@ -1,3 +1,5 @@
# {0} - area name
GEOArea_point_label_header=Area outline point for area: {0}
# {0} - track name # {0} - track name
GEOTrack_point_label_header=Trackpoint for track: {0} GEOTrack_point_label_header=Trackpoint for track: {0}
LastKnownWaypoint_Label=Last Known Location LastKnownWaypoint_Label=Last Known Location

View File

@ -22,6 +22,8 @@ package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
@ -31,6 +33,8 @@ import org.sleuthkit.datamodel.TskCoreException;
* Class representing a series of waypoints that form a path. * Class representing a series of waypoints that form a path.
*/ */
public class GeoPath { public class GeoPath {
private static final Logger LOGGER = Logger.getLogger(GeoPath.class.getName());
private final List<Waypoint> waypointList; private final List<Waypoint> waypointList;
private final String pathName; private final String pathName;
private final BlackboardArtifact artifact; private final BlackboardArtifact artifact;
@ -62,13 +66,13 @@ public class GeoPath {
} }
/** /**
* Gets the list of Routes from the TSK_GPS_TRACK artifacts. * Gets the list of Tracks from the TSK_GPS_TRACK artifacts.
* *
* @param skCase Currently open SleuthkitCase * @param skCase Currently open SleuthkitCase
* @param sourceList List of source to return tracks from, maybe null to * @param sourceList List of source to return tracks from, maybe null to
* return tracks from all sources * return tracks from all sources
* *
* @return List of Route objects, empty list will be returned if no Routes * @return List of Track objects, empty list will be returned if no tracks
* were found * were found
* *
* @throws GeoLocationDataException * @throws GeoLocationDataException
@ -85,6 +89,7 @@ public class GeoPath {
tracks.add(new Track(artifact)); tracks.add(new Track(artifact));
} catch (GeoLocationDataException e) { } catch (GeoLocationDataException e) {
LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e);
allParsedSuccessfully = false; allParsedSuccessfully = false;
} }
} }
@ -94,6 +99,41 @@ public class GeoPath {
} }
return new GeoLocationParseResult<Track>(tracks, allParsedSuccessfully); return new GeoLocationParseResult<Track>(tracks, allParsedSuccessfully);
} }
/**
* Gets the list of Areas from the TSK_GPS_AREA artifacts.
*
* @param skCase Currently open SleuthkitCase
* @param sourceList List of source to return areas from, may be null to
* return areas from all sources
*
* @return List of Area objects, empty list will be returned if no areas
* were found
*
* @throws GeoLocationDataException
*/
public static GeoLocationParseResult<Area> getAreas(SleuthkitCase skCase, List<? extends Content> sourceList) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts;
boolean allParsedSuccessfully = true;
List<Area> areas = new ArrayList<>();
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA);
for (BlackboardArtifact artifact : artifacts) {
if (sourceList == null || sourceList.contains(artifact.getDataSource())) {
try {
areas.add(new Area(artifact));
} catch (GeoLocationDataException e) {
LOGGER.log(Level.WARNING, "Error loading track from artifact with ID " + artifact.getArtifactID(), e);
allParsedSuccessfully = false;
}
}
}
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
}
return new GeoLocationParseResult<>(areas, allParsedSuccessfully);
}
/** /**
* Path constructor. * Path constructor.

View File

@ -22,20 +22,17 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil.InvalidJsonException;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints; import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* A GPS track with which wraps the TSK_GPS_TRACK artifact. * A GPS track with which wraps the TSK_GPS_TRACK artifact.
*/ */
public final class Track extends GeoPath { public final class Track extends GeoPath {
private static final Logger LOGGER = Logger.getLogger(Track.class.getName());
private final Long startTimestamp; private final Long startTimestamp;
private final Long endTimeStamp; private final Long endTimeStamp;
@ -134,14 +131,12 @@ public final class Track extends GeoPath {
private GeoTrackPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException { private GeoTrackPoints getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS);
if (attribute == null) { if (attribute == null) {
LOGGER.log(Level.SEVERE, "No TSK_GEO_TRACKPOINTS attribute was present on the artifact.");
throw new GeoLocationDataException("No TSK_GEO_TRACKPOINTS attribute present in attribute map to parse."); throw new GeoLocationDataException("No TSK_GEO_TRACKPOINTS attribute present in attribute map to parse.");
} }
try { try {
return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class); return BlackboardJsonAttrUtil.fromAttribute(attribute, GeoTrackPoints.class);
} catch (InvalidJsonException ex) { } catch (InvalidJsonException ex) {
LOGGER.log(Level.SEVERE, "TSK_GEO_TRACKPOINTS could not be properly parsed from TSK_GEO_TRACKPOINTS attribute.");
throw new GeoLocationDataException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex); throw new GeoLocationDataException("Unable to parse track points in TSK_GEO_TRACKPOINTS attribute", ex);
} }
} }

View File

@ -178,6 +178,28 @@ public final class WaypointBuilder {
return trackList; return trackList;
} }
/**
* Returns a list of areas from the given list of waypoints.
*
* @param waypoints A list of waypoints
*
* @return A list of areas or an empty list if none were found.
*/
public static List<Area> getAreas(List<Waypoint> waypoints) {
List<Area> areaList = new ArrayList<>();
for (Waypoint point : waypoints) {
GeoPath path = point.getParentGeoPath();
if (path instanceof Area) {
Area area = (Area) path;
if (!areaList.contains(area)) {
areaList.add(area);
}
}
}
return areaList;
}
/** /**
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts. * Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.

View File

@ -7,6 +7,10 @@ HealthMonitorDashboard.createTimingControlPanel.skipOutliers=Do not plot outlier
HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled HealthMonitorDashboard.createTimingPanel.noData=No data to display - monitor is not enabled
HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics HealthMonitorDashboard.createTimingPanel.timingMetricsTitle=Timing Metrics
HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display
# {0} - Report file name
HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}
HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report
HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report
HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled HealthMonitorDashboard.createUserPanel.noData=No data to display - monitor is not enabled
HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics HealthMonitorDashboard.createUserPanel.userMetricsTitle=User Metrics
HealthMonitorDashboard.DateRange.oneDay=One day HealthMonitorDashboard.DateRange.oneDay=One day

View File

@ -65,7 +65,7 @@ public final class HealthMonitor implements PropertyChangeListener {
private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName()); private final static Logger logger = Logger.getLogger(HealthMonitor.class.getName());
private final static String DATABASE_NAME = "HealthMonitor"; private final static String DATABASE_NAME = "HealthMonitor";
private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes
private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 1); private final static CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 2);
private final static AtomicBoolean isEnabled = new AtomicBoolean(false); private final static AtomicBoolean isEnabled = new AtomicBoolean(false);
private static HealthMonitor instance; private static HealthMonitor instance;
@ -77,6 +77,7 @@ public final class HealthMonitor implements PropertyChangeListener {
private BasicDataSource connectionPool = null; private BasicDataSource connectionPool = null;
private CaseDbConnectionInfo connectionSettingsInUse = null; private CaseDbConnectionInfo connectionSettingsInUse = null;
private String hostName; private String hostName;
private final String username;
private HealthMonitor() throws HealthMonitorException { private HealthMonitor() throws HealthMonitorException {
@ -96,6 +97,9 @@ public final class HealthMonitor implements PropertyChangeListener {
hostName = UUID.randomUUID().toString(); hostName = UUID.randomUUID().toString();
logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex);
} }
// Get the user name
username = System.getProperty("user.name");
// Read from the database to determine if the module is enabled // Read from the database to determine if the module is enabled
updateFromGlobalEnabledStatus(); updateFromGlobalEnabledStatus();
@ -153,8 +157,8 @@ public final class HealthMonitor implements PropertyChangeListener {
if (!databaseIsInitialized()) { if (!databaseIsInitialized()) {
initializeDatabaseSchema(); initializeDatabaseSchema();
} }
if (!CURRENT_DB_SCHEMA_VERSION.equals(getVersion())) { if (getVersion().compareTo(CURRENT_DB_SCHEMA_VERSION) < 0) {
upgradeDatabaseSchema(); upgradeDatabaseSchema();
} }
@ -179,10 +183,15 @@ public final class HealthMonitor implements PropertyChangeListener {
if (conn == null) { if (conn == null) {
throw new HealthMonitorException("Error getting database connection"); throw new HealthMonitorException("Error getting database connection");
} }
ResultSet resultSet = null;
try (Statement statement = conn.createStatement()) { try (Statement statement = conn.createStatement()) {
conn.setAutoCommit(false); conn.setAutoCommit(false);
// NOTE: Due to a bug in the upgrade code, earlier versions of Autopsy will erroneously
// run the upgrade if the database is a higher version than it expects. Therefore all
// table changes must account for the possiblility of running multiple times.
// Upgrade from 1.0 to 1.1 // Upgrade from 1.0 to 1.1
// Changes: user_data table added // Changes: user_data table added
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) { if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 1)) < 0) {
@ -197,6 +206,19 @@ public final class HealthMonitor implements PropertyChangeListener {
+ "case_name text NOT NULL" + "case_name text NOT NULL"
+ ")"); + ")");
} }
// Upgrade from 1.1 to 1.2
// Changes: username added to user_data table
if (currentSchema.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
resultSet = statement.executeQuery("SELECT column_name " +
"FROM information_schema.columns " +
"WHERE table_name='user_data' and column_name='username'");
if (! resultSet.next()) {
// Add the user_data table
statement.execute("ALTER TABLE user_data ADD COLUMN username text");
}
}
// Update the schema version // Update the schema version
statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "' WHERE name='SCHEMA_VERSION'"); statement.execute("UPDATE db_info SET value='" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "' WHERE name='SCHEMA_VERSION'");
@ -212,6 +234,13 @@ public final class HealthMonitor implements PropertyChangeListener {
} }
throw new HealthMonitorException("Error upgrading database", ex); throw new HealthMonitorException("Error upgrading database", ex);
} finally { } finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Error closing result set");
}
}
try { try {
conn.close(); conn.close();
} catch (SQLException ex) { } catch (SQLException ex) {
@ -499,7 +528,7 @@ public final class HealthMonitor implements PropertyChangeListener {
// Add metrics to the database // Add metrics to the database
String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)";
String addUserInfoSql = "INSERT INTO user_data (host, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?)"; String addUserInfoSql = "INSERT INTO user_data (host, username, timestamp, event_type, is_examiner, case_name) VALUES (?, ?, ?, ?, ?, ?)";
try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql); try (PreparedStatement timingStatement = conn.prepareStatement(addTimingInfoSql);
PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) { PreparedStatement userStatement = conn.prepareStatement(addUserInfoSql)) {
@ -519,10 +548,11 @@ public final class HealthMonitor implements PropertyChangeListener {
for (UserData userInfo : userDataCopy) { for (UserData userInfo : userDataCopy) {
userStatement.setString(1, hostName); userStatement.setString(1, hostName);
userStatement.setLong(2, userInfo.getTimestamp()); userStatement.setString(2, username);
userStatement.setInt(3, userInfo.getEventType().getEventValue()); userStatement.setLong(3, userInfo.getTimestamp());
userStatement.setBoolean(4, userInfo.isExaminerNode()); userStatement.setInt(4, userInfo.getEventType().getEventValue());
userStatement.setString(5, userInfo.getCaseName()); userStatement.setBoolean(5, userInfo.isExaminerNode());
userStatement.setString(6, userInfo.getCaseName());
userStatement.execute(); userStatement.execute();
} }
@ -903,7 +933,8 @@ public final class HealthMonitor implements PropertyChangeListener {
+ "timestamp bigint NOT NULL," + "timestamp bigint NOT NULL,"
+ "event_type int NOT NULL," + "event_type int NOT NULL,"
+ "is_examiner BOOLEAN NOT NULL," + "is_examiner BOOLEAN NOT NULL,"
+ "case_name text NOT NULL" + "case_name text NOT NULL,"
+ "username text"
+ ")"); + ")");
statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')");
@ -953,7 +984,7 @@ public final class HealthMonitor implements PropertyChangeListener {
getInstance().writeCurrentStateToDatabase(); getInstance().writeCurrentStateToDatabase();
} }
} catch (HealthMonitorException ex) { } catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error performing periodic task", ex); //NON-NLS logger.log(Level.SEVERE, "Error recording health monitor metrics", ex); //NON-NLS
} }
} }
@ -1340,12 +1371,13 @@ public final class HealthMonitor implements PropertyChangeListener {
* Class holding user metric data. Can be used for storing new events or * Class holding user metric data. Can be used for storing new events or
* retrieving events out of the database. * retrieving events out of the database.
*/ */
static class UserData { static class UserData implements Comparable<UserData> {
private final UserEvent eventType; private final UserEvent eventType;
private long timestamp; private long timestamp;
private final boolean isExaminer; private final boolean isExaminer;
private final String hostname; private final String hostname;
private String username;
private String caseName; private String caseName;
/** /**
@ -1359,6 +1391,7 @@ public final class HealthMonitor implements PropertyChangeListener {
this.timestamp = System.currentTimeMillis(); this.timestamp = System.currentTimeMillis();
this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode()); this.isExaminer = (UserPreferences.SelectedMode.STANDALONE == UserPreferences.getMode());
this.hostname = ""; this.hostname = "";
this.username = "";
// If there's a case open, record the name // If there's a case open, record the name
try { try {
@ -1383,6 +1416,10 @@ public final class HealthMonitor implements PropertyChangeListener {
this.eventType = UserEvent.valueOf(resultSet.getInt("event_type")); this.eventType = UserEvent.valueOf(resultSet.getInt("event_type"));
this.isExaminer = resultSet.getBoolean("is_examiner"); this.isExaminer = resultSet.getBoolean("is_examiner");
this.caseName = resultSet.getString("case_name"); this.caseName = resultSet.getString("case_name");
this.username = resultSet.getString("username");
if (this.username == null) {
this.username = "";
}
} }
/** /**
@ -1443,6 +1480,20 @@ public final class HealthMonitor implements PropertyChangeListener {
String getCaseName() { String getCaseName() {
return caseName; return caseName;
} }
/**
* Get the user name for this metric
*
* @return the user name. Will be the empty string for older data.
*/
String getUserName() {
return username;
}
@Override
public int compareTo(UserData otherData) {
return Long.compare(getTimestamp(), otherData.getTimestamp());
}
} }
/** /**

View File

@ -24,12 +24,17 @@ import java.awt.Dimension;
import java.util.Set; import java.util.Set;
import java.util.HashSet; import java.util.HashSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import javax.swing.Box; import javax.swing.Box;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JDialog; import javax.swing.JDialog;
@ -40,13 +45,19 @@ import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import java.util.Map;
import javax.swing.BoxLayout; import javax.swing.BoxLayout;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Collections;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.JFileChooser;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
@ -443,7 +454,11 @@ public class HealthMonitorDashboard {
* Create the panel with controls for the user panel * Create the panel with controls for the user panel
* @return the control panel * @return the control panel
*/ */
@NbBundle.Messages({"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display"}) @NbBundle.Messages({"HealthMonitorDashboard.createUserControlPanel.maxDays=Max days to display",
"HealthMonitorDashboard.createUserControlPanel.userReportButton=Generate Report",
"HealthMonitorDashboard.createUserControlPanel.reportError=Error generating report",
"# {0} - Report file name",
"HealthMonitorDashboard.createUserControlPanel.reportDone=Report saved to: {0}"})
private JPanel createUserControlPanel() { private JPanel createUserControlPanel() {
JPanel userControlPanel = new JPanel(); JPanel userControlPanel = new JPanel();
@ -473,9 +488,132 @@ public class HealthMonitorDashboard {
userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays())); userControlPanel.add(new JLabel(Bundle.HealthMonitorDashboard_createUserControlPanel_maxDays()));
userControlPanel.add(userDateComboBox); userControlPanel.add(userDateComboBox);
// Create a button to create a user report
JButton reportButton = new JButton(Bundle.HealthMonitorDashboard_createUserControlPanel_userReportButton());
// Set up a listener on the report button
reportButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JFileChooser reportFileChooser = new JFileChooser();
reportFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
reportFileChooser.setCurrentDirectory(new File(UserPreferences.getHealthMonitorReportPath()));
final DateFormat csvTimestampFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
String fileName = "UserReport_" + csvTimestampFormat.format(new Date())+ ".csv";
reportFileChooser.setSelectedFile(new File(fileName));
int returnVal = reportFileChooser.showSaveDialog(userControlPanel);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File selectedFile = reportFileChooser.getSelectedFile();
UserPreferences.setHealthMonitorReportPath(selectedFile.getParent());
try {
dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
generateCSVUserReport(selectedFile);
MessageNotifyUtil.Message.info(Bundle.HealthMonitorDashboard_createUserControlPanel_reportDone(selectedFile.getAbsoluteFile()));
} catch (HealthMonitorException ex) {
logger.log(Level.SEVERE, "Error generating report", ex);
MessageNotifyUtil.Message.error(Bundle.HealthMonitorDashboard_createUserControlPanel_reportError());
} finally {
dialog.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
});
userControlPanel.add(reportButton);
return userControlPanel; return userControlPanel;
} }
/**
* Generate a csv report for the last week of user data.
*
* @param reportFile
*
* @throws HealthMonitorException
*/
private void generateCSVUserReport(File reportFile) throws HealthMonitorException {
final DateFormat timestampFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile), StandardCharsets.UTF_8))) {
// Write header
writer.write("Case open,Case close,Duration,Host,User,Case name");
writer.newLine();
// Get the list of user data sorted by timestamp
List<HealthMonitor.UserData> dataForReport = HealthMonitor.getInstance().getUserMetricsFromDatabase(DateRange.ONE_WEEK.getTimestampRange());
Collections.sort(dataForReport);
// Go through the list of events in order of timestamp. For each case open event, look for the next case closed
// event for that host/user/case name.
for (int caseOpenIndex = 0; caseOpenIndex < dataForReport.size() - 1; caseOpenIndex++) {
if (! dataForReport.get(caseOpenIndex).getEventType().equals(HealthMonitor.UserEvent.CASE_OPEN)) {
continue;
}
// Try to find the next event logged for this user/host. We do not check that
// it is a case closed event.
HealthMonitor.UserData caseOpenEvent = dataForReport.get(caseOpenIndex);
HealthMonitor.UserData nextEventAfterCaseOpen = null;
for (int nextEventIndex = caseOpenIndex + 1; nextEventIndex < dataForReport.size(); nextEventIndex++) {
HealthMonitor.UserData nextEvent = dataForReport.get(nextEventIndex);
// If the user and host name do not match, ignore this event
if ( nextEvent.getHostname().equals(caseOpenEvent.getHostname())
&& nextEvent.getUserName().equals(caseOpenEvent.getUserName())) {
nextEventAfterCaseOpen = nextEvent;
break;
}
}
// Prepare the columns
String caseOpenTime = timestampFormat.format(caseOpenEvent.getTimestamp());
// If everything is recorded properly then the next event for a given user after
// a case open will be a case close. In this case we record the close time and
// how long the case was open. If the next event was not a case close event
// or if there is no next event (which could happen if Autopsy crashed or if
// there were network issues, or if the user simply still has the case open),
// leave the close time and duration blank.
String caseCloseTime = "";
String duration = "";
if (nextEventAfterCaseOpen != null
&& nextEventAfterCaseOpen.getEventType().equals(HealthMonitor.UserEvent.CASE_CLOSE)
&& nextEventAfterCaseOpen.getCaseName().equals(caseOpenEvent.getCaseName())) {
caseCloseTime = timestampFormat.format(nextEventAfterCaseOpen.getTimestamp());
duration = getDuration(caseOpenEvent.getTimestamp(), nextEventAfterCaseOpen.getTimestamp());
}
String host = caseOpenEvent.getHostname();
String user = caseOpenEvent.getUserName();
String caseName = caseOpenEvent.getCaseName();
String csvEntry = caseOpenTime + "," + caseCloseTime + "," + duration + "," + host + "," + user + ",\"" + caseName + "\"";
writer.write(csvEntry);
writer.newLine();
}
} catch (IOException ex) {
throw new HealthMonitorException("Error writing to output file " + reportFile.getAbsolutePath(), ex);
}
}
/**
* Generate a string representing the time between
* the given timestamps.
*
* @param start The starting timestamp.
* @param end The ending timestamp.
*
* @return The duration as a string.
*/
private String getDuration(long start, long end) {
long durationInSeconds = (end - start) / 1000;
long second = durationInSeconds % 60;
long minute = (durationInSeconds / 60) % 60;
long hours = durationInSeconds / (60 * 60);
return String.format("%d:%02d:%02d", hours, minute, second);
}
/** /**
* Update the user graphs. * Update the user graphs.
* @throws HealthMonitorException * @throws HealthMonitorException

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

View File

@ -1793,6 +1793,7 @@ class TableReportGenerator {
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()
|| artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID() || artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED.getTypeID()

View File

@ -384,6 +384,9 @@ public class HTMLReport implements TableReportModule {
case TSK_WEB_FORM_ADDRESS: case TSK_WEB_FORM_ADDRESS:
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
break; break;
case TSK_GPS_AREA:
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
break;
default: default:
logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS

View File

@ -27,6 +27,7 @@ ReportKML.genReport.reportName=KML Report
ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n
ReportKML.latLongEndPoint={0};{1};;{2} (End)\n ReportKML.latLongEndPoint={0};{1};;{2} (End)\n
Route_Details_Header=GPS Route Route_Details_Header=GPS Route
Waypoint_Area_Point_Display_String=GPS Area Outline Point
Waypoint_Bookmark_Display_String=GPS Bookmark Waypoint_Bookmark_Display_String=GPS Bookmark
Waypoint_EXIF_Display_String=EXIF Metadata With Location Waypoint_EXIF_Display_String=EXIF Metadata With Location
Waypoint_Last_Known_Display_String=GPS Last Known Location Waypoint_Last_Known_Display_String=GPS Last Known Location

View File

@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.autopsy.geolocation.datamodel.Track; import org.sleuthkit.autopsy.geolocation.datamodel.Track;
import org.sleuthkit.autopsy.geolocation.datamodel.Area;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder; import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
import org.sleuthkit.autopsy.report.GeneralReportSettings; import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportBranding;
@ -84,6 +85,7 @@ public final class KMLReport implements GeneralReportModule {
private Element gpsSearchesFolder; private Element gpsSearchesFolder;
private Element gpsTrackpointsFolder; private Element gpsTrackpointsFolder;
private Element gpsTracksFolder; private Element gpsTracksFolder;
private Element gpsAreasFolder;
private GeneralReportSettings settings; private GeneralReportSettings settings;
@ -154,7 +156,8 @@ public final class KMLReport implements GeneralReportModule {
"Waypoint_Track_Display_String=GPS Track", "Waypoint_Track_Display_String=GPS Track",
"Route_Details_Header=GPS Route", "Route_Details_Header=GPS Route",
"ReportBodyFile.ingestWarning.text=Ingest Warning message", "ReportBodyFile.ingestWarning.text=Ingest Warning message",
"Waypoint_Track_Point_Display_String=GPS Individual Track Point" "Waypoint_Track_Point_Display_String=GPS Individual Track Point",
"Waypoint_Area_Point_Display_String=GPS Area Outline Point",
}) })
public void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List<Waypoint> waypointList) { public void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List<Waypoint> waypointList) {
@ -216,6 +219,11 @@ public final class KMLReport implements GeneralReportModule {
result = ReportProgressPanel.ReportStatus.ERROR; result = ReportProgressPanel.ReportStatus.ERROR;
errorMessage = Bundle.KMLReport_partialFailure(); errorMessage = Bundle.KMLReport_partialFailure();
} }
entirelySuccessful = makeAreas(skCase);
if (!entirelySuccessful) {
result = ReportProgressPanel.ReportStatus.ERROR;
errorMessage = Bundle.KMLReport_partialFailure();
}
addLocationsToReport(skCase, baseReportDir); addLocationsToReport(skCase, baseReportDir);
} catch (GeoLocationDataException | IOException | TskCoreException ex) { } catch (GeoLocationDataException | IOException | TskCoreException ex) {
@ -326,6 +334,11 @@ public final class KMLReport implements GeneralReportModule {
CDATA cdataTrack = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS CDATA cdataTrack = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS Element hrefTrack = new Element("href", ns).addContent(cdataTrack); //NON-NLS
gpsTracksFolder.addContent(new Element("Icon", ns).addContent(hrefTrack)); //NON-NLS gpsTracksFolder.addContent(new Element("Icon", ns).addContent(hrefTrack)); //NON-NLS
gpsAreasFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataArea = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
Element hrefArea = new Element("href", ns).addContent(cdataArea); //NON-NLS
gpsAreasFolder.addContent(new Element("Icon", ns).addContent(hrefArea)); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
@ -334,6 +347,7 @@ public final class KMLReport implements GeneralReportModule {
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
gpsTracksFolder.addContent(new Element("name", ns).addContent("GPS Tracks")); //NON-NLS gpsTracksFolder.addContent(new Element("name", ns).addContent("GPS Tracks")); //NON-NLS
gpsAreasFolder.addContent(new Element("name", ns).addContent("GPS Areas")); //NON-NLS
document.addContent(gpsExifMetadataFolder); document.addContent(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder); document.addContent(gpsBookmarksFolder);
@ -342,6 +356,7 @@ public final class KMLReport implements GeneralReportModule {
document.addContent(gpsSearchesFolder); document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder); document.addContent(gpsTrackpointsFolder);
document.addContent(gpsTracksFolder); document.addContent(gpsTracksFolder);
document.addContent(gpsAreasFolder);
return kmlDocument; return kmlDocument;
} }
@ -570,6 +585,62 @@ public final class KMLReport implements GeneralReportModule {
point.getTimestamp(), element, formattedCoordinates(point.getLatitude(), point.getLongitude()))); //NON-NLS point.getTimestamp(), element, formattedCoordinates(point.getLatitude(), point.getLongitude()))); //NON-NLS
} }
} }
/**
* Add the area to the area folder in the document.
*
* @param skCase Currently open case.
* @return The operation was entirely successful.
*
* @throws TskCoreException
*/
boolean makeAreas(SleuthkitCase skCase) throws GeoLocationDataException, TskCoreException {
List<Area> areas;
boolean successful = true;
if (waypointList == null) {
GeoLocationParseResult<Area> result = Area.getAreas(skCase, null);
areas = result.getItems();
successful = result.isSuccessfullyParsed();
} else {
areas = WaypointBuilder.getAreas(waypointList);
}
for (Area area : areas) {
if(shouldFilterFromReport(area.getArtifact())) {
continue;
}
addAreaToReport(area);
}
return successful;
}
/**
* Add a area to the KML report.
*
* @param area
*/
private void addAreaToReport(Area area) {
List<Waypoint> areaPoints = area.getPath();
if (areaPoints.isEmpty()) {
return;
}
// Adding a folder with the area name so that all of the
// area border points will be grouped together.
Element areaFolder = new Element("Folder", ns); //NON-NLS
areaFolder.addContent(new Element("name", ns).addContent(area.getLabel())); //NON-NLS
gpsAreasFolder.addContent(areaFolder);
// Create a polygon using the waypoints
Element element = makePolygon(areaPoints);
Waypoint firstWp = areaPoints.get(0);
areaFolder.addContent(makePlacemark("",
FeatureColor.GREEN, getFormattedDetails(firstWp, Bundle.Waypoint_Area_Point_Display_String()),
firstWp.getTimestamp(), element, formattedCoordinates(firstWp.getLatitude(), firstWp.getLongitude()))); //NON-NLS
}
/** /**
* Format a point time stamp (in seconds) to the report format. * Format a point time stamp (in seconds) to the report format.
@ -628,7 +699,7 @@ public final class KMLReport implements GeneralReportModule {
point.addContent(coordinates); point.addContent(coordinates);
return point; return point;
} }
/** /**
* Create a LineString for use in a Placemark. Note in this method, start * Create a LineString for use in a Placemark. Note in this method, start
@ -662,6 +733,35 @@ public final class KMLReport implements GeneralReportModule {
return lineString; return lineString;
} }
/**
* Create a Polygon for use in a Placemark.
*
* @param waypoints The waypoints making up the outline.
*
* @return the Polygon as an Element
*/
private Element makePolygon(List<Waypoint> waypoints) {
Element polygon = new Element("Polygon", ns); //NON-NLS
Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS
polygon.addContent(altitudeMode);
// KML uses lon, lat. Deliberately reversed.
Element coordinates = new Element("coordinates", ns);
for (Waypoint wp : waypoints) {
coordinates.addContent(wp.getLongitude() + "," + wp.getLatitude() + ",0 "); //NON-NLS
}
// Add the first one again
coordinates.addContent(waypoints.get(0).getLongitude() + "," + waypoints.get(0).getLatitude() + ",0 "); //NON-NLS
Element linearRing = new Element("LinearRing", ns).addContent(coordinates);
Element outerBoundary = new Element("outerBoundaryIs", ns).addContent(linearRing);
polygon.addContent(outerBoundary);
return polygon;
}
/** /**
* Make a Placemark for use in displaying features. Takes a * Make a Placemark for use in displaying features. Takes a
* coordinate-bearing feature (Point, LineString, etc) and places it in the * coordinate-bearing feature (Point, LineString, etc) and places it in the

View File

@ -24,6 +24,7 @@ import javafx.application.Platform;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import org.joda.time.Interval;
import org.openide.awt.ActionID; import org.openide.awt.ActionID;
import org.openide.awt.ActionReference; import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences; import org.openide.awt.ActionReferences;
@ -46,13 +47,10 @@ import org.sleuthkit.datamodel.TskCoreException;
* An Action that opens the Timeline window. Has methods to open the window in * An Action that opens the Timeline window. Has methods to open the window in
* various specific states (e.g., showing a specific artifact in the List View) * various specific states (e.g., showing a specific artifact in the List View)
*/ */
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false) @ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
@ActionReferences(value = { @ActionReferences(value = {
@ActionReference(path = "Menu/Tools", position = 104) @ActionReference(path = "Menu/Tools", position = 104),
,
@ActionReference(path = "Toolbars/Case", position = 104)}) @ActionReference(path = "Toolbars/Case", position = 104)})
public final class OpenTimelineAction extends CallableSystemAction { public final class OpenTimelineAction extends CallableSystemAction {
@ -64,7 +62,6 @@ public final class OpenTimelineAction extends CallableSystemAction {
private final JButton toolbarButton = new JButton(getName(), private final JButton toolbarButton = new JButton(getName(),
new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS
public OpenTimelineAction() { public OpenTimelineAction() {
toolbarButton.addActionListener(actionEvent -> performAction()); toolbarButton.addActionListener(actionEvent -> performAction());
menuItem = super.getMenuPresenter(); menuItem = super.getMenuPresenter();
@ -74,9 +71,10 @@ public final class OpenTimelineAction extends CallableSystemAction {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
/** /**
* We used to also check if Case.getCurrentOpenCase().hasData() was true. We * We used to also check if Case.getCurrentOpenCase().hasData() was
* disabled that check because if it is executed while a data source is * true. We disabled that check because if it is executed while a data
* being added, it blocks the edt. We still do that in ImageGallery. * source is being added, it blocks the edt. We still do that in
* ImageGallery.
*/ */
return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited(); return super.isEnabled() && Case.isCaseOpen() && Installer.isJavaFxInited();
} }
@ -103,7 +101,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
@NbBundle.Messages({ @NbBundle.Messages({
"OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.", "OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.",
"OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."}) "OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."})
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) throws TskCoreException { synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact, Interval timeSpan) throws TskCoreException {
try { try {
Case currentCase = Case.getCurrentCaseThrows(); Case currentCase = Case.getCurrentCaseThrows();
if (currentCase.hasData() == false) { if (currentCase.hasData() == false) {
@ -112,6 +110,15 @@ public final class OpenTimelineAction extends CallableSystemAction {
return; return;
} }
TimeLineController controller = TimeLineModule.getController(); TimeLineController controller = TimeLineModule.getController();
// if file or artifact not specified, specify the time range as either
// a) full range if timeSpan is null or b) the timeSpan
if (file == null && artifact == null) {
if (timeSpan == null) {
controller.showFullRange();
} else {
controller.pushTimeRange(timeSpan);
}
}
controller.showTimeLine(file, artifact); controller.showTimeLine(file, artifact);
} catch (NoCurrentCaseException e) { } catch (NoCurrentCaseException e) {
//there is no case... Do nothing. //there is no case... Do nothing.
@ -123,7 +130,18 @@ public final class OpenTimelineAction extends CallableSystemAction {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showTimeline() throws TskCoreException { public void showTimeline() throws TskCoreException {
showTimeline(null, null); showTimeline(null, null, null);
}
/**
* Open timeline with the given timeSpan time range.
*
* @param timeSpan The time range to display.
* @throws TskCoreException
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showTimeline(Interval timeSpan) throws TskCoreException {
showTimeline(null, null, timeSpan);
} }
/** /**
@ -135,7 +153,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showFileInTimeline(AbstractFile file) throws TskCoreException { public void showFileInTimeline(AbstractFile file) throws TskCoreException {
showTimeline(file, null); showTimeline(file, null, null);
} }
/** /**
@ -146,7 +164,7 @@ public final class OpenTimelineAction extends CallableSystemAction {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException { public void showArtifactInTimeline(BlackboardArtifact artifact) throws TskCoreException {
showTimeline(null, artifact); showTimeline(null, artifact, null);
} }
@Override @Override

View File

@ -62,7 +62,6 @@ import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatter;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED; import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
@ -348,7 +347,7 @@ public class TimeLineController {
/** /**
* Show the entire range of the timeline. * Show the entire range of the timeline.
*/ */
private boolean showFullRange() throws TskCoreException { boolean showFullRange() throws TskCoreException {
synchronized (filteredEvents) { synchronized (filteredEvents) {
return pushTimeRange(filteredEvents.getSpanningInterval()); return pushTimeRange(filteredEvents.getSpanningInterval());
} }
@ -359,7 +358,7 @@ public class TimeLineController {
* ViewInTimelineRequestedEvent in the List View. * ViewInTimelineRequestedEvent in the List View.
* *
* @param requestEvent Contains the ID of the requested events and the * @param requestEvent Contains the ID of the requested events and the
* timerange to show. * timerange to show.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException { private void showInListView(ViewInTimelineRequestedEvent requestEvent) throws TskCoreException {
@ -376,14 +375,13 @@ public class TimeLineController {
} }
} }
/** /**
* Shuts down the task executor in charge of handling case events. * Shuts down the task executor in charge of handling case events.
*/ */
void shutDownTimeLineListeners() { void shutDownTimeLineListeners() {
ThreadUtils.shutDownTaskExecutor(executor); ThreadUtils.shutDownTaskExecutor(executor);
} }
/** /**
* "Shut down" Timeline. Close the timeline window. * "Shut down" Timeline. Close the timeline window.
*/ */
@ -394,15 +392,13 @@ public class TimeLineController {
topComponent = null; topComponent = null;
} }
} }
/** /**
* Add the case and ingest listeners, prompt for rebuilding the database if * Add the case and ingest listeners, prompt for rebuilding the database if
* necessary, and show the timeline window. * necessary, and show the timeline window.
* *
* @param file The AbstractFile from which to choose an event to show in * @param file The AbstractFile from which to choose an event to show in the
* the List View. * List View.
* @param artifact The BlackboardArtifact to show in the List View. * @param artifact The BlackboardArtifact to show in the List View.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.AWT) @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@ -421,9 +417,7 @@ public class TimeLineController {
try { try {
if (file == null && artifact == null) { if (file == null && artifact == null) {
SwingUtilities.invokeLater(TimeLineController.this::showWindow); SwingUtilities.invokeLater(TimeLineController.this::showWindow);
this.showFullRange();
} else { } else {
//prompt user to pick specific event and time range //prompt user to pick specific event and time range
ShowInTimelineDialog showInTimelineDilaog = (file == null) ShowInTimelineDialog showInTimelineDilaog = (file == null)
? new ShowInTimelineDialog(this, artifact) ? new ShowInTimelineDialog(this, artifact)
@ -453,7 +447,7 @@ public class TimeLineController {
* around the middle of the currently viewed time range. * around the middle of the currently viewed time range.
* *
* @param period The period of time to show around the current center of the * @param period The period of time to show around the current center of the
* view. * view.
*/ */
synchronized public void pushPeriod(ReadablePeriod period) throws TskCoreException { synchronized public void pushPeriod(ReadablePeriod period) throws TskCoreException {
synchronized (filteredEvents) { synchronized (filteredEvents) {
@ -496,8 +490,8 @@ public class TimeLineController {
*/ */
topComponent.requestActive(); topComponent.requestActive();
} }
synchronized public TimeLineTopComponent getTopComponent(){ synchronized public TimeLineTopComponent getTopComponent() {
return topComponent; return topComponent;
} }
@ -517,7 +511,7 @@ public class TimeLineController {
* @param timeRange The Interval to view. * @param timeRange The Interval to view.
* *
* @return True if the interval was changed. False if the interval was the * @return True if the interval was changed. False if the interval was the
* same as the existing one and no change happened. * same as the existing one and no change happened.
*/ */
synchronized public boolean pushTimeRange(Interval timeRange) throws TskCoreException { synchronized public boolean pushTimeRange(Interval timeRange) throws TskCoreException {
//clamp timerange to case //clamp timerange to case
@ -709,7 +703,7 @@ public class TimeLineController {
* Register the given object to receive events. * Register the given object to receive events.
* *
* @param listener The object to register. Must implement public methods * @param listener The object to register. Must implement public methods
* annotated with Subscribe. * annotated with Subscribe.
*/ */
synchronized public void registerForEvents(Object listener) { synchronized public void registerForEvents(Object listener) {
eventbus.register(listener); eventbus.register(listener);

View File

@ -300,7 +300,7 @@ public class UserActivitySummaryTest {
Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel()); Assert.assertEquals(acceptedDevice, results.get(0).getDeviceModel());
Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake()); Assert.assertEquals("MAKE " + acceptedDevice, results.get(0).getDeviceMake());
Assert.assertEquals("ID " + acceptedDevice, results.get(0).getDeviceId()); Assert.assertEquals("ID " + acceptedDevice, results.get(0).getDeviceId());
Assert.assertEquals(time, results.get(0).getDateAccessed().getTime() / 1000); Assert.assertEquals(time, results.get(0).getLastAccessed().getTime() / 1000);
} }
/** /**
@ -353,7 +353,7 @@ public class UserActivitySummaryTest {
List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10); List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10);
Assert.assertEquals(1, results.size()); Assert.assertEquals(1, results.size());
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getDateAccessed().getTime() / 1000); Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getLastAccessed().getTime() / 1000);
Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId())); Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId()));
Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake())); Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake()));
Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel())); Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel()));
@ -403,9 +403,9 @@ public class UserActivitySummaryTest {
Assert.assertEquals("Expected two different search queries", 2, results.size()); Assert.assertEquals("Expected two different search queries", 2, results.size());
Assert.assertTrue(query1.equalsIgnoreCase(results.get(0).getSearchString())); Assert.assertTrue(query1.equalsIgnoreCase(results.get(0).getSearchString()));
Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getDateAccessed().getTime() / 1000); Assert.assertEquals(DAY_SECONDS * 5, results.get(0).getLastAccessed().getTime() / 1000);
Assert.assertTrue(query2.equalsIgnoreCase(results.get(1).getSearchString())); Assert.assertTrue(query2.equalsIgnoreCase(results.get(1).getSearchString()));
Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getDateAccessed().getTime() / 1000); Assert.assertEquals(DAY_SECONDS * 3, results.get(1).getLastAccessed().getTime() / 1000);
} }
private void webSearchTranslationTest(List<String> queries, boolean hasProvider, String translationSuffix) private void webSearchTranslationTest(List<String> queries, boolean hasProvider, String translationSuffix)
@ -599,11 +599,11 @@ public class UserActivitySummaryTest {
Assert.assertEquals(2, domains.size()); Assert.assertEquals(2, domains.size());
Assert.assertTrue("Expected " + domain1 + " to be first domain", domain1.equalsIgnoreCase(domains.get(0).getDomain())); Assert.assertTrue("Expected " + domain1 + " to be first domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastVisit().getTime() / 1000); Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS * 2, domains.get(0).getLastAccessed().getTime() / 1000);
Assert.assertEquals((Long) 2L, domains.get(0).getVisitTimes()); Assert.assertEquals((Long) 2L, domains.get(0).getVisitTimes());
Assert.assertTrue("Expected " + domain3 + " to be second domain", domain3.equalsIgnoreCase(domains.get(1).getDomain())); Assert.assertTrue("Expected " + domain3 + " to be second domain", domain3.equalsIgnoreCase(domains.get(1).getDomain()));
Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastVisit().getTime() / 1000); Assert.assertEquals(DAY_SECONDS * DOMAIN_WINDOW_DAYS, domains.get(1).getLastAccessed().getTime() / 1000);
Assert.assertEquals((Long) 1L, domains.get(1).getVisitTimes()); Assert.assertEquals((Long) 1L, domains.get(1).getVisitTimes());
} }
@ -643,7 +643,7 @@ public class UserActivitySummaryTest {
Assert.assertEquals(1, domains.size()); Assert.assertEquals(1, domains.size());
Assert.assertTrue("Expected " + domain1 + " to be most recent domain", domain1.equalsIgnoreCase(domains.get(0).getDomain())); Assert.assertTrue("Expected " + domain1 + " to be most recent domain", domain1.equalsIgnoreCase(domains.get(0).getDomain()));
Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastVisit().getTime() / 1000); Assert.assertEquals(DAY_SECONDS, domains.get(0).getLastAccessed().getTime() / 1000);
} }
/** /**
@ -680,11 +680,11 @@ public class UserActivitySummaryTest {
Assert.assertEquals(2, domains.size()); Assert.assertEquals(2, domains.size());
Assert.assertTrue(domain1.equalsIgnoreCase(domains.get(1).getDomain())); Assert.assertTrue(domain1.equalsIgnoreCase(domains.get(1).getDomain()));
Assert.assertEquals(6L, domains.get(1).getLastVisit().getTime() / 1000); Assert.assertEquals(6L, domains.get(1).getLastAccessed().getTime() / 1000);
Assert.assertEquals((Long) 2L, domains.get(1).getVisitTimes()); Assert.assertEquals((Long) 2L, domains.get(1).getVisitTimes());
Assert.assertTrue(domain2.equalsIgnoreCase(domains.get(0).getDomain())); Assert.assertTrue(domain2.equalsIgnoreCase(domains.get(0).getDomain()));
Assert.assertEquals(4L, domains.get(0).getLastVisit().getTime() / 1000); Assert.assertEquals(4L, domains.get(0).getLastAccessed().getTime() / 1000);
Assert.assertEquals((Long) 3L, domains.get(0).getVisitTimes()); Assert.assertEquals((Long) 3L, domains.get(0).getVisitTimes());
} }
@ -869,7 +869,8 @@ public class UserActivitySummaryTest {
// since this may be somewhat variable // since this may be somewhat variable
Assert.assertTrue(expectedItem.getAccountType().equalsIgnoreCase(receivedItem.getAccountType())); Assert.assertTrue(expectedItem.getAccountType().equalsIgnoreCase(receivedItem.getAccountType()));
Assert.assertEquals(expectedItem.getLastAccess().getTime(), receivedItem.getLastAccess().getTime()); Assert.assertEquals(expectedItem.getLastAccessed().getTime(), receivedItem.getLastAccessed().getTime());
Assert.assertEquals(expectedItem.getArtifact(), receivedItem.getArtifact());
} }
} }
@ -896,13 +897,13 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, email1, getRecentAccountsOneArtTest(ds1, email1,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), email1));
BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS); BlackboardArtifact email2 = getEmailArtifact(2, ds1, null, DAY_SECONDS);
getRecentAccountsOneArtTest(ds1, email2, getRecentAccountsOneArtTest(ds1, email2,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), email2));
BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null); BlackboardArtifact email3 = getEmailArtifact(3, ds1, null, null);
getRecentAccountsOneArtTest(ds1, email3, null); getRecentAccountsOneArtTest(ds1, email3, null);
@ -911,19 +912,19 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, email4, getRecentAccountsOneArtTest(ds1, email4,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(),
new Date(DAY_SECONDS * 2 * 1000))); new Date(DAY_SECONDS * 2 * 1000), email4));
BlackboardArtifact callog1 = getCallogArtifact(11, ds1, DAY_SECONDS, null); BlackboardArtifact callog1 = getCallogArtifact(11, ds1, DAY_SECONDS, null);
getRecentAccountsOneArtTest(ds1, callog1, getRecentAccountsOneArtTest(ds1, callog1,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), callog1));
BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS); BlackboardArtifact callog2 = getCallogArtifact(12, ds1, null, DAY_SECONDS);
getRecentAccountsOneArtTest(ds1, callog2, getRecentAccountsOneArtTest(ds1, callog2,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 1000))); new Date(DAY_SECONDS * 1000), callog2));
BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null); BlackboardArtifact callog3 = getCallogArtifact(13, ds1, null, null);
getRecentAccountsOneArtTest(ds1, callog3, null); getRecentAccountsOneArtTest(ds1, callog3, null);
@ -932,7 +933,7 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, callog4, getRecentAccountsOneArtTest(ds1, callog4,
new TopAccountResult( new TopAccountResult(
Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(),
new Date(DAY_SECONDS * 2 * 1000))); new Date(DAY_SECONDS * 2 * 1000), callog4));
BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null); BlackboardArtifact message1 = getMessageArtifact(21, ds1, "Skype", null);
getRecentAccountsOneArtTest(ds1, message1, null); getRecentAccountsOneArtTest(ds1, message1, null);
@ -944,7 +945,7 @@ public class UserActivitySummaryTest {
getRecentAccountsOneArtTest(ds1, message3, null); getRecentAccountsOneArtTest(ds1, message3, null);
BlackboardArtifact message4 = getMessageArtifact(24, ds1, "Skype", DAY_SECONDS); BlackboardArtifact message4 = getMessageArtifact(24, ds1, "Skype", DAY_SECONDS);
getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000))); getRecentAccountsOneArtTest(ds1, message4, new TopAccountResult("Skype", new Date(DAY_SECONDS * 1000), message4));
} }
@ -977,10 +978,10 @@ public class UserActivitySummaryTest {
getRecentAccountsTest(ds1, 10, getRecentAccountsTest(ds1, 10,
Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b), Arrays.asList(email1, email2, email3, callog1, callog2, message1a, message1b, message2a, message2b),
Arrays.asList( Arrays.asList(
new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000)), new TopAccountResult("Facebook", new Date((DAY_SECONDS + 42) * 1000), message2b),
new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000)), new TopAccountResult("Skype", new Date((DAY_SECONDS + 32) * 1000), message1b),
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000)), new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_calllogMessage(), new Date((DAY_SECONDS + 22) * 1000), callog2),
new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000)) new TopAccountResult(Bundle.DataSourceUserActivitySummary_getRecentAccounts_emailMessage(), new Date((DAY_SECONDS + 13) * 1000), email3)
)); ));
} }
@ -1156,17 +1157,17 @@ public class UserActivitySummaryTest {
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(0).getProgramName())); Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(0).getProgramName()));
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath())); Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(0).getProgramPath()));
Assert.assertEquals((Long) 31L, results.get(0).getRunTimes()); Assert.assertEquals((Long) 31L, results.get(0).getRunTimes());
Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastRun().getTime() / 1000)); Assert.assertEquals((Long) 31L, (Long) (results.get(0).getLastAccessed().getTime() / 1000));
Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(1).getProgramName())); Assert.assertTrue("program1.exe".equalsIgnoreCase(results.get(1).getProgramName()));
Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath())); Assert.assertTrue("/Program Files/etc/".equalsIgnoreCase(results.get(1).getProgramPath()));
Assert.assertEquals((Long) 21L, results.get(1).getRunTimes()); Assert.assertEquals((Long) 21L, results.get(1).getRunTimes());
Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastRun().getTime() / 1000)); Assert.assertEquals((Long) 31L, (Long) (results.get(1).getLastAccessed().getTime() / 1000));
Assert.assertTrue("program2.exe".equalsIgnoreCase(results.get(2).getProgramName())); Assert.assertTrue("program2.exe".equalsIgnoreCase(results.get(2).getProgramName()));
Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath())); Assert.assertTrue("/Program Files/another/".equalsIgnoreCase(results.get(2).getProgramPath()));
Assert.assertEquals((Long) 10L, results.get(2).getRunTimes()); Assert.assertEquals((Long) 10L, results.get(2).getRunTimes());
Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastRun().getTime() / 1000)); Assert.assertEquals((Long) 22L, (Long) (results.get(2).getLastAccessed().getTime() / 1000));
} }
private void assertProgramOrder(DataSource ds1, List<BlackboardArtifact> artifacts, List<String> programNamesReturned) private void assertProgramOrder(DataSource ds1, List<BlackboardArtifact> artifacts, List<String> programNamesReturned)

View File

@ -15,10 +15,7 @@ echo "Testing Autopsy..." && echo -en 'travis_fold:start:script.tests\\r'
echo "Free Space:" echo "Free Space:"
echo `df -h .` echo `df -h .`
if [ "${TRAVIS_OS_NAME}" = "osx" ]; then if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
# if os x, just run it
ant -q test-no-regression
elif [ "${TRAVIS_OS_NAME}" = "linux" ]; then
# if linux use xvfb # if linux use xvfb
xvfb-run ant -q test-no-regression xvfb-run ant -q test-no-regression
fi fi