Merge pull request #6254 from gdicristofaro/6779-pastCasesTab

6779 past cases tab
This commit is contained in:
Richard Cordovano 2020-09-15 11:08:25 -04:00 committed by GitHub
commit c40bdf1072
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 857 additions and 1 deletions

View File

@ -0,0 +1,430 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.centralrepository.ingestmodule.CentralRepoIngestModuleFactory;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Provides information about how a datasource relates to a previous case. NOTE:
* This code is fragile and has certain expectations about how the central
* repository handles creating artifacts. So, if the central repository changes
* ingest process, this code could break. This code expects that the central
* repository ingest module:
*
* a) Creates a TSK_INTERESTING_FILE_HIT artifact for a file whose hash is in
* the central repository as a notable file.
*
* b) Creates a TSK_INTERESTING_ARTIFACT_HIT artifact for a matching id in the
* central repository.
*
* c) The created artifact will have a TSK_COMMENT attribute attached where one
* of the sources for the attribute matches
* CentralRepoIngestModuleFactory.getModuleName(). The module display name at
* time of ingest will match CentralRepoIngestModuleFactory.getModuleName() as
* well.
*
* d) The content of that TSK_COMMENT attribute will be of the form "Previous
* Case: case1,case2...caseN"
*/
public class PastCasesSummary {
/**
* Exception that is thrown in the event that a data source has not been
* ingested with a particular ingest module.
*/
public static class NotIngestedWithModuleException extends Exception {
private static final long serialVersionUID = 1L;
private final String moduleDisplayName;
/**
* Constructor.
*
* @param moduleName The module name.
* @param message The message for the exception.
*/
public NotIngestedWithModuleException(String moduleName, String message) {
super(message);
this.moduleDisplayName = moduleName;
}
/**
* Constructor.
*
* @param moduleName The module name.
* @param message The message for the exception.
* @param thrwbl Inner exception if applicable.
*/
public NotIngestedWithModuleException(String moduleName, String message, Throwable thrwbl) {
super(message, thrwbl);
this.moduleDisplayName = moduleName;
}
/**
* @return The module display name.
*/
public String getModuleDisplayName() {
return moduleDisplayName;
}
}
/**
* Return type for results items in the past cases tab.
*/
public static class PastCasesResult {
private final List<Pair<String, Long>> sameIdsResults;
private final List<Pair<String, Long>> taggedNotable;
/**
* Main constructor.
*
* @param sameIdsResults Data for the cases with same id table.
* @param taggedNotable Data for the tagged notable table.
*/
public PastCasesResult(List<Pair<String, Long>> sameIdsResults, List<Pair<String, Long>> taggedNotable) {
this.sameIdsResults = sameIdsResults;
this.taggedNotable = taggedNotable;
}
/**
* @return Data for the cases with same id table.
*/
public List<Pair<String, Long>> getSameIdsResults() {
return sameIdsResults;
}
/**
* @return Data for the tagged notable table.
*/
public List<Pair<String, Long>> getTaggedNotable() {
return taggedNotable;
}
}
private static final String CENTRAL_REPO_INGEST_NAME = CentralRepoIngestModuleFactory.getModuleName().toUpperCase().trim();
private static final BlackboardAttribute.Type TYPE_COMMENT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_COMMENT);
private static final BlackboardAttribute.Type TYPE_ASSOCIATED_ARTIFACT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT);
private static final Set<Integer> CR_DEVICE_TYPE_IDS = new HashSet<>(Arrays.asList(
ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID(),
ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID(),
ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID(),
ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID()
));
private static final String CASE_SEPARATOR = ",";
private static final String PREFIX_END = ":";
private final SleuthkitCaseProvider caseProvider;
private final java.util.logging.Logger logger;
/**
* Main constructor.
*/
public PastCasesSummary() {
this(
SleuthkitCaseProvider.DEFAULT,
(artifact) -> CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact),
org.sleuthkit.autopsy.coreutils.Logger.getLogger(DataSourceUserActivitySummary.class.getName())
);
}
/**
* Main constructor with external dependencies specified. This constructor
* is designed with unit testing in mind since mocked dependencies can be
* utilized.
*
* @param provider The object providing the current SleuthkitCase.
* @param logger The logger to use.
*/
public PastCasesSummary(
SleuthkitCaseProvider provider,
Function<BlackboardArtifact, List<CorrelationAttributeInstance>> corrAttrRetriever,
java.util.logging.Logger logger) {
this.caseProvider = provider;
this.logger = logger;
}
/**
* Given the provided sources for an attribute, aims to determine if one of
* those sources is the Central Repository Ingest Module.
*
* @param sources The list of sources found on an attribute.
*
* @return Whether or not this attribute (and subsequently the parent
* artifact) is created by the Central Repository Ingest Module.
*/
private static boolean isCentralRepoGenerated(List<String> sources) {
if (sources == null) {
return false;
}
return sources.stream().anyMatch((str) -> {
return str != null && CENTRAL_REPO_INGEST_NAME.equalsIgnoreCase(str.trim());
});
}
/**
* Gets a list of cases from the TSK_COMMENT of an artifact. The cases
* string is expected to be of a form of "Previous Case:
* case1,case2...caseN".
*
* @param artifact The artifact.
*
* @return The list of cases if found or empty list if not.
*/
private static List<String> getCasesFromArtifact(BlackboardArtifact artifact) {
if (artifact == null) {
return Collections.emptyList();
}
BlackboardAttribute commentAttr = null;
try {
commentAttr = artifact.getAttribute(TYPE_COMMENT);
} catch (TskCoreException ignored) {
// ignore if no attribute can be found
}
if (commentAttr == null) {
return Collections.emptyList();
}
if (!isCentralRepoGenerated(commentAttr.getSources())) {
return Collections.emptyList();
}
String commentStr = commentAttr.getValueString();
int prefixCharIdx = commentStr.indexOf(PREFIX_END);
if (prefixCharIdx < 0 || prefixCharIdx >= commentStr.length() - 1) {
return Collections.emptyList();
}
String justCasesStr = commentStr.substring(prefixCharIdx + 1).trim();
return Stream.of(justCasesStr.split(CASE_SEPARATOR))
.map(String::trim)
.collect(Collectors.toList());
}
/**
* Given a stream of case ids, groups the strings in a case-insensitive
* manner, and then provides a list of cases and the occurrence count sorted
* from max to min.
*
* @param cases A stream of cases.
*
* @return The list of unique cases and their occurrences sorted from max to min.
*/
private List<Pair<String, Long>> getCaseCounts(Stream<String> cases) {
Collection<List<String>> groupedCases = cases
// group by case insensitive compare of cases
.collect(Collectors.groupingBy((caseStr) -> caseStr.toUpperCase().trim()))
.values();
return groupedCases
.stream()
// get any cases where an actual case is found
.filter((lst) -> lst != null && lst.size() > 0)
// get non-normalized (i.e. not all caps) case name and number of items found
.map((lst) -> Pair.of(lst.get(0), (long) lst.size()))
// sorted descending
.sorted((a, b) -> -Long.compare(a.getValue(), b.getValue()))
.collect(Collectors.toList());
}
/**
* Given an artifact with a TYPE_ASSOCIATED_ARTIFACT attribute, retrieves the related artifact.
* @param skCase The sleuthkit case.
* @param artifact The artifact with the TYPE_ASSOCIATED_ARTIFACT attribute.
* @return The artifact if found or null if not.
* @throws SleuthkitCaseProviderException
*/
private BlackboardArtifact getParentArtifact(BlackboardArtifact artifact) throws SleuthkitCaseProviderException {
Long parentId = DataSourceInfoUtilities.getLongOrNull(artifact, TYPE_ASSOCIATED_ARTIFACT);
if (parentId == null) {
return null;
}
SleuthkitCase skCase = caseProvider.get();
try {
return skCase.getArtifactByArtifactId(parentId);
} catch (TskCoreException ex) {
logger.log(Level.WARNING,
String.format("There was an error fetching the parent artifact of a TSK_INTERESTING_ARTIFACT_HIT (parent id: %d)", parentId),
ex);
return null;
}
}
/**
* Returns true if the artifact has an associated artifact of a device type.
* @param artifact The artifact.
* @return True if there is a device associated artifact.
* @throws SleuthkitCaseProviderException
*/
private boolean hasDeviceAssociatedArtifact(BlackboardArtifact artifact) throws SleuthkitCaseProviderException {
BlackboardArtifact parent = getParentArtifact(artifact);
if (parent == null) {
return false;
}
return CR_DEVICE_TYPE_IDS.contains(parent.getArtifactTypeID());
}
/**
* Returns the past cases data to be shown in the past cases tab.
* @param dataSource The data source.
* @return The retrieved data.
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws NotIngestedWithModuleException
*/
public PastCasesResult getPastCasesData(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
throwOnNotCentralRepoIngested(dataSource);
SleuthkitCase skCase = caseProvider.get();
List<String> deviceArtifactCases = new ArrayList<>();
List<String> nonDeviceArtifactCases = new ArrayList<>();
for (BlackboardArtifact artifact : skCase.getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(), dataSource.getId())) {
List<String> cases = getCasesFromArtifact(artifact);
if (cases == null || cases.isEmpty()) {
continue;
}
if (hasDeviceAssociatedArtifact(artifact)) {
deviceArtifactCases.addAll(cases);
} else {
nonDeviceArtifactCases.addAll(cases);
}
}
Stream<String> filesCases = skCase.getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), dataSource.getId()).stream()
.flatMap((art) -> getCasesFromArtifact(art).stream());
return new PastCasesResult(
getCaseCounts(deviceArtifactCases.stream()),
getCaseCounts(Stream.concat(filesCases, nonDeviceArtifactCases.stream()))
);
}
/**
* Returns true if the ingest job info contains an ingest module that
* matches the Central Repo Module ingest display name.
*
* @param info The info.
*
* @return True if there is a central repo ingest match.
*/
private boolean hasCentralRepoIngest(IngestJobInfo info) {
if (info == null || info.getIngestModuleInfo() == null) {
return false;
}
return info.getIngestModuleInfo().stream()
.anyMatch((moduleInfo) -> {
return StringUtils.isNotBlank(moduleInfo.getDisplayName())
&& moduleInfo.getDisplayName().trim().equalsIgnoreCase(CENTRAL_REPO_INGEST_NAME);
});
}
/**
* Returns true if the central repository ingest module has been run on the
* datasource.
*
* @param dataSource The data source.
*
* @return True if there is an ingest job pertaining to the data source
* where an ingest module matches the central repo ingest module
* display name.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
public boolean isCentralRepoIngested(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException {
if (dataSource == null) {
return false;
}
long dataSourceId = dataSource.getId();
return this.caseProvider.get().getIngestJobs().stream()
.anyMatch((ingestJob) -> {
return ingestJob != null
&& ingestJob.getObjectId() == dataSourceId
&& hasCentralRepoIngest(ingestJob);
});
}
/**
* Throws an exception if the current data source has not been ingested with
* the Central Repository Ingest Module.
*
* @param dataSource The data source to check if it has been ingested with
* the Central Repository Ingest Module.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws NotIngestedWithModuleException
*/
private void throwOnNotCentralRepoIngested(DataSource dataSource)
throws SleuthkitCaseProvider.SleuthkitCaseProviderException, TskCoreException, NotIngestedWithModuleException {
if (!isCentralRepoIngested(dataSource)) {
String objectId = (dataSource == null) ? "<null>" : String.valueOf(dataSource.getId());
String message = String.format("Data source: %s has not been ingested with the Central Repository Ingest Module.", objectId);
throw new NotIngestedWithModuleException(CENTRAL_REPO_INGEST_NAME, message);
}
}
}

View File

@ -39,3 +39,5 @@ AnalysisPanel.interestingItemLabel.text=Interesting Item Hits
RecentFilesPanel.openDocsLabel.text=Recently Opened Documents
RecentFilesPanel.downloadLabel.text=Recent Downloads
RecentFilesPanel.attachmentLabel.text=Recent Attachements
PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs

View File

@ -44,6 +44,7 @@ DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
DataSourceSummaryTabbedPane_analysisTab_title=Analysis
DataSourceSummaryTabbedPane_detailsTab_title=Container
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases
DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files
DataSourceSummaryTabbedPane_typesTab_title=Types
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
@ -72,6 +73,9 @@ DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program
DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_dateAccessed_header=Date Accessed
DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_searchString_header=Search String
DataSourceSummaryUserActivityPanel_TopWebSearchTableModel_translatedResult_header=Translated
PastCasesPanel_caseColumn_title=Case
PastCasesPanel_countColumn_title=Count
PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run.
RecentFilePanel_col_header_domain=Domain
RecentFilePanel_col_header_path=Path
RecentFilePanel_col_header_sender=Sender
@ -79,6 +83,8 @@ RecentFilePanel_no_open_documents=No recently open documents found.
RecentFilesPanel.openDocsLabel.text=Recently Opened Documents
RecentFilesPanel.downloadLabel.text=Recent Downloads
RecentFilesPanel.attachmentLabel.text=Recent Attachements
PastCasesPanel.notableFileLabel.text=Cases with Common Items That Were Tagged as Notable
PastCasesPanel.sameIdLabel.text=Past Cases with the Same Device IDs
RecentFilesPanel_col_head_date=Date
SizeRepresentationUtil_units_bytes=\ bytes
SizeRepresentationUtil_units_gigabytes=\ GB

View File

@ -38,6 +38,7 @@ import org.sleuthkit.datamodel.DataSource;
"DataSourceSummaryTabbedPane_userActivityTab_title=User Activity",
"DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History",
"DataSourceSummaryTabbedPane_recentFileTab_title=Recent Files",
"DataSourceSummaryTabbedPane_pastCasesTab_title=Past Cases",
"DataSourceSummaryTabbedPane_analysisTab_title=Analysis"
})
public class DataSourceSummaryTabbedPane extends JTabbedPane {
@ -106,8 +107,9 @@ public class DataSourceSummaryTabbedPane extends JTabbedPane {
private final List<DataSourceTab> tabs = Arrays.asList(
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_typesTab_title(), new TypesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new DataSourceSummaryUserActivityPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_analysisTab_title(), new AnalysisPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_recentFileTab_title(), new RecentFilesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_pastCasesTab_title(), new PastCasesPanel()),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource),
new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new DataSourceSummaryDetailsPanel())
);

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" 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">
<Component id="mainScrollPane" alignment="0" pref="400" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="mainScrollPane" alignment="0" pref="300" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="mainScrollPane">
<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.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="mainContentPanel">
<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>
</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="notableFileLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="PastCasesPanel.notableFileLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="PastCasesPanel.notableFileLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<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="filler1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 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.VerticalStrut"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="notableFilePanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 106]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 106]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 106]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="notableFileTable"/>
<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>
<Component class="javax.swing.Box$Filler" name="filler2">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 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.VerticalStrut"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="sameIdLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties" key="PastCasesPanel.sameIdLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler3">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 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.VerticalStrut"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="sameIdPanel">
<Properties>
<Property name="alignmentX" type="float" value="0.0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 106]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 106]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 106]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="sameIdTable"/>
<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>
<Component class="javax.swing.Box$Filler" name="filler5">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 32767]"/>
</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.VerticalGlue"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,222 @@
/*
* 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.ui;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.NotIngestedWithModuleException;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary.PastCasesResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.JTablePanel.ColumnModel;
import org.sleuthkit.datamodel.DataSource;
/**
* A tab shown in data source summary displaying information about a datasource
* and how it pertains to other cases.
*/
@Messages({
"PastCasesPanel_caseColumn_title=Case",
"PastCasesPanel_countColumn_title=Count",
"PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run."
})
public class PastCasesPanel extends BaseDataSourceSummaryPanel {
private static final long serialVersionUID = 1L;
private static final ColumnModel<Pair<String, Long>> CASE_COL = new ColumnModel<>(
Bundle.PastCasesPanel_caseColumn_title(),
(pair) -> new DefaultCellModel(pair.getKey()),
300
);
private static final ColumnModel<Pair<String, Long>> COUNT_COL = new ColumnModel<>(
Bundle.PastCasesPanel_countColumn_title(),
(pair) -> new DefaultCellModel(String.valueOf(pair.getValue())),
100
);
private static final List<ColumnModel<Pair<String, Long>>> DEFAULT_COLUMNS = Arrays.asList(CASE_COL, COUNT_COL);
private final JTablePanel<Pair<String, Long>> notableFileTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS);
private final JTablePanel<Pair<String, Long>> sameIdTable = JTablePanel.getJTablePanel(DEFAULT_COLUMNS);
private final List<JTablePanel<?>> tables = Arrays.asList(
notableFileTable,
sameIdTable
);
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
public PastCasesPanel() {
this(new PastCasesSummary());
}
/**
* Creates new form PastCasesPanel
*/
public PastCasesPanel(PastCasesSummary pastCaseData) {
// set up data acquisition methods
dataFetchComponents = Arrays.asList(
new DataFetchWorker.DataFetchComponents<>(
(dataSource) -> pastCaseData.getPastCasesData(dataSource),
(result) -> handleResult(result))
);
initComponents();
}
/**
* handles displaying the result for the table. If a
* NotCentralRepoIngestedException is thrown, then an appropriate message is
* shown. Otherwise, this method uses the tables default showDataFetchResult
* method.
*
* @param result The result.
*/
private void handleResult(DataFetchResult<PastCasesResult> result) {
if (result.getResultType() == ResultType.ERROR && result.getException() instanceof NotIngestedWithModuleException) {
notableFileTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
sameIdTable.showMessage(Bundle.PastCasesPanel_onNoCrIngest_message());
} else {
notableFileTable.showDataFetchResult(getSubResult(result, (res) -> res.getTaggedNotable()));
sameIdTable.showDataFetchResult(getSubResult(result, (res) -> res.getSameIdsResults()));
}
}
/**
* Given an input data fetch result, creates an error result if the original
* is an error. Otherwise, uses the getSubResult function on the underlying
* data to create a new DataFetchResult.
*
* @param inputResult The input result.
* @param getSubComponent The means of getting the data given the original
* data.
*
* @return The new result with the error of the original or the processed
* data.
*/
private <I, O> DataFetchResult<O> getSubResult(DataFetchResult<I> inputResult, Function<I, O> getSubResult) {
if (inputResult.getResultType() == ResultType.SUCCESS) {
return DataFetchResult.getSuccessResult(getSubResult.apply(inputResult.getData()));
} else {
return DataFetchResult.getErrorResult(inputResult.getException());
}
}
@Override
protected void onNewDataSource(DataSource dataSource) {
// if no data source is present or the case is not open,
// set results for tables to null.
if (dataSource == null || !Case.isCaseOpen()) {
this.dataFetchComponents.forEach((item) -> item.getResultHandler()
.accept(DataFetchResult.getSuccessResult(null)));
} else {
// set tables to display loading screen
this.tables.forEach((table) -> table.showDefaultLoadingMessage());
// create swing workers to run for each table
List<DataFetchWorker<?, ?>> workers = dataFetchComponents
.stream()
.map((components) -> new DataFetchWorker<>(components, dataSource))
.collect(Collectors.toList());
// submit swing workers to run
submit(workers);
}
}
/**
* 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.JScrollPane mainScrollPane = new javax.swing.JScrollPane();
javax.swing.JPanel mainContentPanel = new javax.swing.JPanel();
javax.swing.JLabel notableFileLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.JPanel notableFilePanel = notableFileTable;
javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(32767, 20));
javax.swing.JLabel sameIdLabel = new javax.swing.JLabel();
javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(32767, 2));
javax.swing.JPanel sameIdPanel = sameIdTable;
javax.swing.Box.Filler filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
mainContentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
mainContentPanel.setLayout(new javax.swing.BoxLayout(mainContentPanel, javax.swing.BoxLayout.PAGE_AXIS));
org.openide.awt.Mnemonics.setLocalizedText(notableFileLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
mainContentPanel.add(notableFileLabel);
notableFileLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.notableFileLabel.text")); // NOI18N
mainContentPanel.add(filler1);
notableFilePanel.setAlignmentX(0.0F);
notableFilePanel.setMaximumSize(new java.awt.Dimension(32767, 106));
notableFilePanel.setMinimumSize(new java.awt.Dimension(100, 106));
notableFilePanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(notableFilePanel);
mainContentPanel.add(filler2);
org.openide.awt.Mnemonics.setLocalizedText(sameIdLabel, org.openide.util.NbBundle.getMessage(PastCasesPanel.class, "PastCasesPanel.sameIdLabel.text")); // NOI18N
mainContentPanel.add(sameIdLabel);
mainContentPanel.add(filler3);
sameIdPanel.setAlignmentX(0.0F);
sameIdPanel.setMaximumSize(new java.awt.Dimension(32767, 106));
sameIdPanel.setMinimumSize(new java.awt.Dimension(100, 106));
sameIdPanel.setPreferredSize(new java.awt.Dimension(100, 106));
mainContentPanel.add(sameIdPanel);
mainContentPanel.add(filler5);
mainScrollPane.setViewportView(mainContentPanel);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(mainScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
}