bug fixes

This commit is contained in:
Greg DiCristofaro 2020-08-24 11:26:24 -04:00
parent e0351c7c0a
commit 78d69f173b
13 changed files with 543 additions and 282 deletions

View File

@ -108,7 +108,24 @@ final class DataSourceInfoUtilities {
* obtained.
*/
static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) {
try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
return getBaseQueryResult(SleuthkitCaseProvider.DEFAULT, query, processor, errorMessage);
}
/**
* Retrieves a result based on the provided query.
*
* @param provider The means of obtaining a SleuthkitCase.
* @param query The query.
* @param processor The result set handler.
* @param errorMessage The error message to display if there is an error
* retrieving the resultset.
*
* @return The ResultSetHandler value or null if no ResultSet could be
* obtained.
*/
static <T> T getBaseQueryResult(SleuthkitCaseProvider provider, String query, ResultSetHandler<T> processor, String errorMessage) {
try (SleuthkitCase.CaseDbQuery dbQuery = provider.get().executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet();
try {
return processor.process(resultSet);

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.sleuthkit.datamodel.DataSource;
/**
* Provides summary information about top domains in a datasource.
*/
public class DataSourceTopDomainsSummary {
private static final long SLEEP_TIME = 5000;
// private final SleuthkitCaseProvider provider;
//
// public DataSourceTopDomainsSummary() {
// this(SleuthkitCaseProvider.DEFAULT);
// }
//
// public DataSourceTopDomainsSummary(SleuthkitCaseProvider provider) {
// this.provider = provider;
// }
interface Function2<A1,A2,O> {
O apply(A1 a1, A2 a2);
}
public List<TopDomainsResult> getRecentDomains(DataSource dataSource, int count) throws InterruptedException {
Thread.sleep(SLEEP_TIME);
final String dId = Long.toString(dataSource.getId());
final Function2<String, Integer, String> getId = (s,idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx);
return IntStream.range(0, count)
.mapToObj(num -> new TopDomainsResult(
getId.apply("domain", num),
getId.apply("url", num),
(long)num,
new Date(120, 1, num)
))
.collect(Collectors.toList());
}
}

View File

@ -66,6 +66,36 @@ public class DataSourceTopProgramsSummary {
*/
private static final String QUERY_SUFFIX = "_query";
/**
* 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;
}
);
/**
* Creates a sql statement querying the blackboard attributes table for a
* particular attribute type and returning a specified value. That query
@ -139,6 +169,17 @@ public class DataSourceTopProgramsSummary {
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
}
private final SleuthkitCaseProvider provider;
public DataSourceTopProgramsSummary() {
this(SleuthkitCaseProvider.DEFAULT);
}
public DataSourceTopProgramsSummary(SleuthkitCaseProvider provider) {
this.provider = provider;
}
/**
* Retrieves a list of the top programs used on the data source. Currently
* determines this based off of which prefetch results return the highest
@ -149,7 +190,7 @@ public class DataSourceTopProgramsSummary {
*
* @return
*/
public static List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) {
public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) {
if (dataSource == null || count <= 0) {
return Collections.emptyList();
}
@ -225,38 +266,9 @@ public class DataSourceTopProgramsSummary {
return progResults;
};
return getBaseQueryResult(query, handler, errorMessage);
return getBaseQueryResult(provider, 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.
@ -289,7 +301,4 @@ public class DataSourceTopProgramsSummary {
return "";
}
private DataSourceTopProgramsSummary() {
}
}

View File

@ -0,0 +1,33 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* An interface to provide the current SleuthkitCase object. By default, this
* uses Case.getCurrentCaseThrows().getSleuthkkitCase().
*/
public interface SleuthkitCaseProvider {
public static final SleuthkitCaseProvider DEFAULT = () -> Case.getCurrentCaseThrows().getSleuthkitCase();
SleuthkitCase get() throws NoCurrentCaseException;
}

View File

@ -0,0 +1,57 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.Date;
/**
* Describes a result of a program run on a datasource.
*/
public class TopDomainsResult {
private final String domain;
private final String url;
private final Long visitTimes;
private final Date lastVisit;
public TopDomainsResult(String domain, String url, Long visitTimes, Date lastVisit) {
this.domain = domain;
this.url = url;
this.visitTimes = visitTimes;
this.lastVisit = lastVisit;
}
public String getDomain() {
return domain;
}
public String getUrl() {
return url;
}
public Long getVisitTimes() {
return visitTimes;
}
public Date getLastVisit() {
return lastVisit;
}
}

View File

@ -35,4 +35,5 @@ DataSourceSummaryDetailsPanel.unallocatedSizeLabel.text=Unallocated Space:
DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs
DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains

View File

@ -75,8 +75,12 @@ 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.programsRunLabel.text=Recent Programs
DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains
DataSourceSummaryUserActivityPanel_tab_title=User Activity
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access
DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="null"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -11,59 +16,180 @@
<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"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,-58,0,0,2,-42"/>
</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 class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="contentScrollPane">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="null"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="null"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="contentPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
<EmptyBorder bottom="10" left="10" right="10" top="10"/>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[720, 450]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[720, 450]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
<Property name="axis" type="int" value="3"/>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="programsRunLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="DataSourceSummaryUserActivityPanel.programsRunLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="alignmentX" type="float" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="Component.LEFT_ALIGNMENT" type="code"/>
</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">
<Component class="javax.swing.Box$Filler" name="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[750, 187]"/>
<Dimension value="[0, 2]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="topProgramsTablePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="topProgramsTable"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="topProgramsTable">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.Box$Filler" name="filler3">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 20]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="recentDomainsLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="DataSourceSummaryUserActivityPanel.recentDomainsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler2">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 2]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.RigidArea"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="recentDomainsTablePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 187]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="recentDomainsTable"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>

View File

@ -21,20 +21,26 @@ package org.sleuthkit.autopsy.datasourcesummary.ui;
import java.awt.Component;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
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 java.util.stream.Collectors;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopDomainsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsResult;
import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker;
import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.guiutils.internal.DataLoadingResult;
import org.sleuthkit.autopsy.guiutils.internal.DataResultJTable;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultCellModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultColumnModel;
import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.HorizontalAlign;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.autopsy.guiutils.internal.SwingWorkerSequentialRunner;
/**
* A panel to display user activity.
@ -44,18 +50,21 @@ import org.sleuthkit.datamodel.DataSource;
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run"
})
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run",
"DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain",
"DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL",
"DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access",})
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();
private static final int TOP_DOMAINS_COUNT = 10;
static {
RIGHT_ALIGNED_RENDERER.setHorizontalAlignment(JLabel.RIGHT);
}
private final SwingWorkerSequentialRunner loader = new SwingWorkerSequentialRunner();
private final DataResultJTable<TopProgramsResult> topProgramsTable;
private final DataResultJTable<TopDomainsResult> recentDomainsTable;
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private DataSource dataSource;
@ -63,8 +72,61 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
* Creates new form DataSourceUserActivityPanel
*/
public DataSourceSummaryUserActivityPanel() {
this(new DataSourceTopProgramsSummary(), new DataSourceTopDomainsSummary());
}
public DataSourceSummaryUserActivityPanel(DataSourceTopProgramsSummary topProgramsData, DataSourceTopDomainsSummary topDomainsData) {
// set up recent programs table
this.topProgramsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList(
new DefaultColumnModel<TopProgramsResult>(
(prog) -> new DefaultCellModel(prog.getProgramName())
.setTooltip(prog.getProgramPath()),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header())
.setWidth(250),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(),
(prog) -> topProgramsData.getShortFolderName(prog.getProgramPath(), prog.getProgramName()))
.setWidth(150),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(),
(prog) -> prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes()))
.setWidth(80)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT),
new DefaultColumnModel<TopProgramsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header(),
(prog) -> prog.getLastRun() == null ? "" : DATETIME_FORMAT.format(prog.getLastRun()))
.setWidth(150)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT)
)));
// set up recent domains table
recentDomainsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList(
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header(),
(d) -> d.getDomain())
.setWidth(250),
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header(),
(d) -> d.getUrl())
.setWidth(250),
new DefaultColumnModel<TopDomainsResult>(
Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header(),
(d) -> DATETIME_FORMAT.format(d.getLastVisit()))
.setWidth(150)
.setCellHorizontalAlignment(HorizontalAlign.RIGHT)
)));
// set up acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
topProgramsTable::setResult),
new DataFetchComponents<DataSource, List<TopDomainsResult>>(
(dataSource) -> topDomainsData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT),
recentDomainsTable::setResult)
);
initComponents();
topProgramsTable.getTableHeader().setReorderingAllowed(false);
}
/**
@ -81,170 +143,25 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
*
* @param dataSource The datasource to use in this panel.
*/
public void setDataSource(DataSource dataSource) {
public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
if (dataSource == null || !Case.isCaseOpen()) {
updateTopPrograms(new TopProgramsModel(null));
dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataLoadingResult.getLoaded(null)));
} else {
updateTopPrograms(getTopProgramsModel(dataSource));
dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataLoadingResult.getLoading()));
List<DataFetchWorker<?, ?>> workers = dataFetchComponents
.stream()
.map((components) -> new DataFetchWorker<>(components, dataSource))
.collect(Collectors.toList());
loader.resetLoad(workers);
}
}
/**
* 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<TopProgramsResult> topProgramList
= DataSourceTopProgramsSummary.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<TopProgramsResult> programResults;
/**
* Main constructor.
*
* @param programResults The results to display.
*/
TopProgramsModel(List<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;
}
TopProgramsResult result = programResults.get(rowIndex);
switch (columnIndex) {
case 0:
return new ProgramNameCellValue(result.getProgramName(), result.getProgramPath());
case 1:
return DataSourceTopProgramsSummary.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
@ -254,40 +171,57 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.JScrollPane contentScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel contentPanel = new javax.swing.JPanel();
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
topProgramsScrollPane = new javax.swing.JScrollPane();
topProgramsTable = new javax.swing.JTable();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel topProgramsTablePanel = topProgramsTable;
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20));
javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2));
javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable;
setMaximumSize(null);
setLayout(new java.awt.BorderLayout());
contentScrollPane.setMaximumSize(null);
contentScrollPane.setMinimumSize(null);
contentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
contentPanel.setMaximumSize(new java.awt.Dimension(720, 450));
contentPanel.setMinimumSize(new java.awt.Dimension(720, 450));
contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS));
programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N
programsRunLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
contentPanel.add(programsRunLabel);
contentPanel.add(filler1);
topProgramsScrollPane.setPreferredSize(new java.awt.Dimension(750, 187));
topProgramsScrollPane.setViewportView(topProgramsTable);
topProgramsTablePanel.setAlignmentX(0.0F);
topProgramsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187));
topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187));
topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187));
contentPanel.add(topProgramsTablePanel);
contentPanel.add(filler3);
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))
);
recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
org.openide.awt.Mnemonics.setLocalizedText(recentDomainsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.recentDomainsLabel.text")); // NOI18N
contentPanel.add(recentDomainsLabel);
contentPanel.add(filler2);
recentDomainsTablePanel.setAlignmentX(0.0F);
recentDomainsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187));
recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187));
recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187));
contentPanel.add(recentDomainsTablePanel);
contentScrollPane.setViewportView(contentPanel);
add(contentScrollPane, java.awt.BorderLayout.CENTER);
}// </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
}

View File

@ -0,0 +1,2 @@
DataResultJTable_errorMessage_defaultText=There was an error loading results.
DataResultJTable_loadingMessage_defaultText=Loading results...

View File

@ -121,24 +121,35 @@ public class DataFetchWorker<A, R> extends SwingWorker<R, Void> {
@Override
protected void done() {
// if cancelled, simply return
if (Thread.interrupted() || isCancelled()) {
return;
}
R result = null;
try {
result = get();
} catch (InterruptedException ignored) {
// if cancelled, set not loaded andt return
resultHandler.accept(DataLoadingResult.getNotLoaded());
// if cancelled, simply return
return;
} catch (ExecutionException ex) {
logger.log(Level.WARNING, "There was an error while fetching results.", ex);
Throwable inner = ex.getCause();
// if cancelled during operation, simply return
if (inner != null && inner instanceof InterruptedException) {
return;
}
// otherwise, there is an error to log
logger.log(Level.WARNING, "There was an error while fetching results.", ex);
if (inner != null && inner instanceof DataProcessorException) {
resultHandler.accept(DataLoadingResult.getLoadError((DataProcessorException) inner));
}
return;
}
// if cancelled, simply return
if (Thread.interrupted() || isCancelled()) {
resultHandler.accept(DataLoadingResult.getNotLoaded());
return;
}

View File

@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.guiutils.internal;
/**
* The intermediate or end result of a loading process.
*/
class DataLoadingResult<R> {
public class DataLoadingResult<R> {
// The state of loading in the result.
public enum ProcessorState {

View File

@ -243,7 +243,7 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
TableColumn col = new TableColumn(i);
ColumnModel<T> model = columns.get(i);
if (model.getWidth() != null && model.getWidth() >= 0) {
col.setWidth(model.getWidth());
col.setPreferredWidth(model.getWidth());
}
col.setHeaderValue(model.getTitle());
@ -278,11 +278,15 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
String text = cellModel.getText();
if (StringUtils.isNotBlank(text)) {
defaultCell.setText(text);
} else {
defaultCell.setText(null);
}
String tooltip = cellModel.getTooltip();
if (StringUtils.isNotBlank(tooltip)) {
defaultCell.setToolTipText(tooltip);
} else {
defaultCell.setToolTipText(null);
}
if (columnModel.getCellHorizontalAlignment() != null) {
@ -297,6 +301,8 @@ public class DefaultPojoListTableDataModel<T> extends AbstractTableModel impleme
defaultCell.setHorizontalAlignment(JLabel.RIGHT);
break;
}
} else {
defaultCell.setHorizontalAlignment(JLabel.LEFT);
}
return defaultCell;