diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 6c88edea66..19b4a67a42 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -61,7 +61,7 @@ file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-tran file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar -file.reference.sleuthkit-postgresql-4.6.5.jar=release/modules/ext/sleuthkit-postgresql-4.6.5.jar +file.reference.sleuthkit-postgresql-4.6.6.jar=release/modules/ext/sleuthkit-postgresql-4.6.6.jar file.reference.tagsoup-1.2.1.jar=release/modules/ext/tagsoup-1.2.1.jar file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index eee0ae059f..e91c3e7ff9 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -450,8 +450,8 @@ release/modules/ext/jhighlight-1.0.2.jar - ext/sleuthkit-postgresql-4.6.5.jar - release/modules/ext/sleuthkit-postgresql-4.6.5.jar + ext/sleuthkit-postgresql-4.6.6.jar + release/modules/ext/sleuthkit-postgresql-4.6.6.jar ext/jempbox-1.8.13.jar diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java index 03175347ca..8b8ed63699 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationDataSource.java @@ -168,7 +168,7 @@ public class CorrelationDataSource implements Serializable { * * @return the ID or -1 if unknown */ - int getID() { + public int getID() { return dataSourceID; } diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/AddDataSourceCallback.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/AddDataSourceCallback.java deleted file mode 100755 index b4a000b0f3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/AddDataSourceCallback.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 Basis Technology Corp. - * Contact: carrier sleuthkit 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.commandlineingest; - -import java.util.List; -import java.util.UUID; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; -import org.sleuthkit.datamodel.Content; - - -/** - * A "callback" that collects the results of running a data source processor on - * a data source and unblocks the job processing thread when the data source - * processor finishes running in its own thread. - */ -class AddDataSourceCallback extends DataSourceProcessorCallback { - - private final Case caseForJob; - private final DataSource dataSourceInfo; - private final UUID taskId; - private final Object lock; - - /** - * Constructs a "callback" that collects the results of running a data - * source processor on a data source and unblocks the job processing thread - * when the data source processor finishes running in its own thread. - * - * @param caseForJob The case for the current job. - * @param dataSourceInfo The data source - * @param taskId The task id to associate with ingest job events. - */ - AddDataSourceCallback(Case caseForJob, DataSource dataSourceInfo, UUID taskId, Object lock) { - this.caseForJob = caseForJob; - this.dataSourceInfo = dataSourceInfo; - this.taskId = taskId; - this.lock = lock; - } - - /** - * Called by the data source processor when it finishes running in its own - * thread. - * - * @param result The result code for the processing of the data source. - * @param errorMessages Any error messages generated during the processing - * of the data source. - * @param dataSourceContent The content produced by processing the data - * source. - */ - @Override - public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List errorMessages, List dataSourceContent) { - if (!dataSourceContent.isEmpty()) { - caseForJob.notifyDataSourceAdded(dataSourceContent.get(0), taskId); - } else { - caseForJob.notifyFailedAddingDataSource(taskId); - } - dataSourceInfo.setDataSourceProcessorOutput(result, errorMessages, dataSourceContent); - dataSourceContent.addAll(dataSourceContent); - synchronized (lock) { - lock.notifyAll(); - } - } - - /** - * Called by the data source processor when it finishes running in its own - * thread, if that thread is the AWT (Abstract Window Toolkit) event - * dispatch thread (EDT). - * - * @param result The result code for the processing of the data source. - * @param errorMessages Any error messages generated during the processing - * of the data source. - * @param dataSourceContent The content produced by processing the data - * source. - */ - @Override - public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List errorMessages, List dataSources) { - done(result, errorMessages, dataSources); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java index 13b76a5eca..8039aa0479 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java @@ -43,6 +43,9 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.TimeStampUtils; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSource; +import org.sleuthkit.autopsy.datasourceprocessors.AddDataSourceCallback; +import org.sleuthkit.autopsy.datasourceprocessors.DataSourceProcessorUtility; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJobSettings; @@ -177,7 +180,7 @@ public class CommandLineIngestManager { return; } - DataSource dataSource = new DataSource("", Paths.get(dataSourcePath)); + AutoIngestDataSource dataSource = new AutoIngestDataSource("", Paths.get(dataSourcePath)); try { // run data source processor runDataSourceProcessor(caseForJob, dataSource); @@ -228,7 +231,7 @@ public class CommandLineIngestManager { * @param dataSource DataSource object * @return object ID */ - private Long getDataSourceId(DataSource dataSource) { + private Long getDataSourceId(AutoIngestDataSource dataSource) { Content content = dataSource.getContent().get(0); return content.getId(); } @@ -271,7 +274,7 @@ public class CommandLineIngestManager { * task is interrupted while blocked, i.e., if auto ingest is shutting * down. */ - private void runDataSourceProcessor(Case caseForJob, DataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { + private void runDataSourceProcessor(Case caseForJob, AutoIngestDataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { LOGGER.log(Level.INFO, "Adding data source {0} ", dataSource.getPath().toString()); @@ -329,7 +332,7 @@ public class CommandLineIngestManager { * * @param dataSource The data source. */ - private void logDataSourceProcessorResult(DataSource dataSource) { + private void logDataSourceProcessorResult(AutoIngestDataSource dataSource) { DataSourceProcessorCallback.DataSourceProcessorResult resultCode = dataSource.getResultDataSourceProcessorResultCode(); if (null != resultCode) { @@ -376,7 +379,7 @@ public class CommandLineIngestManager { * task is interrupted while blocked, i.e., if auto ingest is shutting * down. */ - private void analyze(DataSource dataSource) throws AnalysisStartupException, InterruptedException { + private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, InterruptedException { LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", dataSource.getPath()); IngestJobEventListener ingestJobEventListener = new IngestJobEventListener(); diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java index 20811d0fb8..5d3a0787ce 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java @@ -193,7 +193,12 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { if (outputPath.isEmpty()) { jLabelInvalidResultsFolder.setVisible(true); jLabelInvalidResultsFolder.setText(NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified")); - return false; + /* + NOTE: JIRA-4850: Returning false disables OK and Apply buttons for the entire + Tools->Options bar until the path is set. It was decided to only validate + the path if the path is set. + */ + return true; } if (!isFolderPathValid(outputPath)) { diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/DataSource.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/DataSource.java deleted file mode 100755 index 527a4e57f3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/DataSource.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019-2019 Basis Technology Corp. - * Contact: carrier sleuthkit 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.commandlineingest; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; -import org.sleuthkit.datamodel.Content; - -class DataSource { - - private final String deviceId; - private final Path path; - private DataSourceProcessorResult resultCode; - private List errorMessages; - private List content; - - DataSource(String deviceId, Path path) { - this.deviceId = deviceId; - this.path = path; - } - - String getDeviceId() { - return deviceId; - } - - Path getPath() { - return this.path; - } - - synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List errorMessages, List content) { - this.resultCode = result; - this.errorMessages = new ArrayList<>(errorMessages); - this.content = new ArrayList<>(content); - } - - synchronized DataSourceProcessorResult getResultDataSourceProcessorResultCode() { - return resultCode; - } - - synchronized List getDataSourceProcessorErrorMessages() { - return new ArrayList<>(errorMessages); - } - - synchronized List getContent() { - return new ArrayList<>(content); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/AbstractCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/AbstractCommonAttributeSearcher.java index 00a4a0f653..1880489cc4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/AbstractCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/AbstractCommonAttributeSearcher.java @@ -130,12 +130,12 @@ public abstract class AbstractCommonAttributeSearcher { } } - static Map collateMatchesByNumberOfInstances(Map commonFiles) { + static TreeMap collateMatchesByNumberOfInstances(Map commonFiles) { //collate matches by number of matching instances - doing this in sql doesnt seem efficient - Map instanceCollatedCommonFiles = new TreeMap<>(); + TreeMap instanceCollatedCommonFiles = new TreeMap<>(); for (CommonAttributeValue md5Metadata : commonFiles.values()) { - Integer size = md5Metadata.getInstanceCount(); + Integer size = md5Metadata.getNumberOfDataSourcesInCurrentCase(); if (instanceCollatedCommonFiles.containsKey(size)) { instanceCollatedCommonFiles.get(size).addMetadataToList(md5Metadata); @@ -186,9 +186,9 @@ public abstract class AbstractCommonAttributeSearcher { * when checkType is ONLY_TEXT_FILES. ".doc", ".docx", ".odt", ".xls", * ".xlsx", ".ppt", ".pptx" ".txt", ".rtf", ".log", ".text", ".xml" ".html", * ".htm", ".css", ".js", ".php", ".aspx" ".pdf" + * //ignore text/plain due to large number of results with that type */ static final Set TEXT_FILES_MIME_TYPES = Stream.of( - "text/plain", //NON-NLS "application/rtf", //NON-NLS "application/pdf", //NON-NLS "text/css", //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties index cf530cf013..563c433322 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties @@ -44,5 +44,5 @@ CommonAttributePanel.resultsDisplayLabel.text_2=Display results organized by: CommonAttributePanel.organizeByCaseRadio.text=Case CommonAttributePanel.organizeByCountRadio.text=Number of occurrences CommonAttributePanel.caseResultsRadioButton.text=Case -CommonAttributePanel.countResultsRadioButton.text=Number of occurrences +CommonAttributePanel.countResultsRadioButton.text=Number of data sources CommonAttributePanel.displayResultsLabel.text_2=Display results organized by: diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties-MERGED index 10370577d7..d164529639 100755 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/Bundle.properties-MERGED @@ -60,7 +60,7 @@ CommonFilesSearchResultsViewerTable.noDescText=\ CommonFilesSearchResultsViewerTable.pathColLbl=Parent Path CommonFilesSearchResultsViewerTable.valueColLbl=Value InstanceCountNode.createSheet.noDescription=\ -InstanceCountNode.displayName=Files with %s instances (%s) +InstanceCountNode.displayName=Exists in %s data sources (%s) IntraCasePanel.selectDataSourceComboBox.actionCommand= CommonAttributePanel.jCheckBox1.text=Hide files found in over CommonAttributePanel.jLabel1.text=% of data sources in central repository. @@ -101,7 +101,7 @@ CommonAttributePanel.resultsDisplayLabel.text_2=Display results organized by: CommonAttributePanel.organizeByCaseRadio.text=Case CommonAttributePanel.organizeByCountRadio.text=Number of occurrences CommonAttributePanel.caseResultsRadioButton.text=Case -CommonAttributePanel.countResultsRadioButton.text=Number of occurrences +CommonAttributePanel.countResultsRadioButton.text=Number of data sources CommonAttributePanel.displayResultsLabel.text_2=Display results organized by: # {0} - case name # {1} - attr type diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java index c1d2dcb481..8265a8db25 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeMap; import java.util.logging.Level; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; @@ -58,7 +59,7 @@ final public class CommonAttributeCountSearchResults { */ CommonAttributeCountSearchResults(Map metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType) { //wrap in a new object in case any client code has used an unmodifiable collection - this.instanceCountToAttributeValues = new HashMap<>(metadata); + this.instanceCountToAttributeValues = new TreeMap<>(metadata); this.percentageThreshold = percentageThreshold; this.resultTypeId = resultType.getId(); } @@ -73,7 +74,7 @@ final public class CommonAttributeCountSearchResults { */ CommonAttributeCountSearchResults(Map metadata, int percentageThreshold) { //wrap in a new object in case any client code has used an unmodifiable collection - this.instanceCountToAttributeValues = new HashMap<>(metadata); + this.instanceCountToAttributeValues = new TreeMap<>(metadata); this.percentageThreshold = percentageThreshold; this.resultTypeId = CorrelationAttributeInstance.FILES_TYPE_ID; } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java index 72086983de..a9c9ba1762 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java @@ -294,7 +294,11 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer DataResultTopComponent.createInstance(tabTitle, Bundle.CommonAttributePanel_search_results_pathText(), commonFilesNode, 1); } else { // -3969 - Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata); + CorrelationAttributeInstance.Type correlationType = null; + if (interCaseRadio.isSelected()){ + correlationType = interCasePanel.getSelectedCorrelationType(); + } + Node commonFilesNode = new CommonAttributeSearchResultRootNode(metadata, correlationType); DataResultFilterNode dataResultFilterNode = new DataResultFilterNode(commonFilesNode, ExplorerManager.find(CommonAttributePanel.this)); TableFilterNode tableFilterWithDescendantsNode = new TableFilterNode(dataResultFilterNode, 3); DataResultViewerTable table = new CommonAttributesSearchResultsViewerTable(); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeSearchResultRootNode.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeSearchResultRootNode.java index 6576c9219f..27156f97fa 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeSearchResultRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeSearchResultRootNode.java @@ -25,6 +25,7 @@ import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -35,8 +36,8 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; */ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNode { - CommonAttributeSearchResultRootNode(CommonAttributeCountSearchResults metadataList) { - super(Children.create(new InstanceCountNodeFactory(metadataList), true)); + CommonAttributeSearchResultRootNode(CommonAttributeCountSearchResults metadataList, CorrelationAttributeInstance.Type type) { + super(Children.create(new InstanceCountNodeFactory(metadataList, type), true)); } CommonAttributeSearchResultRootNode(CommonAttributeCaseSearchResults metadataList) { @@ -73,6 +74,7 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo private static final Logger LOGGER = Logger.getLogger(InstanceCountNodeFactory.class.getName()); private final CommonAttributeCountSearchResults searchResults; + private final CorrelationAttributeInstance.Type type; /** * Build a factory which converts a @@ -81,8 +83,9 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo * * @param searchResults */ - InstanceCountNodeFactory(CommonAttributeCountSearchResults searchResults) { + InstanceCountNodeFactory(CommonAttributeCountSearchResults searchResults, CorrelationAttributeInstance.Type type) { this.searchResults = searchResults; + this.type = type; } @Override @@ -94,7 +97,7 @@ final public class CommonAttributeSearchResultRootNode extends DisplayableItemNo @Override protected Node createNodeForKey(Integer instanceCount) { CommonAttributeValueList attributeValues = this.searchResults.getAttributeValuesForInstanceCount(instanceCount); - return new InstanceCountNode(instanceCount, attributeValues); + return new InstanceCountNode(instanceCount, attributeValues, type); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValue.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValue.java index d86eff5a4f..c4fe25f14b 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValue.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValue.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,10 +22,13 @@ package org.sleuthkit.autopsy.commonpropertiessearch; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.sleuthkit.datamodel.AbstractFile; /** * Defines a value that was in the common file search results as well as @@ -35,6 +38,7 @@ final public class CommonAttributeValue { private final String value; private final List fileInstances; + private final Map fileNames = new HashMap<>(); CommonAttributeValue(String value) { this.value = value; @@ -45,6 +49,23 @@ final public class CommonAttributeValue { return this.value; } + /** + * Get the file name of the first available instance of this value. + * + * @return the file name of an instance of this file + */ + String getTokenFileName() { + String tokenFileName = null; + int maxValue = 0; + for (String key : fileNames.keySet()){ + if (fileNames.get(key) > maxValue){ + maxValue = fileNames.get(key); + tokenFileName = key; + } + } + return tokenFileName; + } + /** * concatenate cases this value was seen into a single string * @@ -54,16 +75,43 @@ final public class CommonAttributeValue { return this.fileInstances.stream().map(AbstractCommonAttributeInstance::getCaseName).collect(Collectors.joining(", ")); } - public String getDataSources() { + /** + * Get the set of data sources names this value exists in + * + * @return a set of data source names + */ + public Set getDataSources() { Set sources = new HashSet<>(); for (AbstractCommonAttributeInstance data : this.fileInstances) { sources.add(data.getDataSource()); } + return sources; + } - return String.join(", ", sources); + /** + * Get the number of unique data sources in the current case which the value + * appeared in. + * + * @return the number of unique data sources in the current case which + * contained the value + */ + int getNumberOfDataSourcesInCurrentCase() { + Set dataSourceIds = new HashSet<>(); + for (AbstractCommonAttributeInstance data : this.fileInstances) { + AbstractFile file = data.getAbstractFile(); + if (file != null) { + dataSourceIds.add(file.getDataSourceObjectId()); + } + } + return dataSourceIds.size(); } void addInstance(AbstractCommonAttributeInstance metadata) { + if (metadata.getAbstractFile() != null) { + Integer currentValue = fileNames.get(metadata.getAbstractFile().getName()); + currentValue = currentValue == null ? 1 : currentValue+1; + fileNames.put(metadata.getAbstractFile().getName(), currentValue); + } this.fileInstances.add(metadata); } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValueNode.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValueNode.java index d149847d78..ed8c395b5f 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValueNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeValueNode.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,8 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import static org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.FILES_TYPE_ID; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; @@ -49,15 +51,19 @@ public class CommonAttributeValueNode extends DisplayableItemNode { * * @param data the common feature, and the children */ - public CommonAttributeValueNode(CommonAttributeValue data) { + public CommonAttributeValueNode(CommonAttributeValue data, CorrelationAttributeInstance.Type type) { super(Children.create( new FileInstanceNodeFactory(data), true)); this.commonFileCount = data.getInstanceCount(); this.cases = data.getCases(); - // @@ We seem to be doing this string concat twice. We also do it in getDataSources() this.dataSources = String.join(", ", data.getDataSources()); this.value = data.getValue(); - this.setDisplayName(String.format(Bundle.CommonAttributeValueNode_CommonAttributeValueNode_format(), this.value)); + //if the type is null (indicating intra-case) or files then make the node name the representitive file name + if (type == null || type.getId() == FILES_TYPE_ID) { + this.setDisplayName(data.getTokenFileName()); + } else { + this.setDisplayName(String.format(Bundle.CommonAttributeValueNode_CommonAttributeValueNode_format(), this.value)); + } this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java index 29f817210d..973d7711c4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InstanceCountNode.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; @@ -45,6 +46,7 @@ public final class InstanceCountNode extends DisplayableItemNode { final private int instanceCount; final private CommonAttributeValueList attributeValues; + final private CorrelationAttributeInstance.Type type; /** * Create a node with the given number of instances, and the given selection @@ -54,11 +56,11 @@ public final class InstanceCountNode extends DisplayableItemNode { * @param attributeValues */ @NbBundle.Messages({ - "InstanceCountNode.displayName=Files with %s instances (%s)" + "InstanceCountNode.displayName=Exists in %s data sources (%s)" }) - public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues) { - super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), false)); - + public InstanceCountNode(int instanceCount, CommonAttributeValueList attributeValues, CorrelationAttributeInstance.Type type) { + super(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), false)); + this.type = type; this.instanceCount = instanceCount; this.attributeValues = attributeValues; @@ -81,7 +83,7 @@ public final class InstanceCountNode extends DisplayableItemNode { */ void createChildren() { attributeValues.displayDelayedMetadata(); - setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList()), false)); + setChildren(Children.create(new CommonAttributeValueNodeFactory(attributeValues.getMetadataList(), type), false)); } /** @@ -146,10 +148,11 @@ public final class InstanceCountNode extends DisplayableItemNode { */ // maps sting version of value to value Object (??) private final Map metadata; + private final CorrelationAttributeInstance.Type type; - CommonAttributeValueNodeFactory(List attributeValues) { + CommonAttributeValueNodeFactory(List attributeValues, CorrelationAttributeInstance.Type type) { this.metadata = new HashMap<>(); - + this.type = type; Iterator iterator = attributeValues.iterator(); while (iterator.hasNext()) { CommonAttributeValue attributeValue = iterator.next(); @@ -167,7 +170,7 @@ public final class InstanceCountNode extends DisplayableItemNode { @Override protected Node createNodeForKey(String attributeValue) { CommonAttributeValue md5Metadata = this.metadata.get(attributeValue); - return new CommonAttributeValueNode(md5Metadata); + return new CommonAttributeValueNode(md5Metadata, type); } } } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.form b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.form index f878323524..93a06ca05c 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.form +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.form @@ -134,7 +134,6 @@ - @@ -152,6 +151,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java index 2c6100a42c..c437bb0a3a 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -164,7 +164,6 @@ public final class InterCasePanel extends javax.swing.JPanel { categoriesLabel.setName(""); // NOI18N buttonGroup.add(allFileCategoriesRadioButton); - allFileCategoriesRadioButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.text")); // NOI18N allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N allFileCategoriesRadioButton.setEnabled(false); @@ -175,6 +174,7 @@ public final class InterCasePanel extends javax.swing.JPanel { }); buttonGroup.add(selectedFileCategoriesButton); + selectedFileCategoriesButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.text")); // NOI18N selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N selectedFileCategoriesButton.setEnabled(false); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseSearchResultsProcessor.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseSearchResultsProcessor.java index 52f2cec276..a6dd6833b3 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseSearchResultsProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseSearchResultsProcessor.java @@ -29,7 +29,9 @@ import java.util.HashSet; import java.util.Set; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; +import java.util.stream.Collectors; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type; @@ -173,7 +175,7 @@ final class InterCaseSearchResultsProcessor { } catch (EamDbException | TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex); } - return new HashMap<>(); + return new TreeMap<>(); } /** @@ -205,7 +207,7 @@ final class InterCaseSearchResultsProcessor { } catch (EamDbException | TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error accessing EamDb processing CaseInstancesTable.", ex); } - return new HashMap<>(); + return new TreeMap<>(); } /** @@ -248,7 +250,7 @@ final class InterCaseSearchResultsProcessor { */ private class InterCaseByCountCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback, InstanceTableCallback { - private final Map instanceCollatedCommonFiles = new HashMap<>(); + private final TreeMap instanceCollatedCommonFiles = new TreeMap<>(); private final int caseID; private final int targetCase; @@ -284,7 +286,7 @@ final class InterCaseSearchResultsProcessor { } else { instances = EamDb.getInstance().getArtifactInstancesByTypeValuesAndCases(correlationType, Arrays.asList(corValue), targetCases); } - int size = instances.size(); + int size = instances.stream().map(instance -> instance.getCorrelationDataSource().getID()).collect(Collectors.toSet()).size(); if (size > 1) { CommonAttributeValue commonAttributeValue = new CommonAttributeValue(corValue); boolean anotherCase = false; @@ -311,7 +313,7 @@ final class InterCaseSearchResultsProcessor { } Map getInstanceCollatedCommonFiles() { - return Collections.unmodifiableMap(instanceCollatedCommonFiles); + return Collections.unmodifiableSortedMap(instanceCollatedCommonFiles); } } diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCaseCommonAttributeSearcher.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCaseCommonAttributeSearcher.java index f48d23d030..dac83a333e 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCaseCommonAttributeSearcher.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCaseCommonAttributeSearcher.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,8 +37,8 @@ import org.sleuthkit.datamodel.TskCoreException; /** * * Generates a List when - * findMatchesByCount() is called, which organizes files by md5 to prepare - * to display in viewer. + * findMatchesByCount() is called, which organizes files by md5 to + * prepare to display in viewer. * * This entire thing runs on a background thread where exceptions are handled. */ diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.form b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.form index d4a11098bb..2d79515463 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.form +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.form @@ -99,6 +99,7 @@ + @@ -116,7 +117,6 @@ - @@ -128,7 +128,6 @@ - @@ -139,7 +138,6 @@ - diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.java index d0c7ab1579..69e02eb7a7 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/IntraCasePanel.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -154,6 +154,7 @@ public final class IntraCasePanel extends javax.swing.JPanel { categoriesLabel.setName(""); // NOI18N buttonGroup.add(selectedFileCategoriesButton); + selectedFileCategoriesButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.text")); // NOI18N selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() { @@ -164,7 +165,6 @@ public final class IntraCasePanel extends javax.swing.JPanel { pictureVideoCheckbox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.pictureVideoCheckbox.text")); // NOI18N - pictureVideoCheckbox.setEnabled(false); pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { pictureVideoCheckboxActionPerformed(evt); @@ -173,7 +173,6 @@ public final class IntraCasePanel extends javax.swing.JPanel { documentsCheckbox.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.documentsCheckbox.text")); // NOI18N - documentsCheckbox.setEnabled(false); documentsCheckbox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { documentsCheckboxActionPerformed(evt); @@ -181,7 +180,6 @@ public final class IntraCasePanel extends javax.swing.JPanel { }); buttonGroup.add(allFileCategoriesRadioButton); - allFileCategoriesRadioButton.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.text")); // NOI18N allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() { diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 1d610a5e82..128de511de 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -32,7 +32,16 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomOutButton.toolTipText=Zoom out VisualizationPanel.zoomOutButton.text= +<<<<<<< HEAD VisualizationPanel.fastOrganicLayoutButton.text=Redraw VisualizationPanel.clearVizButton.text_1=Clear VisualizationPanel.backButton.text_1=Undo VisualizationPanel.forwardButton.text=Redo +======= +VisualizationPanel.circleLayoutButton.text=Circle +VisualizationPanel.organicLayoutButton.text=Organic +VisualizationPanel.fastOrganicLayoutButton.text=Redraw +VisualizationPanel.hierarchyLayoutButton.text=Hierarchical +VisualizationPanel.clearVizButton.text_1=Clear Viz. +VisualizationPanel.snapshotButton.text_1=Snapshot Report +>>>>>>> develop diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index f3d5aba759..b62e471df2 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -32,6 +32,23 @@ ResetAndPinAccountsAction.singularText=Visualize Only Selected Account UnpinAccountsAction.pluralText=Remove Selected Accounts UnpinAccountsAction.singularText=Remove Selected Account VisalizationPanel.paintingError=Problem painting visualization. +# {0} - default name +VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0} +VisualizationPane_blank_report_title=Blank Report Name +VisualizationPane_DisplayName=Open Report +VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report: +VisualizationPane_MessageBoxTitle=Open Report Failure +VisualizationPane_MissingReportFileMessage=The report file no longer exists. +VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch. +VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way. +VisualizationPane_Open_Report=Open Report +# {0} - report name +VisualizationPane_overrite_exiting=Overwrite existing report?\n{0} +VisualizationPane_Report_OK_Button=OK +# {0} - report path +VisualizationPane_Report_Success=Report Successfully create at:\n{0} +VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied. +VisualizationPane_reportName=Communications Snapshot VisualizationPanel.cancelButton.text=Cancel VisualizationPanel.computingLayout=Computing Layout VisualizationPanel.jButton1.text=Fast Organic @@ -59,7 +76,20 @@ VisualizationPanel.zoomInButton.toolTipText=Zoom in VisualizationPanel.zoomInButton.text= VisualizationPanel.zoomOutButton.toolTipText=Zoom out VisualizationPanel.zoomOutButton.text= +<<<<<<< HEAD VisualizationPanel.fastOrganicLayoutButton.text=Redraw VisualizationPanel.clearVizButton.text_1=Clear VisualizationPanel.backButton.text_1=Undo VisualizationPanel.forwardButton.text=Redo +======= +VisualizationPanel.circleLayoutButton.text=Circle +VisualizationPanel.organicLayoutButton.text=Organic +VisualizationPanel.fastOrganicLayoutButton.text=Redraw +VisualizationPanel.hierarchyLayoutButton.text=Hierarchical +VisualizationPanel.clearVizButton.text_1=Clear Viz. +VisualizationPanel.snapshotButton.text_1=Snapshot Report +>>>>>>> develop +VisualizationPanel_action_dialogs_title=Communications +VisualizationPanel_action_name_text=Snapshot Report +VisualizationPanel_module_name=Communications +VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation. diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form index 910ff48a3e..c50fcfe761 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.form @@ -11,7 +11,7 @@ - + @@ -49,9 +49,9 @@ - + - + @@ -114,7 +114,11 @@ - + + + + + @@ -134,6 +138,8 @@ + + @@ -279,6 +285,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 4233fcb866..b8a5fe5325 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -29,6 +29,7 @@ import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.swing.mxGraphComponent; +import com.mxgraph.util.mxCellRenderer; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource; @@ -41,20 +42,30 @@ import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Cursor; +import java.awt.Desktop; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; +import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; +import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; +import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -76,14 +87,17 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSplitPane; import javax.swing.JTextArea; +import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.Notifications; import org.jdesktop.layout.GroupLayout; import org.jdesktop.layout.LayoutStyle; @@ -93,8 +107,11 @@ import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ProxyLookup; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.communications.snapshot.CommSnapShotReportWriter; +import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; @@ -104,7 +121,6 @@ import org.sleuthkit.datamodel.Content; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; import org.sleuthkit.datamodel.TskCoreException; - /** * A panel that goes in the Visualize tab of the Communications Visualization * Tool. Hosts an JGraphX mxGraphComponent that implements the communications @@ -392,6 +408,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider jSeparator2 = new JToolBar.Separator(); backButton = new JButton(); forwardButton = new JButton(); + snapshotButton = new JButton(); + jSeparator3 = new JToolBar.Separator(); notificationsJFXPanel = new JFXPanel(); setLayout(new BorderLayout()); @@ -411,9 +429,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider placeHolderPanel.setLayout(placeHolderPanelLayout); placeHolderPanelLayout.setHorizontalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() - .addContainerGap(280, Short.MAX_VALUE) + .addContainerGap(143, Short.MAX_VALUE) .add(jTextArea1, GroupLayout.PREFERRED_SIZE, 424, GroupLayout.PREFERRED_SIZE) - .addContainerGap(455, Short.MAX_VALUE)) + .addContainerGap(317, Short.MAX_VALUE)) ); placeHolderPanelLayout.setVerticalGroup(placeHolderPanelLayout.createParallelGroup(GroupLayout.LEADING) .add(placeHolderPanelLayout.createSequentialGroup() @@ -505,6 +523,16 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider } }); + snapshotButton.setIcon(new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/report/images/image.png"))); // NOI18N + snapshotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapshotButton.text_1")); // NOI18N + snapshotButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + snapshotButtonActionPerformed(evt); + } + }); + + jSeparator3.setOrientation(SwingConstants.VERTICAL); + GroupLayout toolbarLayout = new GroupLayout(toolbar); toolbar.setLayout(toolbarLayout); toolbarLayout.setHorizontalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) @@ -531,7 +559,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(zoomActualButton, GroupLayout.PREFERRED_SIZE, 33, GroupLayout.PREFERRED_SIZE) .addPreferredGap(LayoutStyle.RELATED) .add(fitZoomButton, GroupLayout.PREFERRED_SIZE, 32, GroupLayout.PREFERRED_SIZE) - .addContainerGap(157, Short.MAX_VALUE)) + .addPreferredGap(LayoutStyle.RELATED) + .add(jSeparator3, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.RELATED) + .add(snapshotButton) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); toolbarLayout.setVerticalGroup(toolbarLayout.createParallelGroup(GroupLayout.LEADING) .add(toolbarLayout.createSequentialGroup() @@ -547,7 +579,9 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider .add(clearVizButton) .add(jSeparator2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .add(backButton) - .add(forwardButton)) + .add(forwardButton) + .add(snapshotButton) + .add(jSeparator3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .add(3, 3, 3)) ); @@ -652,8 +686,8 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider if(newState == null) { return; } - - // If the zoom was changed, only change the zoom. + + // If the zoom was changed, only change the zoom. if(newState.isZoomChange()) { graph.getView().setScale(newState.getZoomValue()); return; @@ -678,13 +712,13 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider newState.getCommunicationsFilters().forEach(filter -> { currentFilter.addAndFilter(filter); }); - + rebuildGraph(); // Updates the display graph.getModel().endUpdate(); - - fitGraph(); - + + fitGraph(); + } private void setStateButtonsEnabled() { @@ -692,6 +726,24 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider forwardButton.setEnabled(stateManager.canAdvance()); } + @NbBundle.Messages({ + "VisualizationPanel_snapshot_report_failure=Snapshot report not created. An error occurred during creation." + }) + private void snapshotButtonActionPerformed(ActionEvent evt) {//GEN-FIRST:event_snapshotButtonActionPerformed + try { + handleSnapshotEvent(); + } catch (NoCurrentCaseException | IOException ex) { + logger.log(Level.SEVERE, "Unable to create communications snapsot report", ex); //NON-NLS + + Platform.runLater(() + -> Notifications.create().owner(notificationsJFXPanel.getScene().getWindow()) + .text(Bundle.VisualizationPanel_snapshot_report_failure()) + .showWarning()); + } catch( TskCoreException ex) { + logger.log(Level.WARNING, "Unable to add report to currenct case", ex); //NON-NLS + } + }//GEN-LAST:event_snapshotButtonActionPerformed + private void fitGraph() { graphComponent.zoomTo(1, true); mxPoint translate = graph.getView().getTranslate(); @@ -719,6 +771,128 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider graphComponent.zoom((heightFactor + widthFactor) / 2.0); } + /** + * Handle the ActionPerformed event from the Snapshot button. + * + * @throws NoCurrentCaseException + * @throws IOException + */ + @NbBundle.Messages({ + "VisualizationPanel_action_dialogs_title=Communications", + "VisualizationPanel_module_name=Communications", + "VisualizationPanel_action_name_text=Snapshot Report", + "VisualizationPane_fileName_prompt=Enter name for the Communications Snapshot Report:", + "VisualizationPane_reportName=Communications Snapshot", + "# {0} - default name", + "VisualizationPane_accept_defaultName=Report name was empty. Press OK to accept default report name: {0}", + "VisualizationPane_blank_report_title=Blank Report Name", + "# {0} - report name", + "VisualizationPane_overrite_exiting=Overwrite existing report?\n{0}" + }) + private void handleSnapshotEvent() throws NoCurrentCaseException, IOException, TskCoreException { + Case currentCase = Case.getCurrentCaseThrows(); + Date generationDate = new Date(); + + final String defaultReportName = FileUtil.escapeFileName(currentCase.getDisplayName() + " " + new SimpleDateFormat("MMddyyyyHHmmss").format(generationDate)); //NON_NLS + + final JTextField text = new JTextField(50); + final JPanel panel = new JPanel(new GridLayout(2, 1)); + panel.add(new JLabel(Bundle.VisualizationPane_fileName_prompt())); + panel.add(text); + + text.setText(defaultReportName); + + int result = JOptionPane.showConfirmDialog(graphComponent, panel, + Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION); + + if (result == JOptionPane.OK_OPTION) { + String enteredReportName = text.getText(); + + if(enteredReportName.trim().isEmpty()){ + result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_accept_defaultName(defaultReportName), Bundle.VisualizationPane_blank_report_title(), JOptionPane.OK_CANCEL_OPTION); + if(result != JOptionPane.OK_OPTION) { + return; + } + } + + String reportName = StringUtils.defaultIfBlank(enteredReportName, defaultReportName); + Path reportPath = Paths.get(currentCase.getReportDirectory(), reportName); + if (Files.exists(reportPath)) { + result = JOptionPane.showConfirmDialog(graphComponent, Bundle.VisualizationPane_overrite_exiting(reportName), + Bundle.VisualizationPanel_action_dialogs_title(), JOptionPane.OK_CANCEL_OPTION); + + if (result == JOptionPane.OK_OPTION) { + FileUtil.deleteFileDir(reportPath.toFile()); + createReport(currentCase, reportName); + } + } else { + createReport(currentCase, reportName); + currentCase.addReport(reportPath.toString(), Bundle.VisualizationPanel_module_name(), reportName); + + } + } + } + + /** + * Create the Snapshot Report. + * + * @param currentCase The current case + * @param reportName User selected name for the report + * + * @throws IOException + */ + @NbBundle.Messages({ + "VisualizationPane_DisplayName=Open Report", + "VisualizationPane_NoAssociatedEditorMessage=There is no associated editor for reports of this type or the associated application failed to launch.", + "VisualizationPane_MessageBoxTitle=Open Report Failure", + "VisualizationPane_NoOpenInEditorSupportMessage=This platform (operating system) does not support opening a file in an editor this way.", + "VisualizationPane_MissingReportFileMessage=The report file no longer exists.", + "VisualizationPane_ReportFileOpenPermissionDeniedMessage=Permission to open the report file was denied.", + "# {0} - report path", + "VisualizationPane_Report_Success=Report Successfully create at:\n{0}", + "VisualizationPane_Report_OK_Button=OK", + "VisualizationPane_Open_Report=Open Report",}) + private void createReport(Case currentCase, String reportName) throws IOException { + + // Create the report. + Path reportFolderPath = Paths.get(currentCase.getReportDirectory(), reportName, Bundle.VisualizationPane_reportName()); //NON_NLS + BufferedImage image = mxCellRenderer.createBufferedImage(graph, null, graph.getView().getScale(), Color.WHITE, true, null); + Path reportPath = new CommSnapShotReportWriter(currentCase, reportFolderPath, reportName, new Date(), image, currentFilter).writeReport(); + + // Report success to the user and offer to open the report. + String message = Bundle.VisualizationPane_Report_Success(reportPath.toAbsolutePath()); + String[] buttons = {Bundle.VisualizationPane_Open_Report(), Bundle.VisualizationPane_Report_OK_Button()}; + + int result = JOptionPane.showOptionDialog(graphComponent, message, + Bundle.VisualizationPanel_action_dialogs_title(), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, + null, buttons, buttons[1]); + if (result == JOptionPane.YES_NO_OPTION) { + try { + Desktop.getDesktop().open(reportPath.toFile()); + } catch (IOException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_NoAssociatedEditorMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (UnsupportedOperationException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_NoOpenInEditorSupportMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (IllegalArgumentException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_MissingReportFileMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } catch (SecurityException ex) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.VisualizationPane_ReportFileOpenPermissionDeniedMessage(), + Bundle.VisualizationPane_MessageBoxTitle(), + JOptionPane.ERROR_MESSAGE); + } + } + } // Variables declaration - do not modify//GEN-BEGIN:variables private JButton backButton; @@ -729,9 +903,11 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider private JButton forwardButton; private JLabel jLabel2; private JToolBar.Separator jSeparator2; + private JToolBar.Separator jSeparator3; private JTextArea jTextArea1; private JFXPanel notificationsJFXPanel; private JPanel placeHolderPanel; + private JButton snapshotButton; private JSplitPane splitPane; private JPanel toolbar; private JButton zoomActualButton; @@ -1042,4 +1218,4 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider lockedVertexModel.lock(selectedVertices); } } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java new file mode 100755 index 0000000000..7b4dee6d19 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/CommSnapShotReportWriter.java @@ -0,0 +1,175 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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.snapshot; + +import java.util.List; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Set; +import javax.imageio.ImageIO; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.report.uisnapshot.UiSnapShotReportWriter; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.CommunicationsFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter; +import org.sleuthkit.datamodel.CommunicationsFilter.SubFilter; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Generate and write the Communication snapshot report to disk. + */ +public class CommSnapShotReportWriter extends UiSnapShotReportWriter { + + private final BufferedImage image; + private final CommunicationsFilter filter; + + /** + * Constructor + * + * @param currentCase The Case to write a report for. + * @param reportFolderPath The Path to the folder that will contain the + * report. + * @param reportName The name of the report. + * @param generationDate The generation Date of the report. + * @param snapshot A snapshot of the view to include in the report. + */ + public CommSnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, Date generationDate, BufferedImage snapshot, CommunicationsFilter filter) { + + super(currentCase, reportFolderPath, reportName, generationDate); + + this.image = snapshot; + this.filter = filter; + + } + + /** + * Generate and write the html page that shows the snapshot and the state of + * the CommunicationFilters + * + * @throws IOException If there is a problem writing the html file to disk. + */ + @Override + protected void writeSnapShotHTMLFile() throws IOException { + SimpleDateFormat formatter = new SimpleDateFormat("MMMMM dd, yyyy"); //NON-NLS + + ImageIO.write(image, "png", getReportFolderPath().resolve("snapshot.png").toFile()); //NON-NLS + + //make a map of context objects to resolve template paramaters against + HashMap snapShotContext = new HashMap<>(); + snapShotContext.put("reportTitle", getReportName()); //NON-NLS + + List filters = filter.getAndFilters(); + + for (SubFilter filter : filters) { + if (filter instanceof DateRangeFilter) { + long startDate = ((DateRangeFilter) filter).getStartDate(); + long endDate = ((DateRangeFilter) filter).getEndDate(); + + if (startDate > 0) { + + snapShotContext.put("startTime", formatter.format(new Date((Instant.ofEpochSecond(startDate)).toEpochMilli()))); //NON-NLS + } + + if (endDate > 0) { + snapShotContext.put("endTime", formatter.format(new Date((Instant.ofEpochSecond(endDate)).toEpochMilli()))); //NON-NLS + } + } else if (filter instanceof AccountTypeFilter) { + + Set selectedAccounts = ((AccountTypeFilter) filter).getAccountTypes(); + ArrayList fullAccountList = new ArrayList<>(); + for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { + if (type == Account.Type.CREDIT_CARD) { + continue; + } + + fullAccountList.add(new ReportWriterHelper(type.getDisplayName(), selectedAccounts.contains(type))); + } + + snapShotContext.put("accounts", fullAccountList); + } else if (filter instanceof DeviceFilter) { + Collection ids = ((DeviceFilter) filter).getDevices(); + ArrayList list = new ArrayList<>(); + try { + final SleuthkitCase sleuthkitCase = getCurrentCase().getSleuthkitCase(); + for (DataSource dataSource : sleuthkitCase.getDataSources()) { + boolean selected = ids.contains(dataSource.getDeviceId()); + String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); + list.add(new ReportWriterHelper(dsName, selected)); + } + } catch (TskCoreException ex) { + + } + + snapShotContext.put("devices", list); + } + } + + fillTemplateAndWrite("/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html", "Snapshot", snapShotContext, getReportFolderPath().resolve("snapshot.html")); //NON-NLS + } + + /** + * Helper class for use with the html template + */ + private final class ReportWriterHelper { + + private final String label; + private final boolean selected; + + /** + * Helper class for use with the html template. + * + * @param label Display label + * @param selected Boolean selected state + */ + ReportWriterHelper(String label, boolean selected) { + this.label = label; + this.selected = selected; + } + + /** + * Returns the display label + * + * @return The display label + */ + public String getLabel(){ + return label; + } + + /** + * Returns the selection state + * + * @return The selection state + */ + public boolean isSelected(){ + return selected; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html new file mode 100755 index 0000000000..078f6fc978 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/snapshot/comm_snapshot_template.html @@ -0,0 +1,21 @@ + + + Communications Snapshot: {{reportTitle}} + + + + +
+ Snapshot + + + + + + {{#devices}}{{/devices}} + + {{#accounts}}{{/accounts}} +
Date Range
Start:{{startTime}}
End:{{endTime}}
Devices:
{{label}}
Account Types
{{label}}
+
+ + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index c25f37199e..02ea2b02cb 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -342,9 +342,9 @@ - + - + - + - + @@ -258,8 +258,8 @@ - - + + @@ -289,7 +289,7 @@ - + diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index 3d2abda74e..d1f5f25777 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -165,8 +165,35 @@ final class VcardParser { List accountInstances = new ArrayList<>(); extractPhotos(vcard, abstractFile); - - ThunderbirdMboxFileIngestModule.addArtifactAttribute(vcard.getFormattedName().getValue(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON, attributes); + + String name = ""; + if (vcard.getFormattedName() != null) { + name = vcard.getFormattedName().getValue(); + } else { + if (vcard.getStructuredName() != null) { + // Attempt to put the name together if there was no formatted version + for (String prefix:vcard.getStructuredName().getPrefixes()) { + name += prefix + " "; + } + if (vcard.getStructuredName().getGiven() != null) { + name += vcard.getStructuredName().getGiven() + " "; + } + if (vcard.getStructuredName().getFamily() != null) { + name += vcard.getStructuredName().getFamily() + " "; + } + for (String suffix:vcard.getStructuredName().getSuffixes()) { + name += suffix + " "; + } + if (! vcard.getStructuredName().getAdditionalNames().isEmpty()) { + name += "("; + for (String addName:vcard.getStructuredName().getAdditionalNames()) { + name += addName + " "; + } + name += ")"; + } + } + } + ThunderbirdMboxFileIngestModule.addArtifactAttribute(name, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON, attributes); for (Telephone telephone : vcard.getTelephoneNumbers()) { addPhoneAttributes(telephone, abstractFile, attributes); diff --git a/unix_setup.sh b/unix_setup.sh old mode 100755 new mode 100644 index 8e7c9a94c5..0b252b2598 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -5,7 +5,7 @@ # NOTE: update_sleuthkit_version.pl updates this value and relies # on it keeping the same name and whitespace. Don't change it. -TSK_VERSION=4.6.5 +TSK_VERSION=4.6.6 # In the beginning...