mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-16 17:57:43 +00:00
Merge branch 'develop' into data-src-summaries
This commit is contained in:
commit
ec3cb76de0
@ -36,3 +36,4 @@ DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
|
||||
DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type
|
||||
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
|
||||
DataSourceSummaryCountsPanel.jLabel1.text=Results by Type
|
||||
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run
|
||||
|
@ -61,9 +61,6 @@ DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
|
||||
DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type
|
||||
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
|
||||
DataSourceSummaryCountsPanel.jLabel1.text=Results by Type
|
||||
DataSourceSummaryDialog.countsTab.title=Counts
|
||||
DataSourceSummaryDialog.detailsTab.title=Details
|
||||
DataSourceSummaryDialog.ingestHistoryTab.title=Ingest History
|
||||
DataSourceSummaryDialog.window.title=Data Sources Summary
|
||||
DataSourceSummaryNode.column.dataSourceName.header=Data Source Name
|
||||
DataSourceSummaryNode.column.files.header=Files
|
||||
@ -72,4 +69,14 @@ DataSourceSummaryNode.column.status.header=Ingest Status
|
||||
DataSourceSummaryNode.column.tags.header=Tags
|
||||
DataSourceSummaryNode.column.type.header=Type
|
||||
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
|
||||
DataSourceSummaryTabbedPane_countsTab_title=Counts
|
||||
DataSourceSummaryTabbedPane_detailsTab_title=Details
|
||||
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
|
||||
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
|
||||
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run
|
||||
DataSourceSummaryUserActivityPanel_tab_title=User Activity
|
||||
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times
|
||||
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder
|
||||
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run
|
||||
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program
|
||||
ViewSummaryInformationAction.name.text=View Summary Information
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Copyright 2019 - 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,8 +18,11 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
@ -27,13 +30,20 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
|
||||
/**
|
||||
@ -193,6 +203,31 @@ final class DataSourceInfoUtilities {
|
||||
return getBaseQueryResult(query, handler, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a result set handler that will return a map of string to long.
|
||||
*
|
||||
* @param keyParam The named parameter in the result set representing the
|
||||
* key.
|
||||
* @param valueParam The named parameter in the result set representing the
|
||||
* value.
|
||||
*
|
||||
* @return The result set handler to generate the map of string to long.
|
||||
*/
|
||||
private static ResultSetHandler<LinkedHashMap<String, Long>> getStringLongResultSetHandler(String keyParam, String valueParam) {
|
||||
return (resultSet) -> {
|
||||
LinkedHashMap<String, Long> toRet = new LinkedHashMap<>();
|
||||
while (resultSet.next()) {
|
||||
try {
|
||||
toRet.put(resultSet.getString(keyParam), resultSet.getLong(valueParam));
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get a result pair from the result set.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return toRet;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves counts for each artifact type in a data source.
|
||||
*
|
||||
@ -215,24 +250,311 @@ final class DataSourceInfoUtilities {
|
||||
+ " WHERE bba.data_source_obj_id =" + selectedDataSource.getId()
|
||||
+ " GROUP BY bbt.display_name";
|
||||
|
||||
ResultSetHandler<Map<String, Long>> handler = (resultSet) -> {
|
||||
Map<String, Long> toRet = new HashMap<>();
|
||||
while (resultSet.next()) {
|
||||
String errorMessage = "Unable to get artifact type counts; returning null.";
|
||||
return getBaseQueryResult(query, getStringLongResultSetHandler(nameParam, valueParam), errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a result of a program run on a datasource.
|
||||
*/
|
||||
static class TopProgramsResult {
|
||||
|
||||
private final String programName;
|
||||
private final String programPath;
|
||||
private final Long runTimes;
|
||||
private final Date lastRun;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param programName The name of the program.
|
||||
* @param programPath The path of the program.
|
||||
* @param runTimes The number of runs.
|
||||
*/
|
||||
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
|
||||
this.programName = programName;
|
||||
this.programPath = programPath;
|
||||
this.runTimes = runTimes;
|
||||
this.lastRun = lastRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the program
|
||||
*/
|
||||
String getProgramName() {
|
||||
return programName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path of the program.
|
||||
*/
|
||||
String getProgramPath() {
|
||||
return programPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of run times or null if not present.
|
||||
*/
|
||||
Long getRunTimes() {
|
||||
return runTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The last time the program was run or null if not present.
|
||||
*/
|
||||
public Date getLastRun() {
|
||||
return lastRun;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SQL join type.
|
||||
*/
|
||||
private enum JoinType {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
INNER,
|
||||
OUTER
|
||||
}
|
||||
|
||||
/**
|
||||
* A blackboard attribute value column.
|
||||
*/
|
||||
private enum AttributeColumn {
|
||||
value_text,
|
||||
value_int32,
|
||||
value_int64
|
||||
}
|
||||
|
||||
/**
|
||||
* The suffix joined to a key name for use as an identifier of a query.
|
||||
*/
|
||||
private static final String QUERY_SUFFIX = "_query";
|
||||
|
||||
/**
|
||||
* Creates a sql statement querying the blackboard attributes table for a
|
||||
* particular attribute type and returning a specified value. That query
|
||||
* also joins with the blackboard artifact table.
|
||||
*
|
||||
* @param joinType The type of join statement to create.
|
||||
* @param attributeColumn The blackboard attribute column that should be
|
||||
* returned.
|
||||
* @param attrType The attribute type to query for.
|
||||
* @param keyName The aliased name of the attribute to return. This
|
||||
* is also used to calculate the alias of the query
|
||||
* same as getFullKey.
|
||||
* @param bbaName The blackboard artifact table alias.
|
||||
*
|
||||
* @return The generated sql statement.
|
||||
*/
|
||||
private static String getAttributeJoin(JoinType joinType, AttributeColumn attributeColumn, ATTRIBUTE_TYPE attrType, String keyName, String bbaName) {
|
||||
String queryName = keyName + QUERY_SUFFIX;
|
||||
String innerQueryName = "inner_attribute_" + queryName;
|
||||
|
||||
return "\n" + joinType + " JOIN (\n"
|
||||
+ " SELECT \n"
|
||||
+ " " + innerQueryName + ".artifact_id,\n"
|
||||
+ " " + innerQueryName + "." + attributeColumn + " AS " + keyName + "\n"
|
||||
+ " FROM blackboard_attributes " + innerQueryName + "\n"
|
||||
+ " WHERE " + innerQueryName + ".attribute_type_id = " + attrType.getTypeID() + " -- " + attrType.name() + "\n"
|
||||
+ ") " + queryName + " ON " + queryName + ".artifact_id = " + bbaName + ".artifact_id\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a column key, creates the full name for the column key.
|
||||
*
|
||||
* @param key The column key.
|
||||
*
|
||||
* @return The full identifier for the column key.
|
||||
*/
|
||||
private static String getFullKey(String key) {
|
||||
return key + QUERY_SUFFIX + "." + key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SQL 'where' statement from a list of clauses and puts
|
||||
* parenthesis around each clause.
|
||||
*
|
||||
* @param clauses The clauses
|
||||
*
|
||||
* @return The generated 'where' statement.
|
||||
*/
|
||||
private static String getWhereString(List<String> clauses) {
|
||||
if (clauses.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> parenthesized = clauses.stream()
|
||||
.map(c -> "(" + c + ")")
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return "\nWHERE " + String.join("\n AND ", parenthesized) + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a [column] LIKE sql clause.
|
||||
*
|
||||
* @param column The column identifier.
|
||||
* @param likeString The string that will be used as column comparison.
|
||||
* @param isLike if false, the statement becomes NOT LIKE.
|
||||
*
|
||||
* @return The generated statement.
|
||||
*/
|
||||
private static String getLikeClause(String column, String likeString, boolean isLike) {
|
||||
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of the top programs used on the data source. Currently
|
||||
* determines this based off of which prefetch results return the highest
|
||||
* count.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @param count The number of programs to return.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) {
|
||||
if (dataSource == null || count <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// ntosboot should be ignored
|
||||
final String ntosBootIdentifier = "NTOSBOOT";
|
||||
// programs in windows directory to be ignored
|
||||
final String windowsDir = "/WINDOWS%";
|
||||
|
||||
final String nameParam = "name";
|
||||
final String pathParam = "path";
|
||||
final String runCountParam = "run_count";
|
||||
final String lastRunParam = "last_run";
|
||||
|
||||
String bbaQuery = "bba";
|
||||
|
||||
final String query = "SELECT\n"
|
||||
+ " " + getFullKey(nameParam) + " AS " + nameParam + ",\n"
|
||||
+ " " + getFullKey(pathParam) + " AS " + pathParam + ",\n"
|
||||
+ " MAX(" + getFullKey(runCountParam) + ") AS " + runCountParam + ",\n"
|
||||
+ " MAX(" + getFullKey(lastRunParam) + ") AS " + lastRunParam + "\n"
|
||||
+ "FROM blackboard_artifacts " + bbaQuery + "\n"
|
||||
+ getAttributeJoin(JoinType.INNER, AttributeColumn.value_text, ATTRIBUTE_TYPE.TSK_PROG_NAME, nameParam, bbaQuery)
|
||||
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_text, ATTRIBUTE_TYPE.TSK_PATH, pathParam, bbaQuery)
|
||||
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int32, ATTRIBUTE_TYPE.TSK_COUNT, runCountParam, bbaQuery)
|
||||
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int64, ATTRIBUTE_TYPE.TSK_DATETIME, lastRunParam, bbaQuery)
|
||||
+ getWhereString(Arrays.asList(
|
||||
bbaQuery + ".artifact_type_id = " + ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(),
|
||||
bbaQuery + ".data_source_obj_id = " + dataSource.getId(),
|
||||
// exclude ntosBootIdentifier from results
|
||||
getLikeClause(getFullKey(nameParam), ntosBootIdentifier, false),
|
||||
// exclude windows directory items from results
|
||||
getFullKey(pathParam) + " IS NULL OR " + getLikeClause(getFullKey(pathParam), windowsDir, false)
|
||||
))
|
||||
+ "GROUP BY " + getFullKey(nameParam) + ", " + getFullKey(pathParam) + "\n"
|
||||
+ "ORDER BY \n"
|
||||
+ " MAX(" + getFullKey(runCountParam) + ") DESC,\n"
|
||||
+ " MAX(" + getFullKey(lastRunParam) + ") DESC,\n"
|
||||
+ " " + getFullKey(nameParam) + " ASC";
|
||||
|
||||
final String errorMessage = "Unable to get top program results; returning null.";
|
||||
|
||||
ResultSetHandler<List<TopProgramsResult>> handler = (resultSet) -> {
|
||||
List<TopProgramsResult> progResults = new ArrayList<>();
|
||||
|
||||
boolean quitAtCount = false;
|
||||
|
||||
while (resultSet.next() && (!quitAtCount || progResults.size() < count)) {
|
||||
try {
|
||||
toRet.put(resultSet.getString(nameParam), resultSet.getLong(valueParam));
|
||||
long lastRunEpoch = resultSet.getLong(lastRunParam);
|
||||
Date lastRun = (resultSet.wasNull()) ? null : new Date(lastRunEpoch * 1000);
|
||||
|
||||
Long runCount = resultSet.getLong(runCountParam);
|
||||
if (resultSet.wasNull()) {
|
||||
runCount = null;
|
||||
}
|
||||
|
||||
if (lastRun != null || runCount != null) {
|
||||
quitAtCount = true;
|
||||
}
|
||||
|
||||
progResults.add(new TopProgramsResult(
|
||||
resultSet.getString(nameParam),
|
||||
resultSet.getString(pathParam),
|
||||
runCount,
|
||||
lastRun));
|
||||
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get a result pair from the result set.", ex);
|
||||
logger.log(Level.WARNING, "Failed to get a top program result from the result set.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return toRet;
|
||||
return progResults;
|
||||
};
|
||||
|
||||
String errorMessage = "Unable to get artifact type counts; returning null.";
|
||||
|
||||
return getBaseQueryResult(query, handler, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions that determine the folder name of a list of path elements. If
|
||||
* not matched, function returns null.
|
||||
*/
|
||||
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
|
||||
// handle Program Files and Program Files (x86) - if true, return the next folder
|
||||
(pathList) -> {
|
||||
if (pathList.size() < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String rootParent = pathList.get(0).toUpperCase();
|
||||
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
|
||||
return pathList.get(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// if there is a folder named "APPLICATION DATA" or "APPDATA"
|
||||
(pathList) -> {
|
||||
for (String pathEl : pathList) {
|
||||
String uppered = pathEl.toUpperCase();
|
||||
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
|
||||
return "AppData";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Determines a short folder name if any. Otherwise, returns empty string.
|
||||
*
|
||||
* @param strPath The string path.
|
||||
*
|
||||
* @return The short folder name or empty string if not found.
|
||||
*/
|
||||
static String getShortFolderName(String strPath, String applicationName) {
|
||||
if (strPath == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
|
||||
|
||||
File file = new File(strPath);
|
||||
while (file != null && StringUtils.isNotBlank(file.getName())) {
|
||||
pathEls.add(file.getName());
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
Collections.reverse(pathEls);
|
||||
|
||||
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
|
||||
String result = matchEntry.apply(pathEls);
|
||||
if (StringUtils.isNotBlank(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string which is a concatenation of the value received from
|
||||
* the result set.
|
||||
|
@ -49,10 +49,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
|
||||
* datasource.
|
||||
*/
|
||||
@Messages({
|
||||
"DataSourceSummaryDialog.window.title=Data Sources Summary",
|
||||
"DataSourceSummaryDialog.countsTab.title=Counts",
|
||||
"DataSourceSummaryDialog.detailsTab.title=Details",
|
||||
"DataSourceSummaryDialog.ingestHistoryTab.title=Ingest History"
|
||||
"DataSourceSummaryDialog.window.title=Data Sources Summary"
|
||||
})
|
||||
DataSourceSummaryDialog(Frame owner) {
|
||||
super(owner, Bundle.DataSourceSummaryDialog_window_title(), true);
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
|
||||
|
||||
import javax.swing.JTabbedPane;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
|
||||
@ -27,13 +28,20 @@ import org.sleuthkit.datamodel.DataSource;
|
||||
* DataSourceSummaryCountsPanel, DataSourceSummaryDetailsPanel, and
|
||||
* IngestJobInfoPanel.
|
||||
*/
|
||||
@Messages({
|
||||
"DataSourceSummaryTabbedPane_countsTab_title=Counts",
|
||||
"DataSourceSummaryTabbedPane_detailsTab_title=Details",
|
||||
"DataSourceSummaryTabbedPane_userActivityTab_title=User Activity",
|
||||
"DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History"
|
||||
})
|
||||
public class DataSourceSummaryTabbedPane extends JTabbedPane {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final DataSourceSummaryCountsPanel countsPanel;
|
||||
private final DataSourceSummaryDetailsPanel detailsPanel;
|
||||
private final IngestJobInfoPanel ingestHistoryPanel;
|
||||
|
||||
private final DataSourceSummaryCountsPanel countsPanel = new DataSourceSummaryCountsPanel();
|
||||
private final DataSourceSummaryDetailsPanel detailsPanel = new DataSourceSummaryDetailsPanel();
|
||||
private final DataSourceSummaryUserActivityPanel userActivityPanel = new DataSourceSummaryUserActivityPanel();
|
||||
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
|
||||
|
||||
private DataSource dataSource = null;
|
||||
|
||||
@ -41,13 +49,11 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane {
|
||||
* Constructs a tabbed pane showing the summary of a data source.
|
||||
*/
|
||||
public DataSourceSummaryTabbedPane() {
|
||||
countsPanel = new DataSourceSummaryCountsPanel();
|
||||
detailsPanel = new DataSourceSummaryDetailsPanel();
|
||||
ingestHistoryPanel = new IngestJobInfoPanel();
|
||||
|
||||
addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel);
|
||||
addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel);
|
||||
addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel);
|
||||
|
||||
addTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), detailsPanel);
|
||||
addTab(Bundle.DataSourceSummaryTabbedPane_countsTab_title(), countsPanel);
|
||||
addTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), userActivityPanel);
|
||||
addTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,6 +75,7 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane {
|
||||
|
||||
detailsPanel.setDataSource(dataSource);
|
||||
countsPanel.setDataSource(dataSource);
|
||||
userActivityPanel.setDataSource(dataSource);
|
||||
ingestHistoryPanel.setDataSource(dataSource);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="programsRunLabel" min="-2" pref="155" max="-2" attributes="0"/>
|
||||
<Component id="topProgramsScrollPane" min="-2" pref="460" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="128" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="programsRunLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="topProgramsScrollPane" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="programsRunLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryUserActivityPanel.programsRunLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</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>
|
||||
<Container class="javax.swing.JScrollPane" name="topProgramsScrollPane">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[750, 187]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="topProgramsTable">
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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.casemodule.datasourcesummary;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
|
||||
/**
|
||||
* A panel to display user activity.
|
||||
*/
|
||||
@Messages({
|
||||
"DataSourceSummaryUserActivityPanel_tab_title=User Activity",
|
||||
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program",
|
||||
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder",
|
||||
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times",
|
||||
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run"
|
||||
})
|
||||
public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault());
|
||||
private static final int TOP_PROGS_COUNT = 10;
|
||||
private static final DefaultTableCellRenderer RIGHT_ALIGNED_RENDERER = new DefaultTableCellRenderer();
|
||||
|
||||
static {
|
||||
RIGHT_ALIGNED_RENDERER.setHorizontalAlignment(JLabel.RIGHT);
|
||||
}
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* Creates new form DataSourceUserActivityPanel
|
||||
*/
|
||||
public DataSourceSummaryUserActivityPanel() {
|
||||
initComponents();
|
||||
topProgramsTable.getTableHeader().setReorderingAllowed(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The datasource currently used as the model in this panel.
|
||||
*
|
||||
* @return The datasource currently being used as the model in this panel.
|
||||
*/
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets datasource to visualize in the panel.
|
||||
*
|
||||
* @param dataSource The datasource to use in this panel.
|
||||
*/
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
if (dataSource == null || !Case.isCaseOpen()) {
|
||||
updateTopPrograms(new TopProgramsModel(null));
|
||||
} else {
|
||||
updateTopPrograms(getTopProgramsModel(dataSource));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Top Programs Table in the gui.
|
||||
*
|
||||
* @param data The data in Object[][] form to be used by the
|
||||
* DefaultTableModel.
|
||||
*/
|
||||
private void updateTopPrograms(TopProgramsModel model) {
|
||||
topProgramsTable.setModel(model);
|
||||
topProgramsTable.getColumnModel().getColumn(0).setPreferredWidth(250);
|
||||
topProgramsTable.getColumnModel().getColumn(0).setCellRenderer(PATH_CELL_RENDERER);
|
||||
topProgramsTable.getColumnModel().getColumn(1).setPreferredWidth(150);
|
||||
topProgramsTable.getColumnModel().getColumn(2).setCellRenderer(RIGHT_ALIGNED_RENDERER);
|
||||
topProgramsTable.getColumnModel().getColumn(2).setPreferredWidth(80);
|
||||
topProgramsTable.getColumnModel().getColumn(3).setPreferredWidth(150);
|
||||
topProgramsScrollPane.getVerticalScrollBar().setValue(0);
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* The counts of top programs run.
|
||||
*
|
||||
* @param selectedDataSource The DataSource.
|
||||
*
|
||||
* @return The JTable data model of counts of program runs.
|
||||
*/
|
||||
private static TopProgramsModel getTopProgramsModel(DataSource selectedDataSource) {
|
||||
List<DataSourceInfoUtilities.TopProgramsResult> topProgramList
|
||||
= DataSourceInfoUtilities.getTopPrograms(selectedDataSource, TOP_PROGS_COUNT);
|
||||
|
||||
if (topProgramList == null) {
|
||||
return new TopProgramsModel(null);
|
||||
} else {
|
||||
return new TopProgramsModel(topProgramList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A POJO defining the values present in the name cell. Defines the name as
|
||||
* well as the path for the tooltip.
|
||||
*/
|
||||
private static class ProgramNameCellValue {
|
||||
|
||||
private final String programName;
|
||||
private final String programPath;
|
||||
|
||||
ProgramNameCellValue(String programName, String programPath) {
|
||||
this.programName = programName;
|
||||
this.programPath = programPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// override so that the value in the cell reads as programName
|
||||
return programName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The program name.
|
||||
*/
|
||||
String getProgramName() {
|
||||
return programName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path of the program.
|
||||
*/
|
||||
String getProgramPath() {
|
||||
return programPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cell renderer for the first cell rendering the name as the text
|
||||
* and path as the tooltip.
|
||||
*/
|
||||
private static TableCellRenderer PATH_CELL_RENDERER = new DefaultTableCellRenderer() {
|
||||
|
||||
public Component getTableCellRendererComponent(
|
||||
JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus,
|
||||
int row, int column) {
|
||||
JLabel c = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
if (value instanceof ProgramNameCellValue) {
|
||||
ProgramNameCellValue cellValue = (ProgramNameCellValue) value;
|
||||
c.setToolTipText(cellValue.getProgramPath());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the table model for a JTable of the programs. Accepts a list of
|
||||
* TopProgramsResult objects as rows data source.
|
||||
*/
|
||||
private static class TopProgramsModel extends AbstractTableModel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// column headers for artifact counts table
|
||||
private static final String[] TOP_PROGS_COLUMN_HEADERS = new String[]{
|
||||
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(),
|
||||
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(),
|
||||
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(),
|
||||
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header()
|
||||
};
|
||||
|
||||
private final List<DataSourceInfoUtilities.TopProgramsResult> programResults;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param programResults The results to display.
|
||||
*/
|
||||
TopProgramsModel(List<DataSourceInfoUtilities.TopProgramsResult> programResults) {
|
||||
this.programResults = programResults == null ? new ArrayList<>() : Collections.unmodifiableList(programResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
return column < 0 || column >= TOP_PROGS_COLUMN_HEADERS.length ? null : TOP_PROGS_COLUMN_HEADERS[column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return programResults.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return TOP_PROGS_COLUMN_HEADERS.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (rowIndex < 0 || rowIndex >= programResults.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DataSourceInfoUtilities.TopProgramsResult result = programResults.get(rowIndex);
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return new ProgramNameCellValue(result.getProgramName(), result.getProgramPath());
|
||||
case 1:
|
||||
return DataSourceInfoUtilities.getShortFolderName(result.getProgramPath(), result.getProgramName());
|
||||
case 2:
|
||||
return result.getRunTimes();
|
||||
case 3:
|
||||
return result.getLastRun() == null ? null : DATETIME_FORMAT.format(result.getLastRun());
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
|
||||
topProgramsScrollPane = new javax.swing.JScrollPane();
|
||||
topProgramsTable = new javax.swing.JTable();
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N
|
||||
|
||||
topProgramsScrollPane.setPreferredSize(new java.awt.Dimension(750, 187));
|
||||
topProgramsScrollPane.setViewportView(topProgramsTable);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(programsRunLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 460, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap(128, Short.MAX_VALUE))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(programsRunLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JScrollPane topProgramsScrollPane;
|
||||
private javax.swing.JTable topProgramsTable;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
@ -35,8 +35,12 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.AccountDeviceInstance;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Node to represent an Account Device Instance in the CVT
|
||||
@ -49,6 +53,8 @@ final class AccountDeviceInstanceNode extends AbstractNode {
|
||||
private final CommunicationsManager commsManager;
|
||||
private final Account account;
|
||||
|
||||
private static final BlackboardAttribute.Type NAME_ATTRIBUTE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_NAME.getTypeID()));
|
||||
|
||||
AccountDeviceInstanceNode(AccountDeviceInstanceKey accountDeviceInstanceKey, CommunicationsManager commsManager) {
|
||||
super(Children.LEAF, Lookups.fixed(accountDeviceInstanceKey, commsManager));
|
||||
this.accountDeviceInstanceKey = accountDeviceInstanceKey;
|
||||
@ -122,15 +128,17 @@ final class AccountDeviceInstanceNode extends AbstractNode {
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
List<PersonaAccount> personaList;
|
||||
List<BlackboardArtifact> contactArtifactList;
|
||||
try {
|
||||
personaList = CVTPersonaCache.getPersonaAccounts(account);
|
||||
contactArtifactList = ContactCache.getContacts(account);
|
||||
} catch (ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "Failed to retrieve Persona details for node.", ex);
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
String personaName;
|
||||
if (!personaList.isEmpty()) {
|
||||
if (personaList != null && !personaList.isEmpty()) {
|
||||
personaName = personaList.get(0).getPersona().getName();
|
||||
if (personaList.size() > 1) {
|
||||
personaName += Bundle.AccountInstanceNode_Tooltip_suffix(Integer.toString(personaList.size()));
|
||||
@ -139,6 +147,19 @@ final class AccountDeviceInstanceNode extends AbstractNode {
|
||||
personaName = "None";
|
||||
}
|
||||
|
||||
return Bundle.AccountInstanceNode_Tooltip_Template(getDisplayName(), personaName);
|
||||
String contactName = getDisplayName();
|
||||
if (contactArtifactList != null && !contactArtifactList.isEmpty()) {
|
||||
try {
|
||||
BlackboardArtifact contactArtifact = contactArtifactList.get(0);
|
||||
BlackboardAttribute attribute = contactArtifact.getAttribute(NAME_ATTRIBUTE);
|
||||
if (attribute != null) {
|
||||
contactName = attribute.getValueString();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to retrive name attribute from contact artifact.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return Bundle.AccountInstanceNode_Tooltip_Template(contactName, personaName);
|
||||
}
|
||||
}
|
||||
|
166
Core/src/org/sleuthkit/autopsy/communications/ContactCache.java
Executable file
166
Core/src/org/sleuthkit/autopsy/communications/ContactCache.java
Executable file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.communications;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A singleton cache of the Contact artifacts for accounts. The map of account
|
||||
* unique ids to list of contact artifacts is stored in a LoadingCache which
|
||||
* expires after 10 of non-use.
|
||||
*
|
||||
*/
|
||||
final class ContactCache {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ContactCache.class.getName());
|
||||
|
||||
private static ContactCache instance;
|
||||
|
||||
private final LoadingCache<String, Map<String, List<BlackboardArtifact>>> accountMap;
|
||||
|
||||
/**
|
||||
* Returns the list of Contacts for the given Account.
|
||||
*
|
||||
* @param account Account instance.
|
||||
*
|
||||
* @return List of TSK_CONTACT artifacts that references the given Account.
|
||||
* An empty list is returned if no contacts are found.
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
static synchronized List<BlackboardArtifact> getContacts(Account account) throws ExecutionException {
|
||||
return getInstance().accountMap.get("realMap").get(account.getTypeSpecificID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the cache to invalidate all entries.
|
||||
*/
|
||||
static synchronized void invalidateCache() {
|
||||
getInstance().accountMap.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new instance.
|
||||
*/
|
||||
private ContactCache() {
|
||||
|
||||
accountMap = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(
|
||||
new CacheLoader<String, Map<String, List<BlackboardArtifact>>>() {
|
||||
@Override
|
||||
public Map<String, List<BlackboardArtifact>> load(String key) {
|
||||
try {
|
||||
return buildMap();
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to build account to contact map", ex);
|
||||
}
|
||||
return new HashMap<>(); // Return an empty map if there is an exception to avoid NPE and continual trying.
|
||||
}
|
||||
});
|
||||
|
||||
PropertyChangeListener ingestListener = pce -> {
|
||||
String eventType = pce.getPropertyName();
|
||||
if (eventType.equals(DATA_ADDED.toString())) {
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
|
||||
if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
|
||||
invalidateCache();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent event) -> {
|
||||
if (event.getNewValue() == null) {
|
||||
invalidateCache();
|
||||
}
|
||||
});
|
||||
|
||||
IngestManager.getInstance().addIngestModuleEventListener(EnumSet.of(DATA_ADDED), ingestListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of the cache object.
|
||||
*
|
||||
* @return AccountCache instance.
|
||||
*/
|
||||
private static synchronized ContactCache getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ContactCache();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the map of account IDs to contacts that reference them.
|
||||
*
|
||||
* @return A map of account IDs to contact artifacts.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws SQLException
|
||||
*/
|
||||
private Map<String, List<BlackboardArtifact>> buildMap() throws TskCoreException, SQLException {
|
||||
Map<String, List<BlackboardArtifact>> acctMap = new HashMap<>();
|
||||
List<BlackboardArtifact> contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
|
||||
|
||||
for (BlackboardArtifact contactArtifact : contactList) {
|
||||
List<BlackboardAttribute> contactAttributes = contactArtifact.getAttributes();
|
||||
for (BlackboardAttribute attribute : contactAttributes) {
|
||||
String typeName = attribute.getAttributeType().getTypeName();
|
||||
|
||||
if (typeName.startsWith("TSK_EMAIL")
|
||||
|| typeName.startsWith("TSK_PHONE")
|
||||
|| typeName.startsWith("TSK_NAME")
|
||||
|| typeName.startsWith("TSK_ID")) {
|
||||
String accountID = attribute.getValueString();
|
||||
List<BlackboardArtifact> artifactList = acctMap.getOrDefault(accountID, new ArrayList<>());
|
||||
|
||||
acctMap.put(accountID, artifactList);
|
||||
|
||||
if (!artifactList.contains(contactArtifact)) {
|
||||
artifactList.add(contactArtifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return acctMap;
|
||||
}
|
||||
}
|
@ -26,6 +26,9 @@ CallLogArtifactViewer_label_from=From
|
||||
CallLogArtifactViewer_label_to=To
|
||||
CallLogArtifactViewer_suffix_local=(Local)
|
||||
CallLogArtifactViewer_value_unknown=Unknown
|
||||
#{0} - contact name
|
||||
CommunicationArtifactViewerHelper_contact_label=Contact: {0}
|
||||
CommunicationArtifactViewerHelper_contact_label_unknown=Unknown
|
||||
CommunicationArtifactViewerHelper_menuitem_copy=Copy
|
||||
CommunicationArtifactViewerHelper_persona_button_create=Create
|
||||
CommunicationArtifactViewerHelper_persona_button_view=View
|
||||
@ -61,6 +64,7 @@ DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
MessageAccountPanel_button_create_label=Create
|
||||
MessageAccountPanel_button_view_label=View
|
||||
MessageAccountPanel_contact_label=Contact:
|
||||
MessageAccountPanel_no_matches=No matches found.
|
||||
MessageAccountPanel_persona_label=Persona:
|
||||
MessageAccountPanel_unknown_label=Unknown
|
||||
|
@ -36,6 +36,7 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.guiutils.ContactCache;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -66,7 +67,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
|
||||
private GridBagLayout m_gridBagLayout = new GridBagLayout();
|
||||
private GridBagConstraints m_constraints = new GridBagConstraints();
|
||||
|
||||
|
||||
private PersonaAccountFetcher currentAccountFetcher = null;
|
||||
|
||||
/**
|
||||
@ -106,9 +107,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
// update the view with the call log data
|
||||
if (callLogViewData != null) {
|
||||
List<AccountPersonaSearcherData> personaSearchDataList = updateView(callLogViewData);
|
||||
if(!personaSearchDataList.isEmpty()) {
|
||||
if (!personaSearchDataList.isEmpty()) {
|
||||
currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this);
|
||||
currentAccountFetcher.execute();
|
||||
currentAccountFetcher.execute();
|
||||
} else {
|
||||
currentAccountFetcher = null;
|
||||
}
|
||||
@ -143,6 +144,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
String fromAccountIdentifier = null;
|
||||
String toAccountIdentifier = null;
|
||||
List<String> otherParties = null;
|
||||
List<String> toContactNames = null;
|
||||
List<String> fromContactNames = null;
|
||||
|
||||
Content dataSource = artifact.getDataSource();
|
||||
String deviceId = ((DataSource) dataSource).getDeviceId();
|
||||
@ -186,6 +189,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
String fromAccountAttrValue = fromAccountAttr.getValueString();
|
||||
if (fromAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
|
||||
fromAccountIdentifier = fromAccountAttrValue;
|
||||
fromContactNames = ContactCache.getContactNameList(fromAccountIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +199,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
String toAccountAttrValue = StringUtils.trim(numbers[0]);
|
||||
if (toAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
|
||||
toAccountIdentifier = toAccountAttrValue;
|
||||
toContactNames = ContactCache.getContactNameList(toAccountIdentifier);
|
||||
}
|
||||
|
||||
// if more than one To address, then stick the rest of them in the
|
||||
@ -228,6 +233,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
}
|
||||
|
||||
callLogViewData.setOtherAttributes(extractOtherAttributes(artifact));
|
||||
|
||||
callLogViewData.setFromContactNameList(fromContactNames);
|
||||
callLogViewData.setToContactNameList(toContactNames);
|
||||
}
|
||||
|
||||
return callLogViewData;
|
||||
@ -237,9 +245,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* Extract the call time and duration from the artifact and saves in the
|
||||
* CallLogViewData.
|
||||
*
|
||||
* @param artifact Call log artifact.
|
||||
* @param artifact Call log artifact.
|
||||
* @param callLogViewData CallLogViewData object to save the time & duration
|
||||
* in.
|
||||
* in.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@ -290,7 +298,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* Update the viewer with the call log data.
|
||||
*
|
||||
* @param callLogViewData Call log data to update the view with.
|
||||
*
|
||||
*
|
||||
* @return List of AccountPersonaSearcherData objects.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
@ -312,8 +320,13 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData);
|
||||
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
|
||||
|
||||
List<String> contactNames = callLogViewData.getFromContactNameList();
|
||||
for (String name : contactNames) {
|
||||
CommunicationArtifactViewerHelper.addContactRow(this, m_gridBagLayout, m_constraints, name);
|
||||
}
|
||||
|
||||
// show persona
|
||||
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount()));
|
||||
dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount()));
|
||||
} else {
|
||||
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown());
|
||||
}
|
||||
@ -324,8 +337,13 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
String accountDisplayString = getAccountDisplayString(callLogViewData.getToAccount(), callLogViewData);
|
||||
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
|
||||
|
||||
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount()));
|
||||
|
||||
List<String> contactNames = callLogViewData.getToContactNameList();
|
||||
for (String name : contactNames) {
|
||||
CommunicationArtifactViewerHelper.addContactRow(this, m_gridBagLayout, m_constraints, name);
|
||||
}
|
||||
|
||||
dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount()));
|
||||
|
||||
} else {
|
||||
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown());
|
||||
}
|
||||
@ -335,7 +353,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_to());
|
||||
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, otherParty);
|
||||
|
||||
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty));
|
||||
dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty));
|
||||
}
|
||||
|
||||
updateMetadataView(callLogViewData);
|
||||
@ -353,7 +371,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
this.setLayout(m_gridBagLayout);
|
||||
this.revalidate();
|
||||
this.repaint();
|
||||
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@ -441,7 +459,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* local account, if it is known. If it is, it appends a "(Local)" suffix to
|
||||
* account display string.
|
||||
*
|
||||
* @param accountIdentifier Account identifier to check.
|
||||
* @param accountIdentifier Account identifier to check.
|
||||
* @param callLogViewDataNew Call log data which may have the lock account.
|
||||
*
|
||||
* @return Account string to display.
|
||||
@ -474,7 +492,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
private void resetComponent() {
|
||||
|
||||
// cancel any outstanding persona searching threads.
|
||||
if(currentAccountFetcher != null && !currentAccountFetcher.isDone()) {
|
||||
if (currentAccountFetcher != null && !currentAccountFetcher.isDone()) {
|
||||
currentAccountFetcher.cancel(true);
|
||||
currentAccountFetcher = null;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -47,11 +48,14 @@ final class CallLogViewData {
|
||||
|
||||
private String dataSourceName = null;
|
||||
|
||||
private List<String> toContactNameList = null;
|
||||
private List<String> fromContactNameList = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param fromAccount From account identifier, may be null;
|
||||
* @param toAccount To account identifier, may be null;
|
||||
* @param toAccount To account identifier, may be null;
|
||||
*/
|
||||
CallLogViewData(String fromAccount, String toAccount) {
|
||||
this(fromAccount, toAccount, null);
|
||||
@ -61,8 +65,8 @@ final class CallLogViewData {
|
||||
* Constructor.
|
||||
*
|
||||
* @param fromAccount From account identifier, may be null;
|
||||
* @param toAccount To account identifier, may be null;
|
||||
* @param direction Direction, may be null.
|
||||
* @param toAccount To account identifier, may be null;
|
||||
* @param direction Direction, may be null.
|
||||
*/
|
||||
CallLogViewData(String fromAccount, String toAccount, String direction) {
|
||||
this.fromAccount = fromAccount;
|
||||
@ -146,4 +150,28 @@ final class CallLogViewData {
|
||||
this.localAccountId = localAccountId;
|
||||
}
|
||||
|
||||
public void setToContactNameList(List<String> contactNameList) {
|
||||
if (contactNameList != null) {
|
||||
this.toContactNameList = new ArrayList<>(contactNameList);
|
||||
} else {
|
||||
this.toContactNameList = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getToContactNameList() {
|
||||
return Collections.unmodifiableList(this.toContactNameList);
|
||||
}
|
||||
|
||||
public void setFromContactNameList(List<String> contactNameList) {
|
||||
if (contactNameList != null) {
|
||||
this.fromContactNameList = new ArrayList<>(contactNameList);
|
||||
} else {
|
||||
this.fromContactNameList = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getFromContactNameList() {
|
||||
return Collections.unmodifiableList(this.fromContactNameList);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -378,7 +378,7 @@ final class CommunicationArtifactViewerHelper {
|
||||
"CommunicationArtifactViewerHelper_persona_button_view=View",
|
||||
"CommunicationArtifactViewerHelper_persona_button_create=Create"
|
||||
})
|
||||
|
||||
|
||||
static List<AccountPersonaSearcherData> addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) {
|
||||
List<AccountPersonaSearcherData> dataList = new ArrayList<>();
|
||||
|
||||
@ -428,6 +428,35 @@ final class CommunicationArtifactViewerHelper {
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a contact row to the panel.
|
||||
*
|
||||
* @param panel Panel to update.
|
||||
* @param gridbagLayout Layout to use.
|
||||
* @param constraints Constrains to use.
|
||||
* @param contactId Contact name to display.
|
||||
*
|
||||
* @return A JLabel with the contact information.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"#{0} - contact name",
|
||||
"CommunicationArtifactViewerHelper_contact_label=Contact: {0}",
|
||||
"CommunicationArtifactViewerHelper_contact_label_unknown=Unknown"
|
||||
})
|
||||
static JLabel addContactRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String contactId) {
|
||||
// Increase the y value because we are not calling the addKey
|
||||
constraints.gridy++;
|
||||
//Don't change the origian constraints, just make a copy to modify
|
||||
GridBagConstraints indentedConstraints = (GridBagConstraints) constraints.clone();
|
||||
|
||||
// Add an indent to match persona labels
|
||||
indentedConstraints.insets = new java.awt.Insets(0, 2 * LEFT_INSET, 0, 0);
|
||||
|
||||
String contactInfo = Bundle.CommunicationArtifactViewerHelper_contact_label(contactId != null && !contactId.isEmpty() ? contactId : Bundle.CommunicationArtifactViewerHelper_contact_label_unknown());
|
||||
|
||||
return addValueAtCol(panel, gridbagLayout, indentedConstraints, contactInfo, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for mouse click event. Attaches a 'Copy' menu item to right
|
||||
|
@ -16,7 +16,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
@ -39,6 +38,7 @@ import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
|
||||
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog;
|
||||
@ -46,8 +46,10 @@ import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialogCallb
|
||||
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
|
||||
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.guiutils.ContactCache;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.CommunicationsManager;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@ -113,13 +115,24 @@ final class MessageAccountPanel extends JPanel {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
|
||||
if (personAccounts != null && !personAccounts.isEmpty()) {
|
||||
for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) {
|
||||
dataList.add(new AccountContainer(account, personaAccount));
|
||||
List<BlackboardArtifact> contactList = ContactCache.getContacts(account);
|
||||
BlackboardArtifact contact = null;
|
||||
|
||||
if (contactList != null && !contactList.isEmpty()) {
|
||||
contact = contactList.get(0);
|
||||
}
|
||||
|
||||
if (CentralRepository.isEnabled()) {
|
||||
Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
|
||||
if (personAccounts != null && !personAccounts.isEmpty()) {
|
||||
for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) {
|
||||
dataList.add(new AccountContainer(account, personaAccount, contact));
|
||||
}
|
||||
} else {
|
||||
dataList.add(new AccountContainer(account, null, contact));
|
||||
}
|
||||
} else {
|
||||
dataList.add(new AccountContainer(account, null));
|
||||
dataList.add(new AccountContainer(account, null, contact));
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,8 +140,7 @@ final class MessageAccountPanel extends JPanel {
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"MessageAccountPanel_no_matches=No matches found.",
|
||||
})
|
||||
"MessageAccountPanel_no_matches=No matches found.",})
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
@ -199,6 +211,7 @@ final class MessageAccountPanel extends JPanel {
|
||||
for (AccountContainer o : data) {
|
||||
group.addGap(5)
|
||||
.addComponent(o.getAccountLabel())
|
||||
.addGroup(o.getContactLineVerticalGroup(layout))
|
||||
.addGroup(o.getPersonLineVerticalGroup(layout));
|
||||
}
|
||||
|
||||
@ -234,6 +247,7 @@ final class MessageAccountPanel extends JPanel {
|
||||
group.addGap(10);
|
||||
for (AccountContainer o : data) {
|
||||
pgroup.addGroup(o.getPersonaSequentialGroup(layout));
|
||||
pgroup.addGroup(o.getContactSequentialGroup(layout));
|
||||
}
|
||||
group.addGap(10)
|
||||
.addGroup(pgroup)
|
||||
@ -253,10 +267,13 @@ final class MessageAccountPanel extends JPanel {
|
||||
|
||||
private final Account account;
|
||||
private Persona persona = null;
|
||||
private final String contactName;
|
||||
|
||||
private JLabel accountLabel;
|
||||
private JLabel personaHeader;
|
||||
private JLabel personaDisplayName;
|
||||
private JLabel contactHeader;
|
||||
private JLabel contactDisplayName;
|
||||
private JButton button;
|
||||
|
||||
/**
|
||||
@ -265,16 +282,22 @@ final class MessageAccountPanel extends JPanel {
|
||||
* @param account
|
||||
* @param personaAccount
|
||||
*/
|
||||
AccountContainer(Account account, PersonaAccount personaAccount) {
|
||||
AccountContainer(Account account, PersonaAccount personaAccount, BlackboardArtifact contactArtifact) throws TskCoreException {
|
||||
if (contactArtifact != null && contactArtifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
|
||||
throw new IllegalArgumentException("Failed to create AccountContainer object, passed in artifact was not a TSK_CONTACT");
|
||||
}
|
||||
|
||||
this.account = account;
|
||||
this.persona = personaAccount != null ? personaAccount.getPersona() : null;
|
||||
this.contactName = getNameFromContactArtifact(contactArtifact);
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"MessageAccountPanel_persona_label=Persona:",
|
||||
"MessageAccountPanel_unknown_label=Unknown",
|
||||
"MessageAccountPanel_button_view_label=View",
|
||||
"MessageAccountPanel_button_create_label=Create"
|
||||
"MessageAccountPanel_button_create_label=Create",
|
||||
"MessageAccountPanel_contact_label=Contact:"
|
||||
})
|
||||
/**
|
||||
* Swing components will not be initialized until this method is called.
|
||||
@ -282,16 +305,29 @@ final class MessageAccountPanel extends JPanel {
|
||||
private void initalizeSwingControls() {
|
||||
accountLabel = new JLabel();
|
||||
personaHeader = new JLabel(Bundle.MessageAccountPanel_persona_label());
|
||||
contactHeader = new JLabel(Bundle.MessageAccountPanel_contact_label());
|
||||
personaDisplayName = new JLabel();
|
||||
contactDisplayName = new JLabel();
|
||||
button = new JButton();
|
||||
button.addActionListener(new PersonaButtonListener(this));
|
||||
|
||||
accountLabel.setText(account.getTypeSpecificID());
|
||||
|
||||
contactDisplayName.setText(contactName);
|
||||
personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label());
|
||||
button.setText(persona != null ? Bundle.MessageAccountPanel_button_view_label() : Bundle.MessageAccountPanel_button_create_label());
|
||||
}
|
||||
|
||||
private String getNameFromContactArtifact(BlackboardArtifact contactArtifact) throws TskCoreException {
|
||||
if (contactArtifact != null) {
|
||||
BlackboardAttribute attribute = contactArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
|
||||
if (attribute != null) {
|
||||
return attribute.getValueString();
|
||||
}
|
||||
}
|
||||
|
||||
return Bundle.MessageAccountPanel_unknown_label();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new persona for this object and update the controls.
|
||||
*
|
||||
@ -365,6 +401,17 @@ final class MessageAccountPanel extends JPanel {
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private SequentialGroup getContactSequentialGroup(GroupLayout layout) {
|
||||
SequentialGroup group = layout.createSequentialGroup();
|
||||
|
||||
group
|
||||
.addComponent(contactHeader)
|
||||
.addPreferredGap(ComponentPlacement.RELATED)
|
||||
.addComponent(contactDisplayName);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the vertical layout code for the persona line.
|
||||
@ -379,6 +426,12 @@ final class MessageAccountPanel extends JPanel {
|
||||
.addComponent(personaDisplayName)
|
||||
.addComponent(button);
|
||||
}
|
||||
|
||||
private ParallelGroup getContactLineVerticalGroup(GroupLayout layout) {
|
||||
return layout.createParallelGroup(Alignment.BASELINE)
|
||||
.addComponent(contactHeader)
|
||||
.addComponent(contactDisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -447,10 +447,8 @@ public class MessageArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
resetComponent();
|
||||
}
|
||||
|
||||
msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, CentralRepository.isEnabled());
|
||||
if(CentralRepository.isEnabled()) {
|
||||
accountsPanel.setArtifact(artifact);
|
||||
}
|
||||
msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, true);
|
||||
accountsPanel.setArtifact(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
|
192
Core/src/org/sleuthkit/autopsy/guiutils/ContactCache.java
Executable file
192
Core/src/org/sleuthkit/autopsy/guiutils/ContactCache.java
Executable file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.guiutils;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A singleton cache of the Contact artifacts for accounts. The map of account
|
||||
* unique ids to list of contact artifacts is stored in a LoadingCache which
|
||||
* expires after 10 of non-use.
|
||||
*
|
||||
*/
|
||||
public final class ContactCache {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ContactCache.class.getName());
|
||||
|
||||
private static ContactCache instance;
|
||||
|
||||
private final LoadingCache<String, Map<String, List<BlackboardArtifact>>> accountMap;
|
||||
|
||||
/**
|
||||
* Returns the list of Contacts for the given Account.
|
||||
*
|
||||
* @param account Account instance.
|
||||
*
|
||||
* @return List of TSK_CONTACT artifacts that references the given Account.
|
||||
* An empty list is returned if no contacts are found.
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public static synchronized List<BlackboardArtifact> getContacts(Account account) throws ExecutionException {
|
||||
return getInstance().accountMap.get("realMap").get(account.getTypeSpecificID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Contact TSK_NAME values for the given account type
|
||||
* specific id.
|
||||
*
|
||||
* @param accountTypeSpecificID Account type specific id
|
||||
*
|
||||
* @return List of contact string names or empty list if none were found.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public static synchronized List<String> getContactNameList(String accountTypeSpecificID) throws TskCoreException {
|
||||
List<BlackboardArtifact> contactList;
|
||||
try {
|
||||
contactList = getInstance().accountMap.get("realMap").get(accountTypeSpecificID);
|
||||
} catch (ExecutionException ex) {
|
||||
throw new TskCoreException("Unable to get contact list from cache", ex);
|
||||
}
|
||||
List<String> contactNameList = new ArrayList<>();
|
||||
|
||||
if (contactList != null) {
|
||||
for (BlackboardArtifact artifact : contactList) {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_NAME.getTypeID())));
|
||||
if (attribute != null && !contactNameList.contains(attribute.getValueString())) {
|
||||
contactNameList.add(attribute.getValueString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return contactNameList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the cache to invalidate all entries.
|
||||
*/
|
||||
static synchronized void invalidateCache() {
|
||||
getInstance().accountMap.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new instance.
|
||||
*/
|
||||
private ContactCache() {
|
||||
|
||||
accountMap = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(
|
||||
new CacheLoader<String, Map<String, List<BlackboardArtifact>>>() {
|
||||
@Override
|
||||
public Map<String, List<BlackboardArtifact>> load(String key) {
|
||||
try {
|
||||
return buildMap();
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to build account to contact map", ex);
|
||||
}
|
||||
return new HashMap<>(); // Return an empty map if there is an exception to avoid NPE and continual trying.
|
||||
}
|
||||
});
|
||||
|
||||
PropertyChangeListener ingestListener = pce -> {
|
||||
String eventType = pce.getPropertyName();
|
||||
if (eventType.equals(DATA_ADDED.toString())) {
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
|
||||
if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
|
||||
invalidateCache();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IngestManager.getInstance().addIngestModuleEventListener(EnumSet.of(DATA_ADDED), ingestListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of the cache object.
|
||||
*
|
||||
* @return AccountCache instance.
|
||||
*/
|
||||
private static synchronized ContactCache getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ContactCache();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the map of account IDs to contacts that reference them.
|
||||
*
|
||||
* @return A map of account IDs to contact artifacts.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws SQLException
|
||||
*/
|
||||
private Map<String, List<BlackboardArtifact>> buildMap() throws TskCoreException, SQLException {
|
||||
Map<String, List<BlackboardArtifact>> acctMap = new HashMap<>();
|
||||
List<BlackboardArtifact> contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
|
||||
|
||||
for (BlackboardArtifact contactArtifact : contactList) {
|
||||
List<BlackboardAttribute> contactAttributes = contactArtifact.getAttributes();
|
||||
for (BlackboardAttribute attribute : contactAttributes) {
|
||||
String typeName = attribute.getAttributeType().getTypeName();
|
||||
|
||||
if (typeName.startsWith("TSK_EMAIL")
|
||||
|| typeName.startsWith("TSK_PHONE")
|
||||
|| typeName.startsWith("TSK_NAME")
|
||||
|| typeName.startsWith("TSK_ID")) {
|
||||
String accountID = attribute.getValueString();
|
||||
List<BlackboardArtifact> artifactList = acctMap.get(accountID);
|
||||
if (artifactList == null) {
|
||||
artifactList = new ArrayList<>();
|
||||
acctMap.put(accountID, artifactList);
|
||||
}
|
||||
if (!artifactList.contains(contactArtifact)) {
|
||||
artifactList.add(contactArtifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return acctMap;
|
||||
}
|
||||
}
|
@ -2,7 +2,14 @@ OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy.
|
||||
OpenIDE-Module-Name=RecentActivity
|
||||
OpenIDE-Module-Short-Description=Recent Activity finder ingest module
|
||||
Chrome.moduleName=Chrome
|
||||
Browser.name.Microsoft.Edge=Microsoft Edge
|
||||
Browser.name.Yandex=Yandex
|
||||
Browser.name.Opera=Opera
|
||||
Browser.name.SalamWeb=SalamWeb
|
||||
Browser.name.UC.Browser=UC Browser
|
||||
Browser.name.Brave=Brave
|
||||
Browser.name.Google.Chrome=Google Chrome
|
||||
Chrome.moduleName=Chromium
|
||||
Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files.
|
||||
Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files.
|
||||
Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1}
|
||||
|
@ -5,15 +5,10 @@ ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for an
|
||||
ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.
|
||||
ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s.
|
||||
ChromeCacheExtractor.moduleName=ChromeCacheExtractor
|
||||
# {0} - module name
|
||||
# {1} - row number
|
||||
# {2} - table length
|
||||
# {3} - cache path
|
||||
ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}
|
||||
DataSourceUsage_AndroidMedia=Android Media Card
|
||||
DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card
|
||||
DataSourceUsage_FlashDrive=Flash Drive
|
||||
# {0} - OS name
|
||||
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
|
||||
DataSourceUsageAnalyzer.parentModuleName=Recent Activity
|
||||
Extract.indexError.message=Failed to index artifact for keyword search.
|
||||
@ -77,10 +72,17 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files
|
||||
ExtractZone_Restricted=Restricted Sites Zone
|
||||
ExtractZone_Trusted=Trusted Sites Zone
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy.
|
||||
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy.
|
||||
OpenIDE-Module-Name=RecentActivity
|
||||
OpenIDE-Module-Short-Description=Recent Activity finder ingest module
|
||||
Chrome.moduleName=Chrome
|
||||
Browser.name.Microsoft.Edge=Microsoft Edge
|
||||
Browser.name.Yandex=Yandex
|
||||
Browser.name.Opera=Opera
|
||||
Browser.name.SalamWeb=SalamWeb
|
||||
Browser.name.UC.Browser=UC Browser
|
||||
Browser.name.Brave=Brave
|
||||
Browser.name.Google.Chrome=Google Chrome
|
||||
Chrome.moduleName=Chromium
|
||||
Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files.
|
||||
Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files.
|
||||
Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1}
|
||||
@ -148,14 +150,14 @@ Firefox.getDlV24.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{
|
||||
Firefox.getDlV24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web download artifacts.
|
||||
Progress_Message_Analyze_Registry=Analyzing Registry Files
|
||||
Progress_Message_Analyze_Usage=Data Sources Usage Analysis
|
||||
Progress_Message_Chrome_AutoFill=Chrome Auto Fill
|
||||
Progress_Message_Chrome_Bookmarks=Chrome Bookmarks
|
||||
Progress_Message_Chrome_AutoFill=Chrome Auto Fill Browser {0}
|
||||
Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Browser {0}
|
||||
Progress_Message_Chrome_Cache=Chrome Cache
|
||||
Progress_Message_Chrome_Cookies=Chrome Cookies
|
||||
Progress_Message_Chrome_Downloads=Chrome Downloads
|
||||
Progress_Message_Chrome_Cookies=Chrome Cookies Browser {0}
|
||||
Progress_Message_Chrome_Downloads=Chrome Downloads Browser {0}
|
||||
Progress_Message_Chrome_FormHistory=Chrome Form History
|
||||
Progress_Message_Chrome_History=Chrome History
|
||||
Progress_Message_Chrome_Logins=Chrome Logins
|
||||
Progress_Message_Chrome_History=Chrome History Browser {0}
|
||||
Progress_Message_Chrome_Logins=Chrome Logins Browser {0}
|
||||
Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks
|
||||
Progress_Message_Edge_Cookies=Microsoft Edge Cookies
|
||||
Progress_Message_Edge_History=Microsoft Edge History
|
||||
@ -209,7 +211,6 @@ Recently_Used_Artifacts_Winrar=Recently opened according to WinRAR MRU
|
||||
Registry_System_Bam=Recently Executed according to Background Activity Moderator (BAM)
|
||||
RegRipperFullNotFound=Full version RegRipper executable not found.
|
||||
RegRipperNotFound=Autopsy RegRipper executable not found.
|
||||
# {0} - file name
|
||||
SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}.
|
||||
SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine
|
||||
SearchEngineURLQueryAnalyzer.engineName.none=NONE
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.recentactivity;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
@ -58,9 +59,9 @@ import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper;
|
||||
|
||||
/**
|
||||
* Chrome recent activity extraction
|
||||
* Chromium recent activity extraction
|
||||
*/
|
||||
class Chrome extends Extract {
|
||||
class Chromium extends Extract {
|
||||
|
||||
private static final String HISTORY_QUERY = "SELECT urls.url, urls.title, urls.visit_count, urls.typed_count, " //NON-NLS
|
||||
+ "last_visit_time, urls.hidden, visits.visit_time, (SELECT urls.url FROM urls WHERE urls.id=visits.url) AS from_visit, visits.transition FROM urls, visits WHERE urls.id = visits.url"; //NON-NLS
|
||||
@ -83,20 +84,31 @@ class Chrome extends Extract {
|
||||
private final Logger logger = Logger.getLogger(this.getClass().getName());
|
||||
private Content dataSource;
|
||||
private IngestJobContext context;
|
||||
|
||||
private static final Map<String, String> BROWSERS_MAP = ImmutableMap.<String, String>builder()
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Microsoft.Edge"), "Microsoft/Edge")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Yandex"), "YandexBrowser")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Opera"), "Opera Software")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.SalamWeb"), "SalamWeb")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.UC.Browser"), "UCBrowser")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Brave"), "BraveSoftware")
|
||||
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Google.Chrome"), "Chrome")
|
||||
.build();
|
||||
|
||||
|
||||
@Messages({
|
||||
"Progress_Message_Chrome_History=Chrome History",
|
||||
"Progress_Message_Chrome_Bookmarks=Chrome Bookmarks",
|
||||
"Progress_Message_Chrome_Cookies=Chrome Cookies",
|
||||
"Progress_Message_Chrome_Downloads=Chrome Downloads",
|
||||
"Progress_Message_Chrome_History=Chrome History Browser {0}",
|
||||
"Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Browser {0}",
|
||||
"Progress_Message_Chrome_Cookies=Chrome Cookies Browser {0}",
|
||||
"Progress_Message_Chrome_Downloads=Chrome Downloads Browser {0}",
|
||||
"Progress_Message_Chrome_FormHistory=Chrome Form History",
|
||||
"Progress_Message_Chrome_AutoFill=Chrome Auto Fill",
|
||||
"Progress_Message_Chrome_Logins=Chrome Logins",
|
||||
"Progress_Message_Chrome_AutoFill=Chrome Auto Fill Browser {0}",
|
||||
"Progress_Message_Chrome_Logins=Chrome Logins Browser {0}",
|
||||
"Progress_Message_Chrome_Cache=Chrome Cache",
|
||||
})
|
||||
|
||||
Chrome() {
|
||||
moduleName = NbBundle.getMessage(Chrome.class, "Chrome.moduleName");
|
||||
Chromium() {
|
||||
moduleName = NbBundle.getMessage(Chromium.class, "Chrome.moduleName");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -105,55 +117,60 @@ class Chrome extends Extract {
|
||||
this.context = context;
|
||||
dataFound = false;
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_History());
|
||||
this.getHistory();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_Bookmarks());
|
||||
this.getBookmark();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_Cookies());
|
||||
this.getCookie();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_Logins());
|
||||
this.getLogins();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_AutoFill());
|
||||
this.getAutofill();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_Downloads());
|
||||
this.getDownload();
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
for (Map.Entry<String, String> browser : BROWSERS_MAP.entrySet()) {
|
||||
String browserName = browser.getKey();
|
||||
String browserLocation = browser.getValue();
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_History", browserName));
|
||||
this.getHistory(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Bookmarks", browserName));
|
||||
this.getBookmark(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Cookies", browserName));
|
||||
this.getCookie(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Logins", browserName));
|
||||
this.getLogins(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_AutoFill", browserName));
|
||||
this.getAutofill(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Downloads", browserName));
|
||||
this.getDownload(browser.getKey(), browser.getValue());
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
progressBar.progress(Bundle.Progress_Message_Chrome_Cache());
|
||||
ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar);
|
||||
chromeCacheExtractor.processCaches();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for history databases and add artifacts
|
||||
*/
|
||||
private void getHistory() {
|
||||
private void getHistory(String browser, String browserLocation) {
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> historyFiles;
|
||||
try {
|
||||
historyFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
|
||||
historyFiles = fileManager.findFiles(dataSource, "%History%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -179,10 +196,11 @@ class Chrome extends Extract {
|
||||
dataFound = true;
|
||||
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
|
||||
int j = 0;
|
||||
while (j < historyFiles.size()) {
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + historyFiles.get(j).getName() + j + ".db"; //NON-NLS
|
||||
final AbstractFile historyFile = historyFiles.get(j++);
|
||||
if (historyFile.getSize() == 0) {
|
||||
while (j < allocatedHistoryFiles.size()) {
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS
|
||||
final AbstractFile historyFile = allocatedHistoryFiles.get(j++);
|
||||
if ((historyFile.getSize() == 0) || (historyFile.getName().toLowerCase().contains("-slack"))
|
||||
|| (historyFile.getName().toLowerCase().contains("cache")) || (historyFile.getName().toLowerCase().contains("media"))) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
@ -223,8 +241,7 @@ class Chrome extends Extract {
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
(NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
|
||||
@ -245,11 +262,11 @@ class Chrome extends Extract {
|
||||
/**
|
||||
* Search for bookmark files and make artifacts.
|
||||
*/
|
||||
private void getBookmark() {
|
||||
private void getBookmark(String browser, String browserLocation) {
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> bookmarkFiles;
|
||||
try {
|
||||
bookmarkFiles = fileManager.findFiles(dataSource, "Bookmarks", "Chrome"); //NON-NLS
|
||||
bookmarkFiles = fileManager.findFiles(dataSource, "%Bookmarks%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -268,10 +285,12 @@ class Chrome extends Extract {
|
||||
|
||||
while (j < bookmarkFiles.size()) {
|
||||
AbstractFile bookmarkFile = bookmarkFiles.get(j++);
|
||||
if (bookmarkFile.getSize() == 0) {
|
||||
if ((bookmarkFile.getSize() == 0) || (bookmarkFile.getName().toLowerCase().contains("-slack"))
|
||||
|| (bookmarkFile.getName().toLowerCase().contains("extras")) || (bookmarkFile.getName().toLowerCase().contains("log"))
|
||||
|| (bookmarkFile.getName().toLowerCase().contains("backup")) || (bookmarkFile.getName().toLowerCase().contains("visualized"))) {
|
||||
continue;
|
||||
}
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
|
||||
try {
|
||||
ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStreamException ex) {
|
||||
@ -359,8 +378,7 @@ class Chrome extends Extract {
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600")));
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), domain));
|
||||
bbart.addAttributes(bbattributes);
|
||||
@ -381,12 +399,12 @@ class Chrome extends Extract {
|
||||
/**
|
||||
* Queries for cookie files and adds artifacts
|
||||
*/
|
||||
private void getCookie() {
|
||||
private void getCookie(String browser, String browserLocation) {
|
||||
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> cookiesFiles;
|
||||
try {
|
||||
cookiesFiles = fileManager.findFiles(dataSource, "Cookies", "Chrome"); //NON-NLS
|
||||
cookiesFiles = fileManager.findFiles(dataSource, "%Cookies%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -404,10 +422,10 @@ class Chrome extends Extract {
|
||||
int j = 0;
|
||||
while (j < cookiesFiles.size()) {
|
||||
AbstractFile cookiesFile = cookiesFiles.get(j++);
|
||||
if (cookiesFile.getSize() == 0) {
|
||||
if ((cookiesFile.getSize() == 0) || (cookiesFile.getName().toLowerCase().contains("-slack"))) {
|
||||
continue;
|
||||
}
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
|
||||
try {
|
||||
ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStreamException ex) {
|
||||
@ -447,8 +465,7 @@ class Chrome extends Extract {
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
String domain = result.get("host_key").toString(); //NON-NLS
|
||||
domain = domain.replaceFirst("^\\.+(?!$)", "");
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
|
||||
@ -471,11 +488,11 @@ class Chrome extends Extract {
|
||||
/**
|
||||
* Queries for download files and adds artifacts
|
||||
*/
|
||||
private void getDownload() {
|
||||
private void getDownload(String browser, String browserLocation) {
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> downloadFiles;
|
||||
try {
|
||||
downloadFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
|
||||
downloadFiles = fileManager.findFiles(dataSource, "%History%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -493,10 +510,12 @@ class Chrome extends Extract {
|
||||
int j = 0;
|
||||
while (j < downloadFiles.size()) {
|
||||
AbstractFile downloadFile = downloadFiles.get(j++);
|
||||
if (downloadFile.getSize() == 0) {
|
||||
if ((downloadFile.getSize() == 0) || (downloadFile.getName().toLowerCase().contains("-slack"))
|
||||
|| (downloadFile.getName().toLowerCase().contains("cache"))) {
|
||||
continue;
|
||||
}
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
|
||||
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
|
||||
try {
|
||||
ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStreamException ex) {
|
||||
@ -552,8 +571,7 @@ class Chrome extends Extract {
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), domain));
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
|
||||
BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
|
||||
if (webDownloadArtifact != null) {
|
||||
@ -561,7 +579,7 @@ class Chrome extends Extract {
|
||||
|
||||
// find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
|
||||
try {
|
||||
String normalizedFullPath = FilenameUtils.normalize(fullPath, true);
|
||||
String normalizedFullPath = FilenameUtils.normalize(fullPath, true);
|
||||
for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) {
|
||||
BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
|
||||
associatedObjectArtifact.addAttribute(
|
||||
@ -588,12 +606,12 @@ class Chrome extends Extract {
|
||||
/**
|
||||
* Gets user logins from Login Data sqlite database
|
||||
*/
|
||||
private void getLogins() {
|
||||
private void getLogins(String browser, String browserLocation) {
|
||||
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> loginDataFiles;
|
||||
try {
|
||||
loginDataFiles = fileManager.findFiles(dataSource, "Login Data", "Chrome"); //NON-NLS
|
||||
loginDataFiles = fileManager.findFiles(dataSource, "%Login Data%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -611,10 +629,10 @@ class Chrome extends Extract {
|
||||
int j = 0;
|
||||
while (j < loginDataFiles.size()) {
|
||||
AbstractFile loginDataFile = loginDataFiles.get(j++);
|
||||
if (loginDataFile.getSize() == 0) {
|
||||
if ((loginDataFile.getSize() == 0) || (loginDataFile.getName().toLowerCase().contains("-slack"))) {
|
||||
continue;
|
||||
}
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
|
||||
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
|
||||
try {
|
||||
ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStreamException ex) {
|
||||
@ -661,6 +679,9 @@ class Chrome extends Extract {
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS
|
||||
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
|
||||
BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes);
|
||||
if (bbart != null) {
|
||||
bbartifacts.add(bbart);
|
||||
@ -679,12 +700,12 @@ class Chrome extends Extract {
|
||||
* Gets and parses Autofill data from 'Web Data' database,
|
||||
* and creates TSK_WEB_FORM_AUTOFILL, TSK_WEB_FORM_ADDRESS artifacts
|
||||
*/
|
||||
private void getAutofill() {
|
||||
private void getAutofill(String browser, String browserLocation) {
|
||||
|
||||
FileManager fileManager = currentCase.getServices().getFileManager();
|
||||
List<AbstractFile> webDataFiles;
|
||||
try {
|
||||
webDataFiles = fileManager.findFiles(dataSource, "Web Data", "Chrome"); //NON-NLS
|
||||
webDataFiles = fileManager.findFiles(dataSource, "%Web Data%", browserLocation); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getAutofills.errMsg.errGettingFiles");
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
@ -702,10 +723,10 @@ class Chrome extends Extract {
|
||||
int j = 0;
|
||||
while (j < webDataFiles.size()) {
|
||||
AbstractFile webDataFile = webDataFiles.get(j++);
|
||||
if (webDataFile.getSize() == 0) {
|
||||
if ((webDataFile.getSize() == 0) || (webDataFile.getName().toLowerCase().contains("-slack"))) {
|
||||
continue;
|
||||
}
|
||||
String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
|
||||
String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
|
||||
try {
|
||||
ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
|
||||
} catch (ReadContentInputStreamException ex) {
|
||||
@ -731,7 +752,7 @@ class Chrome extends Extract {
|
||||
boolean isSchemaV8X = Util.checkColumn("date_created", "autofill", tempFilePath);
|
||||
|
||||
// get form autofill artifacts
|
||||
bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X));
|
||||
bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X, browser));
|
||||
try {
|
||||
// get form address atifacts
|
||||
getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X);
|
||||
@ -757,7 +778,7 @@ class Chrome extends Extract {
|
||||
*
|
||||
* @return collection of TSK_WEB_FORM_AUTOFILL artifacts
|
||||
*/
|
||||
private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) {
|
||||
private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X, String browser ) {
|
||||
|
||||
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
|
||||
|
||||
@ -783,7 +804,7 @@ class Chrome extends Extract {
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
(Integer.valueOf(result.get("count").toString())))); //NON-NLS
|
||||
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(),
|
||||
Long.valueOf(result.get("date_created").toString()))); //NON-NLS
|
||||
|
||||
@ -794,6 +815,9 @@ class Chrome extends Extract {
|
||||
Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS
|
||||
}
|
||||
|
||||
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
|
||||
RecentActivityExtracterModuleFactory.getModuleName(), browser));
|
||||
|
||||
// Add an artifact
|
||||
BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes);
|
||||
if (bbart != null) {
|
||||
@ -886,7 +910,7 @@ class Chrome extends Extract {
|
||||
use_count, otherAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isChromePreVersion30(String temps) {
|
||||
String query = "PRAGMA table_info(downloads)"; //NON-NLS
|
||||
List<HashMap<String, Object>> columns = this.dbConnect(temps, query);
|
@ -83,9 +83,12 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_AC
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
|
||||
@ -353,12 +356,10 @@ class ExtractRegistry extends Extract {
|
||||
} catch (IOException | TskCoreException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to get shell bags from file %s", regOutputFiles.fullPlugins), ex);
|
||||
}
|
||||
} else if (regFileNameLocal.toLowerCase().contains("system")) {
|
||||
try {
|
||||
createSystemArtifacts(regOutputFiles.fullPlugins, regFile);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to get artifacts from file %s", regOutputFiles.fullPlugins), ex);
|
||||
}
|
||||
} else if (regFileNameLocal.toLowerCase().contains("system") && parseSystemPluginOutput(regOutputFiles.fullPlugins, regFile) == false) {
|
||||
this.addErrorMessage(
|
||||
NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
|
||||
this.getName(), regFileName));
|
||||
}
|
||||
try {
|
||||
Report report = currentCase.addReport(regOutputFiles.fullPlugins,
|
||||
@ -952,6 +953,119 @@ class ExtractRegistry extends Extract {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean parseSystemPluginOutput(String regfilePath, AbstractFile regAbstractFile) {
|
||||
File regfile = new File(regfilePath);
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
line = line.trim();
|
||||
|
||||
if (line.toLowerCase().matches("^bam v.*")) {
|
||||
parseBamKey(regAbstractFile, reader, Bundle.Registry_System_Bam());
|
||||
} else if (line.toLowerCase().matches("^bthport v..*")) {
|
||||
parseBlueToothDevices(regAbstractFile, reader);
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
return true;
|
||||
} catch (FileNotFoundException ex) {
|
||||
logger.log(Level.WARNING, "Error finding the registry file.", ex); //NON-NLS
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error reading the system hive: {0}", ex); //NON-NLS
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create recently used artifacts to parse the regripper plugin output, this
|
||||
* format is used in several diffent plugins
|
||||
*
|
||||
* @param regFile registry file the artifact is associated with
|
||||
*
|
||||
* @param reader buffered reader to parse adobemru records
|
||||
*
|
||||
* @param comment string that will populate attribute TSK_COMMENT
|
||||
*
|
||||
* @throws FileNotFound and IOException
|
||||
*/
|
||||
private void parseBlueToothDevices(AbstractFile regFile, BufferedReader reader) throws FileNotFoundException, IOException {
|
||||
List<BlackboardArtifact> bbartifacts = new ArrayList<>();
|
||||
String line = reader.readLine();
|
||||
while ((line != null) && (!line.contains(SECTION_DIVIDER))) {
|
||||
line = reader.readLine();
|
||||
|
||||
if (line != null) {
|
||||
line = line.trim();
|
||||
}
|
||||
|
||||
if ((line != null) && (line.toLowerCase().contains("device unique id"))) {
|
||||
// Columns are seperated by colons :
|
||||
// Data : Values
|
||||
// Record is 4 lines in length (Device Unique Id, Name, Last Seen, LastConnected
|
||||
while (line != null && !line.contains(SECTION_DIVIDER) && !line.isEmpty() && !line.toLowerCase().contains("radio support not found")) {
|
||||
Collection<BlackboardAttribute> attributes = new ArrayList<>();
|
||||
addBlueToothAttribute(line, attributes, TSK_DEVICE_ID);
|
||||
line = reader.readLine();
|
||||
// Name may not exist, check for it to make sure.
|
||||
if ((line != null) && (line.toLowerCase().contains("name"))) {
|
||||
addBlueToothAttribute(line, attributes, TSK_NAME);
|
||||
line = reader.readLine();
|
||||
}
|
||||
addBlueToothAttribute(line, attributes, TSK_DATETIME);
|
||||
line = reader.readLine();
|
||||
addBlueToothAttribute(line, attributes, TSK_DATETIME_ACCESSED);
|
||||
BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING, regFile, attributes);
|
||||
if(bba != null) {
|
||||
bbartifacts.add(bba);
|
||||
}
|
||||
// Read blank line between records then next read line is start of next block
|
||||
reader.readLine();
|
||||
line = reader.readLine();
|
||||
}
|
||||
|
||||
if (line != null) {
|
||||
line = line.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bbartifacts.isEmpty()) {
|
||||
postArtifacts(bbartifacts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addBlueToothAttribute(String line, Collection<BlackboardAttribute> attributes, ATTRIBUTE_TYPE attributeType) {
|
||||
if (line == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String tokens[] = line.split(": ");
|
||||
if (tokens.length > 1 && !tokens[1].isEmpty()) {
|
||||
String tokenString = tokens[1];
|
||||
if (attributeType.getDisplayName().toLowerCase().contains("date")) {
|
||||
String dateString = tokenString.toLowerCase().replace(" z", "");
|
||||
// date format for plugin Tue Jun 23 10:27:54 2020 Z
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US);
|
||||
Long dateLong = Long.valueOf(0);
|
||||
try {
|
||||
Date newDate = dateFormat.parse(dateString);
|
||||
dateLong = newDate.getTime()/1000;
|
||||
} catch (ParseException ex) {
|
||||
// catching error and displaying date that could not be parsed
|
||||
// we set the timestamp to 0 and continue on processing
|
||||
logger.log(Level.WARNING, String.format("Failed to parse date/time %s for Bluetooth Last Seen attribute.", dateString), ex); //NON-NLS
|
||||
}
|
||||
attributes.add(new BlackboardAttribute(attributeType, getName(), dateLong));
|
||||
} else {
|
||||
attributes.add(new BlackboardAttribute(attributeType, getName(), tokenString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the output of the SAM regripper plugin to get additional Account
|
||||
* information
|
||||
@ -1280,30 +1394,6 @@ class ExtractRegistry extends Extract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create artifacts from the System registry Hive
|
||||
*
|
||||
* @param regFileName name of the regripper output file
|
||||
*
|
||||
* @param regFile registry file the artifact is associated with
|
||||
*
|
||||
* @throws FileNotFound and IOException
|
||||
*/
|
||||
private void createSystemArtifacts(String regFileName, AbstractFile regFile) throws FileNotFoundException, IOException {
|
||||
File regfile = new File(regFileName);
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
|
||||
String line = reader.readLine();
|
||||
while (line != null) {
|
||||
line = line.trim();
|
||||
|
||||
if (line.matches("^bam v.*")) {
|
||||
parseBamKey(regFile, reader, Bundle.Registry_System_Bam());
|
||||
}
|
||||
line = reader.readLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create artifacts from BAM Regripper Plugin records
|
||||
*
|
||||
@ -2031,4 +2121,4 @@ class ExtractRegistry extends Extract {
|
||||
public String autopsyPlugins = "";
|
||||
public String fullPlugins = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
|
||||
Extract registry = new ExtractRegistry();
|
||||
Extract recentDocuments = new RecentDocumentsByLnk();
|
||||
Extract chrome = new Chrome();
|
||||
Extract chrome = new Chromium();
|
||||
Extract firefox = new Firefox();
|
||||
Extract SEUQA = new SearchEngineURLQueryAnalyzer();
|
||||
Extract osExtract = new ExtractOs();
|
||||
|
1
thirdparty/rr-full/plugins/system
vendored
1
thirdparty/rr-full/plugins/system
vendored
@ -5,6 +5,7 @@ auditfail
|
||||
backuprestore
|
||||
bam
|
||||
bam_tln
|
||||
btconfig
|
||||
bthport
|
||||
comfoo
|
||||
compname
|
||||
|
Loading…
x
Reference in New Issue
Block a user