From babdf9d3460d1a2a896119249a04971de9432a87 Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Fri, 9 Mar 2018 10:42:34 -0700 Subject: [PATCH 001/101] stubbing out new feature --- .../commonfilesearch/Bundle.properties | 1 + .../commonfilesearch/CommonFilesChildren.java | 49 +++++++++++++++++++ .../commonfilesearch/CommonFilesNode.java | 45 +++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties new file mode 100644 index 0000000000..9b1c99c32b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -0,0 +1 @@ +CommonFilesNode.getName.text=Common Files \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java new file mode 100644 index 0000000000..11cfd5c967 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.commonfilesearch; + +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; +import org.openide.nodes.Children; +import org.openide.nodes.Node; + +/** + * + * @author bsweeney + */ +class CommonFilesChildren extends Children.Keys{ + + CommonFilesChildren(boolean lazy, List fileList){ + super(lazy); + this.setKeys(fileList); + } + + @Override + protected Node[] createNodes(AbstractFile t) { + + if(t.isDir()){ + //TODO log an error ??? + + } else { + + } + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java new file mode 100644 index 0000000000..ff52d8e19e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.commonfilesearch; + +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * + * @author bsweeney + */ +class CommonFilesNode extends AbstractNode { + + private CommonFilesChildren children; + + public CommonFilesNode(List keys) { + super(new CommonFilesChildren(true, keys)); + this.children = (CommonFilesChildren) this.getChildren(); + } + + @Override + public String getName(){ + return NbBundle.getMessage(this.getClass(), "CommonFilesNode.getName.text"); + } + +} From d7c610d96af3b82c8f534f132d2e10da4fd78f7c Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Fri, 9 Mar 2018 10:49:04 -0700 Subject: [PATCH 002/101] private member made final --- .../org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java index ff52d8e19e..e6d8c26995 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.AbstractFile; */ class CommonFilesNode extends AbstractNode { - private CommonFilesChildren children; + private final CommonFilesChildren children; public CommonFilesNode(List keys) { super(new CommonFilesChildren(true, keys)); From 043334b82e65f8d25cf3351ceac3b248d045437a Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Fri, 9 Mar 2018 10:42:34 -0700 Subject: [PATCH 003/101] stubbing out new feature --- .../commonfilesearch/Bundle.properties | 1 + .../commonfilesearch/CommonFilesChildren.java | 49 +++++++++++++++++++ .../commonfilesearch/CommonFilesNode.java | 45 +++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java create mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties new file mode 100644 index 0000000000..9b1c99c32b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/Bundle.properties @@ -0,0 +1 @@ +CommonFilesNode.getName.text=Common Files \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java new file mode 100644 index 0000000000..11cfd5c967 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java @@ -0,0 +1,49 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.commonfilesearch; + +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; +import org.openide.nodes.Children; +import org.openide.nodes.Node; + +/** + * + * @author bsweeney + */ +class CommonFilesChildren extends Children.Keys{ + + CommonFilesChildren(boolean lazy, List fileList){ + super(lazy); + this.setKeys(fileList); + } + + @Override + protected Node[] createNodes(AbstractFile t) { + + if(t.isDir()){ + //TODO log an error ??? + + } else { + + } + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java new file mode 100644 index 0000000000..ff52d8e19e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.commonfilesearch; + +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * + * @author bsweeney + */ +class CommonFilesNode extends AbstractNode { + + private CommonFilesChildren children; + + public CommonFilesNode(List keys) { + super(new CommonFilesChildren(true, keys)); + this.children = (CommonFilesChildren) this.getChildren(); + } + + @Override + public String getName(){ + return NbBundle.getMessage(this.getClass(), "CommonFilesNode.getName.text"); + } + +} From aba1c7bd4ce2292fb8f7a8ff4f45f8d9d9cb857a Mon Sep 17 00:00:00 2001 From: Brian Sweeney Date: Fri, 9 Mar 2018 10:49:04 -0700 Subject: [PATCH 004/101] private member made final --- .../org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java index ff52d8e19e..e6d8c26995 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesNode.java @@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.AbstractFile; */ class CommonFilesNode extends AbstractNode { - private CommonFilesChildren children; + private final CommonFilesChildren children; public CommonFilesNode(List keys) { super(new CommonFilesChildren(true, keys)); From c05e92aec20db4fa420541247bcc03abb7642185 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 27 Apr 2018 11:08:46 -0400 Subject: [PATCH 005/101] 3757: Group views in the case tree by data source --- .../autopsy/core/UserPreferences.java | 9 ++ .../datamodel/AbstractContentChildren.java | 6 +- .../datamodel/DataSourceLayerNode.java | 114 +++++++++++++++ .../datamodel/DataSourcesLayerChildren.java | 134 ++++++++++++++++++ .../autopsy/datamodel/DeletedContent.java | 51 +++++-- .../datamodel/DisplayableItemNodeVisitor.java | 7 + .../sleuthkit/autopsy/datamodel/FileSize.java | 53 +++++-- .../autopsy/datamodel/FileTypes.java | 14 +- .../datamodel/FileTypesByExtension.java | 9 +- .../datamodel/FileTypesByMimeType.java | 10 +- .../autopsy/datamodel/ViewsNode.java | 25 ++-- .../autopsy/directorytree/Bundle.properties | 1 + .../DirectoryTreeTopComponent.form | 46 ++++-- .../DirectoryTreeTopComponent.java | 58 ++++++-- 14 files changed, 467 insertions(+), 70 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 904ab285c8..22fa2b7135 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -69,6 +69,7 @@ public final class UserPreferences { private static final String MODE = "AutopsyMode"; // NON-NLS private static final String MAX_NUM_OF_LOG_FILE = "MaximumNumberOfLogFiles"; private static final int LOG_FILE_NUM_INT = 10; + public static final String GROUP_ITEMS_IN_TREE_BY_DATASOURCE = "GroupItemsInTreeByDataSource"; //NON-NLS // Prevent instantiation. private UserPreferences() { @@ -187,6 +188,14 @@ public final class UserPreferences { preferences.putInt(NUMBER_OF_FILE_INGEST_THREADS, value); } + public static boolean groupItemsInTreeByDatasource() { + return preferences.getBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, false); + } + + public static void setGroupItemsInTreeByDatasource(boolean value) { + preferences.putBoolean(GROUP_ITEMS_IN_TREE_BY_DATASOURCE, value); + } + /** * Reads persisted case database connection info. * diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bf139f9a47..25da96db78 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -162,12 +162,12 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(DeletedContent dc) { - return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase()); + return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override public AbstractNode visit(FileSize dc) { - return new FileSize.FileSizeRootNode(dc.getSleuthkitCase()); + return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId()); } @Override @@ -234,7 +234,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) { - return ftByMimeTypeItem.new ByMimeTypeNode(); + return ftByMimeTypeItem.new ByMimeTypeNode(/*ftByMimeTypeItem.filteringDataSourceObjId()*/); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java new file mode 100644 index 0000000000..9573597d5a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java @@ -0,0 +1,114 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.datamodel; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; + + +/** + * Datasource layer node - an optional grouping node in the data tree view + * + */ +public class DataSourceLayerNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(DataSourceLayerNode.class.getName()); + + /** + * Creates the Datasource node for the given data source, + * and initializes the children nodes under it based on the subtree specified + * + * @param dataSourceLayerInfo specifies the + */ + DataSourceLayerNode(DataSourcesLayerChildren.DataSourceLayerInfo dataSourceLayerInfo) { + + super (Optional.ofNullable(createDSLayerNodeChildren(dataSourceLayerInfo)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); + + DataSource dataSource = dataSourceLayerInfo.getDataSource(); + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSLayerNodeChildren(DataSourcesLayerChildren.DataSourceLayerInfo dataSourceLayerInfo) { + + SubtreeEnum subtree = dataSourceLayerInfo.getSubtree(); + long dsObjId = dataSourceLayerInfo.getDataSource().getId(); + + try { + switch (subtree) { + case VIEWS: + return new RootContentChildren(Arrays.asList( + new FileTypes(Case.getOpenCase().getSleuthkitCase(), dsObjId), + new DeletedContent(Case.getOpenCase().getSleuthkitCase(), dsObjId), + new FileSize(Case.getOpenCase().getSleuthkitCase(), dsObjId)) + ); + + case RESULTS: // TBD: + case TAGS: // TBD: + case REPORTS: // TBD: + + default: + { + logger.log(Level.SEVERE, "Unknown subtree type " + subtree.name()); //NON-NLS + return null; + } + } + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java new file mode 100644 index 0000000000..95513b3b97 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java @@ -0,0 +1,134 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.logging.Level; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Child factory for DataSource node layer in the Results, Views Tags subtrees + * + */ +class DataSourcesLayerChildren extends Children.Keys { + + private static final Logger logger = Logger.getLogger(DataSourcesLayerChildren.class.getName()); + private final SleuthkitCase sleuthkitCase; + private final SubtreeEnum subTree; + + /** + * Subtree in which this DataSourcesLayerChildren exist + */ + public enum SubtreeEnum { + VIEWS, + RESULTS, + TAGS, + REPORTS + } + + /** + * Simple wrapper class to pass Datasource and subtree down to children nodes + * + */ + class DataSourceLayerInfo { + + private final DataSource dataSource; + private final SubtreeEnum subTree; + + DataSourceLayerInfo(DataSource dataSource, SubtreeEnum subTree) { + this.dataSource = dataSource; + this.subTree = subTree; + } + + DataSource getDataSource() { + return this.dataSource; + } + + SubtreeEnum getSubtree() { + return this.subTree; + } + } + + /** + * Constructs the factory to create optional datasource nodes + * + * @param tskCase - Case DB + * @param subTree - subtree under which data source nodes are to be created + */ + public DataSourcesLayerChildren(SleuthkitCase tskCase, SubtreeEnum subTree) { + // super(true); + super(false); + + this.sleuthkitCase = tskCase; + this.subTree = subTree; + } + + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + reloadKeys(); + } + } + }; + + private void reloadKeys() { + try { + List keys = sleuthkitCase.getDataSources(); + setKeys(keys); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get Datasources from DB", ex); + } + } + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + reloadKeys(); + } + + @Override + protected void removeNotify() { + super.removeNotify(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + setKeys(Collections.emptyList()); + } + + @Override + protected Node[] createNodes(DataSource ds) { + return new Node[]{createNodeForKey(ds)}; + } + + protected Node createNodeForKey(DataSource ds) { + return new DataSourceLayerNode(new DataSourceLayerInfo(ds, subTree)); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 816105d080..ff0837a14a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskData; public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; @NbBundle.Messages({"DeletedContent.fsDelFilter.text=File System", "DeletedContent.allDelFilter.text=All"}) @@ -101,9 +102,18 @@ public class DeletedContent implements AutopsyVisitableItem { } public DeletedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public DeletedContent(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor v) { return v.visit(this); @@ -118,8 +128,8 @@ public class DeletedContent implements AutopsyVisitableItem { @NbBundle.Messages("DeletedContent.deletedContentsNode.name=Deleted Files") private static final String NAME = Bundle.DeletedContent_deletedContentsNode_name(); - DeletedContentsNode(SleuthkitCase skCase) { - super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME)); + DeletedContentsNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new DeletedContentsChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS @@ -164,11 +174,13 @@ public class DeletedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; private Observable notifier; + private final long datasourceObjId; // true if we have already told user that not all files will be shown private static volatile boolean maxFilesDialogShown = false; - public DeletedContentsChildren(SleuthkitCase skCase) { + public DeletedContentsChildren(SleuthkitCase skCase, long dsObjId) { this.skCase = skCase; + this.datasourceObjId = dsObjId; this.notifier = new DeletedContentsChildrenObservable(); } @@ -257,24 +269,27 @@ public class DeletedContent implements AutopsyVisitableItem { @Override protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) { - return new DeletedContentNode(skCase, key, notifier); + return new DeletedContentNode(skCase, key, notifier, datasourceObjId); } public class DeletedContentNode extends DisplayableItemNode { private final DeletedContent.DeletedContentFilter filter; + private final long datasourceObjId; // Use version that has observer for updates @Deprecated - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter) { - super(Children.create(new DeletedContentChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, null, dsObjId ), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); } - DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o) { - super(Children.create(new DeletedContentChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o, long dsObjId) { + super(Children.create(new DeletedContentChildren(filter, skCase, o, dsObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = dsObjId; init(); o.addObserver(new DeletedContentNodeObserver()); } @@ -299,7 +314,7 @@ public class DeletedContent implements AutopsyVisitableItem { private void updateDisplayName() { //get count of children without preloading all children nodes - final long count = DeletedContentChildren.calculateItems(skCase, filter); + final long count = DeletedContentChildren.calculateItems(skCase, filter, datasourceObjId); //final long count = getChildren().getNodesCount(true); super.setDisplayName(filter.getDisplayName() + " (" + count + ")"); } @@ -351,11 +366,13 @@ public class DeletedContent implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName()); private static final int MAX_OBJECTS = 10001; private final Observable notifier; + private final long datasourceObjId; - DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o) { + DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = datasourceObjId; } private final Observer observer = new DeletedContentChildrenObserver(); @@ -405,7 +422,7 @@ public class DeletedContent implements AutopsyVisitableItem { return true; } - static private String makeQuery(DeletedContent.DeletedContentFilter filter) { + static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) { String query = ""; switch (filter) { case FS_DELETED_FILTER: @@ -443,6 +460,10 @@ public class DeletedContent implements AutopsyVisitableItem { + " OR known IS NULL)"; //NON-NLS } + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + query += " LIMIT " + MAX_OBJECTS; //NON-NLS return query; } @@ -450,7 +471,7 @@ public class DeletedContent implements AutopsyVisitableItem { private List runFsQuery() { List ret = new ArrayList<>(); - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); try { ret = skCase.findAllFilesWhere(query); } catch (TskCoreException e) { @@ -469,9 +490,9 @@ public class DeletedContent implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 2713992139..de41409c42 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -60,6 +60,8 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); + T visit(DataSourceLayerNode vn); + T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); T visit(DeletedContentNode dcn); @@ -307,6 +309,11 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(vn); } + @Override + public T visit(DataSourceLayerNode vn) { + return defaultVisit(vn); + } + @Override public T visit(ResultsNode rn) { return defaultVisit(rn); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 0a9321e4d5..a4c27b69f9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.VirtualDirectory; public class FileSize implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public enum FileSizeFilter implements AutopsyVisitableItem { @@ -97,9 +98,14 @@ public class FileSize implements AutopsyVisitableItem { } public FileSize(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public FileSize(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor v) { return v.visit(this); @@ -109,6 +115,9 @@ public class FileSize implements AutopsyVisitableItem { return this.skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /* * Root node. Children are nodes for specific sizes. */ @@ -116,8 +125,8 @@ public class FileSize implements AutopsyVisitableItem { private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name"); - FileSizeRootNode(SleuthkitCase skCase) { - super(Children.create(new FileSizeRootChildren(skCase), true), Lookups.singleton(NAME)); + FileSizeRootNode(SleuthkitCase skCase, long datasourceObjId) { + super(Children.create(new FileSizeRootChildren(skCase, datasourceObjId), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS @@ -161,10 +170,12 @@ public class FileSize implements AutopsyVisitableItem { public static class FileSizeRootChildren extends ChildFactory { private SleuthkitCase skCase; + private long datasourceObjId; private Observable notifier; - public FileSizeRootChildren(SleuthkitCase skCase) { + public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { this.skCase = skCase; + this.datasourceObjId = datasourceObjId; notifier = new FileSizeRootChildrenObservable(); } @@ -248,7 +259,7 @@ public class FileSize implements AutopsyVisitableItem { @Override protected Node createNodeForKey(FileSizeFilter key) { - return new FileSizeNode(skCase, key, notifier); + return new FileSizeNode(skCase, key, notifier, datasourceObjId); } /* @@ -257,12 +268,14 @@ public class FileSize implements AutopsyVisitableItem { public class FileSizeNode extends DisplayableItemNode { private FileSizeFilter filter; + private final long datasourceObjId; // use version with observer instead so that it updates @Deprecated - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter) { - super(Children.create(new FileSizeChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, null, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); } @@ -272,10 +285,12 @@ public class FileSize implements AutopsyVisitableItem { * @param filter * @param o Observable that provides updates when events are * fired + * @param datasourceObjId filter by data source, if configured in user preferences */ - FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o) { - super(Children.create(new FileSizeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o, long datasourceObjId) { + super(Children.create(new FileSizeChildren(filter, skCase, o, datasourceObjId), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; + this.datasourceObjId = datasourceObjId; init(); o.addObserver(new FileSizeNodeObserver()); } @@ -309,7 +324,7 @@ public class FileSize implements AutopsyVisitableItem { } private void updateDisplayName() { - final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter); + final long numVisibleChildren = FileSizeChildren.calculateItems(skCase, filter, datasourceObjId); super.setDisplayName(filter.getDisplayName() + " (" + numVisibleChildren + ")"); } @@ -349,6 +364,7 @@ public class FileSize implements AutopsyVisitableItem { private final SleuthkitCase skCase; private final FileSizeFilter filter; private final Observable notifier; + private final long datasourceObjId; private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName()); /** @@ -358,10 +374,12 @@ public class FileSize implements AutopsyVisitableItem { * @param o Observable that provides updates when new files are * added to case */ - FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o) { + FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) { this.skCase = skCase; this.filter = filter; this.notifier = o; + this.datasourceObjId = dsObjId; + } @Override @@ -395,7 +413,7 @@ public class FileSize implements AutopsyVisitableItem { return true; } - private static String makeQuery(FileSizeFilter filter) { + private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) { String query; switch (filter) { case SIZE_50_200: @@ -427,6 +445,11 @@ public class FileSize implements AutopsyVisitableItem { query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS } + // filter by datasource if indicated in user preferences + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND data_source_obj_id = " + filteringDSObjId; + } + return query; } @@ -434,7 +457,7 @@ public class FileSize implements AutopsyVisitableItem { List ret = new ArrayList<>(); try { - String query = makeQuery(filter); + String query = makeQuery(filter, datasourceObjId); ret = skCase.findAllFilesWhere(query); } catch (Exception e) { @@ -449,9 +472,9 @@ public class FileSize implements AutopsyVisitableItem { * * @return */ - static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter) { + static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter, long datasourceObjId) { try { - return sleuthkitCase.countFilesWhere(makeQuery(filter)); + return sleuthkitCase.countFilesWhere(makeQuery(filter, datasourceObjId)); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS return 0; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index 53822532bb..49f63749d1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -72,11 +72,18 @@ public final class FileTypes implements AutopsyVisitableItem { private final SleuthkitCase skCase; + private final long datasourceObjId; + FileTypes(SleuthkitCase skCase) { - this.skCase = skCase; - updateShowCounts(); + this(skCase, 0); } + FileTypes(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + updateShowCounts(); + } + @Override public T accept(AutopsyItemVisitor v) { return v.visit(this); @@ -86,6 +93,9 @@ public final class FileTypes implements AutopsyVisitableItem { return skCase; } + long filteringDataSourceObjId() { + return this.datasourceObjId; + } /** * Check the db to determine if the nodes should show child counts. */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 2d8cc564d2..342763a57b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -69,6 +69,10 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return v.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. @@ -153,7 +157,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * @param o Observable that was created by a higher-level node that * provides updates on events */ - private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { + private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o /*, long datasourceObjId */) { super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); @@ -359,6 +363,9 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { + (UserPreferences.hideKnownFilesInViewsTree() ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ") + + (UserPreferences.groupItemsInTreeByDatasource() + ? " AND data_source_obj_id = " + FileTypesByExtension.this.filteringDataSourceObjId() + : " ") + " AND (extension IN (" + filter.getFilter().stream() .map(String::toLowerCase) .map(s -> "'"+StringUtils.substringAfter(s, ".")+"'") diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index e7af90fab7..379b790cd6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree; import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree; import org.sleuthkit.autopsy.coreutils.Logger; @@ -91,15 +92,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * @return The base expression to be used in the where clause of queries for * files by mime type. */ - static private String createBaseWhereExpr() { + private String createBaseWhereExpr() { return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" + " AND (type IN (" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() - + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + "))" + + ( UserPreferences.groupItemsInTreeByDatasource() ? " AND data_source_obj_id = " + this.filteringDataSourceObjId() : " ") + (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : ""); } @@ -188,6 +190,10 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi return v.visit(this); } + long filteringDataSourceObjId() { + return typesRoot.filteringDataSourceObjId(); + } + /** * Method to check if the node in question is a ByMimeTypeNode which is * empty. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 9d1f0e0d63..da907c37b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,8 @@ import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -34,14 +36,19 @@ public class ViewsNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); public ViewsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new FileTypes(sleuthkitCase), - // June '15: Recent Files was removed because it was not useful w/out filtering - // add it back in if we can filter the results to a more managable size. - // new RecentFiles(sleuthkitCase), - new DeletedContent(sleuthkitCase), - new FileSize(sleuthkitCase))), - Lookups.singleton(NAME)); + + super( UserPreferences.groupItemsInTreeByDatasource() ? + new DataSourcesLayerChildren(sleuthkitCase, SubtreeEnum.VIEWS) : + new RootContentChildren(Arrays.asList( + new FileTypes(sleuthkitCase), + // June '15: Recent Files was removed because it was not useful w/out filtering + // add it back in if we can filter the results to a more managable size. + // new RecentFiles(sleuthkitCase), + new DeletedContent(sleuthkitCase), + new FileSize(sleuthkitCase)) + ), + Lookups.singleton(NAME) + ); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 647c6f70d3..0e7f3cf315 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -119,3 +119,4 @@ AddExternalViewerRulePanel.browseButton.text=Browse AddExternalViewerRulePanel.exePathTextField.text= AddExternalViewerRulePanel.exePathLabel.text=Path of the program to use for files with this type or extension AddExternalViewerRulePanel.extRadioButton.text=Extension +DirectoryTreeTopComponent.groupByDatasourceCheckBox.text=Group by Data Source diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form index 90c6ac00a6..48dfad0bb6 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.form @@ -16,14 +16,17 @@ - - - + + + - - + + + + + @@ -31,14 +34,23 @@ - - - - - + + + + + + + + + + + + + + - - + + @@ -130,5 +142,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 13219f863d..00f2c6a110 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -141,6 +141,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat switch (evt.getKey()) { case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE: case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE: + case UserPreferences.GROUP_ITEMS_IN_TREE_BY_DATASOURCE: refreshContentTreeSafe(); break; case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE: @@ -181,6 +182,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat backButton = new javax.swing.JButton(); forwardButton = new javax.swing.JButton(); showRejectedCheckBox = new javax.swing.JCheckBox(); + groupByDatasourceCheckBox = new javax.swing.JCheckBox(); treeView.setBorder(null); @@ -218,30 +220,45 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat org.openide.awt.Mnemonics.setLocalizedText(showRejectedCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.showRejectedCheckBox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(groupByDatasourceCheckBox, org.openide.util.NbBundle.getMessage(DirectoryTreeTopComponent.class, "DirectoryTreeTopComponent.groupByDatasourceCheckBox.text")); // NOI18N + groupByDatasourceCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + groupByDatasourceCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE) + .addComponent(treeView) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) + .addContainerGap() .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 46, Short.MAX_VALUE) - .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 65, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showRejectedCheckBox) + .addComponent(groupByDatasourceCheckBox)) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(showRejectedCheckBox)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(showRejectedCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(groupByDatasourceCheckBox)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(forwardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(backButton, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 854, Short.MAX_VALUE) + .addComponent(treeView, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) .addGap(0, 0, 0)) ); }// //GEN-END:initComponents @@ -295,9 +312,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.setCursor(null); }//GEN-LAST:event_forwardButtonActionPerformed + private void groupByDatasourceCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_groupByDatasourceCheckBoxActionPerformed + UserPreferences.setGroupItemsInTreeByDatasource(this.groupByDatasourceCheckBox.isSelected()); + }//GEN-LAST:event_groupByDatasourceCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton backButton; private javax.swing.JButton forwardButton; + private javax.swing.JCheckBox groupByDatasourceCheckBox; private javax.swing.JCheckBox showRejectedCheckBox; private javax.swing.JScrollPane treeView; // End of variables declaration//GEN-END:variables @@ -429,6 +451,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); showRejectedCheckBox.setSelected(false); + groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); + Node views = rootChildren.findChild(ViewsNode.NAME); Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); tree.collapseNode(views); @@ -772,7 +796,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { - SwingUtilities.invokeLater(this::refreshDataSourceTree); + SwingUtilities.invokeLater(this::refreshTree); } /** @@ -793,6 +817,18 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat setSelectedNode(selectedPath, DataSourcesNode.NAME); } + /** + * Refreshes the entire tree + */ + private void refreshTree() { + Node selectedNode = getSelectedNode(); + final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); + + contentChildren.refreshContentKeys(); + + setSelectedNode(selectedPath, DataSourcesNode.NAME); + } + /** * Set the selected node using a path to a previously selected node. * From 3f01e351c2b3842967239afc051621596936e0f4 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 27 Apr 2018 11:29:13 -0400 Subject: [PATCH 006/101] 3757: Group views in the case tree by data source - Removed commented code. --- .../org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 451c32f09b..b4dd903e83 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -157,7 +157,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * @param o Observable that was created by a higher-level node that * provides updates on events */ - private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o /*, long datasourceObjId */) { + private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); From 97df1d2c215d66648529399eb535392511eba890 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 27 Apr 2018 13:36:48 -0400 Subject: [PATCH 007/101] Addressed Codacy comments. --- Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java | 2 +- .../org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index 83cbde398e..f1871c46bf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -170,7 +170,7 @@ public class FileSize implements AutopsyVisitableItem { public static class FileSizeRootChildren extends ChildFactory { private SleuthkitCase skCase; - private long datasourceObjId; + private final long datasourceObjId; private Observable notifier; public FileSizeRootChildren(SleuthkitCase skCase, long datasourceObjId) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index b4dd903e83..4689dd515a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -364,7 +364,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { ? " AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")" : " ") + (UserPreferences.groupItemsInTreeByDatasource() - ? " AND data_source_obj_id = " + FileTypesByExtension.this.filteringDataSourceObjId() + ? " AND data_source_obj_id = " + filteringDataSourceObjId() : " ") + " AND (extension IN (" + filter.getFilter().stream() .map(String::toLowerCase) From 05c41132f80904101324e63f91f87eb8c1f987c3 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 2 May 2018 08:27:07 -0400 Subject: [PATCH 008/101] Group Results tree by Datasource. Interim commit to save the partial work. Some of this will need to be fixed for Vik-3802 --- .../datamodel/DataSourceLayerNode.java | 9 ++++++ .../autopsy/datamodel/ExtractedContent.java | 30 +++++++++++++++++-- .../autopsy/datamodel/ResultsNode.java | 21 ++++++++----- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java index 9573597d5a..95eb015c09 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; +import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.LocalFilesDataSource; @@ -87,6 +88,14 @@ public class DataSourceLayerNode extends DisplayableItemNode { ); case RESULTS: // TBD: + return new RootContentChildren(Arrays.asList( + new ExtractedContent(Case.getOpenCase().getSleuthkitCase()), + new KeywordHits(Case.getOpenCase().getSleuthkitCase()), + new HashsetHits(Case.getOpenCase().getSleuthkitCase()), + new EmailExtracted(Case.getOpenCase().getSleuthkitCase()), + new InterestingHits(Case.getOpenCase().getSleuthkitCase()), + new Accounts(Case.getOpenCase().getSleuthkitCase()) + )); case TAGS: // TBD: case REPORTS: // TBD: diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index f6b25ad2ba..054f0eea16 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -35,6 +35,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -58,11 +59,28 @@ public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); + private final long datasourceObjId; + /** + * Constructs extracted content object + * + * @param skCase Case DB + */ public ExtractedContent(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + /** + * Constructs extracted content object + * + * @param skCase Case DB + * @param objId Object id of the parent datasource + */ + public ExtractedContent(SleuthkitCase skCase, long objId) { + this.skCase = skCase; + this.datasourceObjId = objId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -270,6 +288,7 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { + // RAMAN TBD: filter on datasource obj id List types = skCase.getArtifactTypesInUse(); types.removeAll(doNotShow); Collections.sort(types, @@ -332,7 +351,9 @@ public class ExtractedContent implements AutopsyVisitableItem { // a performance increase might be had by adding a // "getBlackboardArtifactCount()" method to skCase try { - this.childCount = skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); + this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? + skCase.getBlackboardArtifactsCount(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) .log(Level.WARNING, "Error getting child count", ex); //NON-NLS @@ -454,7 +475,10 @@ public class ExtractedContent implements AutopsyVisitableItem { protected boolean createKeys(List list) { if (skCase != null) { try { - List arts = skCase.getBlackboardArtifacts(type.getTypeID()); + List arts = + UserPreferences.groupItemsInTreeByDatasource() ? + skCase.getBlackboardArtifacts(TSK_ACCOUNT, datasourceObjId) : + skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 87a3d5f903..cb192ced64 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -23,6 +23,8 @@ import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -36,14 +38,17 @@ public class ResultsNode extends DisplayableItemNode { public ResultsNode(SleuthkitCase sleuthkitCase) { - super(new RootContentChildren(Arrays.asList( - new ExtractedContent(sleuthkitCase), - new KeywordHits(sleuthkitCase), - new HashsetHits(sleuthkitCase), - new EmailExtracted(sleuthkitCase), - new InterestingHits(sleuthkitCase), - new Accounts(sleuthkitCase) - )), Lookups.singleton(NAME)); + super(UserPreferences.groupItemsInTreeByDatasource() ? + new DataSourcesLayerChildren(sleuthkitCase, SubtreeEnum.RESULTS) : + new RootContentChildren(Arrays.asList( + new ExtractedContent(sleuthkitCase), + new KeywordHits(sleuthkitCase), + new HashsetHits(sleuthkitCase), + new EmailExtracted(sleuthkitCase), + new InterestingHits(sleuthkitCase), + new Accounts(sleuthkitCase) ) + ), + Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/results.png"); //NON-NLS From 8b92a52b1b408d2f9118eeaaa662ad79d4bcbc10 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 10 May 2018 08:45:45 -0400 Subject: [PATCH 009/101] 3802: Group by datasource Build the top level tree using new RootContentChildrenFactory --- .../datamodel/AbstractContentChildren.java | 11 +- .../autopsy/datamodel/AutopsyItemVisitor.java | 7 + .../autopsy/datamodel/Bundle.properties | 2 +- .../autopsy/datamodel/DataSourceGrouping.java | 52 ++++++ ...n.java => DataSourceGroupingChildren.java} | 59 ++----- .../datamodel/DataSourceGroupingNode.java | 99 +++++++++++ .../datamodel/DataSourceLayerNode.java | 123 -------------- .../autopsy/datamodel/DataSources.java | 11 ++ .../autopsy/datamodel/DataSourcesNode.java | 38 ++++- .../autopsy/datamodel/DeletedContent.java | 2 +- .../datamodel/DisplayableItemNodeVisitor.java | 6 +- .../autopsy/datamodel/ResultsNode.java | 12 +- .../datamodel/RootContentChildrenFactory.java | 158 ++++++++++++++++++ .../sleuthkit/autopsy/datamodel/Views.java | 12 +- .../autopsy/datamodel/ViewsNode.java | 15 +- .../autoingest/AutoIngestManager.java | 2 +- 16 files changed, 409 insertions(+), 200 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java rename Core/src/org/sleuthkit/autopsy/datamodel/{DataSourcesLayerChildren.java => DataSourceGroupingChildren.java} (66%) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java delete mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 25da96db78..e300efdf1b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -192,21 +192,28 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { + // RAMAN TBD: pass dsObjID down here return tagsNodeKey.new RootNode(); } @Override public AbstractNode visit(DataSources i) { - return new DataSourcesNode(); + return new DataSourcesNode(i.filteringDataSourceObjId()); } + @Override + public AbstractNode visit(DataSourceGrouping datasourceGrouping) { + return new DataSourceGroupingNode(datasourceGrouping.getgDataSource()); + } + @Override public AbstractNode visit(Views v) { - return new ViewsNode(v.getSleuthkitCase()); + return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId()); } @Override public AbstractNode visit(Results r) { + // RAMAN TBD: pass dsObjID down here return new ResultsNode(r.getSleuthkitCase()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index 2dcc5942db..29843bccb7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.datamodel.accounts.Accounts; public interface AutopsyItemVisitor { T visit(DataSources i); + + T visit(DataSourceGrouping datasourceGrouping); T visit(Views v); @@ -173,6 +175,11 @@ public interface AutopsyItemVisitor { return defaultVisit(v); } + @Override + public T visit(DataSourceGrouping datasourceGrouping) { + return defaultVisit(datasourceGrouping); + } + @Override public T visit(Results r) { return defaultVisit(r); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 733c49e8b7..f392d406d3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -55,7 +55,7 @@ DataModelActionsFactory.fileInDir.text=View File in Directory DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash -DataSourcesNode.name=Data Sources +DataSourcesNode.name=Data Source Files DataSourcesNode.createSheet.name.name=Name DataSourcesNode.createSheet.name.displayName=Name DataSourcesNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java new file mode 100644 index 0000000000..360ee735ce --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.datamodel; + +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * A top level UI grouping of Files, Views, Results, Tags + * for 'Group by Data Source' view of the tree. + * + */ +public class DataSourceGrouping implements AutopsyVisitableItem { + + private final SleuthkitCase skCase; + private final DataSource dataSource; + + public DataSourceGrouping(SleuthkitCase skCase, DataSource dataSource) { + this.skCase = skCase; + this.dataSource = dataSource; + } + + DataSource getgDataSource() { + return this.dataSource; + } + + @Override + public T accept(AutopsyItemVisitor visitor) { + return visitor.visit(this); + } + + public SleuthkitCase getSleuthkitCase() { + return skCase; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java similarity index 66% rename from Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java rename to Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java index 95513b3b97..1b7d969100 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesLayerChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java @@ -33,62 +33,29 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Child factory for DataSource node layer in the Results, Views Tags subtrees + * Child factory for Data Source grouping nodes + * A node is created for each data source in the case * */ -class DataSourcesLayerChildren extends Children.Keys { +public class DataSourceGroupingChildren extends Children.Keys { - private static final Logger logger = Logger.getLogger(DataSourcesLayerChildren.class.getName()); + private static final Logger logger = Logger.getLogger(DataSourceGroupingChildren.class.getName()); private final SleuthkitCase sleuthkitCase; - private final SubtreeEnum subTree; + + /** - * Subtree in which this DataSourcesLayerChildren exist - */ - public enum SubtreeEnum { - VIEWS, - RESULTS, - TAGS, - REPORTS - } - - /** - * Simple wrapper class to pass Datasource and subtree down to children nodes + * Constructs the factory object * + * @param tskCase - Case DB */ - class DataSourceLayerInfo { - - private final DataSource dataSource; - private final SubtreeEnum subTree; - - DataSourceLayerInfo(DataSource dataSource, SubtreeEnum subTree) { - this.dataSource = dataSource; - this.subTree = subTree; - } - - DataSource getDataSource() { - return this.dataSource; - } - - SubtreeEnum getSubtree() { - return this.subTree; - } - } - - /** - * Constructs the factory to create optional datasource nodes - * - * @param tskCase - Case DB - * @param subTree - subtree under which data source nodes are to be created - */ - public DataSourcesLayerChildren(SleuthkitCase tskCase, SubtreeEnum subTree) { - // super(true); - super(false); - + public DataSourceGroupingChildren(SleuthkitCase tskCase) { this.sleuthkitCase = tskCase; - this.subTree = subTree; } + /** + * Listener for handling DATA_SOURCE_ADDED events. + */ private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -128,7 +95,7 @@ class DataSourcesLayerChildren extends Children.Keys { } protected Node createNodeForKey(DataSource ds) { - return new DataSourceLayerNode(new DataSourceLayerInfo(ds, subTree)); + return new DataSourceGroupingNode(ds); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java new file mode 100644 index 0000000000..2b6f2d0e1f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -0,0 +1,99 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.datamodel; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; + + +/** + * Data source grouping node - an optional grouping node in the data tree view + * + */ +public class DataSourceGroupingNode extends DisplayableItemNode { + + private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName()); + + /** + * Creates the Data source grouping node for the given data source, + * + * @param dataSource specifies the data source + */ + DataSourceGroupingNode(DataSource dataSource) { + + super (Optional.ofNullable(createDSGroupingNodeChildren(dataSource)) + .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); + + if (dataSource instanceof Image) { + Image image = (Image) dataSource; + + super.setName(image.getName()); + super.setDisplayName(image.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); + } else if (dataSource instanceof LocalFilesDataSource) { + LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; + + super.setName(localFilesDataSource.getName()); + super.setDisplayName(localFilesDataSource.getName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); + } + + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + private static RootContentChildren createDSGroupingNodeChildren(DataSource dataSource) { + + long dsObjId = dataSource.getId(); + try { + return new RootContentChildren(Arrays.asList( + new DataSources(dsObjId), + new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), + new Results(Case.getCurrentCaseThrows().getSleuthkitCase()), // RAMAN TBD: pass down dsObjId + new Tags() ) // RAMAN TBD: pass down dsObjId + + ); + + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS + return null; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java deleted file mode 100644 index 95eb015c09..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceLayerNode.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 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.datamodel; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Optional; -import java.util.logging.Level; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; -import org.sleuthkit.autopsy.datamodel.accounts.Accounts; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.LocalFilesDataSource; - - -/** - * Datasource layer node - an optional grouping node in the data tree view - * - */ -public class DataSourceLayerNode extends DisplayableItemNode { - - private static final Logger logger = Logger.getLogger(DataSourceLayerNode.class.getName()); - - /** - * Creates the Datasource node for the given data source, - * and initializes the children nodes under it based on the subtree specified - * - * @param dataSourceLayerInfo specifies the - */ - DataSourceLayerNode(DataSourcesLayerChildren.DataSourceLayerInfo dataSourceLayerInfo) { - - super (Optional.ofNullable(createDSLayerNodeChildren(dataSourceLayerInfo)) - .orElse(new RootContentChildren(Arrays.asList(Collections.EMPTY_LIST)))); - - DataSource dataSource = dataSourceLayerInfo.getDataSource(); - if (dataSource instanceof Image) { - Image image = (Image) dataSource; - - super.setName(image.getName()); - super.setDisplayName(image.getName()); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); - } else if (dataSource instanceof LocalFilesDataSource) { - LocalFilesDataSource localFilesDataSource = (LocalFilesDataSource) dataSource; - - super.setName(localFilesDataSource.getName()); - super.setDisplayName(localFilesDataSource.getName()); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); - } - - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - private static RootContentChildren createDSLayerNodeChildren(DataSourcesLayerChildren.DataSourceLayerInfo dataSourceLayerInfo) { - - SubtreeEnum subtree = dataSourceLayerInfo.getSubtree(); - long dsObjId = dataSourceLayerInfo.getDataSource().getId(); - - try { - switch (subtree) { - case VIEWS: - return new RootContentChildren(Arrays.asList( - new FileTypes(Case.getOpenCase().getSleuthkitCase(), dsObjId), - new DeletedContent(Case.getOpenCase().getSleuthkitCase(), dsObjId), - new FileSize(Case.getOpenCase().getSleuthkitCase(), dsObjId)) - ); - - case RESULTS: // TBD: - return new RootContentChildren(Arrays.asList( - new ExtractedContent(Case.getOpenCase().getSleuthkitCase()), - new KeywordHits(Case.getOpenCase().getSleuthkitCase()), - new HashsetHits(Case.getOpenCase().getSleuthkitCase()), - new EmailExtracted(Case.getOpenCase().getSleuthkitCase()), - new InterestingHits(Case.getOpenCase().getSleuthkitCase()), - new Accounts(Case.getOpenCase().getSleuthkitCase()) - )); - case TAGS: // TBD: - case REPORTS: // TBD: - - default: - { - logger.log(Level.SEVERE, "Unknown subtree type " + subtree.name()); //NON-NLS - return null; - } - } - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Error getting open case.", ex); //NON-NLS - return null; - } - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - public String getItemType() { - return getClass().getName(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index 5f8cbfff54..52ca52e89f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -23,9 +23,20 @@ package org.sleuthkit.autopsy.datamodel; */ public class DataSources implements AutopsyVisitableItem { + private final long datasourceObjId; + public DataSources() { + this(0); } + public DataSources(long datasourceObjId) { + this.datasourceObjId = datasourceObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 6e8374252d..68f92b8f4e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -33,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; /** * Nodes for the images @@ -41,18 +43,24 @@ public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); + private final long datasourceObjId; + // NOTE: The images passed in via argument will be ignored. @Deprecated public DataSourcesNode(List images) { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } public DataSourcesNode() { - super(new DataSourcesNodeChildren(), Lookups.singleton(NAME)); - init(); + this(0); } + public DataSourcesNode(long dsObjId) { + super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); + init(); + this.datasourceObjId = dsObjId; + } + private void init() { setName(NAME); setDisplayName(NAME); @@ -70,14 +78,20 @@ public class DataSourcesNode extends DisplayableItemNode { public static class DataSourcesNodeChildren extends AbstractContentChildren { private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName()); - + private final long datasourceObjId; + List currentKeys; public DataSourcesNodeChildren() { - super(); - this.currentKeys = new ArrayList<>(); + this(0); } + public DataSourcesNodeChildren(long dsObjId) { + super(); + this.currentKeys = new ArrayList<>(); + this.datasourceObjId = dsObjId; + } + private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -103,9 +117,15 @@ public class DataSourcesNode extends DisplayableItemNode { private void reloadKeys() { try { - currentKeys = Case.getCurrentCaseThrows().getDataSources(); + if (datasourceObjId == 0) { + currentKeys = Case.getCurrentCaseThrows().getDataSources(); + } + else { + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(datasourceObjId); + currentKeys = new ArrayList<>(Arrays.asList(content)); + } setKeys(currentKeys); - } catch (TskCoreException | NoCurrentCaseException ex) { + } catch (TskCoreException | NoCurrentCaseException | TskDataException ex) { logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS setKeys(Collections.emptySet()); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index ca1dc0063c..370cc5cec7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -383,7 +383,7 @@ public class DeletedContent implements AutopsyVisitableItem { @Override public void update(Observable o, Object arg) { refresh(true); - } + } } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index f633112888..0392614fe3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -61,7 +61,7 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); - T visit(DataSourceLayerNode vn); + T visit(DataSourceGroupingNode dataSourceGroupingNode); T visit(org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileExtensionNode fsfn); @@ -339,8 +339,8 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(DataSourceLayerNode vn) { - return defaultVisit(vn); + public T visit(DataSourceGroupingNode dataSourceGroupingNode) { + return defaultVisit(dataSourceGroupingNode); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index cb192ced64..6fcd9a540c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -23,8 +23,6 @@ import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -35,11 +33,13 @@ public class ResultsNode extends DisplayableItemNode { @NbBundle.Messages("ResultsNode.name.text=Results") public static final String NAME = Bundle.ResultsNode_name_text(); - - public ResultsNode(SleuthkitCase sleuthkitCase) { - super(UserPreferences.groupItemsInTreeByDatasource() ? - new DataSourcesLayerChildren(sleuthkitCase, SubtreeEnum.RESULTS) : + this(sleuthkitCase, 0); + } + + public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) { + super( + // RAMAN TBD: pass down dsObjId to each of these subnodes new RootContentChildren(Arrays.asList( new ExtractedContent(sleuthkitCase), new KeywordHits(sleuthkitCase), diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java new file mode 100644 index 0000000000..5adbb8c826 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java @@ -0,0 +1,158 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.Set; +import java.util.logging.Level; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitVisitableItem; +import org.sleuthkit.datamodel.TskCoreException; + + +/** + * Child factory to create the top level children of the root of the directory tree + * + */ +public class RootContentChildrenFactory extends ChildFactory.Detachable implements Observer { + + private static final Logger logger = Logger.getLogger(RootContentChildrenFactory.class.getName()); + private final SleuthkitCase tskCase; + + + /** + * Constructs the child factory for root nodes + * @param tskCase + */ + public RootContentChildrenFactory(SleuthkitCase tskCase) { + this.tskCase = tskCase; + + } + + /** + * Listener for handling DATA_SOURCE_ADDED events. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + refreshChildren(); + } + } + }; + + @Override + protected void addNotify() { + super.addNotify(); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + protected void removeNotify() { + super.removeNotify(); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + + + + + /** + * Creates keys for the top level children. + * + * @param list list of keys created + */ + @Override + protected boolean createKeys(List list) { + + try { + if (UserPreferences.groupItemsInTreeByDatasource()) { + List dataSources = tskCase.getDataSources(); + List keys = new ArrayList<>(); + dataSources.forEach((ds) -> { + keys.add(new DataSourceGrouping(tskCase, ds)); + }); + + list.addAll(keys); + } else { + + List keys = new ArrayList<>(Arrays.asList( + new DataSources(), + new Views(tskCase), + new Results(tskCase), + new Tags(), + new Reports())); + + list.addAll(keys); + } + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error getting datas sources list form the database.", tskCoreException); + } + return true; + } + + /** + * Creates nodes for the top level Key + * + * @param key + * + * @return Node for the key, null if key is unknown. + */ + @Override + protected Node createNodeForKey(Object key) { + + if (key instanceof SleuthkitVisitableItem) { + return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor()); + } else if (key instanceof AutopsyVisitableItem) { + return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor()); + } + else { + logger.log(Level.SEVERE, "Unknown key type ", key.getClass().getName()); + return null; + } + } + + /** + * Refresh the children + */ + public void refreshChildren() { + refresh(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index b31cfda543..d2a8671678 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -26,11 +26,21 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Views implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Views(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Views(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 261206efff..11b905008c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -22,8 +22,6 @@ import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.core.UserPreferences; -import org.sleuthkit.autopsy.datamodel.DataSourcesLayerChildren.SubtreeEnum; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -36,16 +34,19 @@ public class ViewsNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); public ViewsNode(SleuthkitCase sleuthkitCase) { + this(sleuthkitCase, 0); + } + + public ViewsNode(SleuthkitCase sleuthkitCase, long dsObjId) { - super( UserPreferences.groupItemsInTreeByDatasource() ? - new DataSourcesLayerChildren(sleuthkitCase, SubtreeEnum.VIEWS) : + super( new RootContentChildren(Arrays.asList( - new FileTypes(sleuthkitCase), + new FileTypes(sleuthkitCase, dsObjId), // June '15: Recent Files was removed because it was not useful w/out filtering // add it back in if we can filter the results to a more managable size. // new RecentFiles(sleuthkitCase), - new DeletedContent(sleuthkitCase), - new FileSize(sleuthkitCase)) + new DeletedContent(sleuthkitCase, dsObjId), + new FileSize(sleuthkitCase, dsObjId)) ), Lookups.singleton(NAME) ); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 46fa59daa5..866d1d2e94 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -427,7 +427,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen notifyObservers(Event.SHUTTING_DOWN); break; default: - SYS_LOGGER.log(Level.WARNING, "Received unsupported control event: {0}", event.getControlEventType()); + sysLogger.log(Level.WARNING, "Received unsupported control event: {0}", event.getControlEventType()); break; } } From 140989d9c60d1ded8437fc2cde6f77764f7ea6bd Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 10 May 2018 08:51:00 -0400 Subject: [PATCH 010/101] Resolving conflicts after stash pop --- .../autopsy/datamodel/DataSources.java | 1 + .../autopsy/datamodel/ViewsNode.java | 1 + .../DirectoryTreeTopComponent.java | 100 ++++++++++++------ 3 files changed, 68 insertions(+), 34 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index 52ca52e89f..c5d8fadc46 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; /** * Root node to store the data sources in a case */ +// RAMAN TBD: Rename DataSources/DataSourcesNode/ public class DataSources implements AutopsyVisitableItem { private final long datasourceObjId; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 11b905008c..a1114761bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -55,6 +55,7 @@ public class ViewsNode extends DisplayableItemNode { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS } + @Override public boolean isLeafTypeNode() { return false; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 977b1ccdd0..ff77fbfddb 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.Cursor; import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; @@ -30,6 +32,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.prefs.PreferenceChangeEvent; @@ -37,6 +40,7 @@ import java.util.prefs.PreferenceChangeListener; import javax.swing.Action; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import javax.swing.Timer; import javax.swing.tree.TreeSelectionModel; import org.apache.commons.lang3.StringUtils; import org.openide.explorer.ExplorerManager; @@ -48,6 +52,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.NodeNotFoundException; import org.openide.nodes.NodeOp; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.windows.TopComponent; @@ -64,7 +69,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.CreditCards; -import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmailExtracted; @@ -73,12 +77,8 @@ import org.sleuthkit.autopsy.datamodel.ExtractedContent; import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; -import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.Results; import org.sleuthkit.autopsy.datamodel.ResultsNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildren; -import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Views; +import org.sleuthkit.autopsy.datamodel.RootContentChildrenFactory; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; @@ -106,7 +106,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); - private RootContentChildren contentChildren; + private RootContentChildrenFactory rootChildrenFactory; + private Children contentChildren; /** * the constructor @@ -129,6 +130,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat this.forwardList = new LinkedList<>(); backButton.setEnabled(false); forwardButton.setEnabled(false); + + groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); } /** @@ -397,12 +400,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } else { // if there's at least one image, load the image and open the top component final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - contentChildren = new RootContentChildren(Arrays.asList( - new DataSources(), - new Views(tskCase), - new Results(tskCase), - new Tags(), - new Reports())); + + rootChildrenFactory = new RootContentChildrenFactory(tskCase); + contentChildren = Children.create(rootChildrenFactory, true); Node root = new AbstractNode(contentChildren) { //JIRA-2807: What is the point of these overrides? /** @@ -443,19 +443,23 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat TreeView tree = getTree(); Node results = rootChildren.findChild(ResultsNode.NAME); - tree.expandNode(results); - Children resultsChildren = results.getChildren(); - Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); + if (!Objects.isNull(results)) { + tree.expandNode(results); + Children resultsChildren = results.getChildren(); + Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); - Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); - showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); - showRejectedCheckBox.setSelected(false); + Accounts accounts = resultsChildren.findChild(Accounts.NAME).getLookup().lookup(Accounts.class); + if (!Objects.isNull(accounts)) { + showRejectedCheckBox.setAction(accounts.newToggleShowRejectedAction()); + showRejectedCheckBox.setSelected(false); + } + } - groupByDatasourceCheckBox.setSelected(UserPreferences.groupItemsInTreeByDatasource()); - Node views = rootChildren.findChild(ViewsNode.NAME); - Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); - tree.collapseNode(views); + if (!Objects.isNull(views)) { + Arrays.stream(views.getChildren().getNodes()).forEach(tree::expandNode); + tree.collapseNode(views); + } /* * JIRA-2806: What is this supposed to do? Right now it selects * the data sources node, but the comment seems to indicate @@ -487,7 +491,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // of changing the selected node fires a handler that tries to make // dataResult active) try { - em.setSelectedNodes(get()); + Node[] selections = get(); + if (selections != null && selections.length > 0){ + em.setSelectedNodes(selections); + } } catch (PropertyVetoException ex) { LOGGER.log(Level.SEVERE, "Error setting default selected node.", ex); //NON-NLS } catch (InterruptedException | ExecutionException ex) { @@ -752,7 +759,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ String[] currentLast = backList.peekLast(); String lastNodeName = null; - if (currentLast != null) { + if (currentLast != null && currentLast.length > 0) { lastNodeName = currentLast[currentLast.length - 1]; } @@ -796,7 +803,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Refresh the content node part of the dir tree safely in the EDT thread */ public void refreshContentTreeSafe() { - SwingUtilities.invokeLater(this::refreshTree); + SwingUtilities.invokeLater(this::rebuildTree); } /** @@ -817,18 +824,43 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat setSelectedNode(selectedPath, DataSourcesNode.NAME); } - /** - * Refreshes the entire tree + /** + * Rebuilds the tree */ - private void refreshTree() { - Node selectedNode = getSelectedNode(); - final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); + private void rebuildTree() { - contentChildren.refreshContentKeys(); - - setSelectedNode(selectedPath, DataSourcesNode.NAME); + // refresh all children of the root. + rootChildrenFactory.refreshChildren(); + + // Select the first node and reset the selection history + // This should happen on the EDT once the tree has been rebuilt. + // hence the timer to schedule it + Timer timer = new Timer( 10, (ActionEvent e) -> { + selectFirstChildNode(); + resetHistory(); + }); + + timer.setRepeats( false ); + timer.start(); } + /** + * Selects the first node in the tree. + * + */ + private void selectFirstChildNode () { + Children rootChildren = em.getRootContext().getChildren(); + + if (rootChildren.getNodesCount() > 0) { + //Node[] ttt = new Node[]{rootChildren.getNodeAt(0)}; + //Node firstNode = ttt[0]; + Node firstNode = rootChildren.getNodeAt(0); + if (firstNode != null) { + final String[] selectedPath = NodeOp.createPath(firstNode, em.getRootContext()); + setSelectedNode(selectedPath, null); + } + } + } /** * Set the selected node using a path to a previously selected node. * From bcad232ee6d9e6abbd78c55ab8d090541352aebf Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 10 May 2018 11:15:17 -0400 Subject: [PATCH 011/101] Added JIRA number for a TBD --- .../autopsy/directorytree/DirectoryTreeTopComponent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index ff77fbfddb..3036b64114 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -825,7 +825,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } /** - * Rebuilds the tree + * Rebuilds the directory tree */ private void rebuildTree() { @@ -835,6 +835,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // Select the first node and reset the selection history // This should happen on the EDT once the tree has been rebuilt. // hence the timer to schedule it + // TBD JIRA-3838: need to get rid of this delay hack. Timer timer = new Timer( 10, (ActionEvent e) -> { selectFirstChildNode(); resetHistory(); From e71624e98931328ba873bb8d767e55e82969ee3e Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 10 May 2018 13:39:01 -0400 Subject: [PATCH 012/101] Cleanup after self-review. --- .../sleuthkit/autopsy/datamodel/AbstractContentChildren.java | 5 ++--- .../sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java | 4 ++-- Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java | 1 - .../src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java | 3 --- .../org/sleuthkit/autopsy/datamodel/ExtractedContent.java | 2 +- Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java | 2 +- .../autopsy/datamodel/RootContentChildrenFactory.java | 5 ----- .../autopsy/directorytree/DirectoryTreeTopComponent.java | 2 -- 8 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index e300efdf1b..bfd2f0172a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -192,7 +192,6 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { - // RAMAN TBD: pass dsObjID down here return tagsNodeKey.new RootNode(); } @@ -213,7 +212,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Results r) { - // RAMAN TBD: pass dsObjID down here + // RAMAN TBD JIRA-3763: pass dsObjID down here return new ResultsNode(r.getSleuthkitCase()); } @@ -241,7 +240,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) { - return ftByMimeTypeItem.new ByMimeTypeNode(/*ftByMimeTypeItem.filteringDataSourceObjId()*/); + return ftByMimeTypeItem.new ByMimeTypeNode(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java index 2b6f2d0e1f..87f6d8ed7b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -76,8 +76,8 @@ public class DataSourceGroupingNode extends DisplayableItemNode { return new RootContentChildren(Arrays.asList( new DataSources(dsObjId), new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), - new Results(Case.getCurrentCaseThrows().getSleuthkitCase()), // RAMAN TBD: pass down dsObjId - new Tags() ) // RAMAN TBD: pass down dsObjId + new Results(Case.getCurrentCaseThrows().getSleuthkitCase()), // RAMAN TBD JIRA-3763: pass down dsObjId + new Tags() ) // RAMAN TBD JIRA-3762 : pass down dsObjId ); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index c5d8fadc46..52ca52e89f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; /** * Root node to store the data sources in a case */ -// RAMAN TBD: Rename DataSources/DataSourcesNode/ public class DataSources implements AutopsyVisitableItem { private final long datasourceObjId; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 68f92b8f4e..8441b8aaad 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -43,8 +43,6 @@ public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); - private final long datasourceObjId; - // NOTE: The images passed in via argument will be ignored. @Deprecated public DataSourcesNode(List images) { @@ -58,7 +56,6 @@ public class DataSourcesNode extends DisplayableItemNode { public DataSourcesNode(long dsObjId) { super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); init(); - this.datasourceObjId = dsObjId; } private void init() { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 79797d22a6..b9664eaf34 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -288,7 +288,7 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { - // RAMAN TBD: filter on datasource obj id + // RAMAN TBD JIRA-3763: filter on datasource obj id List types = skCase.getArtifactTypesInUse(); types.removeAll(doNotShow); Collections.sort(types, diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 6fcd9a540c..acb5235c96 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -39,7 +39,7 @@ public class ResultsNode extends DisplayableItemNode { public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) { super( - // RAMAN TBD: pass down dsObjId to each of these subnodes + // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes new RootContentChildren(Arrays.asList( new ExtractedContent(sleuthkitCase), new KeywordHits(sleuthkitCase), diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java index 5adbb8c826..e9e72e05cf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java @@ -49,7 +49,6 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable private static final Logger logger = Logger.getLogger(RootContentChildrenFactory.class.getName()); private final SleuthkitCase tskCase; - /** * Constructs the child factory for root nodes * @param tskCase @@ -89,10 +88,6 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable refresh(true); } - - - - /** * Creates keys for the top level children. * diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 3036b64114..a1002f79a8 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; @@ -52,7 +51,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.NodeNotFoundException; import org.openide.nodes.NodeOp; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.windows.TopComponent; From f5b4dbdfe43bf514103f2ae26c921936faaf71ee Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 11 May 2018 14:02:26 -0400 Subject: [PATCH 013/101] 3763: Groups Results by Data source (Interim commit) - Group the extracted content subtree by data source --- .../autopsy/datamodel/AbstractContentChildren.java | 5 ++--- .../autopsy/datamodel/DataSourceGroupingNode.java | 2 +- .../autopsy/datamodel/ExtractedContent.java | 10 ++++++---- .../org/sleuthkit/autopsy/datamodel/Results.java | 12 +++++++++++- .../sleuthkit/autopsy/datamodel/ResultsNode.java | 14 +++++++------- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index bfd2f0172a..7e2f35f0bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -211,9 +211,8 @@ abstract class AbstractContentChildren extends Keys { } @Override - public AbstractNode visit(Results r) { - // RAMAN TBD JIRA-3763: pass dsObjID down here - return new ResultsNode(r.getSleuthkitCase()); + public AbstractNode visit(Results results) { + return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() ); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java index 87f6d8ed7b..314e2a78da 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -76,7 +76,7 @@ public class DataSourceGroupingNode extends DisplayableItemNode { return new RootContentChildren(Arrays.asList( new DataSources(dsObjId), new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), - new Results(Case.getCurrentCaseThrows().getSleuthkitCase()), // RAMAN TBD JIRA-3763: pass down dsObjId + new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), new Tags() ) // RAMAN TBD JIRA-3762 : pass down dsObjId ); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index b9664eaf34..3c1a2cd1d5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -288,8 +288,10 @@ public class ExtractedContent implements AutopsyVisitableItem { //TEST COMMENT if (skCase != null) { try { - // RAMAN TBD JIRA-3763: filter on datasource obj id - List types = skCase.getArtifactTypesInUse(); + List types = (UserPreferences.groupItemsInTreeByDatasource()) ? + skCase.getArtifactTypesInUseByDataSource(datasourceObjId) : + skCase.getArtifactTypesInUse() ; + types.removeAll(doNotShow); Collections.sort(types, new Comparator() { @@ -352,7 +354,7 @@ public class ExtractedContent implements AutopsyVisitableItem { // "getBlackboardArtifactCount()" method to skCase try { this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? - skCase.getBlackboardArtifactsCount(type.getTypeID(), datasourceObjId) : + skCase.getBlackboardArtifactsCountByDataSource(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) @@ -477,7 +479,7 @@ public class ExtractedContent implements AutopsyVisitableItem { try { List arts = UserPreferences.groupItemsInTreeByDatasource() ? - skCase.getBlackboardArtifacts(TSK_ACCOUNT, datasourceObjId) : + skCase.getBlackboardArtifactsByDataSource(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 7f1cd1cea7..b2d9f4799b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -26,11 +26,17 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class Results implements AutopsyVisitableItem { private SleuthkitCase skCase; + private final long datasourceObjId; public Results(SleuthkitCase skCase) { - this.skCase = skCase; + this(skCase, 0); } + public Results(SleuthkitCase skCase, long dsObjId) { + this.skCase = skCase; + this.datasourceObjId = dsObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -39,4 +45,8 @@ public class Results implements AutopsyVisitableItem { public SleuthkitCase getSleuthkitCase() { return skCase; } + + long filteringDataSourceObjId() { + return datasourceObjId; + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index acb5235c96..e284780bcd 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -39,14 +39,14 @@ public class ResultsNode extends DisplayableItemNode { public ResultsNode(SleuthkitCase sleuthkitCase, long dsObjId) { super( - // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new RootContentChildren(Arrays.asList( - new ExtractedContent(sleuthkitCase), - new KeywordHits(sleuthkitCase), - new HashsetHits(sleuthkitCase), - new EmailExtracted(sleuthkitCase), - new InterestingHits(sleuthkitCase), - new Accounts(sleuthkitCase) ) + new ExtractedContent(sleuthkitCase, dsObjId ), + new KeywordHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new HashsetHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new EmailExtracted(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new InterestingHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new Accounts(sleuthkitCase) ) // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes ), Lookups.singleton(NAME)); setName(NAME); From f73dad988d1110d68eecaaee86c1e175eb53ed88 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 16 May 2018 10:03:31 -0400 Subject: [PATCH 014/101] 3813: Fix 'View In Directory' context menu action. --- .../directorytree/ViewContextAction.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 792252605a..acec3cb1bd 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -24,6 +24,7 @@ import java.beans.PropertyVetoException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.AbstractAction; @@ -32,21 +33,29 @@ import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; +import org.openide.nodes.NodeOp; +import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo; +import org.sleuthkit.autopsy.datamodel.DataSourceGroupingNode; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.VolumeSystem; /** @@ -122,7 +131,9 @@ public class ViewContextAction extends AbstractAction { @Override @Messages({ "ViewContextAction.errorMessage.cannotFindDirectory=Failed to locate directory.", - "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.",}) + "ViewContextAction.errorMessage.cannotSelectDirectory=Failed to select directory in tree.", + "ViewContextAction.errorMessage.cannotFindNode=Failed to locate data source node in tree." + }) public void actionPerformed(ActionEvent event) { EventQueue.invokeLater(() -> { /* @@ -130,7 +141,40 @@ public class ViewContextAction extends AbstractAction { */ DirectoryTreeTopComponent treeViewTopComponent = DirectoryTreeTopComponent.findInstance(); ExplorerManager treeViewExplorerMgr = treeViewTopComponent.getExplorerManager(); - Node parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + + Node parentTreeViewNode; + if (UserPreferences.groupItemsInTreeByDatasource()) { // 'Group by Data Source' view + + SleuthkitCase skCase; + String dsname; + try { + // get the objid/name of the datasource of the selected content. + skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + long contentDSObjid = skCase.getDataSourceObjectId(content.getId()); + DataSource datasource = skCase.getDataSource(contentDSObjid); + dsname = datasource.getName(); + + Children rootChildren = treeViewExplorerMgr.getRootContext().getChildren(); + Node datasourceGroupingNode = rootChildren.findChild(dsname); + if (! Objects.isNull(datasourceGroupingNode) ) { + Children dsChildren = datasourceGroupingNode.getChildren(); + parentTreeViewNode = dsChildren.findChild(DataSourcesNode.NAME); + } + else { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree."); //NON-NLS + return; + } + } catch (NoCurrentCaseException| TskDataException | TskCoreException ex) { + MessageNotifyUtil.Message.error(Bundle.ViewContextAction_errorMessage_cannotFindNode()); + logger.log(Level.SEVERE, "Failed to locate data source node in tree.", ex); //NON-NLS + return; + } + } else { // Classic view + // Start the search at the DataSourcesNode + parentTreeViewNode = treeViewExplorerMgr.getRootContext().getChildren().findChild(DataSourcesNode.NAME); + } + /* * Get the parent content for the content to be selected in the * results view. If the parent content is null, then the specified From 63add9d2b78ee507594534e0f7ab0428e0292d27 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 18 May 2018 15:36:55 -0400 Subject: [PATCH 015/101] 3763: Group Results by data source --- .../autopsy/datamodel/EmailExtracted.java | 23 +++++++++ .../autopsy/datamodel/HashsetHits.java | 28 ++++++++++- .../autopsy/datamodel/InterestingHits.java | 23 +++++++++ .../autopsy/datamodel/KeywordHits.java | 29 ++++++++++- .../autopsy/datamodel/ResultsNode.java | 12 ++--- .../autopsy/datamodel/accounts/Accounts.java | 48 +++++++++++++++++-- 6 files changed, 148 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 409c3165e8..d9df849d86 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -40,6 +40,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -86,10 +87,29 @@ public class EmailExtracted implements AutopsyVisitableItem { } private SleuthkitCase skCase; private final EmailResults emailResults; + private final long datasourceObjId; + + /** + * Constructor + * + * @param skCase Case DB + */ public EmailExtracted(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public EmailExtracted(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; emailResults = new EmailResults(); } @@ -141,6 +161,9 @@ public class EmailExtracted implements AutopsyVisitableItem { + "attribute_type_id=" + pathAttrId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index a720409489..24bae3d368 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -63,9 +64,29 @@ public class HashsetHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(HashsetHits.class.getName()); private SleuthkitCase skCase; private final HashsetResults hashsetResults; - + private final long datasourceObjId; + + + /** + * Constructor + * + * @param skCase Case DB + * + */ public HashsetHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public HashsetHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; hashsetResults = new HashsetResults(); } @@ -120,7 +141,10 @@ public class HashsetHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS - + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); synchronized (hashSetHitsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 7c3249f990..67622d180c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -42,6 +42,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -59,9 +60,28 @@ public class InterestingHits implements AutopsyVisitableItem { private static final Logger logger = Logger.getLogger(InterestingHits.class.getName()); private SleuthkitCase skCase; private final InterestingResults interestingResults = new InterestingResults(); + private final long datasourceObjId; + /** + * Constructor + * + * @param skCase Case DB + * + */ public InterestingHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public InterestingHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; interestingResults.update(); } @@ -112,6 +132,9 @@ public class InterestingHits implements AutopsyVisitableItem { + "attribute_type_id=" + setNameId //NON-NLS + " AND blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id" //NON-NLS + " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS + if (UserPreferences.groupItemsInTreeByDatasource()) { + query += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { synchronized (interestingItemsMap) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c35d9bdda0..51367297b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -44,6 +44,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import static org.sleuthkit.autopsy.datamodel.Bundle.*; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -73,6 +74,7 @@ public class KeywordHits implements AutopsyVisitableItem { private SleuthkitCase skCase; private final KeywordResults keywordResults; + private final long datasourceObjId; /** * String used in the instance MAP so that exact matches and substring can @@ -81,6 +83,7 @@ public class KeywordHits implements AutopsyVisitableItem { */ private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME"; + /** * query attributes table for the ones that we need for the tree */ @@ -101,8 +104,25 @@ public class KeywordHits implements AutopsyVisitableItem { return (instances.size() == 1) && (instances.get(0).equals(DEFAULT_INSTANCE_NAME)); } - public KeywordHits(SleuthkitCase skCase) { + /** + * Constructor + * + * @param skCase Case DB + */ + KeywordHits(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase Case DB + * @param objId Object id of the data source + * + */ + public KeywordHits(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; keywordResults = new KeywordResults(); } @@ -300,7 +320,12 @@ public class KeywordHits implements AutopsyVisitableItem { return; } - try (CaseDbQuery dbQuery = skCase.executeQuery(KEYWORD_HIT_ATTRIBUTES_QUERY)) { + String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY; + if (UserPreferences.groupItemsInTreeByDatasource()) { + queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId; + } + + try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { long artifactId = resultSet.getLong("artifact_id"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index e284780bcd..91d18bbcc6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,11 +42,11 @@ public class ResultsNode extends DisplayableItemNode { new RootContentChildren(Arrays.asList( new ExtractedContent(sleuthkitCase, dsObjId ), - new KeywordHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes - new HashsetHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes - new EmailExtracted(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes - new InterestingHits(sleuthkitCase), // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes - new Accounts(sleuthkitCase) ) // RAMAN TBD JIRA-3763: pass down dsObjId to each of these subnodes + new KeywordHits(sleuthkitCase, dsObjId), + new HashsetHits(sleuthkitCase, dsObjId), + new EmailExtracted(sleuthkitCase, dsObjId), + new InterestingHits(sleuthkitCase, dsObjId ), + new Accounts(sleuthkitCase, dsObjId) ) ), Lookups.singleton(NAME)); setName(NAME); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java index 8301928528..557e6582ca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/accounts/Accounts.java @@ -58,6 +58,7 @@ import org.openide.util.Utilities; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AutopsyItemVisitor; @@ -94,6 +95,8 @@ final public class Accounts implements AutopsyVisitableItem { final public static String NAME = Bundle.AccountsRootNode_name(); private SleuthkitCase skCase; + private final long datasourceObjId; + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); /* Should rejected accounts be shown in the accounts section of the tree. */ @@ -108,12 +111,24 @@ final public class Accounts implements AutopsyVisitableItem { * @param skCase The SleuthkitCase object to use for db queries. */ public Accounts(SleuthkitCase skCase) { + this(skCase, 0); + } + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + * @param objId Object id of the data source + */ + public Accounts(SleuthkitCase skCase, long objId) { this.skCase = skCase; + this.datasourceObjId = objId; this.rejectActionInstance = new RejectAccounts(); this.approveActionInstance = new ApproveAccounts(); } - + + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -130,6 +145,18 @@ final public class Accounts implements AutopsyVisitableItem { return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS } + /** + * Returns the clause to filter artifacts by data source. + * + * @return A clause that will or will not filter artifacts by datasource + * based on the UserPreferences groupItemsInTreeByDatasource setting + */ + private String getFilterByDataSourceClause() { + return (UserPreferences.groupItemsInTreeByDatasource()) ? + " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId + " " + : " "; + } + /** * Gets a new Action that when invoked toggles showing rejected artifacts on * or off. @@ -291,10 +318,14 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( - "SELECT DISTINCT blackboard_attributes.value_text as account_type " - + " FROM blackboard_attributes " - + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + String accountTypesInUseQuery = + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() + + getFilterByDataSourceClause(); + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery ); ResultSet resultSet = executeQuery.getResultSet()) { while (resultSet.next()) { String accountType = resultSet.getString("account_type"); @@ -429,6 +460,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text = '" + accountType.getTypeName() + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet rs = results.getResultSet();) { @@ -739,6 +771,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + " ORDER BY hits DESC "; //NON-NLS @@ -807,6 +840,7 @@ final public class Accounts implements AutopsyVisitableItem { + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.getTypeName() + "'" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text ) AS foo"; try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -943,6 +977,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " GROUP BY BIN " //NON-NLS + " ORDER BY BIN "; //NON-NLS @@ -1009,6 +1044,7 @@ final public class Accounts implements AutopsyVisitableItem { + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { @@ -1304,6 +1340,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause() + " ORDER BY blackboard_attributes.value_text"; //NON-NLS try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); @@ -1375,6 +1412,7 @@ final public class Accounts implements AutopsyVisitableItem { + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + " AND blackboard_attributes.value_text >= '" + bin.getBINStart() + "' AND blackboard_attributes.value_text < '" + (bin.getBINEnd() + 1) + "'" //NON-NLS + + getFilterByDataSourceClause() + getRejectedArtifactFilterClause(); try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); ResultSet resultSet = results.getResultSet();) { From 5da21f94da36ac9a3cc07e095c691a001da69b29 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 23 May 2018 06:47:45 -0400 Subject: [PATCH 016/101] 3762: Group Tags by data source --- .../casemodule/services/TagsManager.java | 209 ++++++++++++++++++ .../datamodel/AbstractContentChildren.java | 2 +- .../datamodel/DataSourceGroupingNode.java | 2 +- .../datamodel/RootContentChildrenFactory.java | 2 - .../org/sleuthkit/autopsy/datamodel/Tags.java | 68 +++++- 5 files changed, 270 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f113a622fe..f26c63db8a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +38,8 @@ import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -157,6 +161,47 @@ public class TagsManager implements Closeable { return caseDb.getTagNamesInUse(); } + /** + * Selects all of the rows from the tag_names table in the case database for + * which there is at least one matching row in the content_tags or + * blackboard_artifact_tags tables, for the given datasource object id. + * + * @return A list, possibly empty, of TagName data transfer objects (DTOs) + * for the rows. + * + * @throws TskCoreException + */ + public List getTagNamesInUse(long dsObjId) throws TskCoreException { + + + ArrayList tagNames = new ArrayList<>(); + String queryStr = "SELECT * FROM tag_names " + + "WHERE tag_name_id IN " + + "( SELECT content_tags.tag_name_id as tag_name_id " + + "FROM content_tags as content_tags, tsk_files as tsk_files" + + " WHERE content_tags.obj_id = tsk_files.obj_id" + + " AND tsk_files.data_source_obj_id = " + dsObjId + + " UNION " + + "SELECT artifact_tags.tag_name_id as tag_name_id " + + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " + + " WHERE artifact_tags.artifact_id = arts.artifact_id" + + " AND arts.data_source_obj_id = " + dsObjId + + " )"; + + try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { + ResultSet resultSet = query.getResultSet(); + while (resultSet.next()) { + tagNames.add(new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"), + resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")), + TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")))); //NON-NLS + } + return tagNames; + } catch (SQLException | TskCoreException ex) { + throw new TskCoreException("Failed to get tag names in use for data source objID : " + dsObjId, ex); + } + + } + /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current @@ -392,6 +437,46 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsCountByTagName(tagName); } + /** + * Gets content tags count by tag name, for the given data source + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames and/or addTagName. + * + * @param dsObjId data source object id + * + * @return A count of the content tags with the specified tag name, and for + * the given data source + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + + if (tagName.getId() == Tag.ID_NOT_SET) { + throw new TskCoreException("TagName object is invalid, id not set"); + } + + String queryStr = + "SELECT COUNT(*) AS count " + + " FROM content_tags as content_tags, tsk_files as tsk_files " + + " WHERE content_tags.obj_id = tsk_files.obj_id" + + " AND tsk_files.data_source_obj_id = " + dsObjId + + " AND content_tags.tag_name_id = " + tagName.getId(); + + try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { + ResultSet resultSet = query.getResultSet(); + if (resultSet.next()) { + return resultSet.getLong("count"); + } else { + throw new TskCoreException("Error getting content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId ); + } + } catch (SQLException | TskCoreException ex) { + throw new TskCoreException("Failed to get content_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); + } + + } + /** * Gets a content tag by tag id. * @@ -421,6 +506,48 @@ public class TagsManager implements Closeable { return caseDb.getContentTagsByTagName(tagName); } + /** + * Gets content tags by tag name, for the given data source. + * + * @param tagName The tag name of interest. + * + * @param dsObjId + * + * @return A list, possibly empty, of the content tags with the specified + * tag name, and for the given data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + + if (tagName.getId() == Tag.ID_NOT_SET) { + throw new TskCoreException("TagName object is invalid, id not set"); + } + + String queryStr = + "SELECT * " + + " FROM content_tags as content_tags, tsk_files as tsk_files " + + " WHERE content_tags.obj_id = tsk_files.obj_id" + + " AND tsk_files.data_source_obj_id = " + dsObjId + + " AND content_tags.tag_name_id = " + tagName.getId(); + + try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { + ResultSet resultSet = query.getResultSet(); + ArrayList tags = new ArrayList<>(); + while (resultSet.next()) { + ContentTag tag = new ContentTag(resultSet.getLong("tag_id"), caseDb.getContentById(resultSet.getLong("obj_id")), + tagName, resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset")); //NON-NLS + tags.add(tag); + } + resultSet.close(); + return tags; + } catch (SQLException | TskCoreException ex) { + throw new TskCoreException("Failed to get content_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); + } + + } + /** * Gets content tags count by content. * @@ -522,6 +649,45 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } + /** + * Gets an artifact tags count by tag name, for the given data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId + * + * @return A count of the artifact tags with the specified tag name, + * for the given data source. + * + * @throws TskCoreException If there is an error getting the tags count from + * the case database. + */ + public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { + if (tagName.getId() == Tag.ID_NOT_SET) { + throw new TskCoreException("TagName object is invalid, id not set"); + } + + String queryStr = "SELECT COUNT(*) AS count " + + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " + + " WHERE artifact_tags.artifact_id = arts.artifact_id" + + " AND artifact_tags.tag_name_id = " + tagName.getId() + + " AND arts.data_source_obj_id = " + dsObjId + ; + + try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(queryStr);) { + ResultSet resultSet = query.getResultSet(); + if (resultSet.next()) { + return resultSet.getLong("count"); + } else { + throw new TskCoreException("Error getting blackboard_artifact_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId); + } + } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { + throw new TskCoreException("Failed to get blackboard_artifact_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); + } + + } + /** * Gets an artifact tag by tag id. * @@ -553,6 +719,49 @@ public class TagsManager implements Closeable { return caseDb.getBlackboardArtifactTagsByTagName(tagName); } + /** + * Gets artifact tags by tag name, for specified data source. + * + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. + * @param dsObjId + * + * @return A list, possibly empty, of the artifact tags with the specified + * tag name, for the specified data source. + * + * @throws TskCoreException If there is an error getting the tags from the + * case database. + */ + public List getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { + if (tagName.getId() == Tag.ID_NOT_SET) { + throw new TskCoreException("TagName object is invalid, id not set"); + } + + String queryStr = "SELECT * " + + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " + + " WHERE artifact_tags.artifact_id = arts.artifact_id" + + " AND artifact_tags.tag_name_id = " + tagName.getId() + + " AND arts.data_source_obj_id = " + dsObjId + ; + + try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(queryStr);) { + ResultSet resultSet = query.getResultSet(); + ArrayList tags = new ArrayList<>(); + while (resultSet.next()) { + BlackboardArtifact artifact = caseDb.getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS + Content content = caseDb.getContentById(artifact.getObjectID()); + BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"), + artifact, content, tagName, resultSet.getString("comment")); //NON-NLS + tags.add(tag); + } + return tags; + } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { + throw new TskCoreException("Failed to get blackboard_artifact_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); + } + + } + /** * Gets artifact tags for a particular artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 7e2f35f0bb..9d47861786 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -192,7 +192,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(Tags tagsNodeKey) { - return tagsNodeKey.new RootNode(); + return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java index 314e2a78da..43054427a3 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -77,7 +77,7 @@ public class DataSourceGroupingNode extends DisplayableItemNode { new DataSources(dsObjId), new Views(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), new Results(Case.getCurrentCaseThrows().getSleuthkitCase(), dsObjId), - new Tags() ) // RAMAN TBD JIRA-3762 : pass down dsObjId + new Tags(dsObjId) ) ); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java index e9e72e05cf..f6b7238d90 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java @@ -22,12 +22,10 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Observable; import java.util.Observer; -import java.util.Set; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 9a01fa608f..c41a750f7e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -36,6 +36,7 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -57,6 +58,20 @@ public class Tags implements AutopsyVisitableItem { private final String DISPLAY_NAME = NbBundle.getMessage(RootNode.class, "TagsNode.displayName.text"); private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS + private final long datasourceObjId; + + Tags() { + this(0); + } + + Tags(long dsObjId) { + this.datasourceObjId = dsObjId; + } + + long filteringDataSourceObjId() { + return this.datasourceObjId; + } + @Override public T accept(AutopsyItemVisitor visitor) { return visitor.visit(this); @@ -83,11 +98,13 @@ public class Tags implements AutopsyVisitableItem { */ public class RootNode extends DisplayableItemNode { - public RootNode() { - super(Children.create(new TagNameNodeFactory(), true), Lookups.singleton(DISPLAY_NAME)); + + public RootNode(long objId) { + super(Children.create(new TagNameNodeFactory(objId), true), Lookups.singleton(DISPLAY_NAME)); super.setName(DISPLAY_NAME); super.setDisplayName(DISPLAY_NAME); this.setIconBaseWithExtension(ICON_PATH); + } @Override @@ -121,6 +138,8 @@ public class Tags implements AutopsyVisitableItem { private class TagNameNodeFactory extends ChildFactory.Detachable implements Observer { + private final long datasourceObjId; + private final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.CONTENT_TAG_ADDED, @@ -176,6 +195,15 @@ public class Tags implements AutopsyVisitableItem { } }; + /** + * Constructor + * @param objId data source object id + */ + TagNameNodeFactory(long objId) { + this.datasourceObjId = objId; + + } + @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); @@ -196,7 +224,11 @@ public class Tags implements AutopsyVisitableItem { @Override protected boolean createKeys(List keys) { try { - List tagNamesInUse = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); + + List tagNamesInUse = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse() + ; Collections.sort(tagNamesInUse); keys.addAll(tagNamesInUse); } catch (TskCoreException | NoCurrentCaseException ex) { @@ -244,8 +276,15 @@ public class Tags implements AutopsyVisitableItem { long tagsCount = 0; try { TagsManager tm = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagsCount = tm.getContentTagsCountByTagName(tagName); - tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + if (UserPreferences.groupItemsInTreeByDatasource()) { + tagsCount = tm.getContentTagsCountByTagName(tagName, datasourceObjId); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId); + } + else { + tagsCount = tm.getContentTagsCountByTagName(tagName); + tagsCount += tm.getBlackboardArtifactTagsCountByTagName(tagName); + } + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(TagNameNode.class.getName()).log(Level.SEVERE, "Failed to get tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -348,7 +387,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get content tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -403,7 +444,11 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { // Use the content tags bearing the specified tag name as the keys. try { - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName)); + List contentTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByTagName(tagName); + + keys.addAll(contentTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(ContentTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -447,7 +492,9 @@ public class Tags implements AutopsyVisitableItem { private void updateDisplayName() { long tagsCount = 0; try { - tagsCount = Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); + tagsCount = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsCountByTagName(tagName); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagTypeNode.class.getName()).log(Level.SEVERE, "Failed to get blackboard artifact tags count for " + tagName.getDisplayName() + " tag name", ex); //NON-NLS } @@ -502,7 +549,10 @@ public class Tags implements AutopsyVisitableItem { protected boolean createKeys(List keys) { try { // Use the blackboard artifact tags bearing the specified tag name as the keys. - keys.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName)); + List artifactTags = UserPreferences.groupItemsInTreeByDatasource() ? + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName, datasourceObjId) : + Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByTagName(tagName); + keys.addAll(artifactTags); } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(BlackboardArtifactTagNodeFactory.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } From c0ea7776e593db2345fc46d91e1626fec0fe0456 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 23 May 2018 09:11:52 -0400 Subject: [PATCH 017/101] Testing map lookup before using query to get hasVisibleContentChildren --- .../autopsy/datamodel/AbstractContentNode.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index b4c34e5da7..b35f93c669 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -48,6 +48,8 @@ public abstract class AbstractContentNode extends ContentNode */ T content; private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName()); + private static int tempCountQueries = 0; + private static int tempCountMapLookups = 0; /** * Handles aspects that depend on the Content object @@ -110,6 +112,21 @@ public abstract class AbstractContentNode extends ContentNode */ public static boolean contentHasVisibleContentChildren(Content c){ if (c != null) { + + try { + tempCountMapLookups++; + if( ! c.hasChildren()) { + //if( ! Case.getCurrentCaseThrows().getSleuthkitCase().getHasChildren(c.getId())) { + //System.out.println("Map lookups: " + tempCountMapLookups + " queries: " + tempCountQueries); + return false; + } + } catch (TskCoreException ex) { + + logger.log(Level.SEVERE, "Error checking if the node has children, for content: " + c, ex); //NON-NLS + return false; + } + + tempCountQueries++; String query = "SELECT COUNT(obj_id) AS count FROM " + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId() + " AND type = " + TskData.ObjectType.ARTIFACT.getObjectType() @@ -122,6 +139,7 @@ public abstract class AbstractContentNode extends ContentNode try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); + //System.out.println("Map lookups: " + tempCountMapLookups + " queries: " + tempCountQueries); if(resultSet.next()){ return (0 < resultSet.getInt("count")); } From f5a95d37c66c625e78c97445485dc3c95446f955 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 23 May 2018 13:01:55 -0400 Subject: [PATCH 018/101] 3763: Group Results by data source - Refactored previously added APIs to move from Sleuthkitcase to Blackboard. --- .../sleuthkit/autopsy/datamodel/ExtractedContent.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 3c1a2cd1d5..409b730345 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; @@ -58,6 +59,7 @@ import org.sleuthkit.datamodel.TskException; public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed + private Blackboard blackboard; public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); private final long datasourceObjId; @@ -79,6 +81,7 @@ public class ExtractedContent implements AutopsyVisitableItem { public ExtractedContent(SleuthkitCase skCase, long objId) { this.skCase = skCase; this.datasourceObjId = objId; + this.blackboard = new Blackboard(skCase); } @Override @@ -289,7 +292,7 @@ public class ExtractedContent implements AutopsyVisitableItem { if (skCase != null) { try { List types = (UserPreferences.groupItemsInTreeByDatasource()) ? - skCase.getArtifactTypesInUseByDataSource(datasourceObjId) : + blackboard.getArtifactTypesInUseByDataSource(datasourceObjId) : skCase.getArtifactTypesInUse() ; types.removeAll(doNotShow); @@ -354,7 +357,7 @@ public class ExtractedContent implements AutopsyVisitableItem { // "getBlackboardArtifactCount()" method to skCase try { this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? - skCase.getBlackboardArtifactsCountByDataSource(type.getTypeID(), datasourceObjId) : + blackboard.getBlackboardArtifactsCountByDataSource(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) @@ -479,7 +482,7 @@ public class ExtractedContent implements AutopsyVisitableItem { try { List arts = UserPreferences.groupItemsInTreeByDatasource() ? - skCase.getBlackboardArtifactsByDataSource(type.getTypeID(), datasourceObjId) : + blackboard.getBlackboardArtifactsByDataSource(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { From 0201df71d7a963768f988a36aee85090b7d9acbf Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 24 May 2018 08:00:13 -0400 Subject: [PATCH 019/101] Addressed Codacy comments. --- .../datamodel/DataSourceGroupingChildren.java | 8 ++++---- .../datamodel/RootContentChildrenFactory.java | 6 +++--- .../DirectoryTreeTopComponent.java | 20 +------------------ .../directorytree/ViewContextAction.java | 3 --- 4 files changed, 8 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java index 1b7d969100..3463d807dc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java @@ -90,12 +90,12 @@ public class DataSourceGroupingChildren extends Children.Keys { } @Override - protected Node[] createNodes(DataSource ds) { - return new Node[]{createNodeForKey(ds)}; + protected Node[] createNodes(DataSource datasource) { + return new Node[]{createNodeForKey(datasource)}; } - protected Node createNodeForKey(DataSource ds) { - return new DataSourceGroupingNode(ds); + protected Node createNodeForKey(DataSource datasource) { + return new DataSourceGroupingNode(datasource); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java index f6b7238d90..c1de43c567 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java @@ -82,7 +82,7 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable } @Override - public void update(Observable o, Object arg) { + public void update(Observable observable, Object arg) { refresh(true); } @@ -98,8 +98,8 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable if (UserPreferences.groupItemsInTreeByDatasource()) { List dataSources = tskCase.getDataSources(); List keys = new ArrayList<>(); - dataSources.forEach((ds) -> { - keys.add(new DataSourceGrouping(tskCase, ds)); + dataSources.forEach((datasource) -> { + keys.add(new DataSourceGrouping(tskCase, datasource)); }); list.addAll(keys); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index a1002f79a8..1014cebc68 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -804,24 +804,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat SwingUtilities.invokeLater(this::rebuildTree); } - /** - * Refreshes changed content nodes - */ - private void refreshDataSourceTree() { - Node selectedNode = getSelectedNode(); - final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); - Children rootChildren = em.getRootContext().getChildren(); - Node dataSourcesFilterNode = rootChildren.findChild(DataSourcesNode.NAME); - if (dataSourcesFilterNode == null) { - LOGGER.log(Level.SEVERE, "Cannot find data sources filter node, won't refresh the content tree"); //NON-NLS - return; - } - Node dataSourcesNode = ((DirectoryTreeFilterNode) dataSourcesFilterNode).getOriginal(); - DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) dataSourcesNode.getChildren(); - contentRootChildren.refreshContentKeys(); - setSelectedNode(selectedPath, DataSourcesNode.NAME); - } - /** * Rebuilds the directory tree */ @@ -834,7 +816,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // This should happen on the EDT once the tree has been rebuilt. // hence the timer to schedule it // TBD JIRA-3838: need to get rid of this delay hack. - Timer timer = new Timer( 10, (ActionEvent e) -> { + Timer timer = new Timer( 10, (ActionEvent event) -> { selectFirstChildNode(); resetHistory(); }); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index acec3cb1bd..9798216b9b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -33,8 +33,6 @@ import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.TreeView; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.nodes.NodeOp; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -43,7 +41,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.ContentNodeSelectionInfo; -import org.sleuthkit.autopsy.datamodel.DataSourceGroupingNode; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.RootContentChildren; From a32004ec0ac458a9cde67074c5466af3b35b825b Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 24 May 2018 08:17:52 -0400 Subject: [PATCH 020/101] Removed unused import. --- .../autopsy/directorytree/DirectoryTreeTopComponent.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 1014cebc68..807eaaf715 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -67,7 +67,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.CreditCards; -import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmailExtracted; import org.sleuthkit.autopsy.datamodel.EmptyNode; From 4dc742f29bd8296d2e2c538e5ef0d62e37f35113 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 24 May 2018 08:57:32 -0400 Subject: [PATCH 021/101] Fixed the missing Reports node in "Group by data source" view. --- .../autopsy/datamodel/RootContentChildrenFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java index c1de43c567..276f4c3f17 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java @@ -101,8 +101,9 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable dataSources.forEach((datasource) -> { keys.add(new DataSourceGrouping(tskCase, datasource)); }); - list.addAll(keys); + + list.add(new Reports()); } else { List keys = new ArrayList<>(Arrays.asList( From ab32a318176432cc5bc51d686b66ebb9d546fc5d Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 25 May 2018 09:23:20 -0400 Subject: [PATCH 022/101] 3838: Fix/Remove delay hack. --- .../DirectoryTreeTopComponent.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 807eaaf715..ab50b388b3 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -807,21 +807,32 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Rebuilds the directory tree */ private void rebuildTree() { - + // refresh all children of the root. rootChildrenFactory.refreshChildren(); - + // Select the first node and reset the selection history // This should happen on the EDT once the tree has been rebuilt. - // hence the timer to schedule it - // TBD JIRA-3838: need to get rid of this delay hack. - Timer timer = new Timer( 10, (ActionEvent event) -> { - selectFirstChildNode(); - resetHistory(); - }); + // hence the SwingWorker that does this in the done() method + new SwingWorker() { - timer.setRepeats( false ); - timer.start(); + @Override + protected Void doInBackground() throws Exception { + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + selectFirstChildNode(); + resetHistory(); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Error selecting tree node.", ex); //NON-NLS + } //NON-NLS + } + }.execute(); } /** @@ -832,8 +843,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat Children rootChildren = em.getRootContext().getChildren(); if (rootChildren.getNodesCount() > 0) { - //Node[] ttt = new Node[]{rootChildren.getNodeAt(0)}; - //Node firstNode = ttt[0]; Node firstNode = rootChildren.getNodeAt(0); if (firstNode != null) { final String[] selectedPath = NodeOp.createPath(firstNode, em.getRootContext()); From f3dc5a42f5bdaf26522274ff2e8bcbf9607f1c0d Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 25 May 2018 10:08:19 -0400 Subject: [PATCH 023/101] Remove unused imports. --- .../autopsy/directorytree/DirectoryTreeTopComponent.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index ab50b388b3..b188da42fc 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.Cursor; import java.awt.EventQueue; -import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; @@ -39,7 +38,6 @@ import java.util.prefs.PreferenceChangeListener; import javax.swing.Action; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; -import javax.swing.Timer; import javax.swing.tree.TreeSelectionModel; import org.apache.commons.lang3.StringUtils; import org.openide.explorer.ExplorerManager; From 08e92d359f377d1bc728d4d811bcbd7f31e20562 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 1 Jun 2018 16:08:04 -0400 Subject: [PATCH 024/101] 3819 make SevenZipExtractor static in embeddedFileExtractor module --- .../EmbeddedFileExtractorIngestModule.java | 35 ++++++++++++------- .../SevenZipExtractor.java | 13 +++---- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 5e67e9da8b..0fee416799 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; /** * A file level ingest module that extracts embedded files from supported @@ -48,8 +49,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda private String moduleDirRelative; private String moduleDirAbsolute; private MSOfficeEmbeddedContentExtractor officeExtractor; - private SevenZipExtractor archiveExtractor; + private static SevenZipExtractor archiveExtractor; private FileTypeDetector fileTypeDetector; + private long jobId; + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); /** * Constructs a file level ingest module that extracts embedded files from @@ -66,10 +69,11 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * case database for extracted (derived) file paths. The absolute path * is used to write the extracted (derived) files to local storage. */ + jobId = context.getJobId(); try { - final Case currentCase = Case.getCurrentCaseThrows(); - moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); - moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + final Case currentCase = Case.getCurrentCaseThrows(); + moduleDirRelative = Paths.get(currentCase.getModuleOutputDirectoryRelativePath(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); + moduleDirAbsolute = Paths.get(currentCase.getModuleDirectory(), EmbeddedFileExtractorModuleFactory.getModuleName()).toString(); } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_NoOpenCase_errMsg(), ex); } @@ -93,16 +97,17 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } + if (refCounter.incrementAndGet(jobId) == 1) { + /* + * Construct a 7Zip file extractor for processing archive files. + */ + try { + this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); + } catch (SevenZipNativeInitializationException ex) { + throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); + } - /* - * Construct a 7Zip file extractor for processing archive files. - */ - try { - this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); - } catch (SevenZipNativeInitializationException ex) { - throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); } - /* * Construct an embedded content extractor for processing Microsoft * Office documents. @@ -112,6 +117,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex); } + } @Override @@ -150,6 +156,11 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda return ProcessResult.OK; } + @Override + public void shutDown() { + refCounter.decrementAndGet(jobId); + } + /** * Creates a unique name for a file by concatentating the file name and the * file object id. diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 9e78e0d58e..3b8ced7752 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -85,7 +85,7 @@ class SevenZipExtractor { private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L; private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB //counts archive depth - private ArchiveDepthCountTree archiveDepthCountTree; + private volatile ArchiveDepthCountTree archiveDepthCountTree; private String moduleDirRelative; private String moduleDirAbsolute; @@ -150,8 +150,8 @@ class SevenZipExtractor { } } return false; - } - + } + /** * Check if the item inside archive is a potential zipbomb * @@ -460,7 +460,7 @@ class SevenZipExtractor { * * @param archiveFile file to unpack * - * @return list of unpacked derived files + * @return true if unpacking is complete */ void unpack(AbstractFile archiveFile) { unpack(archiveFile, null); @@ -473,7 +473,7 @@ class SevenZipExtractor { * @param archiveFile - file to unpack * @param password - the password to use, null for no password * - * @return list of unpacked derived files + * @return true if unpacking is complete */ @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) boolean unpack(AbstractFile archiveFile, String password) { @@ -580,7 +580,8 @@ class SevenZipExtractor { //check if possible zip bomb if (isZipBombArchiveItemCheck(archiveFile, item)) { - continue; //skip the item + unpackSuccessful = false; + return unpackSuccessful; } SevenZipExtractor.UnpackedTree.UnpackedNode unpackedNode = unpackedTree.addNode(pathInArchive); //update progress bar From e24ce3215402299c89ca9860cde3b00c90198d1d Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 7 Jun 2018 10:53:42 -0400 Subject: [PATCH 025/101] 3865: Replace Tags - Initial Commit --- .../ReplaceBlackboardArtifactTagAction.java | 202 ++++++++++++++++++ .../actions/ReplaceContentTagAction.java | 202 ++++++++++++++++++ .../datamodel/BlackboardArtifactTagNode.java | 2 + .../autopsy/datamodel/ContentTagNode.java | 2 + 4 files changed, 408 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java new file mode 100644 index 0000000000..ce76a70b26 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -0,0 +1,202 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.actions; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Instances of this Action allow users to replace a tag applied to blackboard + * artifacts, with another tag + */ +@NbBundle.Messages({ + "ReplaceBlackboardArtifactTagAction.replaceTag=Replace Result Tag" +}) +public final class ReplaceBlackboardArtifactTagAction extends AbstractAction implements Presenter.Popup { + + private static final Logger logger = Logger.getLogger(ReplaceBlackboardArtifactTagAction.class.getName()); + + private static final long serialVersionUID = 1L; + private static final String MENU_TEXT = NbBundle.getMessage(ReplaceBlackboardArtifactTagAction.class, + "ReplaceBlackboardArtifactTagAction.replaceTag"); + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ReplaceBlackboardArtifactTagAction instance; + + public static synchronized ReplaceBlackboardArtifactTagAction getInstance() { + if (null == instance) { + instance = new ReplaceBlackboardArtifactTagAction(); + } + return instance; + } + + private ReplaceBlackboardArtifactTagAction() { + super(MENU_TEXT); + } + + @Override + public void actionPerformed(ActionEvent event) { + } + + protected String getActionDisplayName() { + return MENU_TEXT; + } + + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - artifactID", + "ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."}) + protected void replaceTag(TagName oldTagName, TagName newTagName, BlackboardArtifactTag oldArtifactTag) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTagName.getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS + + System.out.println("RAMAN: Replacing tag " + oldTagName.getDisplayName() + " with tag " + newTagName.getDisplayName() ); + tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); + tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName); + + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing artifact tag", ex); //NON-NLS + } + } + }.execute(); + } + + @Override + public JMenuItem getPopupPresenter() { + return new ReplaceArtifactTagMenu(); + } + + /** + * Instances of this class implement a context menu user interface for + * selecting a tag name to replace the tag with + */ + private final class ReplaceArtifactTagMenu extends JMenu { + + private static final long serialVersionUID = 1L; + + ReplaceArtifactTagMenu() { + super(getActionDisplayName()); + + final Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); + + // Get the current set of tag names. + Map tagNamesMap = null; + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + + + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + String tagDisplayName = entry.getKey(); + String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); + // for the bookmark tag name only, added shortcut label + if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { + tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); + } + + // Add action to replace the tag + tagNameItem.addActionListener((ActionEvent e) -> { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag.getName(), entry.getValue(), oldtag); + }); + }); + + add(tagNameItem); + } + } else { + JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); + empty.setEnabled(false); + add(empty); + } + + addSeparator(); + + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent e) -> { + TagName newTagName = GetTagNameDialog.doDialog(); + if (null != newTagName) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag.getName(), newTagName, oldtag); + }); + } + }); + add(newTagMenuItem); + + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java new file mode 100644 index 0000000000..020767238a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -0,0 +1,202 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 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.actions; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Instances of this Action allow users to replace a tag applied to a file + * with another tag + */ +@NbBundle.Messages({ + "ReplaceContentArtifactTagAction.replaceTag=Replace File Tag" +}) +public final class ReplaceContentTagAction extends AbstractAction implements Presenter.Popup { + + private static final Logger logger = Logger.getLogger(ReplaceContentTagAction.class.getName()); + + private static final long serialVersionUID = 1L; + private static final String MENU_TEXT = NbBundle.getMessage(ReplaceContentTagAction.class, + "ReplaceContentArtifactTagAction.replaceTag"); + + // This class is a singleton to support multi-selection of nodes, since + // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every + // node in the array returns a reference to the same action object from Node.getActions(boolean). + private static ReplaceContentTagAction instance; + + public static synchronized ReplaceContentTagAction getInstance() { + if (null == instance) { + instance = new ReplaceContentTagAction(); + } + return instance; + } + + private ReplaceContentTagAction() { + super(MENU_TEXT); + } + + @Override + public void actionPerformed(ActionEvent event) { + } + + protected String getActionDisplayName() { + return MENU_TEXT; + } + + @NbBundle.Messages({ + "# {0} - old tag name", + "# {1} - content obj id", + "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) + protected void replaceTag(TagName oldTagName, TagName newTagName, ContentTag oldTag) { + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + TagsManager tagsManager; + try { + tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldTag.getContent().getName())).show() + ); + return null; + } + + try { + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTagName.getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS + + System.out.println("RAMAN: Replacing tag " + oldTagName.getDisplayName() + " with tag " + newTagName.getDisplayName() ); + + tagsManager.deleteContentTag(oldTag); + tagsManager.addContentTag(oldTag.getContent(), newTagName); + + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldTag.getContent().getName())).show() + ); + } + return null; + } + + @Override + protected void done() { + super.done(); + try { + get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Unexpected exception while replacing content tag", ex); //NON-NLS + } + } + }.execute(); + } + + @Override + public JMenuItem getPopupPresenter() { + return new ReplaceContentTagMenu(); + } + + /** + * Instances of this class implement a context menu user interface for + * selecting a tag name to replace the tag with + */ + private final class ReplaceContentTagMenu extends JMenu { + + private static final long serialVersionUID = 1L; + + ReplaceContentTagMenu() { + super(getActionDisplayName()); + + final Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class); + + // Get the current set of tag names. + Map tagNamesMap = null; + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + + + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + String tagDisplayName = entry.getKey(); + String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); + // for the bookmark tag name only, added shortcut label + if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { + tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); + } + + // Add action to replace the tag + tagNameItem.addActionListener((ActionEvent e) -> { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag.getName(), entry.getValue(), oldtag); + }); + }); + + add(tagNameItem); + } + } else { + JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); + empty.setEnabled(false); + add(empty); + } + + addSeparator(); + + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent e) -> { + TagName newTagName = GetTagNameDialog.doDialog(); + if (null != newTagName) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag.getName(), newTagName, oldtag); + }); + } + }); + add(newTagMenuItem); + + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index 6e7a8205c7..e9cbe09ea0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -29,6 +29,7 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; @@ -134,6 +135,7 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { actions.add(new ViewTaggedArtifactAction(BlackboardArtifactTagNode_viewSourceArtifact_text(), artifact)); actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), true)); actions.add(DeleteBlackboardArtifactTagAction.getInstance()); + actions.add(ReplaceBlackboardArtifactTagAction.getInstance()); return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 59d7d21015..7fcf7d0e1c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -30,6 +30,7 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.actions.DeleteContentTagAction; +import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -130,6 +131,7 @@ class ContentTagNode extends DisplayableItemNode { } actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), false)); actions.add(DeleteContentTagAction.getInstance()); + actions.add(ReplaceContentTagAction.getInstance()); return actions.toArray(new Action[actions.size()]); } From 1d3fffadf9c9099a6eaebf7caaab2765b5333d96 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 7 Jun 2018 13:48:38 -0400 Subject: [PATCH 026/101] Refactored to remove code duplication --- .../ReplaceBlackboardArtifactTagAction.java | 127 ++++------------ .../actions/ReplaceContentTagAction.java | 115 +++----------- .../autopsy/actions/ReplaceTagAction.java | 140 ++++++++++++++++++ 3 files changed, 184 insertions(+), 198 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java index ce76a70b26..48d771ac71 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -18,21 +18,14 @@ */ package org.sleuthkit.autopsy.actions; -import java.awt.event.ActionEvent; import java.util.Collection; -import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javafx.application.Platform; import javafx.scene.control.Alert; -import javax.swing.AbstractAction; -import javax.swing.JMenu; -import javax.swing.JMenuItem; import javax.swing.SwingWorker; import org.openide.util.NbBundle; import org.openide.util.Utilities; -import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; @@ -40,22 +33,15 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; /** * Instances of this Action allow users to replace a tag applied to blackboard * artifacts, with another tag */ -@NbBundle.Messages({ - "ReplaceBlackboardArtifactTagAction.replaceTag=Replace Result Tag" -}) -public final class ReplaceBlackboardArtifactTagAction extends AbstractAction implements Presenter.Popup { +public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction { private static final Logger logger = Logger.getLogger(ReplaceBlackboardArtifactTagAction.class.getName()); - private static final long serialVersionUID = 1L; - private static final String MENU_TEXT = NbBundle.getMessage(ReplaceBlackboardArtifactTagAction.class, - "ReplaceBlackboardArtifactTagAction.replaceTag"); // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every @@ -73,21 +59,21 @@ public final class ReplaceBlackboardArtifactTagAction extends AbstractAction imp super(MENU_TEXT); } - @Override - public void actionPerformed(ActionEvent event) { - } - - protected String getActionDisplayName() { - return MENU_TEXT; - } - + /** + * Replaces the specified tag on the given artifact with the new one + * + * @param oldArtifactTag tag to be replaced + * @param newTagName name of the tag to replace with + */ @NbBundle.Messages({ "# {0} - old tag name", "# {1} - artifactID", "ReplaceBlackboardArtifactTagAction.replaceTag.alert=Unable to replace tag {0} for artifact {1}."}) - protected void replaceTag(TagName oldTagName, TagName newTagName, BlackboardArtifactTag oldArtifactTag) { + @Override + protected void replaceTag( BlackboardArtifactTag oldArtifactTag, TagName newTagName) { new SwingWorker() { + @Override protected Void doInBackground() throws Exception { TagsManager tagsManager; @@ -96,23 +82,21 @@ public final class ReplaceBlackboardArtifactTagAction extends AbstractAction imp } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS Platform.runLater(() - -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() ); - return null; + return null; } try { - logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTagName.getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS - - System.out.println("RAMAN: Replacing tag " + oldTagName.getDisplayName() + " with tag " + newTagName.getDisplayName() ); - tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); - tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName); - - + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldArtifactTag.getName().getDisplayName(), newTagName.getDisplayName(), oldArtifactTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteBlackboardArtifactTag(oldArtifactTag); + tagsManager.addBlackboardArtifactTag(oldArtifactTag.getArtifact(), newTagName); + } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS Platform.runLater(() - -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceBlackboardArtifactTagAction_replaceTag_alert(oldArtifactTag.getName().getDisplayName(), oldArtifactTag.getArtifact().getArtifactID())).show() ); } return null; @@ -130,73 +114,14 @@ public final class ReplaceBlackboardArtifactTagAction extends AbstractAction imp }.execute(); } - @Override - public JMenuItem getPopupPresenter() { - return new ReplaceArtifactTagMenu(); - } - - /** - * Instances of this class implement a context menu user interface for - * selecting a tag name to replace the tag with + /** + * Returns list of tags selected by user to replace + * + * @return list of tags */ - private final class ReplaceArtifactTagMenu extends JMenu { - - private static final long serialVersionUID = 1L; - - ReplaceArtifactTagMenu() { - super(getActionDisplayName()); - - final Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); - - // Get the current set of tag names. - Map tagNamesMap = null; - try { - TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); - } catch (TskCoreException | NoCurrentCaseException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS - } - - - if (null != tagNamesMap && !tagNamesMap.isEmpty()) { - for (Map.Entry entry : tagNamesMap.entrySet()) { - String tagDisplayName = entry.getKey(); - String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; - JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); - // for the bookmark tag name only, added shortcut label - if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { - tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); - } - - // Add action to replace the tag - tagNameItem.addActionListener((ActionEvent e) -> { - selectedTags.forEach((oldtag) -> { - replaceTag(oldtag.getName(), entry.getValue(), oldtag); - }); - }); - - add(tagNameItem); - } - } else { - JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); - empty.setEnabled(false); - add(empty); - } - - addSeparator(); - - JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); - newTagMenuItem.addActionListener((ActionEvent e) -> { - TagName newTagName = GetTagNameDialog.doDialog(); - if (null != newTagName) { - selectedTags.forEach((oldtag) -> { - replaceTag(oldtag.getName(), newTagName, oldtag); - }); - } - }); - add(newTagMenuItem); - - } + @Override + Collection getSelectedTags() { + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); } - + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java index 020767238a..dfeee5519d 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -18,21 +18,14 @@ */ package org.sleuthkit.autopsy.actions; -import java.awt.event.ActionEvent; import java.util.Collection; -import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javafx.application.Platform; import javafx.scene.control.Alert; -import javax.swing.AbstractAction; -import javax.swing.JMenu; -import javax.swing.JMenuItem; import javax.swing.SwingWorker; import org.openide.util.NbBundle; import org.openide.util.Utilities; -import org.openide.util.actions.Presenter; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; @@ -40,22 +33,18 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; /** - * Instances of this Action allow users to replace a tag applied to a file - * with another tag + * This Action allow users to replace a content tag with another tag */ @NbBundle.Messages({ "ReplaceContentArtifactTagAction.replaceTag=Replace File Tag" }) -public final class ReplaceContentTagAction extends AbstractAction implements Presenter.Popup { +public final class ReplaceContentTagAction extends ReplaceTagAction { private static final Logger logger = Logger.getLogger(ReplaceContentTagAction.class.getName()); private static final long serialVersionUID = 1L; - private static final String MENU_TEXT = NbBundle.getMessage(ReplaceContentTagAction.class, - "ReplaceContentArtifactTagAction.replaceTag"); // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every @@ -73,19 +62,12 @@ public final class ReplaceContentTagAction extends AbstractAction implements Pre super(MENU_TEXT); } - @Override - public void actionPerformed(ActionEvent event) { - } - - protected String getActionDisplayName() { - return MENU_TEXT; - } - @NbBundle.Messages({ "# {0} - old tag name", "# {1} - content obj id", "ReplaceContentTagAction.replaceTag.alert=Unable to replace tag {0} for {1}."}) - protected void replaceTag(TagName oldTagName, TagName newTagName, ContentTag oldTag) { + @Override + protected void replaceTag(ContentTag oldTag, TagName newTagName) { new SwingWorker() { @Override @@ -96,23 +78,21 @@ public final class ReplaceContentTagAction extends AbstractAction implements Pre } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error replacing artifact tag. No open case found.", ex); //NON-NLS Platform.runLater(() - -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldTag.getContent().getName())).show() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() ); - return null; + return null; } try { - logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTagName.getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS - - System.out.println("RAMAN: Replacing tag " + oldTagName.getDisplayName() + " with tag " + newTagName.getDisplayName() ); - - tagsManager.deleteContentTag(oldTag); - tagsManager.addContentTag(oldTag.getContent(), newTagName); + logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS + + tagsManager.deleteContentTag(oldTag); + tagsManager.addContentTag(oldTag.getContent(), newTagName); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS Platform.runLater(() - -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTagName.getDisplayName(), oldTag.getContent().getName())).show() + -> new Alert(Alert.AlertType.ERROR, Bundle.ReplaceContentTagAction_replaceTag_alert(oldTag.getName().getDisplayName(), oldTag.getContent().getName())).show() ); } return null; @@ -130,73 +110,14 @@ public final class ReplaceContentTagAction extends AbstractAction implements Pre }.execute(); } - @Override - public JMenuItem getPopupPresenter() { - return new ReplaceContentTagMenu(); - } - /** - * Instances of this class implement a context menu user interface for - * selecting a tag name to replace the tag with + * Returns list of tags selected by user to replace + * + * @return list of tags */ - private final class ReplaceContentTagMenu extends JMenu { - - private static final long serialVersionUID = 1L; - - ReplaceContentTagMenu() { - super(getActionDisplayName()); - - final Collection selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class); - - // Get the current set of tag names. - Map tagNamesMap = null; - try { - TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); - } catch (TskCoreException | NoCurrentCaseException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS - } - - - if (null != tagNamesMap && !tagNamesMap.isEmpty()) { - for (Map.Entry entry : tagNamesMap.entrySet()) { - String tagDisplayName = entry.getKey(); - String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; - JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); - // for the bookmark tag name only, added shortcut label - if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { - tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); - } - - // Add action to replace the tag - tagNameItem.addActionListener((ActionEvent e) -> { - selectedTags.forEach((oldtag) -> { - replaceTag(oldtag.getName(), entry.getValue(), oldtag); - }); - }); - - add(tagNameItem); - } - } else { - JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); - empty.setEnabled(false); - add(empty); - } - - addSeparator(); - - JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); - newTagMenuItem.addActionListener((ActionEvent e) -> { - TagName newTagName = GetTagNameDialog.doDialog(); - if (null != newTagName) { - selectedTags.forEach((oldtag) -> { - replaceTag(oldtag.getName(), newTagName, oldtag); - }); - } - }); - add(newTagMenuItem); - - } + @Override + Collection getSelectedTags() { + return Utilities.actionsGlobalContext().lookupAll(ContentTag.class); } - + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java new file mode 100644 index 0000000000..e14cc9e214 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -0,0 +1,140 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.actions; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import org.openide.util.NbBundle; +import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + + +/** + * Abstract class to define context action to replace a tag with another + * + * @param tag type + */ +@NbBundle.Messages({ + "ReplaceTagAction.replaceTag=Replace Tag" +}) +abstract class ReplaceTagAction extends AbstractAction implements Presenter.Popup { + + private static final long serialVersionUID = 1L; + protected static final String MENU_TEXT = NbBundle.getMessage(ReplaceTagAction.class, + "ReplaceTagAction.replaceTag"); + + ReplaceTagAction(String menuText) { + super(menuText); + } + + @Override + public void actionPerformed(ActionEvent event) { + } + + protected String getActionDisplayName() { + return MENU_TEXT; + } + + + /** + * Method to actually replace the selected tag with the given new tag + * + * @param oldTag + * @param newTagName + */ + abstract protected void replaceTag(T oldTag, TagName newTagName ); + + /** + * Returns elected tags which are to be replaced + * + * @return + */ + abstract Collection getSelectedTags(); + + @Override + public JMenuItem getPopupPresenter() { + return new ReplaceTagMenu(); + } + + /** + * Instances of this class implement a context menu user interface for + * selecting a tag name to replace the tag with + */ + private final class ReplaceTagMenu extends JMenu { + + private static final long serialVersionUID = 1L; + + ReplaceTagMenu() { + super(getActionDisplayName()); + + // final CollectionselectedTags = Utilities.actionsGlobalContext().lookupAll(T.class); + final CollectionselectedTags = getSelectedTags(); + + // Get the current set of tag names. + Map tagNamesMap = null; + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + } catch (TskCoreException | NoCurrentCaseException ex) { + Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS + } + + + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + String tagDisplayName = entry.getKey(); + String notableString = entry.getValue().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; + JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); + // for the bookmark tag name only, added shortcut label + if (tagDisplayName.equals(NbBundle.getMessage(AddTagAction.class, "AddBookmarkTagAction.bookmark.text"))) { + tagNameItem.setAccelerator(AddBookmarkTagAction.BOOKMARK_SHORTCUT); + } + + // Add action to replace the tag + tagNameItem.addActionListener((ActionEvent e) -> { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, entry.getValue() ); + }); + }); + + add(tagNameItem); + } + } else { + JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); + empty.setEnabled(false); + add(empty); + } + + addSeparator(); + + JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); + newTagMenuItem.addActionListener((ActionEvent e) -> { + TagName newTagName = GetTagNameDialog.doDialog(); + if (null != newTagName) { + selectedTags.forEach((oldtag) -> { + replaceTag(oldtag, newTagName); + }); + } + }); + add(newTagMenuItem); + + } + } + +} From d8412271f7d133b0004b21aa15ea88200a3d1d4a Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 7 Jun 2018 16:13:38 -0400 Subject: [PATCH 027/101] Minor cleaanup --- .../ReplaceBlackboardArtifactTagAction.java | 7 ++-- .../actions/ReplaceContentTagAction.java | 6 +-- .../autopsy/actions/ReplaceTagAction.java | 42 ++++++++----------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java index 48d771ac71..a014257daf 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceBlackboardArtifactTagAction.java @@ -35,7 +35,7 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * Instances of this Action allow users to replace a tag applied to blackboard + * This Action allows users to replace a tag applied to blackboard * artifacts, with another tag */ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction { @@ -72,7 +72,6 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction() { - @Override protected Void doInBackground() throws Exception { @@ -117,10 +116,10 @@ public final class ReplaceBlackboardArtifactTagAction extends ReplaceTagAction getSelectedTags() { + Collection getTagsToReplace() { return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java index dfeee5519d..287ae95415 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -111,12 +111,12 @@ public final class ReplaceContentTagAction extends ReplaceTagAction } /** - * Returns list of tags selected by user to replace + * Returns list of content tags selected by user to replace * - * @return list of tags + * @return a list of tags */ @Override - Collection getSelectedTags() { + Collection getTagsToReplace() { return Utilities.actionsGlobalContext().lookupAll(ContentTag.class); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index e14cc9e214..2c27d185d7 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -24,10 +24,9 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; - /** * Abstract class to define context action to replace a tag with another - * + * * @param tag type */ @NbBundle.Messages({ @@ -51,30 +50,29 @@ abstract class ReplaceTagAction extends AbstractAction implements return MENU_TEXT; } - /** - * Method to actually replace the selected tag with the given new tag + * Method to actually replace the selected tag with the given new tag * - * @param oldTag + * @param oldTag * @param newTagName */ - abstract protected void replaceTag(T oldTag, TagName newTagName ); - + abstract protected void replaceTag(T oldTag, TagName newTagName); + /** * Returns elected tags which are to be replaced - * - * @return + * + * @return */ - abstract Collection getSelectedTags(); - + abstract Collection getTagsToReplace(); + @Override public JMenuItem getPopupPresenter() { - return new ReplaceTagMenu(); + return new ReplaceTagMenu(); } - - /** + + /** * Instances of this class implement a context menu user interface for - * selecting a tag name to replace the tag with + * selecting a tag name to replace the tag with */ private final class ReplaceTagMenu extends JMenu { @@ -82,9 +80,8 @@ abstract class ReplaceTagAction extends AbstractAction implements ReplaceTagMenu() { super(getActionDisplayName()); - - // final CollectionselectedTags = Utilities.actionsGlobalContext().lookupAll(T.class); - final CollectionselectedTags = getSelectedTags(); + + final Collection selectedTags = getTagsToReplace(); // Get the current set of tag names. Map tagNamesMap = null; @@ -95,7 +92,6 @@ abstract class ReplaceTagAction extends AbstractAction implements Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -109,10 +105,10 @@ abstract class ReplaceTagAction extends AbstractAction implements // Add action to replace the tag tagNameItem.addActionListener((ActionEvent e) -> { selectedTags.forEach((oldtag) -> { - replaceTag(oldtag, entry.getValue() ); + replaceTag(oldtag, entry.getValue()); }); }); - + add(tagNameItem); } } else { @@ -122,7 +118,6 @@ abstract class ReplaceTagAction extends AbstractAction implements } addSeparator(); - JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); newTagMenuItem.addActionListener((ActionEvent e) -> { TagName newTagName = GetTagNameDialog.doDialog(); @@ -133,8 +128,7 @@ abstract class ReplaceTagAction extends AbstractAction implements } }); add(newTagMenuItem); - + } } - } From 3a86bfc5ec85ccbc087b85c25e3a4d1525a67956 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 7 Jun 2018 16:30:08 -0400 Subject: [PATCH 028/101] Codacy-inspired minor cleanup --- .../sleuthkit/autopsy/actions/ReplaceTagAction.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index 2c27d185d7..992066923e 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -42,7 +42,14 @@ abstract class ReplaceTagAction extends AbstractAction implements super(menuText); } + /** + * Subclasses of replaceTagAction should not override actionPerformed, + * but instead override replaceTag. + * + * @param event + */ @Override + @SuppressWarnings("NoopMethodInAbstractClass") public void actionPerformed(ActionEvent event) { } @@ -103,7 +110,7 @@ abstract class ReplaceTagAction extends AbstractAction implements } // Add action to replace the tag - tagNameItem.addActionListener((ActionEvent e) -> { + tagNameItem.addActionListener((ActionEvent event) -> { selectedTags.forEach((oldtag) -> { replaceTag(oldtag, entry.getValue()); }); @@ -119,7 +126,7 @@ abstract class ReplaceTagAction extends AbstractAction implements addSeparator(); JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); - newTagMenuItem.addActionListener((ActionEvent e) -> { + newTagMenuItem.addActionListener((ActionEvent event) -> { TagName newTagName = GetTagNameDialog.doDialog(); if (null != newTagName) { selectedTags.forEach((oldtag) -> { From 1a0197ab104def2f0e0d5752a207a8b3b2de8d38 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 7 Jun 2018 17:46:51 -0400 Subject: [PATCH 029/101] 3819 make depth count fix have some thread safety --- .../EmbeddedFileExtractorIngestModule.java | 28 +++-- .../ExtractArchiveWithPasswordAction.java | 3 +- .../SevenZipExtractor.java | 106 ++++++------------ 3 files changed, 56 insertions(+), 81 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 0fee416799..75383261a5 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.io.File; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.AbstractFile; @@ -31,6 +33,7 @@ import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Archive; /** * A file level ingest module that extracts embedded files from supported @@ -49,9 +52,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda private String moduleDirRelative; private String moduleDirAbsolute; private MSOfficeEmbeddedContentExtractor officeExtractor; - private static SevenZipExtractor archiveExtractor; + private SevenZipExtractor archiveExtractor; private FileTypeDetector fileTypeDetector; private long jobId; + private static final HashMap> mapOfDepthTrees = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); /** @@ -97,16 +101,16 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); } + try { + this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); + } catch (SevenZipNativeInitializationException ex) { + throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); + } if (refCounter.incrementAndGet(jobId) == 1) { /* - * Construct a 7Zip file extractor for processing archive files. + * Construct a concurrentHashmap to keep track of depth in archives while processing archive files. */ - try { - this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); - } catch (SevenZipNativeInitializationException ex) { - throw new IngestModuleException(Bundle.UnableToInitializeLibraries(), ex); - } - + mapOfDepthTrees.put(jobId, new ConcurrentHashMap<>()); } /* * Construct an embedded content extractor for processing Microsoft @@ -149,7 +153,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda * type/format. */ if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) { - archiveExtractor.unpack(abstractFile); + archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId)); } else if (officeExtractor.isContentExtractionSupported(abstractFile)) { officeExtractor.extractEmbeddedContent(abstractFile); } @@ -158,9 +162,11 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda @Override public void shutDown() { - refCounter.decrementAndGet(jobId); + if (refCounter.decrementAndGet(jobId) == 0) { + mapOfDepthTrees.remove(jobId); + } } - + /** * Creates a unique name for a file by concatentating the file name and the * file object id. diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java index 722f211951..b122dbd4fb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/ExtractArchiveWithPasswordAction.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.awt.event.ActionEvent; import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -125,7 +126,7 @@ public class ExtractArchiveWithPasswordAction extends AbstractAction { } try { SevenZipExtractor extractor = new SevenZipExtractor(null, fileTypeDetector, moduleDirRelative, moduleDirAbsolute); - done = extractor.unpack(archive, password); + done = extractor.unpack(archive, new ConcurrentHashMap<>(), password); } catch (SevenZipNativeInitializationException ex) { IngestServices.getInstance().postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Unable to extract file with password", password)); logger.log(Level.INFO, "Unable to extract file with password", ex); diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 3b8ced7752..a6d8a69c51 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import net.sf.sevenzipjbinding.ArchiveFormat; import static net.sf.sevenzipjbinding.ArchiveFormat.RAR; @@ -84,8 +85,6 @@ class SevenZipExtractor { private static final int MAX_COMPRESSION_RATIO = 600; private static final long MIN_COMPRESSION_RATIO_SIZE = 500 * 1000000L; private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB - //counts archive depth - private volatile ArchiveDepthCountTree archiveDepthCountTree; private String moduleDirRelative; private String moduleDirAbsolute; @@ -131,7 +130,7 @@ class SevenZipExtractor { this.fileTypeDetector = fileTypeDetector; this.moduleDirRelative = moduleDirRelative; this.moduleDirAbsolute = moduleDirAbsolute; - this.archiveDepthCountTree = new ArchiveDepthCountTree(); +// this.archiveDepthCountTree = new ArchiveDepthCountTree(); } /** @@ -462,8 +461,8 @@ class SevenZipExtractor { * * @return true if unpacking is complete */ - void unpack(AbstractFile archiveFile) { - unpack(archiveFile, null); + void unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap) { + unpack(archiveFile, depthMap, null); } /** @@ -476,7 +475,7 @@ class SevenZipExtractor { * @return true if unpacking is complete */ @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) - boolean unpack(AbstractFile archiveFile, String password) { + boolean unpack(AbstractFile archiveFile, ConcurrentHashMap archiveDepthCountTree, String password) { boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and boolean hasEncrypted = false; boolean fullEncryption = true; @@ -492,7 +491,7 @@ class SevenZipExtractor { final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName()); //recursion depth check for zip bomb final long archiveId = archiveFile.getId(); - SevenZipExtractor.ArchiveDepthCountTree.Archive parentAr; + Archive parentAr; try { blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { @@ -515,10 +514,10 @@ class SevenZipExtractor { unpackSuccessful = false; return unpackSuccessful; } - parentAr = archiveDepthCountTree.findArchive(archiveId); + parentAr = archiveDepthCountTree.get(archiveId); if (parentAr == null) { - parentAr = archiveDepthCountTree.addArchive(null, archiveId); - + parentAr = new Archive(null, archiveId, 0); + parentAr = archiveDepthCountTree.put(archiveId, parentAr); } else if (parentAr.getDepth() == MAX_DEPTH) { String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); @@ -667,7 +666,9 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - archiveDepthCountTree.addArchive(parentAr, unpackedFile.getId()); + Archive child = new Archive(parentAr, unpackedFile.getId(), parentAr.getDepth()+1); + parentAr.addChild(child); + archiveDepthCountTree.put(unpackedFile.getId(), child); } } @@ -1168,72 +1169,39 @@ class SevenZipExtractor { } } - /** - * Tracks archive hierarchy and archive depth - */ - private static class ArchiveDepthCountTree { + static class Archive { - //keeps all nodes refs for easy search - private final List archives = new ArrayList<>(); + private final int depth; + private final long objectId; + private final Archive parent; + private final List children; - /** - * Search for previously added parent archive by id - * - * @param objectId parent archive object id - * - * @return the archive node or null if not found - */ - Archive findArchive(long objectId) { - for (Archive ar : archives) { - if (ar.objectId == objectId) { - return ar; - } - } + Archive(Archive parent, long objectId, int depth) { + this.parent = parent; + this.objectId = objectId; + this.children = new ArrayList<>(); + this.depth = depth; + } - return null; + void addChild(Archive child){ + children.add(child); + } + + long getParentObjectId() { + return parent.getObjectId(); + } + + long getObjectId() { + return objectId; } /** - * Add a new archive to track of depth + * get archive depth of this archive * - * @param parent parent archive or null - * @param objectId object id of the new archive - * - * @return the archive added + * @return */ - Archive addArchive(Archive parent, long objectId) { - Archive child = new Archive(parent, objectId); - archives.add(child); - return child; - } - - private static class Archive { - - int depth; - long objectId; - Archive parent; - List children; - - Archive(Archive parent, long objectId) { - this.parent = parent; - this.objectId = objectId; - children = new ArrayList<>(); - if (parent != null) { - parent.children.add(this); - this.depth = parent.depth + 1; - } else { - this.depth = 0; - } - } - - /** - * get archive depth of this archive - * - * @return - */ - int getDepth() { - return depth; - } + int getDepth() { + return depth; } } From 18a4c402b3b96f3188bdd35d9b6e14701574fd12 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 7 Jun 2018 18:02:42 -0400 Subject: [PATCH 030/101] 3819 fix bug with null parent archive --- .../modules/embeddedfileextractor/SevenZipExtractor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index a6d8a69c51..fa35e01b7f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -475,7 +475,7 @@ class SevenZipExtractor { * @return true if unpacking is complete */ @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) - boolean unpack(AbstractFile archiveFile, ConcurrentHashMap archiveDepthCountTree, String password) { + boolean unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap, String password) { boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and boolean hasEncrypted = false; boolean fullEncryption = true; @@ -514,10 +514,10 @@ class SevenZipExtractor { unpackSuccessful = false; return unpackSuccessful; } - parentAr = archiveDepthCountTree.get(archiveId); + parentAr = depthMap.get(archiveId); if (parentAr == null) { parentAr = new Archive(null, archiveId, 0); - parentAr = archiveDepthCountTree.put(archiveId, parentAr); + depthMap.put(archiveId, parentAr); } else if (parentAr.getDepth() == MAX_DEPTH) { String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); @@ -668,7 +668,7 @@ class SevenZipExtractor { if (isSevenZipExtractionSupported(unpackedFile)) { Archive child = new Archive(parentAr, unpackedFile.getId(), parentAr.getDepth()+1); parentAr.addChild(child); - archiveDepthCountTree.put(unpackedFile.getId(), child); + depthMap.put(unpackedFile.getId(), child); } } From 38f34d7b0a3804904c1759cd161a05942505bbd4 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 8 Jun 2018 11:31:02 -0400 Subject: [PATCH 031/101] Cleanup --- .../sleuthkit/autopsy/datamodel/AbstractContentNode.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index b35f93c669..3fc3fa9f4e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -48,8 +48,6 @@ public abstract class AbstractContentNode extends ContentNode */ T content; private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName()); - private static int tempCountQueries = 0; - private static int tempCountMapLookups = 0; /** * Handles aspects that depend on the Content object @@ -114,10 +112,7 @@ public abstract class AbstractContentNode extends ContentNode if (c != null) { try { - tempCountMapLookups++; if( ! c.hasChildren()) { - //if( ! Case.getCurrentCaseThrows().getSleuthkitCase().getHasChildren(c.getId())) { - //System.out.println("Map lookups: " + tempCountMapLookups + " queries: " + tempCountQueries); return false; } } catch (TskCoreException ex) { @@ -126,7 +121,6 @@ public abstract class AbstractContentNode extends ContentNode return false; } - tempCountQueries++; String query = "SELECT COUNT(obj_id) AS count FROM " + " ( SELECT obj_id FROM tsk_objects WHERE par_obj_id = " + c.getId() + " AND type = " + TskData.ObjectType.ARTIFACT.getObjectType() @@ -139,7 +133,6 @@ public abstract class AbstractContentNode extends ContentNode try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); - //System.out.println("Map lookups: " + tempCountMapLookups + " queries: " + tempCountQueries); if(resultSet.next()){ return (0 < resultSet.getInt("count")); } From 9980fee6ed797549f8a81b1273aa35c9fa808500 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 8 Jun 2018 12:09:24 -0400 Subject: [PATCH 032/101] 3819 clean up and comment for seven zip extractor regarding depth fix --- .../EmbeddedFileExtractorIngestModule.java | 8 +- .../SevenZipExtractor.java | 107 +++++++++++++----- 2 files changed, 84 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 75383261a5..39a3453df8 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -48,15 +48,14 @@ import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Arc }) public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAdapter { - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // "iso"}; NON-NLS + private static final HashMap> mapOfDepthTrees = new HashMap<>(); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private String moduleDirRelative; private String moduleDirAbsolute; private MSOfficeEmbeddedContentExtractor officeExtractor; private SevenZipExtractor archiveExtractor; private FileTypeDetector fileTypeDetector; private long jobId; - private static final HashMap> mapOfDepthTrees = new HashMap<>(); - private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); /** * Constructs a file level ingest module that extracts embedded files from @@ -108,7 +107,8 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda } if (refCounter.incrementAndGet(jobId) == 1) { /* - * Construct a concurrentHashmap to keep track of depth in archives while processing archive files. + * Construct a concurrentHashmap to keep track of depth in archives + * while processing archive files. */ mapOfDepthTrees.put(jobId, new ConcurrentHashMap<>()); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index fa35e01b7f..6680c4208e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -74,7 +74,6 @@ class SevenZipExtractor { private IngestServices services = IngestServices.getInstance(); private final IngestJobContext context; private final FileTypeDetector fileTypeDetector; - static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // NON-NLS //encryption type strings private static final String ENCRYPTION_FILE_LEVEL = NbBundle.getMessage(EmbeddedFileExtractorIngestModule.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel"); @@ -130,7 +129,6 @@ class SevenZipExtractor { this.fileTypeDetector = fileTypeDetector; this.moduleDirRelative = moduleDirRelative; this.moduleDirAbsolute = moduleDirAbsolute; -// this.archiveDepthCountTree = new ArchiveDepthCountTree(); } /** @@ -458,6 +456,8 @@ class SevenZipExtractor { * Unpack the file to local folder and return a list of derived files * * @param archiveFile file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives * * @return true if unpacking is complete */ @@ -470,6 +470,8 @@ class SevenZipExtractor { * the password if specified. * * @param archiveFile - file to unpack + * @param depthMap - a concurrent hashmap which keeps track of the depth + * of all nested archives * @param password - the password to use, null for no password * * @return true if unpacking is complete @@ -666,7 +668,7 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - Archive child = new Archive(parentAr, unpackedFile.getId(), parentAr.getDepth()+1); + Archive child = new Archive(parentAr, unpackedFile.getId(), parentAr.getDepth() + 1); parentAr.addChild(child); depthMap.put(unpackedFile.getId(), child); } @@ -910,7 +912,7 @@ class SevenZipExtractor { this.rootNode = new UnpackedNode(); this.rootNode.setFile(archiveFile); this.rootNode.setFileName(archiveFile.getName()); - this.rootNode.localRelPath = localPathRoot; + this.rootNode.setLocalRelPath(localPathRoot); } /** @@ -953,6 +955,7 @@ class SevenZipExtractor { // create new node if (child == null) { child = new UnpackedNode(childName, parent); + parent.addChild(child); } // go down one more level @@ -967,7 +970,7 @@ class SevenZipExtractor { */ List getRootFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { ret.add(child.getFile()); } return ret; @@ -981,7 +984,7 @@ class SevenZipExtractor { */ List getAllFileObjects() { List ret = new ArrayList<>(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { getAllFileObjectsRec(ret, child); } return ret; @@ -989,7 +992,7 @@ class SevenZipExtractor { private void getAllFileObjectsRec(List list, UnpackedNode parent) { list.add(parent.getFile()); - for (UnpackedNode child : parent.children) { + for (UnpackedNode child : parent.getChildren()) { getAllFileObjectsRec(list, child); } } @@ -1000,7 +1003,7 @@ class SevenZipExtractor { */ void updateOrAddFileToCaseRec(HashMap statusMap, String archiveFilePath) throws TskCoreException, NoCurrentCaseException { final FileManager fileManager = Case.getCurrentCaseThrows().getServices().getFileManager(); - for (UnpackedNode child : rootNode.children) { + for (UnpackedNode child : rootNode.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, archiveFilePath); } } @@ -1057,7 +1060,7 @@ class SevenZipExtractor { node.getFileName()), ex); } //recurse adding the children if this file was incomplete the children presumably need to be added - for (UnpackedNode child : node.children) { + for (UnpackedNode child : node.getChildren()) { updateOrAddFileToCaseRec(child, fileManager, statusMap, getKeyFromUnpackedNode(node, archiveFilePath)); } } @@ -1084,31 +1087,53 @@ class SevenZipExtractor { UnpackedNode(String fileName, UnpackedNode parent) { this.fileName = fileName; this.parent = parent; - this.localRelPath = parent.localRelPath + File.separator + fileName; - //new child derived file will be set by unpack() method - parent.children.add(this); + this.localRelPath = parent.getLocalRelPath() + File.separator + fileName; } - public long getCtime() { + long getCtime() { return ctime; } - public long getCrtime() { + long getCrtime() { return crtime; } - public long getAtime() { + long getAtime() { return atime; } - public long getMtime() { + long getMtime() { return mtime; } - public void setFileName(String fileName) { + void setFileName(String fileName) { this.fileName = fileName; } + /** + * Add a child to the list of child nodes associated with this node. + * + * @param child - the node which is a child node of this node + */ + void addChild(UnpackedNode child) { + children.add(child); + } + + /** + * Get this nodes list of child UnpackedNode + * + * @return children - the UnpackedNodes which are children of this + * node. + */ + List getChildren() { + return children; + } + + /** + * Gets the parent node of this node. + * + * @return - the parent UnpackedNode + */ UnpackedNode getParent() { return parent; } @@ -1139,7 +1164,7 @@ class SevenZipExtractor { UnpackedNode getChild(String childFileName) { UnpackedNode ret = null; for (UnpackedNode child : children) { - if (child.fileName.equals(childFileName)) { + if (child.getFileName().equals(childFileName)) { ret = child; break; } @@ -1147,23 +1172,33 @@ class SevenZipExtractor { return ret; } - public String getFileName() { + String getFileName() { return fileName; } - public AbstractFile getFile() { + AbstractFile getFile() { return file; } - public String getLocalRelPath() { + String getLocalRelPath() { return localRelPath; } - public long getSize() { + /** + * Set the local relative path associated with this UnpackedNode + * + * @param localRelativePath - the local relative path to be + * associated with this node. + */ + void setLocalRelPath(String localRelativePath) { + localRelPath = localRelativePath; + } + + long getSize() { return size; } - public boolean isIsFile() { + boolean isIsFile() { return isFile; } } @@ -1183,22 +1218,40 @@ class SevenZipExtractor { this.depth = depth; } - void addChild(Archive child){ + /** + * Add a child to the list of child archives associated with this + * archive. + * + * @param child - the archive which is a child archive of this archive + */ + void addChild(Archive child) { children.add(child); } - + + /** + * Get the object id of the parent of this archive. + * + * @return parent.objectId - the unique objectId of this archives parent + * archive + */ long getParentObjectId() { return parent.getObjectId(); } + /** + * Get the object id of this archive. + * + * @return objectId - the unique objectId of this archive + */ long getObjectId() { return objectId; } /** - * get archive depth of this archive + * Get archive depth of this archive * - * @return + * @return depth - an integer representing how many layers of archives + * this archive is inside. */ int getDepth() { return depth; From 8a035b87e5f3c2558f9ca8fbc3f6b58d65b6266d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 8 Jun 2018 12:16:15 -0400 Subject: [PATCH 033/101] 3819 additional clean up and removal of unnecessary fields depth fix --- .../SevenZipExtractor.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 6680c4208e..6f90fc19b3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -518,7 +518,7 @@ class SevenZipExtractor { } parentAr = depthMap.get(archiveId); if (parentAr == null) { - parentAr = new Archive(null, archiveId, 0); + parentAr = new Archive(archiveId, 0); depthMap.put(archiveId, parentAr); } else if (parentAr.getDepth() == MAX_DEPTH) { String msg = NbBundle.getMessage(SevenZipExtractor.class, @@ -668,7 +668,7 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - Archive child = new Archive(parentAr, unpackedFile.getId(), parentAr.getDepth() + 1); + Archive child = new Archive(unpackedFile.getId(), parentAr.getDepth() + 1); parentAr.addChild(child); depthMap.put(unpackedFile.getId(), child); } @@ -1072,7 +1072,7 @@ class SevenZipExtractor { private String fileName; private AbstractFile file; - private List children = new ArrayList<>(); + private final List children = new ArrayList<>(); private String localRelPath = ""; private long size; private long ctime, crtime, atime, mtime; @@ -1208,11 +1208,9 @@ class SevenZipExtractor { private final int depth; private final long objectId; - private final Archive parent; private final List children; - Archive(Archive parent, long objectId, int depth) { - this.parent = parent; + Archive(long objectId, int depth) { this.objectId = objectId; this.children = new ArrayList<>(); this.depth = depth; @@ -1228,16 +1226,6 @@ class SevenZipExtractor { children.add(child); } - /** - * Get the object id of the parent of this archive. - * - * @return parent.objectId - the unique objectId of this archives parent - * archive - */ - long getParentObjectId() { - return parent.getObjectId(); - } - /** * Get the object id of this archive. * From 0510be519160ae249fbe5658dd95f6b625e1f8f2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 8 Jun 2018 12:39:30 -0400 Subject: [PATCH 034/101] 3819 additional fix to jobId map and comments added --- .../EmbeddedFileExtractorIngestModule.java | 3 +-- .../embeddedfileextractor/SevenZipExtractor.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 39a3453df8..798cc05f81 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.modules.embeddedfileextractor; import java.io.File; import java.nio.file.Paths; -import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; @@ -48,7 +47,7 @@ import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Arc }) public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAdapter { - private static final HashMap> mapOfDepthTrees = new HashMap<>(); + private static final ConcurrentHashMap> mapOfDepthTrees = new ConcurrentHashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private String moduleDirRelative; private String moduleDirAbsolute; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 6f90fc19b3..08409c4039 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -1204,12 +1204,22 @@ class SevenZipExtractor { } } + /** + * Class to keep track of an objects id and its depth in the archive + * structure. + */ static class Archive { private final int depth; private final long objectId; private final List children; + /** + * Create a new Archive object. + * + * @param objectId the unique object id of the archive object + * @param depth the depth in the archive structure + */ Archive(long objectId, int depth) { this.objectId = objectId; this.children = new ArrayList<>(); From 1667784659aa61bb11843bc3949299740eff4c6a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 8 Jun 2018 13:26:45 -0400 Subject: [PATCH 035/101] 3819 add comment to state what the key for maps are --- .../EmbeddedFileExtractorIngestModule.java | 1 + .../modules/embeddedfileextractor/SevenZipExtractor.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java index 798cc05f81..045fadb0b9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/EmbeddedFileExtractorIngestModule.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.modules.embeddedfileextractor.SevenZipExtractor.Arc }) public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAdapter { + //Outer concurrent hashmap with keys of JobID, inner concurrentHashmap with keys of objectID private static final ConcurrentHashMap> mapOfDepthTrees = new ConcurrentHashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private String moduleDirRelative; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 08409c4039..8fc4d5a70d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -457,7 +457,7 @@ class SevenZipExtractor { * * @param archiveFile file to unpack * @param depthMap - a concurrent hashmap which keeps track of the depth - * of all nested archives + * of all nested archives, key of objectID * * @return true if unpacking is complete */ @@ -471,7 +471,7 @@ class SevenZipExtractor { * * @param archiveFile - file to unpack * @param depthMap - a concurrent hashmap which keeps track of the depth - * of all nested archives + * of all nested archives, key of objectID * @param password - the password to use, null for no password * * @return true if unpacking is complete From 6afaad08e3e4912f1d893ba8bd70c7ca29efc8c2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 8 Jun 2018 13:30:22 -0400 Subject: [PATCH 036/101] Fifth batch updated for PMD error suppression and correction. --- .../casemodule/AddImageErrorsDialog.java | 13 ++++++++--- .../AddImageWizardAddingProgressPanel.java | 3 ++- ...AddImageWizardDataSourceSettingsPanel.java | 1 + .../AddImageWizardIngestConfigPanel.java | 3 ++- .../AddImageWizardSelectDspPanel.java | 3 ++- .../casemodule/MultiUserCasesDialog.java | 3 ++- .../ManageCorrelationPropertiesDialog.java | 7 +++--- .../AdvancedConfigurationCleanDialog.java | 23 +++++++++++++++---- .../AdvancedConfigurationDialog.java | 16 ++++--------- .../AddExternalViewerRuleDialog.java | 1 + .../autopsy/filesearch/FileSearchDialog.java | 10 ++------ .../ingest/IngestProgressSnapshotDialog.java | 1 + .../AddFileTypeSignatureDialog.java | 4 ++-- .../filetypeid/AddFileTypeSignaturePanel.java | 1 + .../report/ArtifactSelectionDialog.java | 4 ++++ .../timeline/ShowInTimelineDialog.java | 3 ++- .../keywordsearch/AdHocSearchPanel.java | 9 ++++---- .../keywordsearch/ExtractedContentPanel.java | 2 +- .../KeywordSearchGlobalSettingsPanel.java | 3 ++- 19 files changed, 66 insertions(+), 44 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java index 742cb1750c..a469a36584 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageErrorsDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,15 +18,22 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Frame; +import javax.swing.JDialog; + /** * Dialog to show add image error messages */ -public class AddImageErrorsDialog extends javax.swing.JDialog { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class AddImageErrorsDialog extends JDialog { /** * Creates new form AddImageErrorsDialog + * + * @param parent The parent frame. + * @param modal Does this dialog act as a modal? */ - public AddImageErrorsDialog(java.awt.Frame parent, boolean modal) { + public AddImageErrorsDialog(Frame parent, boolean modal) { super(parent, modal); initComponents(); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java index 6c6097717e..15eb0c3c67 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardAddingProgressPanel.java @@ -57,6 +57,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; * {@link AddImageWizardIngestConfigPanel} (which is a bit weird if you ask m * -jm) */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { private boolean readyToIngest = false; @@ -79,7 +80,7 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel { */ private AddImageWizardAddingProgressVisual component; private final Set listeners = new HashSet<>(1); // or can use ChangeSupport in NB 6.0 - private final List newContents = Collections.synchronizedList(new ArrayList()); + private final List newContents = Collections.synchronizedList(new ArrayList<>()); private final DSPProgressMonitorImpl dspProgressMonitorImpl = new DSPProgressMonitorImpl(); private IngestJobSettings ingestJobSettings; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java index e4d6d09984..15f1ae86a5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsPanel.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * The "Add Image" wizard panel1 handling the logic of selecting image file(s) * to add to Case, and pick the time zone. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardDataSourceSettingsPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java index 4678e136a7..cfcbdd015c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardIngestConfigPanel.java @@ -40,10 +40,11 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * TODO: review this for dead code. think about moving logic of adding image to * 3rd panel( {@link AddImageWizardAddingProgressPanel}) separate class -jm */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddImageWizardIngestConfigPanel extends ShortcutWizardDescriptorPanel { @Messages("AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules") - private IngestJobSettingsPanel ingestJobSettingsPanel; + private final IngestJobSettingsPanel ingestJobSettingsPanel; /** * The visual component that displays this panel. If you need to access the * component from this class, just use getComponent(). diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java index 86981e9d5d..5db401fd0f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.ShortcutWizardDescript * Create a wizard panel which contains a panel allowing the selection of the * DataSourceProcessor */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddImageWizardSelectDspPanel extends ShortcutWizardDescriptorPanel implements PropertyChangeListener { @NbBundle.Messages("SelectDataSourceProcessorPanel.name.text=Select Type of Data Source To Add") diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java index 8c90aeccb4..af95b0c5a0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ import org.openide.windows.WindowManager; /** * This class extends a JDialog and maintains the MultiUserCasesPanel. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MultiUserCasesDialog extends JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java index a7c3016d96..3fa46b3306 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ManageCorrelationPropertiesDialog.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.centralrepository.optionspanel; -import java.awt.Dimension; -import java.awt.Toolkit; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -38,8 +36,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; /** * Dialog to handle management of artifact types handled by the Central - * Repository + * Repository */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ManageCorrelationPropertiesDialog extends javax.swing.JDialog { private static final Logger LOGGER = Logger.getLogger(ManageCorrelationPropertiesDialog.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java index cccb6e1a46..4589c43755 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationCleanDialog.java @@ -1,12 +1,24 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2011-2018 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.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; @@ -16,6 +28,7 @@ import org.openide.windows.WindowManager; * the panel given to it. No additional buttons or features, except the default * close operation, which is set to dispose. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** @@ -27,6 +40,8 @@ public class AdvancedConfigurationCleanDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationCleanDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java index 35a26344ea..a62d3a1459 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AdvancedConfigurationDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,26 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * AdvancedConfigurationDialog.java - * - * Created on Feb 28, 2012, 4:47:31 PM - */ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; -import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import org.openide.windows.WindowManager; /** - * - * @author dfickling + * Advanced configuration dialog. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** @@ -47,6 +39,8 @@ public class AdvancedConfigurationDialog extends javax.swing.JDialog { /** * Creates new form AdvancedConfigurationDialog + * + * @param resizable Is the dialog resizable? */ public AdvancedConfigurationDialog(boolean resizable) { super((JFrame) WindowManager.getDefault().getMainWindow(), true); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java index 073f7b4eee..fad5d90b61 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/AddExternalViewerRuleDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog for adding or editing an external viewer rule */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddExternalViewerRuleDialog extends JDialog { private ExternalViewerRule rule; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java index 9171104238..845c39f06c 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchDialog.java @@ -16,24 +16,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * FileSearchDialog.java - * - * Created on Mar 5, 2012, 1:57:33 PM - */ package org.sleuthkit.autopsy.filesearch; -import org.openide.util.NbBundle; - import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; +import org.openide.util.NbBundle; import org.openide.windows.WindowManager; /** * File search dialog */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchDialog extends javax.swing.JDialog { /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java index eddebd59c4..c86f90ad4b 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotDialog.java @@ -35,6 +35,7 @@ import org.openide.windows.WindowManager; /** * A dialog that displays ingest task progress snapshots. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class IngestProgressSnapshotDialog extends JDialog { private static final String TITLE = NbBundle.getMessage(IngestProgressSnapshotDialog.class, "IngestProgressSnapshotDialog.title.text"); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java index 372c62769a..e5373bc555 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignatureDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.awt.BorderLayout; import java.awt.Dimension; -import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; @@ -40,6 +39,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; * A dialog box that allows a user to create a file type signature, to be added * to a selected file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class AddFileTypeSignatureDialog extends JDialog { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java index c22ecd94a7..bd047b86e0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/AddFileTypeSignaturePanel.java @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * Panel for creating a file type signature to be added to a file type. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class AddFileTypeSignaturePanel extends javax.swing.JPanel { private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem"); diff --git a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java index 50e9715a16..b0655dfa2b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java +++ b/Core/src/org/sleuthkit/autopsy/report/ArtifactSelectionDialog.java @@ -41,6 +41,10 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Allow examiner to select artifacts on which to report. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class ArtifactSelectionDialog extends javax.swing.JDialog { private ArtifactModel model; diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java index 82e479c4ab..dede3d537a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ShowInTimelineDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,6 +72,7 @@ import org.sleuthkit.datamodel.TskCoreException; * to choose a specific event and a time range around it to show in the Timeline * List View. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class ShowInTimelineDialog extends Dialog { private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 13798614c5..818eca286a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2013 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.openide.util.NbBundle; * class and extended classes model the user's intentions, not necessarily how * the search manager and 3rd party tools actually perform the search. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); @@ -48,7 +49,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { Object newValue = evt.getNewValue(); if (changed.equals(KeywordSearch.NUM_FILES_CHANGE_EVT)) { - int newFilesIndexed = ((Integer) newValue).intValue(); + int newFilesIndexed = ((Integer) newValue); filesIndexed = newFilesIndexed; postFilesIndexedChange(); } @@ -110,8 +111,6 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { } } - AdHocSearchDelegator man = null; - final List keywordLists = getKeywordLists(); if (keywordLists.isEmpty()) { KeywordSearchUtil.displayDialog(keywordSearchErrorDialogHeader, NbBundle.getMessage(this.getClass(), @@ -119,7 +118,7 @@ abstract class AdHocSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new AdHocSearchDelegator(keywordLists); + AdHocSearchDelegator man = new AdHocSearchDelegator(keywordLists); if (man.validate()) { man.execute(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index f7463bbccb..5061a2e2f0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JTextPane; import javax.swing.SizeRequirements; import javax.swing.SwingWorker; import javax.swing.text.Element; @@ -48,6 +47,7 @@ import org.sleuthkit.autopsy.coreutils.TextUtil; * Panel displays HTML content sent to ExtractedContentViewer, and provides a * combo-box to select between multiple sources. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class ExtractedContentPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java index c34e672a1c..156bbd0220 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchGlobalSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; /** * Global options panel for keyword searching. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class KeywordSearchGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; From 486e5b9ad76bde51f8ca5ded32ec9b957c44a28b Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 8 Jun 2018 13:39:20 -0400 Subject: [PATCH 037/101] 3819 add comments to explain what is meanth by depth in 7zipextractor --- .../embeddedfileextractor/SevenZipExtractor.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 8fc4d5a70d..eb31e7d30a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -1210,6 +1210,7 @@ class SevenZipExtractor { */ static class Archive { + //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within private final int depth; private final long objectId; private final List children; @@ -1218,7 +1219,10 @@ class SevenZipExtractor { * Create a new Archive object. * * @param objectId the unique object id of the archive object - * @param depth the depth in the archive structure + * @param depth the depth in the archive structure - 0 will be the + * root archive unpack was called on, and it will + * increase as unpack recurses down through archives + * contained within */ Archive(long objectId, int depth) { this.objectId = objectId; @@ -1248,8 +1252,9 @@ class SevenZipExtractor { /** * Get archive depth of this archive * - * @return depth - an integer representing how many layers of archives - * this archive is inside. + * @return depth - an integer representing that represents how many + * times the upack method has been recursed from the root + * archive unpack was called on */ int getDepth() { return depth; From 43847af69c4bf344ce62765686e5e63c4d40fbc2 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 8 Jun 2018 14:03:44 -0400 Subject: [PATCH 038/101] 3866: User defined tags appear first in tags menu --- .../autopsy/actions/AddTagAction.java | 23 ++++++++++++++++--- .../autopsy/actions/ReplaceTagAction.java | 19 ++++++++++++++- .../casemodule/services/TagsManager.java | 9 ++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 7d0212b51c..5df684a207 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; @@ -83,7 +85,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { */ // @@@ This user interface has some significant usability issues and needs // to be reworked. - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -92,6 +94,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Get the current set of tag names. Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); @@ -106,6 +109,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Each tag name in the current set of tags gets its own menu item in // the "Quick Tags" sub-menu. Selecting one of these menu items adds // a tag with the associated tag name. + List standardTagMenuitems = new ArrayList<>(); if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -119,7 +123,13 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagNameItem.addActionListener((ActionEvent e) -> { getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); - quickTagMenu.add(tagNameItem); + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + quickTagMenu.add(tagNameItem); + } } } else { JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); @@ -127,8 +137,15 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { quickTagMenu.add(empty); } + if (quickTagMenu.getItemCount() > 0) { + quickTagMenu.addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + quickTagMenu.add(menuItem); + }); + quickTagMenu.addSeparator(); - + // The "Quick Tag" menu also gets an "Choose Tag..." menu item. // Selecting this item initiates a dialog that can be used to create // or select a tag name and adds a tag with the resulting name. diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index 992066923e..9c82934e27 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -6,7 +6,9 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; @@ -92,6 +94,7 @@ abstract class ReplaceTagAction extends AbstractAction implements // Get the current set of tag names. Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); @@ -99,6 +102,7 @@ abstract class ReplaceTagAction extends AbstractAction implements Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } + List standardTagMenuitems = new ArrayList<>(); if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -116,7 +120,12 @@ abstract class ReplaceTagAction extends AbstractAction implements }); }); - add(tagNameItem); + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } else { JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); @@ -124,6 +133,14 @@ abstract class ReplaceTagAction extends AbstractAction implements add(empty); } + // + if (this.getItemCount() > 0) { + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + addSeparator(); JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag")); newTagMenuItem.addActionListener((ActionEvent event) -> { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f113a622fe..522c439d26 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -121,6 +121,15 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * Returns a list of names of standard/predefined tags + * + * @return list of predefined tag names + */ + public static List getStandardTagNames() { + return TagNameDefinition.getStandardTagNames(); + } + /** * Constructs a per case Autopsy service that manages the addition of * content and artifact tags to the case database. From 661efce4c75a3d5040fab27c01cd565c4eb718a0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 11 Jun 2018 10:21:24 -0400 Subject: [PATCH 039/101] Adding SparseBitSet jar --- Core/build.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/build.xml b/Core/build.xml index 98644b8cb3..1dac4fc7d2 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -78,6 +78,8 @@ tofile="${ext.dir}/mchange-commons-java-0.2.9.jar"/> + From f9796204614a52b1cc3940c3be7c665693746618 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 11 Jun 2018 11:52:36 -0400 Subject: [PATCH 040/101] Import the SparseBitSet TSK jar --- Core/nbproject/project.properties | 1 + Core/nbproject/project.xml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 7ae5484bc9..1891312c27 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -40,6 +40,7 @@ file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8 file.reference.xmpcore-5.1.3.jar=release/modules/ext/xmpcore-5.1.3.jar file.reference.xz-1.6.jar=release/modules/ext/xz-1.6.jar file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar +file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index ac3ec60d44..002f2d2466 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -506,6 +506,10 @@ ext/xmpcore-5.1.3.jar release/modules/ext/xmpcore-5.1.3.jar + + + ext/SparseBitSet-1.1.jar + release/modules/ext/SparseBitSet-1.1.jar From 169897916bdcabd0a15dc3aaaad169627b37781b Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 13 Jun 2018 09:53:10 -0400 Subject: [PATCH 041/101] 3865: Examiner can replace a tag with another tag - Reworded context menu items - Show all Tag related menu items together --- .../AddBlackboardArtifactTagAction.java | 4 +- .../autopsy/actions/AddContentTagAction.java | 4 +- .../DeleteBlackboardArtifactTagAction.java | 2 +- .../actions/DeleteContentTagAction.java | 2 +- .../actions/ReplaceContentTagAction.java | 3 - .../autopsy/actions/ReplaceTagAction.java | 39 +++++++-- .../datamodel/BlackboardArtifactTagNode.java | 9 +-- .../autopsy/datamodel/ContentTagNode.java | 6 +- .../datamodel/DataModelActionsFactory.java | 80 +++++++++++++++++++ 9 files changed, 126 insertions(+), 23 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 7ad185ac8e..9015d9a6ad 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -37,8 +37,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to blackboard artifacts. */ @NbBundle.Messages({ - "AddBlackboardArtifactTagAction.singularTagResult=Tag Result", - "AddBlackboardArtifactTagAction.pluralTagResult=Tag Results", + "AddBlackboardArtifactTagAction.singularTagResult=Add Result Tag", + "AddBlackboardArtifactTagAction.pluralTagResult=Add Result Tags", "# {0} - artifactName", "AddBlackboardArtifactTagAction.unableToTag.msg=Unable to tag {0}.", "AddBlackboardArtifactTagAction.taggingErr=Tagging Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index 602789735d..34e7b2a110 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -38,8 +38,8 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to apply tags to content. */ @NbBundle.Messages({ - "AddContentTagAction.singularTagFile=Tag File", - "AddContentTagAction.pluralTagFile=Tag Files", + "AddContentTagAction.singularTagFile=Add File Tag", + "AddContentTagAction.pluralTagFile=Add File Tags", "# {0} - fileName", "AddContentTagAction.unableToTag.msg=Unable to tag {0}, not a regular file.", "AddContentTagAction.cannotApplyTagErr=Cannot Apply Tag", diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java index 18ae9ac7e5..c3878e6b59 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteBlackboardArtifactTagAction.java @@ -38,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException; * artifacts. */ @NbBundle.Messages({ - "DeleteBlackboardArtifactTagAction.deleteTag=Delete Tag", + "DeleteBlackboardArtifactTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteBlackboardArtifactTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteBlackboardArtifactTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java index 07cce02bfc..b7ff3a73a0 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteContentTagAction.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Instances of this Action allow users to delete tags applied to content. */ @NbBundle.Messages({ - "DeleteContentTagAction.deleteTag=Delete Tag", + "DeleteContentTagAction.deleteTag=Remove Selected Tag(s)", "# {0} - tagName", "DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}.", "DeleteContentTagAction.tagDelErr=Tag Deletion Error" diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java index 287ae95415..484a30178c 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceContentTagAction.java @@ -37,9 +37,6 @@ import org.sleuthkit.datamodel.TskCoreException; /** * This Action allow users to replace a content tag with another tag */ -@NbBundle.Messages({ - "ReplaceContentArtifactTagAction.replaceTag=Replace File Tag" -}) public final class ReplaceContentTagAction extends ReplaceTagAction { private static final Logger logger = Logger.getLogger(ReplaceContentTagAction.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index 992066923e..0107803856 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -1,13 +1,28 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 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.actions; import java.awt.event.ActionEvent; import java.util.Collection; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -30,7 +45,7 @@ import org.sleuthkit.datamodel.TskData; * @param tag type */ @NbBundle.Messages({ - "ReplaceTagAction.replaceTag=Replace Tag" + "ReplaceTagAction.replaceTag=Replace Selected Tag(s) With" }) abstract class ReplaceTagAction extends AbstractAction implements Presenter.Popup { @@ -72,6 +87,7 @@ abstract class ReplaceTagAction extends AbstractAction implements */ abstract Collection getTagsToReplace(); + @Override public JMenuItem getPopupPresenter() { return new ReplaceTagMenu(); @@ -89,7 +105,7 @@ abstract class ReplaceTagAction extends AbstractAction implements super(getActionDisplayName()); final Collection selectedTags = getTagsToReplace(); - + // Get the current set of tag names. Map tagNamesMap = null; try { @@ -99,6 +115,14 @@ abstract class ReplaceTagAction extends AbstractAction implements Logger.getLogger(ReplaceTagMenu.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } + // Ideally we should'nt allow user to pick a replacement tag that's already been applied to an item + // In the very least we don't allow them to pick the same tag as the one they are trying to replace + Set existingTagNames = new HashSet<>(); + if (!selectedTags.isEmpty()) { + T firstTag = selectedTags.iterator().next(); + existingTagNames.add(firstTag.getName().getDisplayName()); + } + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); @@ -116,6 +140,11 @@ abstract class ReplaceTagAction extends AbstractAction implements }); }); + // Don't allow replacing a tag with same tag. + if (existingTagNames.contains(tagDisplayName)) { + tagNameItem.setEnabled(false); + } + add(tagNameItem); } } else { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index e9cbe09ea0..ea944b6b43 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2016 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,8 +28,6 @@ import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; -import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; @@ -133,9 +131,8 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file)); } actions.add(new ViewTaggedArtifactAction(BlackboardArtifactTagNode_viewSourceArtifact_text(), artifact)); - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), true)); - actions.add(DeleteBlackboardArtifactTagAction.getInstance()); - actions.add(ReplaceBlackboardArtifactTagAction.getInstance()); + actions.addAll(DataModelActionsFactory.getActions(tag, true)); + return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 7fcf7d0e1c..2d8e544375 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -129,9 +129,9 @@ class ContentTagNode extends DisplayableItemNode { if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } - actions.addAll(DataModelActionsFactory.getActions(tag.getContent(), false)); - actions.add(DeleteContentTagAction.getInstance()); - actions.add(ReplaceContentTagAction.getInstance()); + + actions.addAll(DataModelActionsFactory.getActions(tag, false)); + return actions.toArray(new Action[actions.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java index b4b88463c4..6b4b1c8faf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataModelActionsFactory.java @@ -28,8 +28,12 @@ import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.datamodel.Reports.ReportNode; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; @@ -39,7 +43,9 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.File; @@ -351,6 +357,80 @@ public class DataModelActionsFactory { return actionsList; } + public static List getActions(ContentTag contentTag, boolean isArtifactSource) { + + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), contentTag.getContent())); + final ContentTagNode tagNode = new ContentTagNode(contentTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteContentTagAction.getInstance()); + actionsList.add(ReplaceContentTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + + + public static List getActions(BlackboardArtifactTag artifactTag, boolean isArtifactSource) { + List actionsList = new ArrayList<>(); + actionsList.add(new ViewContextAction((isArtifactSource ? VIEW_SOURCE_FILE_IN_DIR : VIEW_FILE_IN_DIR), artifactTag.getContent())); + final BlackboardArtifactTagNode tagNode = new BlackboardArtifactTagNode(artifactTag); + actionsList.add(null); // creates a menu separator + actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, tagNode)); + actionsList.add(new ExternalViewerAction(OPEN_IN_EXTERNAL_VIEWER, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(ExtractAction.getInstance()); + actionsList.add(new HashSearchAction(SEARCH_FOR_FILES_SAME_MD5, tagNode)); + actionsList.add(null); // creates a menu separator + actionsList.add(AddContentTagAction.getInstance()); + if (isArtifactSource) { + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + } + + final Collection selectedFilesList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + if(selectedFilesList.size() == 1) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + if(isArtifactSource) { + final Collection selectedArtifactsList = + new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + if(selectedArtifactsList.size() == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); + } + } + + actionsList.add(DeleteBlackboardArtifactTagAction.getInstance()); + actionsList.add(ReplaceBlackboardArtifactTagAction.getInstance()); + + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + return actionsList; + } + public static List getActions(Content content, boolean isArtifactSource) { if (content instanceof File) { return getActions((File) content, isArtifactSource); From c02f503388e4728050eca83f8ab08a8f4b0d4108 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 13 Jun 2018 10:27:47 -0400 Subject: [PATCH 042/101] Remove unused imports. --- Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 2d8e544375..547303878b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -29,8 +29,6 @@ import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.actions.DeleteContentTagAction; -import org.sleuthkit.autopsy.actions.ReplaceContentTagAction; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; From 06b72eb558056e16abe9a96e08f93aca1b3081a0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 13 Jun 2018 13:35:22 -0400 Subject: [PATCH 043/101] Use new class for other occurrence data --- .../DataContentViewerOtherCases.java | 150 ++++++++---------- ...DataContentViewerOtherCasesTableModel.java | 50 +++--- .../OtherOccurrenceNodeData.java | 104 ++++++++++++ 3 files changed, 194 insertions(+), 110 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 2a9fd30a49..ea886c1e93 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -466,31 +466,43 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * * @return A collection of correlated artifact instances from other cases */ - private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { + private Map getCorrelatedInstances(CorrelationAttribute corAttr, String dataSourceName, String deviceId) { // @@@ Check exception try { final Case openCase = Case.getCurrentCase(); String caseUUID = openCase.getName(); - HashMap artifactInstances = new HashMap<>(); + HashMap nodeDataMap = new HashMap<>(); if (EamDb.isEnabled()) { - EamDb dbManager = EamDb.getInstance(); - artifactInstances.putAll(dbManager.getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).stream() - .filter(artifactInstance -> !artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) - || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) - || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) - .collect(Collectors.toMap(correlationAttr -> new UniquePathKey(correlationAttr.getCorrelationDataSource().getDeviceID(), correlationAttr.getFilePath()), - correlationAttr -> correlationAttr))); - } + List instances = EamDb.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()); - if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = addCaseDbMatches(corAttr, openCase); - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateAttributeInstance(openCase, artifactInstances, caseDbFile); + for (CorrelationAttributeInstance artifactInstance:instances) { + + // Only add the attribute if it isn't the object the user selected. + // We consider it to be a different object if at least one of the following is true: + // - the case UUID is different + // - the data source name is different + // - the data source device ID is different + // - (TODO) file path is different + if (!artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) + || !artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName) + || !artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) { + + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(artifactInstance, corAttr.getCorrelationType().getDisplayName(), corAttr.getCorrelationValue()); + UniquePathKey uniquePathKey = new UniquePathKey(newNode); + nodeDataMap.put(uniquePathKey, newNode); + } } } - return artifactInstances; + if (corAttr.getCorrelationType().getDisplayName().equals("Files")) { + List caseDbFiles = getCaseDbMatches(corAttr, openCase); + for (AbstractFile caseDbFile : caseDbFiles) { + addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); + } + } + + return nodeDataMap; } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { @@ -504,7 +516,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return new HashMap<>(0); } - private List addCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { + private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); SleuthkitCase tsk = openCase.getSleuthkitCase(); @@ -522,58 +534,44 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Adds the file to the artifactInstances map if it does not already exist + * Adds the file to the nodeDataMap map if it does not already exist * * @param autopsyCase - * @param artifactInstances + * @param nodeDataMap * @param newFile * @throws TskCoreException * @throws EamDbException */ - private void addOrUpdateAttributeInstance(final Case autopsyCase, Map artifactInstances, AbstractFile newFile) throws TskCoreException, EamDbException { + private void addOrUpdateNodeData(final Case autopsyCase, Map nodeDataMap, AbstractFile newFile) throws TskCoreException, EamDbException { - // figure out if the casedb file is known via either hash or tags - TskData.FileKnown localKnown = newFile.getKnown(); - - if (localKnown != TskData.FileKnown.BAD) { + OtherOccurrenceNodeData newNode = new OtherOccurrenceNodeData(newFile, autopsyCase); + + // If the caseDB object has a notable tag associated with it, update + // the known status to BAD + if (newNode.getKnown() != TskData.FileKnown.BAD) { List fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile); for (ContentTag tag : fileMatchTags) { TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus(); if (tagKnownStatus.equals(TskData.FileKnown.BAD)) { - localKnown = TskData.FileKnown.BAD; + newNode.updateKnown(TskData.FileKnown.BAD); break; } } } - // make a key to see if the file is already in the map - String filePath = newFile.getParentPath() + newFile.getName(); - String deviceId; - try { - deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); - } catch (TskDataException | TskCoreException ex) { - LOGGER.log(Level.WARNING, "Error getting data source info: " + ex); - return; - } - UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); + // Make a key to see if the file is already in the map + UniquePathKey uniquePathKey = new UniquePathKey(newNode); - // double check that the CR version is BAD if the caseDB version is BAD. - if (artifactInstances.containsKey(uniquePathKey)) { - if (localKnown == TskData.FileKnown.BAD) { - CorrelationAttributeInstance prevInstance = artifactInstances.get(uniquePathKey); - prevInstance.setKnownStatus(localKnown); + // If this node is already in the list, the only thing we need to do is + // update the known status to BAD if the caseDB version had known status BAD. + // Otherwise, add the new node to the map. + if (nodeDataMap.containsKey(uniquePathKey)) { + if (newNode.getKnown() == TskData.FileKnown.BAD) { + OtherOccurrenceNodeData prevInstance = nodeDataMap.get(uniquePathKey); + prevInstance.updateKnown(newNode.getKnown()); } - } - // add the data from the case DB by pushing data into CorrelationAttributeInstance class - else { - // NOTE: If we are in here, it is likely because CR is not enabled. So, we cannot rely - // on any of the methods that query the DB. - CorrelationCase correlationCase = new CorrelationCase(autopsyCase.getName(), autopsyCase.getDisplayName()); - - CorrelationDataSource correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, newFile.getDataSource()); - - CorrelationAttributeInstance caseDbInstance = new CorrelationAttributeInstance(correlationCase, correlationDataSource, filePath, "", localKnown); - artifactInstances.put(uniquePathKey, caseDbInstance); + } else { + nodeDataMap.put(uniquePathKey, newNode); } } @@ -632,22 +630,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // get the attributes we can correlate on correlationAttributes.addAll(getCorrelationAttributesFromNode(node)); for (CorrelationAttribute corAttr : correlationAttributes) { - Map corAttrInstances = new HashMap<>(0); + Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB - corAttrInstances.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); + correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId)); - corAttrInstances.values().forEach((corAttrInstance) -> { - try { - CorrelationAttribute newCeArtifact = new CorrelationAttribute( - corAttr.getCorrelationType(), - corAttr.getCorrelationValue() - ); - newCeArtifact.addInstance(corAttrInstance); - tableModel.addEamArtifact(newCeArtifact); - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex); - } + correlatedNodeDataMap.values().forEach((nodeData) -> { + tableModel.addNodeData(nodeData); }); } @@ -816,33 +805,26 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private final String dataSourceID; private final String filePath; + private final String type; - UniquePathKey(String theDataSource, String theFilePath) { + UniquePathKey(OtherOccurrenceNodeData nodeData) { super(); - dataSourceID = theDataSource; - filePath = theFilePath.toLowerCase(); - } - - /** - * - * @return the dataSourceID device ID - */ - String getDataSourceID() { - return dataSourceID; - } - - /** - * - * @return the filPath including the filename and extension. - */ - String getFilePath() { - return filePath; + dataSourceID = nodeData.getDeviceID(); + if (nodeData.getFilePath() != null) { + filePath = nodeData.getFilePath().toLowerCase(); + } else { + filePath = null; + } + type = nodeData.getType(); } @Override public boolean equals(Object other) { if (other instanceof UniquePathKey) { - return ((UniquePathKey) other).getDataSourceID().equals(dataSourceID) && ((UniquePathKey) other).getFilePath().equals(filePath); + UniquePathKey otherKey = (UniquePathKey)(other); + return ( Objects.equals(otherKey.dataSourceID, this.dataSourceID) + && Objects.equals(otherKey.filePath, this.filePath) + && Objects.equals(otherKey.type, this.type)); } return false; } @@ -852,7 +834,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi //int hash = 7; //hash = 67 * hash + this.dataSourceID.hashCode(); //hash = 67 * hash + this.filePath.hashCode(); - return Objects.hash(dataSourceID, filePath); + return Objects.hash(dataSourceID, filePath, type); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java index f0ecb31e72..5febf88dc3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCasesTableModel.java @@ -68,10 +68,10 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } }; - List eamArtifacts; + List nodeDataList; DataContentViewerOtherCasesTableModel() { - eamArtifacts = new ArrayList<>(); + nodeDataList = new ArrayList<>(); } @Override @@ -95,7 +95,7 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public int getRowCount() { - return eamArtifacts.size(); + return nodeDataList.size(); } @Override @@ -105,15 +105,15 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == eamArtifacts.size()) { + if (0 == nodeDataList.size()) { return Bundle.DataContentViewerOtherCasesTableModel_noData(); } return mapValueById(rowIdx, TableColumns.values()[colIdx]); } - public Object getRow(int rowIdx) { - return eamArtifacts.get(rowIdx); + Object getRow(int rowIdx) { + return nodeDataList.get(rowIdx); } /** @@ -125,40 +125,39 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { * @return value in the cell */ private Object mapValueById(int rowIdx, TableColumns colId) { - CorrelationAttribute eamArtifact = eamArtifacts.get(rowIdx); - CorrelationAttributeInstance eamArtifactInstance = eamArtifact.getInstances().get(0); + OtherOccurrenceNodeData nodeData = nodeDataList.get(rowIdx); String value = Bundle.DataContentViewerOtherCasesTableModel_noData(); switch (colId) { case CASE_NAME: - if (null != eamArtifactInstance.getCorrelationCase()) { - value = eamArtifactInstance.getCorrelationCase().getDisplayName(); + if (null != nodeData.getCaseName()) { + value = nodeData.getCaseName(); } break; case DEVICE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getDeviceID(); + if (null != nodeData.getDeviceID()) { + value = nodeData.getDeviceID(); } break; case DATA_SOURCE: - if (null != eamArtifactInstance.getCorrelationDataSource()) { - value = eamArtifactInstance.getCorrelationDataSource().getName(); + if (null != nodeData.getDataSourceName()) { + value = nodeData.getDataSourceName(); } break; case FILE_PATH: - value = eamArtifactInstance.getFilePath(); + value = nodeData.getFilePath(); break; case TYPE: - value = eamArtifact.getCorrelationType().getDisplayName(); + value = nodeData.getType(); break; case VALUE: - value = eamArtifact.getCorrelationValue(); + value = nodeData.getValue(); break; case KNOWN: - value = eamArtifactInstance.getKnownStatus().getName(); + value = nodeData.getKnown().getName(); break; case COMMENT: - value = eamArtifactInstance.getComment(); + value = nodeData.getComment(); break; } return value; @@ -170,18 +169,17 @@ public class DataContentViewerOtherCasesTableModel extends AbstractTableModel { } /** - * Add one local central repository artifact to the table. + * Add one correlated instance object to the table * - * @param eamArtifact central repository artifact to add to the - * table + * @param newNodeData data to add to the table */ - public void addEamArtifact(CorrelationAttribute eamArtifact) { - eamArtifacts.add(eamArtifact); + void addNodeData(OtherOccurrenceNodeData newNodeData) { + nodeDataList.add(newNodeData); fireTableDataChanged(); } - public void clearTable() { - eamArtifacts.clear(); + void clearTable() { + nodeDataList.clear(); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java new file mode 100644 index 0000000000..c988ff30d9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -0,0 +1,104 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository.contentviewer; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; +import org.sleuthkit.datamodel.TskDataException; + +class OtherOccurrenceNodeData { + + private String caseName; + private String deviceID; + private String dataSourceName; + private final String filePath; + private final String type; + private final String value; + private TskData.FileKnown known; + private final String comment; + + OtherOccurrenceNodeData(CorrelationAttributeInstance instance, String type, String value) { + caseName = instance.getCorrelationCase().getDisplayName(); + deviceID = instance.getCorrelationDataSource().getDeviceID(); + dataSourceName = instance.getCorrelationDataSource().getName(); + filePath = instance.getFilePath(); + this.type = type; + this.value = value; + known = instance.getKnownStatus(); + comment = instance.getComment(); + } + + OtherOccurrenceNodeData(AbstractFile newFile, Case autopsyCase) throws EamDbException { + caseName = autopsyCase.getDisplayName(); + try { + DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()); + deviceID = dataSource.getDeviceId(); + dataSourceName = dataSource.getName(); + } catch (TskDataException | TskCoreException ex) { + throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId()); + } + + filePath = newFile.getParentPath() + newFile.getName(); + type = "Files"; + value = newFile.getMd5Hash(); + known = newFile.getKnown(); + comment = ""; + } + + void updateKnown(TskData.FileKnown newKnownStatus) { + known = newKnownStatus; + } + + String getCaseName() { + return caseName; + } + + String getDeviceID() { + return deviceID; + } + + String getDataSourceName() { + return dataSourceName; + } + + String getFilePath() { + return filePath; + } + + String getType() { + return type; + } + + String getValue() { + return value; + } + + TskData.FileKnown getKnown() { + return known; + } + + String getComment() { + return comment; + } +} From b60d07e736c2cb05600994ab7c850e14f165281b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 14 Jun 2018 13:35:42 -0400 Subject: [PATCH 044/101] 3881: Normalized the content for index and query. --- .../src/org/sleuthkit/autopsy/keywordsearch/Chunker.java | 7 +++++-- .../src/org/sleuthkit/autopsy/keywordsearch/Keyword.java | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index 06df441bf3..04d0fb86f3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import java.io.PushbackReader; import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.text.Normalizer; import java.util.Iterator; import java.util.NoSuchElementException; import javax.annotation.concurrent.NotThreadSafe; @@ -148,7 +149,9 @@ class Chunker implements Iterator, Iterable { } private static StringBuilder sanitize(String s) { - return sanitizeToUTF8(replaceInvalidUTF16(s)); + String normStr = Normalizer.normalize(s, Normalizer.Form.NFKD); + return sanitizeToUTF8(replaceInvalidUTF16(normStr)); + } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index 2033dfbdb7..25367b8da3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2016 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.keywordsearch; +import java.text.Normalizer; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -57,7 +58,7 @@ class Keyword { * than a substring. */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord) { - this.searchTerm = searchTerm; + this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKD);; this.isLiteral = isLiteral; this.isWholeWord = isWholeWord; this.listName = ""; @@ -93,11 +94,12 @@ class Keyword { * used (e.g. for highlighting purposes). */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord, String listName, String originalTerm) { - this.searchTerm = searchTerm; + this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKD); this.isLiteral = isLiteral; this.isWholeWord = isWholeWord; this.listName = listName; this.originalTerm = originalTerm; + } /** From 7dc14784dfe00b5df0b362977be698909f812f5a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 14 Jun 2018 14:09:25 -0400 Subject: [PATCH 045/101] 3881: Keyword constructor internal call. --- .../src/org/sleuthkit/autopsy/keywordsearch/Keyword.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index 25367b8da3..35d618d390 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -58,11 +58,7 @@ class Keyword { * than a substring. */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord) { - this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKD);; - this.isLiteral = isLiteral; - this.isWholeWord = isWholeWord; - this.listName = ""; - this.originalTerm = searchTerm; + this(searchTerm, isLiteral, isWholeWord, "", searchTerm); } /** From e101c84c5b42ae6baa756219c0d286fc8ee10f24 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 14 Jun 2018 14:24:59 -0400 Subject: [PATCH 046/101] 3881: Use NFKC instead of NFKD --- .../src/org/sleuthkit/autopsy/keywordsearch/Chunker.java | 2 +- .../src/org/sleuthkit/autopsy/keywordsearch/Keyword.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java index 04d0fb86f3..82494f2f0d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Chunker.java @@ -149,7 +149,7 @@ class Chunker implements Iterator, Iterable { } private static StringBuilder sanitize(String s) { - String normStr = Normalizer.normalize(s, Normalizer.Form.NFKD); + String normStr = Normalizer.normalize(s, Normalizer.Form.NFKC); return sanitizeToUTF8(replaceInvalidUTF16(normStr)); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java index 35d618d390..b56415094e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Keyword.java @@ -90,7 +90,7 @@ class Keyword { * used (e.g. for highlighting purposes). */ Keyword(String searchTerm, boolean isLiteral, boolean isWholeWord, String listName, String originalTerm) { - this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKD); + this.searchTerm = Normalizer.normalize(searchTerm, Normalizer.Form.NFKC); this.isLiteral = isLiteral; this.isWholeWord = isWholeWord; this.listName = listName; From ebf2cb09d7fd9f60529f6e930e16782b7e87f604 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 14 Jun 2018 14:43:09 -0400 Subject: [PATCH 047/101] Undoing the accidental commit of branding files. --- .../core.jar/org/netbeans/core/startup/Bundle.properties | 4 ++-- .../org/netbeans/core/windows/view/ui/Bundle.properties | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 9a9a904cc5..b1adb5d40b 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Tue, 08 May 2018 10:29:55 -0600 +#Mon, 19 Mar 2018 11:17:11 -0700 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18 SplashRunningTextColor=0x0 SplashRunningTextFontSize=19 -currentVersion=Autopsy 4.7.0 +currentVersion=Autopsy 4.6.0 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index db3cd01af5..6cb9d4bdea 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Tue, 08 May 2018 10:29:55 -0600 -CTL_MainWindow_Title=Autopsy 4.7.0 -CTL_MainWindow_Title_No_Project=Autopsy 4.7.0 +#Fri, 09 Mar 2018 13:03:41 -0700 +CTL_MainWindow_Title=Autopsy 4.6.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.6.0 From b488dee2cdceb96a6030ccb3161b724a61f55acb Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Wed, 30 May 2018 12:26:12 -0700 Subject: [PATCH 048/101] Add progress handler to search to inform user Autopsy is busy, with 3 stages. # Conflicts: # Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java --- .../commonfilesearch/CommonFilesPanel.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index 3ce2c34e26..5fe62496a2 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -22,12 +22,11 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.ComboBoxModel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -164,6 +163,9 @@ public final class CommonFilesPanel extends javax.swing.JPanel { "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)", "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)", "CommonFilesPanel.search.results.pathText=Common Files Search Results", + "CommonFilesPanel.search.done.searchProgress1=Gathering Common Files Search Results.", + "CommonFilesPanel.search.done.searchProgress2=Generating Common Files Search Results.", + "CommonFilesPanel.search.done.searchProgress3=Displaying Common Files Search Results.", "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.", "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.", "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.", @@ -175,7 +177,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { new SwingWorker() { private String tabTitle; - + private ProgressHandle progress; + private void setTitleForAllDataSources() { this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll(); } @@ -202,7 +205,11 @@ public final class CommonFilesPanel extends javax.swing.JPanel { @Override @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) - protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException { + protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException, Exception { + progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgress1()); + progress.start(); + progress.switchToIndeterminate(); + Long dataSourceId = determineDataSourceId(); CommonFilesMetadataBuilder builder; @@ -237,8 +244,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { protected void done() { try { super.done(); - - CommonFilesMetadata metadata = get(); + progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgress2()); + CommonFilesMetadata metadata = get(); CommonFilesNode commonFilesNode = new CommonFilesNode(metadata); @@ -250,9 +257,9 @@ public final class CommonFilesPanel extends javax.swing.JPanel { Collection viewers = new ArrayList<>(1); viewers.add(table); - + progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgress3()); DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); - + progress.finish(); } catch (InterruptedException ex) { LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex); MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted()); From b6d8bf7a1d68e5d170800041e63cd485c2bde831 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 7 Jun 2018 08:56:33 -0700 Subject: [PATCH 049/101] Wrapper Child factory in callable to further delay execution. Still doesn't make Search lazy since DataResultFilterNode will gather all children and do lookups which triggers the children queries. --- .../sleuthkit/autopsy/datamodel/Md5Node.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java index 96de7eba36..38a3f78b64 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java @@ -22,6 +22,7 @@ package org.sleuthkit.autopsy.datamodel; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -53,16 +54,33 @@ public class Md5Node extends DisplayableItemNode { private final String dataSources; public Md5Node(Md5Metadata data) { - super(Children.create( - new FileInstanceNodeFactory(data), true), - Lookups.singleton(data.getMd5())); - + super(Children.createLazy(new Md5ChildCallable(data)), Lookups.singleton(data.getMd5())); this.commonFileCount = data.size(); this.dataSources = String.join(", ", data.getDataSources()); this.md5Hash = data.getMd5(); this.setDisplayName(this.md5Hash); } + + private static class Md5ChildCallable implements Callable { + private final Md5Metadata key; + private Md5ChildCallable(Md5Metadata key) { + this.key = key; + } + @Override + public Children call() throws Exception { + //Check, somehow, that your key has children, + //e.g., create "hasChildren" on the object + //to look in the database to see whether + //the object has children; + //if it doesn't have children, return a leaf: + if (key.getMetadata().isEmpty()) { + return Children.LEAF; + } else { + return Children.create(new FileInstanceNodeFactory(key), true); + } + } + } int getCommonFileCount() { return this.commonFileCount; From d376fbaeb051345846bbdd5ea074e59a0d17057a Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 14 Jun 2018 12:35:01 -0700 Subject: [PATCH 050/101] Cleanup. --- .../sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java | 3 +++ Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index 5fe62496a2..23c0f60afc 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -22,14 +22,17 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.ComboBoxModel; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.netbeans.api.progress.ProgressHandle; import org.openide.explorer.ExplorerManager; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java index 38a3f78b64..d32c77ca13 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java @@ -69,10 +69,7 @@ public class Md5Node extends DisplayableItemNode { } @Override public Children call() throws Exception { - //Check, somehow, that your key has children, - //e.g., create "hasChildren" on the object - //to look in the database to see whether - //the object has children; + //Check that the key has children, //if it doesn't have children, return a leaf: if (key.getMetadata().isEmpty()) { return Children.LEAF; From 0cd055b94770fb82516ce703e56b16c48ee0ed87 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 4 Jun 2018 17:27:02 -0700 Subject: [PATCH 051/101] add date of earliest CR case to top of other occurances viwer # Conflicts: # Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties # Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java --- .../contentviewer/Bundle.properties | 3 + .../DataContentViewerOtherCases.form | 98 ++++++++++---- .../DataContentViewerOtherCases.java | 124 ++++++++++++++---- 3 files changed, 175 insertions(+), 50 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index 39759e3eb7..58101b7abf 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -3,4 +3,7 @@ DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency +DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date +DataContentViewerOtherCases.earliestCaseLabel.toolTipText= +DataContentViewerOtherCases.earliestCaseLabel.text=Earliest Case: DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Comment diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 0097313943..14530e7e23 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - + @@ -103,10 +103,10 @@ - + - + @@ -124,17 +124,34 @@ - - + + + + + + + + + + + - - - - - + + + + + + + + + + + + + @@ -184,38 +201,69 @@ - - - - - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index bf7fef5fa8..6d3065f06d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -25,6 +25,9 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -45,7 +48,11 @@ import javax.swing.JPanel; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; @@ -80,7 +87,9 @@ import org.sleuthkit.datamodel.TskDataException; "DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",}) public class DataContentViewerOtherCases extends JPanel implements DataContentViewer { - private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + private static final long serialVersionUID = -1L; + + private final static Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private final DataContentViewerOtherCasesTableModel tableModel; private final Collection correlationAttributes; @@ -137,6 +146,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer(); otherCasesTable.setDefaultRenderer(Object.class, renderer); tableStatusPanelLabel.setVisible(false); + } @Messages({"DataContentViewerOtherCases.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.", @@ -157,7 +167,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } else { - StringBuilder msg = new StringBuilder(); + StringBuilder msg = new StringBuilder(correlationAttributes.size()); int percentage; try { EamDb dbManager = EamDb.getInstance(); @@ -307,6 +317,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // start with empty table tableModel.clearTable(); correlationAttributes.clear(); + earliestCaseDate.setText("None"); } @Override @@ -453,6 +464,34 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return ret; } + private void setEarliestCaseDate() { + LocalDate earliestDate = LocalDate.now(); + + DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + + if (EamDb.isEnabled()) { + try { + EamDb dbManager = EamDb.getInstance(); + List cases = dbManager.getCases(); + for (CorrelationCase aCase : cases) { + LocalDate caseDate = LocalDate.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); + + if (caseDate.isBefore(earliestDate)) { + earliestDate = caseDate; + } + + } + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS + } catch (ParseException ex) { + Exceptions.printStackTrace(ex); + } + // NON-NLS + + } + earliestCaseDate.setText(datetimeFormat.format(earliestDate.toDate())); + } + /** * Query the database for artifact instances from other cases correlated to * the given central repository artifact. Instances from the same datasource @@ -506,7 +545,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private List addCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); - SleuthkitCase tsk = openCase.getSleuthkitCase(); List matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5})); @@ -659,6 +697,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else { clearMessageOnTableStatusPanel(); setColumnWidths(); + setEarliestCaseDate(); } } @@ -702,6 +741,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableScrollPane = new javax.swing.JScrollPane(); otherCasesTable = new javax.swing.JTable(); tableStatusPanel = new javax.swing.JPanel(); + earliestDatePanel = new javax.swing.JPanel(); + earliestCaseLabel = new javax.swing.JLabel(); + earliestCaseDate = new javax.swing.JLabel(); tableStatusPanelLabel = new javax.swing.JLabel(); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { @@ -748,42 +790,71 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); - tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); - javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel); tableStatusPanel.setLayout(tableStatusPanelLayout); tableStatusPanelLayout.setHorizontalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) - .addContainerGap())) + .addGap(0, 1388, Short.MAX_VALUE) ); tableStatusPanelLayout.setVerticalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 16, Short.MAX_VALUE) - .addGroup(tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableStatusPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE))) ); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N + earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N + + javax.swing.GroupLayout earliestDatePanelLayout = new javax.swing.GroupLayout(earliestDatePanel); + earliestDatePanel.setLayout(earliestDatePanelLayout); + earliestDatePanelLayout.setHorizontalGroup( + earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(earliestDatePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(earliestCaseLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(earliestCaseDate) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + earliestDatePanelLayout.setVerticalGroup( + earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, earliestDatePanelLayout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addGroup(earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate))) + ); + + tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); + javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); tableContainerPanelLayout.setHorizontalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(tableStatusPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 715, Short.MAX_VALUE)) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))) ); javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); @@ -796,10 +867,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); otherCasesPanelLayout.setVerticalGroup( otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 60, Short.MAX_VALUE) + .addGap(0, 144, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 52, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 144, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -811,7 +882,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 52, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -832,6 +903,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JFileChooser CSVFileChooser; + private javax.swing.JLabel earliestCaseDate; + private javax.swing.JLabel earliestCaseLabel; + private javax.swing.JPanel earliestDatePanel; private javax.swing.JMenuItem addCommentMenuItem; private javax.swing.JMenuItem exportToCSVMenuItem; private javax.swing.JPanel otherCasesPanel; From 084034593136fe98af4a192eea2abd548af8483d Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 4 Jun 2018 17:37:28 -0700 Subject: [PATCH 052/101] fix exception --- .../contentviewer/DataContentViewerOtherCases.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 6d3065f06d..819d550d0e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -484,7 +484,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS } catch (ParseException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS } // NON-NLS From 4a6f02c63c3c42b5addc602c324a3efd9a72806d Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 12 Jun 2018 14:49:36 -0700 Subject: [PATCH 053/101] Create and add ShowCaseDialog to Options CR panel, which displays a table of CorrelationCases. Table needs sorting. Adjust size of OtherOccurances table after adding earliest date. --- .../DataContentViewerOtherCases.form | 12 +- .../DataContentViewerOtherCases.java | 15 +- .../optionspanel/Bundle.properties | 9 + .../optionspanel/GlobalSettingsPanel.form | 130 +++++++++++-- .../optionspanel/GlobalSettingsPanel.java | 94 +++++++-- .../optionspanel/ShowCasesDialog.form | 134 +++++++++++++ .../optionspanel/ShowCasesDialog.java | 163 ++++++++++++++++ .../optionspanel/ShowCasesTableModel.java | 180 ++++++++++++++++++ 8 files changed, 691 insertions(+), 46 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form create mode 100644 Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java create mode 100644 Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 14530e7e23..32a90e61ea 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - + @@ -103,10 +103,10 @@ - + - + @@ -142,9 +142,9 @@ - - - + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 819d550d0e..567007cbd3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -49,10 +49,7 @@ import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import org.joda.time.LocalDate; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; @@ -847,9 +844,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() .addGap(0, 0, 0) .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0) - .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableContainerPanelLayout.createSequentialGroup() .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -867,10 +864,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); otherCasesPanelLayout.setVerticalGroup( otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 144, Short.MAX_VALUE) + .addGap(0, 170, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 144, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -882,7 +879,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties index 0106566831..08a9519bc9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties @@ -65,3 +65,12 @@ GlobalSettingsPanel.manageOrganizationButton.text=Manage Organizations GlobalSettingsPanel.lbCentralRepository.text=A central repository allows you to correlate files and results between cases. GlobalSettingsPanel.pnCorrelationProperties.border.title=Correlation Properties GlobalSettingsPanel.organizationPanel.border.title=Organizations +GlobalSettingsPanel.casesPanel.border.title=Case Details +GlobalSettingsPanel.showCasesButton.text=Show Cases +GlobalSettingsPanel.casesTextArea1.text=Display table that lists central repository case details. +ShowCasesDialog.closeButton.AccessibleContext.accessibleName=Close +ShowCasesDialog.closeButton.actionCommand=Close +ShowCasesDialog.closeButton.text=Close +ShowCasesDialog.caseDetailsTable.toolTipText=Click column name to sort. Right-click on the table for more options. +ShowCasesDialog.title=Case Details +GlobalSettingsPanel.Case\ Details.AccessibleContext.accessibleName=Cases Details diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form index 788fef4660..0d647a920f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form @@ -27,7 +27,7 @@ - + @@ -57,24 +57,26 @@ - + - - - - - - - - - - + - - + + + + + + + + + + + + + @@ -90,8 +92,10 @@ + + - + @@ -328,7 +332,7 @@ - + @@ -398,6 +402,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index a1daad3a71..761653eeb5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -124,6 +124,10 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i manageOrganizationButton = new javax.swing.JButton(); organizationScrollPane = new javax.swing.JScrollPane(); organizationTextArea = new javax.swing.JTextArea(); + casesPanel = new javax.swing.JPanel(); + showCasesButton = new javax.swing.JButton(); + casesScrollPane = new javax.swing.JScrollPane(); + casesTextArea1 = new javax.swing.JTextArea(); tbOops = new javax.swing.JTextField(); setName(""); // NOI18N @@ -276,7 +280,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addGroup(organizationPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(organizationPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(organizationScrollPane) + .addComponent(organizationScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 992, Short.MAX_VALUE) .addGroup(organizationPanelLayout.createSequentialGroup() .addComponent(manageOrganizationButton) .addGap(0, 0, Short.MAX_VALUE))) @@ -292,6 +296,52 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addGap(8, 8, 8)) ); + casesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesPanel.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Tahoma", 0, 12))); // NOI18N + casesPanel.setName("Case Details"); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(showCasesButton, org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.showCasesButton.text")); // NOI18N + showCasesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + showCasesButtonActionPerformed(evt); + } + }); + + casesScrollPane.setBorder(null); + + casesTextArea1.setEditable(false); + casesTextArea1.setBackground(new java.awt.Color(240, 240, 240)); + casesTextArea1.setColumns(20); + casesTextArea1.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + casesTextArea1.setLineWrap(true); + casesTextArea1.setRows(2); + casesTextArea1.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesTextArea1.text")); // NOI18N + casesTextArea1.setWrapStyleWord(true); + casesTextArea1.setBorder(null); + casesScrollPane.setViewportView(casesTextArea1); + + javax.swing.GroupLayout casesPanelLayout = new javax.swing.GroupLayout(casesPanel); + casesPanel.setLayout(casesPanelLayout); + casesPanelLayout.setHorizontalGroup( + casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(casesPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(casesScrollPane) + .addGroup(casesPanelLayout.createSequentialGroup() + .addComponent(showCasesButton) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + casesPanelLayout.setVerticalGroup( + casesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, casesPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(casesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(showCasesButton) + .addGap(8, 8, 8)) + ); + tbOops.setEditable(false); tbOops.setFont(tbOops.getFont().deriveFont(tbOops.getFont().getStyle() | java.awt.Font.BOLD, 12)); tbOops.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.tbOops.text")); // NOI18N @@ -301,20 +351,21 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbCentralRepository, javax.swing.GroupLayout.DEFAULT_SIZE, 1022, Short.MAX_VALUE) .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(cbUseCentralRepo) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addContainerGap() - .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 974, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(36, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pnCorrelationProperties, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 1012, Short.MAX_VALUE) - .addComponent(organizationPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pnDatabaseConfiguration, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnCorrelationProperties, javax.swing.GroupLayout.DEFAULT_SIZE, 1016, Short.MAX_VALUE) + .addComponent(organizationPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(casesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(cbUseCentralRepo) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 974, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -329,10 +380,14 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(organizationPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(casesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 92, Short.MAX_VALUE)) + .addContainerGap()) ); + casesPanel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.Case Details.AccessibleContext.accessibleName")); // NOI18N + jScrollPane1.setViewportView(jPanel1); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -343,7 +398,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 488, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 517, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -377,6 +432,11 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ManageOrganizationsDialog dialog = new ManageOrganizationsDialog(); }//GEN-LAST:event_manageOrganizationButtonActionPerformed + private void showCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showCasesButtonActionPerformed + store(); + ShowCasesDialog dialog = new ShowCasesDialog(); + }//GEN-LAST:event_showCasesButtonActionPerformed + @Override @Messages({"GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module."}) public void load() { @@ -559,6 +619,9 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton bnDbConfigure; private javax.swing.JButton bnManageTypes; + private javax.swing.JPanel casesPanel; + private javax.swing.JScrollPane casesScrollPane; + private javax.swing.JTextArea casesTextArea1; private javax.swing.JCheckBox cbUseCentralRepo; private javax.swing.JScrollPane correlationPropertiesScrollPane; private javax.swing.JTextArea correlationPropertiesTextArea; @@ -577,6 +640,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private javax.swing.JTextArea organizationTextArea; private javax.swing.JPanel pnCorrelationProperties; private javax.swing.JPanel pnDatabaseConfiguration; + private javax.swing.JButton showCasesButton; private javax.swing.JTextField tbOops; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form new file mode 100644 index 0000000000..e3b98501ca --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form @@ -0,0 +1,134 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java new file mode 100644 index 0000000000..440fdb18a2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -0,0 +1,163 @@ +/* + * Central Repository + * + * Copyright 2015-2018 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.centralrepository.optionspanel; + +import java.util.List; +import javax.swing.JDialog; +import javax.swing.JFrame; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; + +public final class ShowCasesDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private EamDb dbManager; + private final static Logger logger = Logger.getLogger(ShowCasesDialog.class.getName()); + private ShowCasesTableModel tableModel; + @Messages({"ShowCasesDialog.title_text=All Cases Details"}) + /** + * Creates new form ManageOrganizationsPanel + */ + public ShowCasesDialog() { + super((JFrame) WindowManager.getDefault().getMainWindow(), + Bundle.ShowCasesDialog_title_text(), + true); // NON-NLS + tableModel = new ShowCasesTableModel(); + initComponents(); + try { + this.dbManager = EamDb.getInstance(); + List eamCases = dbManager.getCases(); + for(CorrelationCase eamCase : eamCases) { + tableModel.addEamCase(eamCase); + } + } catch (EamDbException ex) { + Exceptions.printStackTrace(ex); + } + display(); + } + + private void display() { + this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + setVisible(true); + } + + + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + showCasesScrollPane = new javax.swing.JScrollPane(); + showCasesPanel = new javax.swing.JPanel(); + closeButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + caseDetailsTable = new javax.swing.JTable(); + + setTitle(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.title")); // NOI18N + setMinimumSize(new java.awt.Dimension(545, 415)); + + showCasesScrollPane.setMinimumSize(null); + showCasesScrollPane.setPreferredSize(new java.awt.Dimension(535, 415)); + + showCasesPanel.setPreferredSize(new java.awt.Dimension(527, 407)); + + org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.text")); // NOI18N + closeButton.setActionCommand(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.actionCommand")); // NOI18N + closeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + closeButtonActionPerformed(evt); + } + }); + + caseDetailsTable.setModel(tableModel); + caseDetailsTable.setToolTipText(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.toolTipText")); // NOI18N + jScrollPane1.setViewportView(caseDetailsTable); + + javax.swing.GroupLayout showCasesPanelLayout = new javax.swing.GroupLayout(showCasesPanel); + showCasesPanel.setLayout(showCasesPanelLayout); + showCasesPanelLayout.setHorizontalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(showCasesPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(showCasesPanelLayout.createSequentialGroup() + .addGap(0, 458, Short.MAX_VALUE) + .addComponent(closeButton)) + .addComponent(jScrollPane1)) + .addContainerGap()) + ); + showCasesPanelLayout.setVerticalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(showCasesPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(closeButton) + .addContainerGap()) + ); + + closeButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.AccessibleContext.accessibleName")); // NOI18N + + showCasesScrollPane.setViewportView(showCasesPanel); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed + dispose(); + }//GEN-LAST:event_closeButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTable caseDetailsTable; + private javax.swing.JButton closeButton; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JPanel showCasesPanel; + private javax.swing.JScrollPane showCasesScrollPane; + // End of variables declaration//GEN-END:variables + + + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java new file mode 100644 index 0000000000..9d93e62696 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java @@ -0,0 +1,180 @@ +/* + * Central Repository + * + * Copyright 2015-2017 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.centralrepository.optionspanel; + +import java.util.ArrayList; +import java.util.List; +import javax.swing.table.AbstractTableModel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; + +/** + * Model for cells to display correlation case information + * TODO sorting + */ +public class ShowCasesTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + @Messages({"ShowCasesTableModel.case=Case Name", + "ShowCasesTableModel.creationDate=Creation Date", + "ShowCasesTableModel.caseNumber=Case Number", + "ShowCasesTableModel.examinerName=Examiner Name", + "ShowCasesTableModel.examinerEmail=Examiner Email", + "ShowCasesTableModel.examinerPhone=Examiner Phone", + "ShowCasesTableModel.notes=Notes", + "ShowCasesTableModel.noData=No Cases"}) + enum TableColumns { + // Ordering here determines displayed column order in Content Viewer. + // If order is changed, update the CellRenderer to ensure correct row coloring. + CASE_NAME(Bundle.ShowCasesTableModel_case(), 100), + CREATION_DATE(Bundle.ShowCasesTableModel_creationDate(), 100), + CASE_NUMBER(Bundle.ShowCasesTableModel_caseNumber(), 50), + EXAMINER_NAME(Bundle.ShowCasesTableModel_examinerName(), 200), + EXAMINER_EMAIL(Bundle.ShowCasesTableModel_examinerEmail(), 50), + EXAMINER_PHONE(Bundle.ShowCasesTableModel_examinerPhone(), 100), + NOTES(Bundle.ShowCasesTableModel_notes(), 450); + + private final String columnName; + private final int columnWidth; + + TableColumns(String columnName, int columnWidth) { + this.columnName = columnName; + this.columnWidth = columnWidth; + } + + public String columnName() { + return columnName; + } + + public int columnWidth() { + return columnWidth; + } + }; + + private List eamCases; + + ShowCasesTableModel() { + eamCases = new ArrayList<>(); + } + + @Override + public int getColumnCount() { + return TableColumns.values().length; + } + + /** + * Get the preferred width that has been configured for this column. + * + * A value of 0 means that no preferred width has been defined for this + * column. + * + * @param colIdx Column index + * + * @return preferred column width >= 0 + */ + public int getColumnPreferredWidth(int colIdx) { + return TableColumns.values()[colIdx].columnWidth(); + } + + @Override + public int getRowCount() { + return eamCases.size(); + } + + @Override + public String getColumnName(int colIdx) { + return TableColumns.values()[colIdx].columnName(); + } + + @Override + public Object getValueAt(int rowIdx, int colIdx) { + if (0 == eamCases.size()) { + return Bundle.ShowCasesTableModel_noData(); + } + + return mapValueById(rowIdx, TableColumns.values()[colIdx]); + } + + public Object getRow(int rowIdx) { + return eamCases.get(rowIdx); + } + + /** + * Map a rowIdx and colId to the value in that cell. + * + * @param rowIdx Index of row to search + * @param colId ID of column to search + * + * @return value in the cell + */ + private Object mapValueById(int rowIdx, TableColumns colId) { + CorrelationCase eamCase = eamCases.get(rowIdx); + String value = Bundle.ShowCasesTableModel_noData(); + + switch (colId) { + case CASE_NAME: + value = eamCase.getDisplayName(); + break; + case CREATION_DATE: + value = eamCase.getCreationDate(); + break; + case CASE_NUMBER: + value = eamCase.getCaseNumber(); + break; + case EXAMINER_NAME: + value = eamCase.getExaminerName(); + break; + case EXAMINER_EMAIL: + value = eamCase.getExaminerEmail(); + break; + case EXAMINER_PHONE: + value = eamCase.getExaminerPhone(); + break; + case NOTES: + value = eamCase.getNotes(); + break; + } + return value; + } + + @Override + public Class getColumnClass(int colIdx) { + return String.class; + } + + /** + * Add one local central repository artifact to the table. + * + * @param eamArtifact central repository artifact to add to the + * table + */ + public void addEamCase(CorrelationCase eamCase) { + eamCases.add(eamCase); + fireTableDataChanged(); + } + + public void clearTable() { + eamCases.clear(); + fireTableDataChanged(); + } + + + +} From ff2257c6d65a5c483464139381bf12b28d0dd3e8 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Wed, 13 Jun 2018 17:46:04 -0700 Subject: [PATCH 054/101] Cleaned up earliest case display time. Made cases panel sortable, and scrollable. --- .../DataContentViewerOtherCases.form | 65 +++---- .../DataContentViewerOtherCases.java | 48 ++--- .../optionspanel/Bundle.properties | 1 + .../optionspanel/ShowCasesDialog.form | 179 +++++++++++------- .../optionspanel/ShowCasesDialog.java | 129 ++++++++----- .../optionspanel/ShowCasesTableModel.java | 16 +- 6 files changed, 254 insertions(+), 184 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 32a90e61ea..64ce01aa31 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -125,6 +125,10 @@ + + + + @@ -140,8 +144,13 @@ - - + + + + + + + @@ -216,46 +225,15 @@ - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - - - - - - @@ -264,6 +242,23 @@
+ + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 567007cbd3..42b7b61a30 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -461,10 +461,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return ret; } + /** + * Gets the list of Eam Cases and determines the earliest case creation date. + * Sets the label to display the earliest date string to the user. + */ private void setEarliestCaseDate() { - LocalDate earliestDate = LocalDate.now(); - + LocalDate earliestDate = LocalDate.now(); DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + String dateStringDisplay = datetimeFormat.format(earliestDate.toDate()); + if (EamDb.isEnabled()) { try { @@ -475,6 +480,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi if (caseDate.isBefore(earliestDate)) { earliestDate = caseDate; + dateStringDisplay = aCase.getCreationDate(); } } @@ -486,7 +492,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // NON-NLS } - earliestCaseDate.setText(datetimeFormat.format(earliestDate.toDate())); + earliestCaseDate.setText(dateStringDisplay); } /** @@ -739,9 +745,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi otherCasesTable = new javax.swing.JTable(); tableStatusPanel = new javax.swing.JPanel(); earliestDatePanel = new javax.swing.JPanel(); + tableStatusPanelLabel = new javax.swing.JLabel(); earliestCaseLabel = new javax.swing.JLabel(); earliestCaseDate = new javax.swing.JLabel(); - tableStatusPanelLabel = new javax.swing.JLabel(); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { @@ -798,39 +804,34 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi .addGap(0, 16, Short.MAX_VALUE) ); - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N - earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N - javax.swing.GroupLayout earliestDatePanelLayout = new javax.swing.GroupLayout(earliestDatePanel); earliestDatePanel.setLayout(earliestDatePanelLayout); earliestDatePanelLayout.setHorizontalGroup( earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(earliestDatePanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(earliestCaseLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(earliestCaseDate) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 206, Short.MAX_VALUE) ); earliestDatePanelLayout.setVerticalGroup( earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, earliestDatePanelLayout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addGroup(earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(earliestCaseLabel) - .addComponent(earliestCaseDate))) + .addGap(0, 16, Short.MAX_VALUE) ); tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N + earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N + javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); tableContainerPanelLayout.setHorizontalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(tableContainerPanelLayout.createSequentialGroup() .addContainerGap() + .addComponent(earliestCaseLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(earliestCaseDate) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE)) .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE) @@ -842,8 +843,11 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties index 08a9519bc9..3e090a5640 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties @@ -74,3 +74,4 @@ ShowCasesDialog.closeButton.text=Close ShowCasesDialog.caseDetailsTable.toolTipText=Click column name to sort. Right-click on the table for more options. ShowCasesDialog.title=Case Details GlobalSettingsPanel.Case\ Details.AccessibleContext.accessibleName=Cases Details +ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription=Click column name to sort. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form index e3b98501ca..6ddcf95177 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form @@ -1,6 +1,6 @@ -
+ @@ -28,107 +28,142 @@ + + + + + - - + + + - - - + + + + + + - + - - - - + - + + + + + + + + + + + + + + + + + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index 440fdb18a2..3d641f0167 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -21,24 +21,24 @@ package org.sleuthkit.autopsy.centralrepository.optionspanel; import java.util.List; import javax.swing.JDialog; import javax.swing.JFrame; +import javax.swing.table.TableColumn; import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; -import org.sleuthkit.autopsy.coreutils.Logger; public final class ShowCasesDialog extends JDialog { private static final long serialVersionUID = 1L; private EamDb dbManager; - private final static Logger logger = Logger.getLogger(ShowCasesDialog.class.getName()); + private ShowCasesTableModel tableModel; @Messages({"ShowCasesDialog.title_text=All Cases Details"}) /** - * Creates new form ManageOrganizationsPanel + * Creates new form ShowCases Panel */ public ShowCasesDialog() { super((JFrame) WindowManager.getDefault().getMainWindow(), @@ -55,6 +55,7 @@ public final class ShowCasesDialog extends JDialog { } catch (EamDbException ex) { Exceptions.printStackTrace(ex); } + // setColumnWidths(); display(); } @@ -74,19 +75,62 @@ public final class ShowCasesDialog extends JDialog { // //GEN-BEGIN:initComponents private void initComponents() { - showCasesScrollPane = new javax.swing.JScrollPane(); showCasesPanel = new javax.swing.JPanel(); - closeButton = new javax.swing.JButton(); - jScrollPane1 = new javax.swing.JScrollPane(); + showCasesScrollPane = new javax.swing.JScrollPane(); + outCasesPane = new javax.swing.JPanel(); + innerCaseScrollPane = new javax.swing.JScrollPane(); caseDetailsTable = new javax.swing.JTable(); + closeButton = new javax.swing.JButton(); setTitle(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.title")); // NOI18N setMinimumSize(new java.awt.Dimension(545, 415)); - showCasesScrollPane.setMinimumSize(null); + showCasesPanel.setPreferredSize(new java.awt.Dimension(527, 407)); + + showCasesScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); showCasesScrollPane.setPreferredSize(new java.awt.Dimension(535, 415)); - showCasesPanel.setPreferredSize(new java.awt.Dimension(527, 407)); + caseDetailsTable.setAutoCreateRowSorter(true); + caseDetailsTable.setModel(tableModel); + caseDetailsTable.setToolTipText(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.toolTipText")); // NOI18N + caseDetailsTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + caseDetailsTable.getTableHeader().setReorderingAllowed(false); + innerCaseScrollPane.setViewportView(caseDetailsTable); + caseDetailsTable.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription")); // NOI18N + + javax.swing.GroupLayout outCasesPaneLayout = new javax.swing.GroupLayout(outCasesPane); + outCasesPane.setLayout(outCasesPaneLayout); + outCasesPaneLayout.setHorizontalGroup( + outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 1222, Short.MAX_VALUE) + .addGroup(outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(outCasesPaneLayout.createSequentialGroup() + .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 1222, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + ); + outCasesPaneLayout.setVerticalGroup( + outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 423, Short.MAX_VALUE) + .addGroup(outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 423, Short.MAX_VALUE)) + ); + + showCasesScrollPane.setViewportView(outCasesPane); + + javax.swing.GroupLayout showCasesPanelLayout = new javax.swing.GroupLayout(showCasesPanel); + showCasesPanel.setLayout(showCasesPanelLayout); + showCasesPanelLayout.setHorizontalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 527, Short.MAX_VALUE) + .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 527, Short.MAX_VALUE)) + ); + showCasesPanelLayout.setVerticalGroup( + showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 426, Short.MAX_VALUE) + .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE)) + ); org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.text")); // NOI18N closeButton.setActionCommand(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.actionCommand")); // NOI18N @@ -96,64 +140,53 @@ public final class ShowCasesDialog extends JDialog { } }); - caseDetailsTable.setModel(tableModel); - caseDetailsTable.setToolTipText(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.toolTipText")); // NOI18N - jScrollPane1.setViewportView(caseDetailsTable); - - javax.swing.GroupLayout showCasesPanelLayout = new javax.swing.GroupLayout(showCasesPanel); - showCasesPanel.setLayout(showCasesPanelLayout); - showCasesPanelLayout.setHorizontalGroup( - showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(showCasesPanelLayout.createSequentialGroup() - .addContainerGap() - .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(showCasesPanelLayout.createSequentialGroup() - .addGap(0, 458, Short.MAX_VALUE) - .addComponent(closeButton)) - .addComponent(jScrollPane1)) + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 4, Short.MAX_VALUE) + .addComponent(showCasesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 4, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(closeButton) .addContainerGap()) ); - showCasesPanelLayout.setVerticalGroup( - showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(showCasesPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 6, Short.MAX_VALUE) + .addComponent(showCasesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 426, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(closeButton) .addContainerGap()) ); closeButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.AccessibleContext.accessibleName")); // NOI18N - showCasesScrollPane.setViewportView(showCasesPanel); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, 0) - .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - pack(); }// //GEN-END:initComponents private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed dispose(); }//GEN-LAST:event_closeButtonActionPerformed - + + private void setColumnWidths() { + for (int idx = 0; idx < tableModel.getColumnCount(); idx++) { + TableColumn column = caseDetailsTable.getColumnModel().getColumn(idx); + int colWidth = tableModel.getColumnPreferredWidth(idx); + if (0 < colWidth) { + column.setPreferredWidth(colWidth); + } + } + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTable caseDetailsTable; private javax.swing.JButton closeButton; - private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane innerCaseScrollPane; + private javax.swing.JPanel outCasesPane; private javax.swing.JPanel showCasesPanel; private javax.swing.JScrollPane showCasesScrollPane; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java index 9d93e62696..eecf395d3f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java @@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; /** * Model for cells to display correlation case information - * TODO sorting */ public class ShowCasesTableModel extends AbstractTableModel { @@ -43,11 +42,11 @@ public class ShowCasesTableModel extends AbstractTableModel { enum TableColumns { // Ordering here determines displayed column order in Content Viewer. // If order is changed, update the CellRenderer to ensure correct row coloring. - CASE_NAME(Bundle.ShowCasesTableModel_case(), 100), - CREATION_DATE(Bundle.ShowCasesTableModel_creationDate(), 100), - CASE_NUMBER(Bundle.ShowCasesTableModel_caseNumber(), 50), + CASE_NAME(Bundle.ShowCasesTableModel_case(), 200), + CREATION_DATE(Bundle.ShowCasesTableModel_creationDate(), 150), + CASE_NUMBER(Bundle.ShowCasesTableModel_caseNumber(), 100), EXAMINER_NAME(Bundle.ShowCasesTableModel_examinerName(), 200), - EXAMINER_EMAIL(Bundle.ShowCasesTableModel_examinerEmail(), 50), + EXAMINER_EMAIL(Bundle.ShowCasesTableModel_examinerEmail(), 100), EXAMINER_PHONE(Bundle.ShowCasesTableModel_examinerPhone(), 100), NOTES(Bundle.ShowCasesTableModel_notes(), 450); @@ -68,6 +67,9 @@ public class ShowCasesTableModel extends AbstractTableModel { } }; + /** + * list of Eam Cases from central repository. + */ private List eamCases; ShowCasesTableModel() { @@ -160,9 +162,9 @@ public class ShowCasesTableModel extends AbstractTableModel { } /** - * Add one local central repository artifact to the table. + * Add one local central repository case to the table. * - * @param eamArtifact central repository artifact to add to the + * @param eamCase central repository case to add to the * table */ public void addEamCase(CorrelationCase eamCase) { From 977c82f4e4473262d4f25ca0ff64a537c9a4176b Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Wed, 13 Jun 2018 17:54:32 -0700 Subject: [PATCH 055/101] Change default string to 'date not available' to be clearer when CR not enabled. --- .../contentviewer/DataContentViewerOtherCases.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 42b7b61a30..4c63f5951e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -461,6 +461,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return ret; } + @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Date Not Available."}) /** * Gets the list of Eam Cases and determines the earliest case creation date. * Sets the label to display the earliest date string to the user. @@ -468,7 +469,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private void setEarliestCaseDate() { LocalDate earliestDate = LocalDate.now(); DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - String dateStringDisplay = datetimeFormat.format(earliestDate.toDate()); + String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); if (EamDb.isEnabled()) { From c1e98754dd5e3c894669d9d062c7cfd37b8f4008 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 14 Jun 2018 12:46:13 -0700 Subject: [PATCH 056/101] Fix for GIT logger variable merge issue. --- .../DataContentViewerOtherCases.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 4c63f5951e..10537a9c4a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -120,7 +120,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { saveToCSV(); } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); @@ -179,7 +179,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error getting commonality details.", ex); + LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex); JOptionPane.showConfirmDialog(showCommonalityMenuItem, Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(), Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), @@ -303,7 +303,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } catch (IOException ex) { - logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); + LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); } } @@ -395,7 +395,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID()); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS return null; } @@ -441,7 +441,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } else { @@ -454,7 +454,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } @@ -535,13 +535,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return artifactInstances; } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { // do nothing. // @@@ Review this behavior - logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS + LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS } return new HashMap<>(0); @@ -595,7 +595,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); } catch (TskDataException | TskCoreException ex) { - logger.log(Level.WARNING, "Error getting data source info: {0}", ex); + LOGGER.log(Level.WARNING, "Error getting data source info: {0}", ex); return; } UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); @@ -688,7 +688,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi newCeArtifact.addInstance(corAttrInstance); tableModel.addEamArtifact(newCeArtifact); } catch (EamDbException ex) { - logger.log(Level.SEVERE, "Error creating correlation attribute", ex); + LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex); } }); } From dbfabd55e1bdeb7284882b0ac0cf95a11b3516d0 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 14 Jun 2018 16:12:17 -0400 Subject: [PATCH 057/101] Data sources renamed. --- Core/build.xml | 22 +++++++++---------- .../datamodel/CentralRepoDatamodelTest.java | 6 ++--- .../commonfilessearch/IntraCaseUtils.java | 17 +++++++------- .../autopsy/ingest/EmbeddedFileTest.java | 6 +++-- .../autopsy/ingest/IngestFileFiltersTest.java | 7 ++++-- .../EncryptionDetectionTest.java | 11 ++++++---- 6 files changed, 38 insertions(+), 31 deletions(-) diff --git a/Core/build.xml b/Core/build.xml index 98644b8cb3..6cba803539 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -89,17 +89,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 0f0e950ec6..f30038b5f5 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -44,7 +44,7 @@ import static junit.framework.Assert.assertTrue; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; /** - * + * Functional tests for the Central Repository data model. */ public class CentralRepoDatamodelTest extends TestCase { @@ -112,7 +112,7 @@ public class CentralRepoDatamodelTest extends TestCase { EamDbUtil.setUseCentralRepo(true); EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.SQLITE.name()); EamDbPlatformEnum.saveSelectedPlatform(); - } catch (Exception ex) { + } catch (EamDbException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } @@ -2499,7 +2499,7 @@ public class CentralRepoDatamodelTest extends TestCase { // This seems to help in allowing the Autopsy case to be deleted try { Thread.sleep(2000); - } catch (Exception ex) { + } catch (InterruptedException ex) { } } catch (CaseActionException ex) { diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java index 66a68f3a38..39f18e0696 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonfilessearch/IntraCaseUtils.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2018 Basis Technology Corp. @@ -84,20 +83,20 @@ class IntraCaseUtils { static final String PDF = "adsf.pdf"; //not a typo - it appears this way in the test image static final String EMPTY = "file.dat"; - static final String SET1 = "commonfiles_image1_v1.vhd"; - static final String SET2 = "commonfiles_image2_v1.vhd"; - static final String SET3 = "commonfiles_image3_v1.vhd"; - static final String SET4 = "commonfiles_image4_v1.vhd"; + static final String SET1 = "CommonFiles_img1_v1.vhd"; + static final String SET2 = "CommonFiles_img2_v1.vhd"; + static final String SET3 = "CommonFiles_img3_v1.vhd"; + static final String SET4 = "CommonFiles_img4_v1.vhd"; private final DataSourceLoader dataSourceLoader; private final String caseName; IntraCaseUtils(NbTestCase nbTestCase, String caseName){ - imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image1_v1.vhd"); - imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image2_v1.vhd"); - imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image3_v1.vhd"); - imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), "commonfiles_image4_v1.vhd"); + imagePath1 = Paths.get(nbTestCase.getDataDir().toString(), SET1); + imagePath2 = Paths.get(nbTestCase.getDataDir().toString(), SET2); + imagePath3 = Paths.get(nbTestCase.getDataDir().toString(), SET3); + imagePath4 = Paths.get(nbTestCase.getDataDir().toString(), SET4); this.dataSourceLoader = new DataSourceLoader(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 813c09334a..8f562d698b 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -39,11 +39,13 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; +/** + * Functional tests for embedded files. + */ public class EmbeddedFileTest extends NbTestCase { private static final String CASE_NAME = "EmbeddedFileTest"; - private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "EmbeddedIM_img1_v1.vhd"); public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; private static final int DEEP_FOLDER_COUNT = 25; private Case openCase; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index fcb2b8a4ca..16b0f2f4a4 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -50,10 +50,13 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +/** + * Functional tests for ingest file filters. + */ public class IngestFileFiltersTest extends NbTestCase { - private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"filter_test1.img"); - private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "local_files_test.zip"); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"IngestFilters_img1_v1.img"); + private final Path ZIPFILE_PATH = Paths.get(this.getDataDir().toString(), "IngestFilters_local1_v1.zip"); private boolean testSucceeded; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index ef639bec5f..dc1f85afcd 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -47,6 +47,9 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; +/** + * Functional tests for Encryption Detection. + */ public class EncryptionDetectionTest extends NbTestCase { private static final String BITLOCKER_DETECTION_CASE_NAME = "testBitlockerEncryption"; @@ -54,10 +57,10 @@ public class EncryptionDetectionTest extends NbTestCase { private static final String VERACRYPT_DETECTION_CASE_NAME = "VeraCryptDetectionTest"; private static final String SQLCIPHER_DETECTION_CASE_NAME = "SQLCipherDetectionTest"; - private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_bitlocker_test.vhd"); - private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); - private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "veracrypt_detection_test.vhd"); - private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "encryption_detection_sqlcipher_test.vhd"); + private final Path BITLOCKER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "BitlockerDetection_img1_v1.vhd"); + private final Path PASSWORD_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "PasswordDetection_img1_v1.img"); + private final Path VERACRYPT_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "VeracryptDetection_img1_v1.vhd"); + private final Path SQLCIPHER_DETECTION_IMAGE_PATH = Paths.get(this.getDataDir().toString(), "SqlCipherDetection_img1_v1.vhd"); private boolean testSucceeded; From 43d42cf0a7d14af9f07180b1c042d421e91ee92e Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 14 Jun 2018 19:46:15 -0400 Subject: [PATCH 058/101] 3894: Clean up Tag & Comment dialog - Leaving the New Tag button intact this time. --- .../autopsy/actions/Bundle.properties | 2 +- .../autopsy/actions/Bundle_ja.properties | 2 +- .../actions/GetTagNameAndCommentDialog.form | 49 +++++++++------ .../actions/GetTagNameAndCommentDialog.java | 59 +++++++++++++------ .../casemodule/services/TagsManager.java | 10 ++++ 5 files changed, 83 insertions(+), 39 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index 1cbc4b70b6..c83c8c2782 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -20,7 +20,7 @@ AddTagAction.newTag=New Tag... AddTagAction.tagAndComment=Tag and Comment... AddBookmarkTagAction.bookmark.text=Bookmark GetTagNameAndCommentDialog.noTags=No Tags -GetTagNameAndCommentDialog.createTag=Create Tag +GetTagNameAndCommentDialog.selectTag=Select Tag GetTagNameAndCommentDialog.cancelName=cancel GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties index d6f865ecf0..10bd2ad0df 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle_ja.properties @@ -23,7 +23,7 @@ AddTagAction.noTags=\u30bf\u30b0\u7121\u3057 AddTagAction.newTag=\u65b0\u898f\u30bf\u30b0\u2026 AddTagAction.tagAndComment=\u30bf\u30b0\u3068\u30b3\u30e1\u30f3\u30c8\u3092\u8ffd\u52a0\u2026 GetTagNameAndCommentDialog.noTags=\u30bf\u30b0\u7121\u3057 -GetTagNameAndCommentDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 +GetTagNameAndCommentDialog.selectTag=\u30bf\u30b0\u3092\u9078\u629e GetTagNameAndCommentDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb GetTagNameDialog.createTag=\u30bf\u30b0\u3092\u4f5c\u6210 GetTagNameDialog.cancelName=\u30ad\u30e3\u30f3\u30bb\u30eb diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 57c43e964b..818d660ff3 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -24,11 +24,11 @@ - + - + @@ -38,14 +38,14 @@ - + - + - +
@@ -58,11 +58,11 @@
- - - + + + - + @@ -125,16 +125,6 @@ - - - - - - - - - - @@ -145,5 +135,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index d2c66f410b..ef7e853d68 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -22,9 +22,13 @@ import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.util.ArrayList; import java.util.logging.Level; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.DefaultListCellRenderer; @@ -50,7 +54,8 @@ import org.sleuthkit.datamodel.TskData; public class GetTagNameAndCommentDialog extends JDialog { private static final long serialVersionUID = 1L; - private final Set tagNamesSet = new HashSet<>(); + private final List tagNamesList = new ArrayList<>(); + private final List standardTagNamesList = new ArrayList<>(); private TagNameAndComment tagNameAndComment = null; public static class TagNameAndComment { @@ -105,7 +110,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private GetTagNameAndCommentDialog(Window owner) { super(owner, - NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.createTag"), + NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.selectTag"), ModalityType.APPLICATION_MODAL); } @@ -144,16 +149,29 @@ public class GetTagNameAndCommentDialog extends JDialog { // not exist in the database). try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - tagNamesSet.addAll(tagsManager.getAllTagNames()); + List standardTagNames = TagsManager.getStandardTagNames(); + Map tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); + + tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> { + if (standardTagNames.contains(tagName.getDisplayName())) { + standardTagNamesList.add(tagName); + } else { + tagNamesList.add(tagName); + } + }); + } catch (TskCoreException | NoCurrentCaseException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class .getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - for (TagName tag : tagNamesSet) { - + tagNamesList.forEach((tag) -> { tagCombo.addItem(tag); - } + }); + + standardTagNamesList.forEach((tag) -> { + tagCombo.addItem(tag); + }); // Center and show the dialog box. this.setLocationRelativeTo(this.getOwner()); @@ -174,8 +192,9 @@ public class GetTagNameAndCommentDialog extends JDialog { tagCombo = new javax.swing.JComboBox(); tagLabel = new javax.swing.JLabel(); commentLabel = new javax.swing.JLabel(); - commentText = new javax.swing.JTextField(); newTagButton = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + commentText = new javax.swing.JTextArea(); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { @@ -203,9 +222,6 @@ public class GetTagNameAndCommentDialog extends JDialog { org.openide.awt.Mnemonics.setLocalizedText(commentLabel, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentLabel.text")); // NOI18N - commentText.setText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.text")); // NOI18N - commentText.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.toolTipText")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(newTagButton, org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.newTagButton.text")); // NOI18N newTagButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -213,6 +229,12 @@ public class GetTagNameAndCommentDialog extends JDialog { } }); + commentText.setColumns(20); + commentText.setRows(5); + commentText.setText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.text")); // NOI18N + commentText.setToolTipText(org.openide.util.NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.commentText.toolTipText")); // NOI18N + jScrollPane1.setViewportView(commentText); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -222,7 +244,7 @@ public class GetTagNameAndCommentDialog extends JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newTagButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 165, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) @@ -232,8 +254,8 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(commentText) - .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(tagCombo, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 318, Short.MAX_VALUE)))) .addContainerGap()) ); @@ -247,10 +269,10 @@ public class GetTagNameAndCommentDialog extends JDialog { .addComponent(tagCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(tagLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(commentLabel) - .addComponent(commentText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 37, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 22, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton) @@ -282,7 +304,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed TagName newTagName = GetTagNameDialog.doDialog(this); if (newTagName != null) { - tagNamesSet.add(newTagName); + tagNamesList.add(newTagName); tagCombo.addItem(newTagName); tagCombo.setSelectedItem(newTagName); } @@ -291,7 +313,8 @@ public class GetTagNameAndCommentDialog extends JDialog { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton cancelButton; private javax.swing.JLabel commentLabel; - private javax.swing.JTextField commentText; + private javax.swing.JTextArea commentText; + private javax.swing.JScrollPane jScrollPane1; private javax.swing.JButton newTagButton; private javax.swing.JButton okButton; private javax.swing.JComboBox tagCombo; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f113a622fe..a79d1ab195 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -121,6 +121,16 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * Returns a list of names of standard/predefined tags + * + * @return list of predefined tag names + */ + public static List getStandardTagNames() { + return TagNameDefinition.getStandardTagNames(); + } + + /** * Constructs a per case Autopsy service that manages the addition of * content and artifact tags to the case database. From b1e65886ef1d3dce4cf8328453b8343263b4dd0a Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 14 Jun 2018 20:43:02 -0700 Subject: [PATCH 059/101] Codeacy fixes --- .../DataContentViewerOtherCases.java | 3 ++- .../optionspanel/GlobalSettingsPanel.java | 4 ++-- .../optionspanel/ShowCasesDialog.java | 21 ++++++------------- .../optionspanel/ShowCasesTableModel.java | 9 ++++++-- .../commonfilesearch/CommonFilesChildren.java | 19 ++++++++--------- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 10537a9c4a..cb3ed48f8e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -33,6 +33,7 @@ import java.util.Calendar; import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.logging.Level; @@ -468,7 +469,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi */ private void setEarliestCaseDate() { LocalDate earliestDate = LocalDate.now(); - DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 761653eeb5..63b7584dec 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -429,12 +429,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void manageOrganizationButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manageOrganizationButtonActionPerformed store(); - ManageOrganizationsDialog dialog = new ManageOrganizationsDialog(); + ManageOrganizationsDialog manageOrganizationsDialog = new ManageOrganizationsDialog(); }//GEN-LAST:event_manageOrganizationButtonActionPerformed private void showCasesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showCasesButtonActionPerformed store(); - ShowCasesDialog dialog = new ShowCasesDialog(); + ShowCasesDialog showCasesDialog = new ShowCasesDialog(); }//GEN-LAST:event_showCasesButtonActionPerformed @Override diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index 3d641f0167..e50f02bb45 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -29,13 +29,14 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +/** + * Dialog to display table of CorrelationCase information from the CR tab of options. + */ public final class ShowCasesDialog extends JDialog { private static final long serialVersionUID = 1L; - private EamDb dbManager; - - private ShowCasesTableModel tableModel; + private final ShowCasesTableModel tableModel; @Messages({"ShowCasesDialog.title_text=All Cases Details"}) /** * Creates new form ShowCases Panel @@ -47,7 +48,7 @@ public final class ShowCasesDialog extends JDialog { tableModel = new ShowCasesTableModel(); initComponents(); try { - this.dbManager = EamDb.getInstance(); + EamDb dbManager = EamDb.getInstance(); List eamCases = dbManager.getCases(); for(CorrelationCase eamCase : eamCases) { tableModel.addEamCase(eamCase); @@ -55,7 +56,6 @@ public final class ShowCasesDialog extends JDialog { } catch (EamDbException ex) { Exceptions.printStackTrace(ex); } - // setColumnWidths(); display(); } @@ -171,16 +171,7 @@ public final class ShowCasesDialog extends JDialog { private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed dispose(); }//GEN-LAST:event_closeButtonActionPerformed - - private void setColumnWidths() { - for (int idx = 0; idx < tableModel.getColumnCount(); idx++) { - TableColumn column = caseDetailsTable.getColumnModel().getColumn(idx); - int colWidth = tableModel.getColumnPreferredWidth(idx); - if (0 < colWidth) { - column.setPreferredWidth(colWidth); - } - } - } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTable caseDetailsTable; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java index eecf395d3f..8ed38c4891 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,9 @@ public class ShowCasesTableModel extends AbstractTableModel { "ShowCasesTableModel.examinerPhone=Examiner Phone", "ShowCasesTableModel.notes=Notes", "ShowCasesTableModel.noData=No Cases"}) + /** + * Enum which lists columns of interest from CorrelationCase. + */ enum TableColumns { // Ordering here determines displayed column order in Content Viewer. // If order is changed, update the CellRenderer to ensure correct row coloring. @@ -107,7 +110,7 @@ public class ShowCasesTableModel extends AbstractTableModel { @Override public Object getValueAt(int rowIdx, int colIdx) { - if (0 == eamCases.size()) { + if (eamCases.isEmpty()) { return Bundle.ShowCasesTableModel_noData(); } @@ -152,6 +155,8 @@ public class ShowCasesTableModel extends AbstractTableModel { case NOTES: value = eamCase.getNotes(); break; + default: + break; } return value; } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java index 11cfd5c967..858244e4c6 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,8 +24,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; /** - * - * @author bsweeney + * */ class CommonFilesChildren extends Children.Keys{ @@ -35,14 +34,14 @@ class CommonFilesChildren extends Children.Keys{ } @Override - protected Node[] createNodes(AbstractFile t) { + protected Node[] createNodes(AbstractFile abstractFile) { - if(t.isDir()){ - //TODO log an error ??? - - } else { - - } +// if(abstractFile.isDir()){ +// //TODO log an error ??? +// +// } else { +// +// } throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } From a51a2da70af156cda2187ba075256b262e3e5ce1 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 14 Jun 2018 20:49:30 -0700 Subject: [PATCH 060/101] Codeacy fixes. --- .../sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java | 2 +- Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index 23c0f60afc..12324b6b90 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -208,7 +208,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { @Override @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) - protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException, Exception { + protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgress1()); progress.start(); progress.switchToIndeterminate(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java index d32c77ca13..3960c27eee 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java @@ -62,6 +62,10 @@ public class Md5Node extends DisplayableItemNode { this.setDisplayName(this.md5Hash); } + /** + * Callable wrapper to further delay lazy ChildFactory creation + * and createNodes() call once lazy loading is functional. + */ private static class Md5ChildCallable implements Callable { private final Md5Metadata key; private Md5ChildCallable(Md5Metadata key) { From e3fe4b2356b72929a3a169b4d1e4c05c5109c23d Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Thu, 14 Jun 2018 20:56:30 -0700 Subject: [PATCH 061/101] Codeacy cleanup. --- .../centralrepository/optionspanel/GlobalSettingsPanel.java | 4 ++-- .../centralrepository/optionspanel/ShowCasesDialog.java | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 63b7584dec..25bf4434a9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -45,7 +45,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteEamDbSettings; public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(GlobalSettingsPanel.class.getName()); + private static final Logger LOGGER = Logger.getLogger(GlobalSettingsPanel.class.getName()); private final IngestJobEventPropertyChangeListener ingestJobEventListener; @@ -404,7 +404,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void bnManageTypesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnManageTypesActionPerformed store(); - ManageCorrelationPropertiesDialog dialog = new ManageCorrelationPropertiesDialog(); + ManageCorrelationPropertiesDialog manageCorrelationDialog = new ManageCorrelationPropertiesDialog(); firePropertyChange(OptionsPanelController.PROP_VALID, null, null); }//GEN-LAST:event_bnManageTypesActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index e50f02bb45..58e4dccb06 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.centralrepository.optionspanel; import java.util.List; import javax.swing.JDialog; import javax.swing.JFrame; -import javax.swing.table.TableColumn; import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; From a31bab151a4ac06691d0a71be20a9e592f62a6c8 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 15 Jun 2018 09:02:32 -0400 Subject: [PATCH 062/101] Moved context menu creation to new provider. --- ...CentralRepoContextMenuActionsProvider.java | 56 +++++++++++++++++++ .../datamodel/AbstractAbstractFileNode.java | 15 ----- .../datamodel/BlackboardArtifactNode.java | 5 -- 3 files changed, 56 insertions(+), 20 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java new file mode 100755 index 0000000000..5a6e8fa652 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoContextMenuActionsProvider.java @@ -0,0 +1,56 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.swing.Action; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; +import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * This creates a single context menu item for adding or editing a Central + * Repository comment. + */ +@ServiceProvider(service = ContextMenuActionsProvider.class) +public class CentralRepoContextMenuActionsProvider implements ContextMenuActionsProvider { + + @Override + public List getActions() { + ArrayList actions = new ArrayList<>(); + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + + if (selectedFiles.size() != 1) { + return actions; + } + + for (AbstractFile file : selectedFiles) { + if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { + actions.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); + } + } + + return actions; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index c9b271a68e..28329f17c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -258,21 +258,6 @@ public abstract class AbstractAbstractFileNode extends A map.put(EXTENSION.toString(), content.getNameExtension()); } - @Override - public Action[] getActions(boolean context) { - List actionsList = new ArrayList<>(); - - actionsList.addAll(Arrays.asList(super.getActions(true))); - - // Create the "Add/Edit Central Repository Comment" menu item if the enabled. - AbstractFile file = content; - if (EamDbUtil.useCentralRepo() && EamArtifactUtil.isSupportedAbstractFileType(file) && file.isFile()) { - actionsList.add(AddEditCentralRepoCommentAction.createAddEditCentralRepoCommentAction(file)); - } - - return actionsList.toArray(new Action[actionsList.size()]); - } - /** * Used by subclasses of AbstractAbstractFileNode to add the tags property * to their sheets. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 9e003c5be4..235f00cb1b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -221,11 +221,6 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Fri, 15 Jun 2018 09:21:45 -0400 Subject: [PATCH 063/101] Disable the CR-based context menu items if CR is not enabled. With CR disabled, don't enabled the other occurrences tab if the file has no MD5. Added comments. --- .../DataContentViewerOtherCases.java | 36 ++++---- .../OtherOccurrenceNodeData.java | 90 +++++++++++++++++-- 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index e9ab49cbf6..298a8763fc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -56,7 +56,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttribute; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -69,7 +68,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; -import org.sleuthkit.datamodel.TskDataException; /** * View correlation results from other cases @@ -458,9 +456,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } /** - * Query the database for artifact instances from other cases correlated to - * the given central repository artifact. Instances from the same datasource - * / device will also be included. + * Query the central repo database (if enabled) and the case database to find all + * artifact instances correlated to the given central repository artifact. If the + * central repo is not enabled, this will only return files from the current case + * with matching MD5 hashes. * * @param corAttr CorrelationAttribute to query for * @param dataSourceName Data source to filter results @@ -521,6 +520,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return new HashMap<>(0); } + /** + * Get all other abstract files in the current case with the same MD5 as the selected node. + * @param corAttr The CorrelationAttribute containing the MD5 to search for + * @param openCase The current case + * @return List of matching AbstractFile objects + * @throws NoCurrentCaseException + * @throws TskCoreException + * @throws EamDbException + */ private List getCaseDbMatches(CorrelationAttribute corAttr, Case openCase) throws NoCurrentCaseException, TskCoreException, EamDbException { String md5 = corAttr.getCorrelationValue(); @@ -570,7 +578,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // If this node is already in the list, the only thing we need to do is // update the known status to BAD if the caseDB version had known status BAD. - // Otherwise, add the new node to the map. + // Otherwise this is a new node so add the new node to the map. if (nodeDataMap.containsKey(uniquePathKey)) { if (newNode.getKnown() == TskData.FileKnown.BAD) { OtherOccurrenceNodeData prevInstance = nodeDataMap.get(uniquePathKey); @@ -583,18 +591,20 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi @Override public boolean isSupported(Node node) { - this.file = this.getAbstractFileFromNode(node); - // Is supported if this node - // has correlatable content (File, BlackboardArtifact) OR - // other common files across datasources. + // Is supported if one of the following is true: + // - The central repo is enabled and the node has correlatable content + // (either through the MD5 hash of the associated file or through a BlackboardArtifact) + // - The central repo is disabled and the backing file has a valid MD5 hash + this.file = this.getAbstractFileFromNode(node); if (EamDb.isEnabled()) { return this.file != null && this.file.getSize() > 0 && !getCorrelationAttributesFromNode(node).isEmpty(); } else { return this.file != null - && this.file.getSize() > 0; + && this.file.getSize() > 0 + && ((this.file.getMd5Hash() != null) && ( ! this.file.getMd5Hash().isEmpty())); } } @@ -813,16 +823,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private void rightClickPopupMenuPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_rightClickPopupMenuPopupMenuWillBecomeVisible boolean enableCentralRepoActions = false; - boolean enableComment = false; if (EamDbUtil.useCentralRepo() && otherCasesTable.getSelectedRowCount() == 1) { int rowIndex = otherCasesTable.getSelectedRow(); OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(rowIndex); if (selectedNode.isCentralRepoNode()) { enableCentralRepoActions = true; - if (selectedNode.isFileType()) { - enableComment = true; - } } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java index 57534eb15e..3a13955974 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -28,9 +28,14 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskDataException; +/** + * Class for populating the Other Occurrences tab + */ class OtherOccurrenceNodeData { - private final String FILE_TYPE = "Files"; + // For now hard code the string for the central repo files type, since + // getting it dynamically can fail. + private final String FILE_TYPE_STR = "Files"; private String caseName; private String deviceID; @@ -45,6 +50,12 @@ class OtherOccurrenceNodeData { private AbstractFile originalAbstractFile = null; private CorrelationAttributeInstance originalCorrelationInstance = null; + /** + * Create a node from a central repo instance. + * @param instance The central repo instance + * @param type The type of the instance + * @param value The value of the instance + */ OtherOccurrenceNodeData(CorrelationAttributeInstance instance, CorrelationAttribute.Type type, String value) { caseName = instance.getCorrelationCase().getDisplayName(); deviceID = instance.getCorrelationDataSource().getDeviceID(); @@ -59,6 +70,12 @@ class OtherOccurrenceNodeData { originalCorrelationInstance = instance; } + /** + * Create a node from an abstract file. + * @param newFile The abstract file + * @param autopsyCase The current case + * @throws EamDbException + */ OtherOccurrenceNodeData(AbstractFile newFile, Case autopsyCase) throws EamDbException { caseName = autopsyCase.getDisplayName(); try { @@ -70,8 +87,8 @@ class OtherOccurrenceNodeData { } filePath = newFile.getParentPath() + newFile.getName(); - typeStr = FILE_TYPE; - this.type = null; // TEMP + typeStr = FILE_TYPE_STR; + this.type = null; // We can't make the Type object without the central repo enabled value = newFile.getMd5Hash(); known = newFile.getKnown(); comment = ""; @@ -79,21 +96,34 @@ class OtherOccurrenceNodeData { originalAbstractFile = newFile; } + /** + * Check if this node is a "file" type + * @return true if it is a file type + */ boolean isFileType() { - return FILE_TYPE.equals(typeStr); + return FILE_TYPE_STR.equals(typeStr); } + /** + * Update the known status for this node + * @param newKnownStatus The new known status + */ void updateKnown(TskData.FileKnown newKnownStatus) { known = newKnownStatus; } + /** + * Check if this is a central repo node. + * @return true if this node was created from a central repo instance, false otherwise + */ boolean isCentralRepoNode() { return (originalCorrelationInstance != null); } /** - * Uses the saved instance plus type and value to make a new CorrelationAttribute - * @return + * Uses the saved instance plus type and value to make a new CorrelationAttribute. + * Should only be called if isCentralRepoNode() is true. + * @return the newly created CorrelationAttribute */ CorrelationAttribute createCorrelationAttribute() throws EamDbException { if (! isCentralRepoNode() ) { @@ -104,42 +134,88 @@ class OtherOccurrenceNodeData { return attr; } + /** + * Get the case name + * @return the case name + */ String getCaseName() { return caseName; } + /** + * Get the device ID + * @return the device ID + */ String getDeviceID() { return deviceID; } + /** + * Get the data source name + * @return the data source name + */ String getDataSourceName() { return dataSourceName; } + /** + * Get the file path + * @return the file path + */ String getFilePath() { return filePath; } + /** + * Get the type (as a string) + * @return the type + */ String getType() { return typeStr; } + /** + * Get the value (MD5 hash for files) + * @return the value + */ String getValue() { return value; } + /** + * Get the known status + * @return the known status + */ TskData.FileKnown getKnown() { return known; } + /** + * Get the comment + * @return the comment + */ String getComment() { return comment; } - AbstractFile getAbstractFile() { + /** + * Get the backing abstract file. + * Should only be called if isCentralRepoNode() is false + * @return the original abstract file + */ + AbstractFile getAbstractFile() throws EamDbException { + if (originalCorrelationInstance == null) { + throw new EamDbException("AbstractFile is null"); + } return originalAbstractFile; } + /** + * Get the backing CorrelationAttributeInstance. + * Should only be called if isCentralRepoNode() is true + * @return the original CorrelationAttributeInstance + * @throws EamDbException + */ CorrelationAttributeInstance getCorrelationAttributeInstance() throws EamDbException { if (originalCorrelationInstance == null) { throw new EamDbException("CorrelationAttributeInstance is null"); From 5eccf7b4deb2f7724b15973aa706aae38460ade7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 15 Jun 2018 10:12:33 -0400 Subject: [PATCH 064/101] Update the comment in the UI --- .../AddEditCentralRepoCommentAction.java | 8 +++++++- .../centralrepository/CentralRepoCommentDialog.java | 11 ++++++++++- .../contentviewer/DataContentViewerOtherCases.java | 5 ++++- .../contentviewer/OtherOccurrenceNodeData.java | 12 ++++++++++-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java index 8c03883523..e3f2899278 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -83,8 +83,12 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { * comment. The comment will be updated in the database if the file instance * exists there, or a new file instance will be added to the database with * the comment attached otherwise. + * + * The new comment is returned in case it is needed to update the display + * + * @return the newly added comment or null if it was not updated */ - public void addEditCentralRepoComment() { + public String addEditCentralRepoComment() { CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); centralRepoCommentDialog.display(); @@ -102,7 +106,9 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); } + return centralRepoCommentDialog.getComment(); } + return null; } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java index 4e42bfe9d7..35e005daed 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -31,6 +31,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { private final CorrelationAttribute correlationAttribute; private boolean commentUpdated = false; + private String comment = ""; /** * Create an instance. @@ -71,6 +72,14 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { boolean isCommentUpdated() { return commentUpdated; } + + /** + * Get the newly added comment + * @return the comment + */ + String getComment() { + return comment; + } /** * This method is called from within the constructor to initialize the form. @@ -168,7 +177,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - String comment = commentTextArea.getText(); + comment = commentTextArea.getText(); correlationAttribute.getInstances().get(0).setComment(comment); commentUpdated = true; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 298a8763fc..de0c6467c8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -120,7 +120,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedNode.createCorrelationAttribute()); - action.addEditCentralRepoComment(); + String newComment = action.addEditCentralRepoComment(); + if (newComment != null) { + selectedNode.updateComment(newComment); + } otherCasesTable.repaint(); } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error performing Add/Edit Comment action", ex); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java index 3a13955974..03d33b3140 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -45,7 +45,7 @@ class OtherOccurrenceNodeData { private final CorrelationAttribute.Type type; private final String value; private TskData.FileKnown known; - private final String comment; + private String comment; private AbstractFile originalAbstractFile = null; private CorrelationAttributeInstance originalCorrelationInstance = null; @@ -88,7 +88,7 @@ class OtherOccurrenceNodeData { filePath = newFile.getParentPath() + newFile.getName(); typeStr = FILE_TYPE_STR; - this.type = null; // We can't make the Type object without the central repo enabled + this.type = null; value = newFile.getMd5Hash(); known = newFile.getKnown(); comment = ""; @@ -112,6 +112,14 @@ class OtherOccurrenceNodeData { known = newKnownStatus; } + /** + * Update the comment for this node + * @param newComment The new comment + */ + void updateComment(String newComment) { + comment = newComment; + } + /** * Check if this is a central repo node. * @return true if this node was created from a central repo instance, false otherwise From 73a2f35fa26965b544251240cf65f2d614403461 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Fri, 15 Jun 2018 10:31:23 -0400 Subject: [PATCH 065/101] 3908: Use correct file url --- test/script/regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/script/regression.py b/test/script/regression.py index bf8068b4c5..e5c608d233 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -884,7 +884,7 @@ class TestConfiguration(object): linkFile = open(os.path.join(self.args.diff_files_output_folder, OUTPUT_DIR_LINK_FILE), "a") index = self.output_dir.find("\\") - linkStr = "file://" + linkStr = "file:\\" linkOutputDir = self.output_dir[index+2:].replace("//", "/").replace("\\\\", "\\") if index == 0: linkStr = linkStr + linkOutputDir From 0766f645a6aeac9cacd221ed1d848bf9e24d8743 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 15 Jun 2018 10:59:56 -0400 Subject: [PATCH 066/101] Codacy --- .../contentviewer/OtherOccurrenceNodeData.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java index 03d33b3140..958068fb14 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceNodeData.java @@ -35,9 +35,9 @@ class OtherOccurrenceNodeData { // For now hard code the string for the central repo files type, since // getting it dynamically can fail. - private final String FILE_TYPE_STR = "Files"; + private static final String FILE_TYPE_STR = "Files"; - private String caseName; + private final String caseName; private String deviceID; private String dataSourceName; private final String filePath; @@ -83,7 +83,7 @@ class OtherOccurrenceNodeData { deviceID = dataSource.getDeviceId(); dataSourceName = dataSource.getName(); } catch (TskDataException | TskCoreException ex) { - throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId()); + throw new EamDbException("Error loading data source for abstract file ID " + newFile.getId(), ex); } filePath = newFile.getParentPath() + newFile.getName(); From 1cf2028d877bb0ec4c19754b82a029049c3b94e0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 15 Jun 2018 11:25:33 -0400 Subject: [PATCH 067/101] Save the current comment to prevent having to return null if the user cancels. Fix a few deprecated function calls. --- .../AddEditCentralRepoCommentAction.java | 8 ++++---- .../CentralRepoCommentDialog.java | 17 ++++++++++++----- .../DataContentViewerOtherCases.java | 6 ++---- .../datamodel/AbstractSqlEamDb.java | 2 +- .../datamodel/PostgresEamDbSettings.java | 2 +- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java index e3f2899278..dfeb4fbdcd 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/AddEditCentralRepoCommentAction.java @@ -84,9 +84,10 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { * exists there, or a new file instance will be added to the database with * the comment attached otherwise. * - * The new comment is returned in case it is needed to update the display + * The current comment for this instance is returned in case it is needed to + * update the display. * - * @return the newly added comment or null if it was not updated + * @return the current comment for this instance */ public String addEditCentralRepoComment() { CentralRepoCommentDialog centralRepoCommentDialog = new CentralRepoCommentDialog(correlationAttribute, title); @@ -106,9 +107,8 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction { } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); } - return centralRepoCommentDialog.getComment(); } - return null; + return centralRepoCommentDialog.getComment(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java index 35e005daed..c95b0bfde7 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/CentralRepoCommentDialog.java @@ -31,7 +31,7 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { private final CorrelationAttribute correlationAttribute; private boolean commentUpdated = false; - private String comment = ""; + private String currentComment = ""; /** * Create an instance. @@ -45,6 +45,11 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { initComponents(); CorrelationAttributeInstance instance = correlationAttribute.getInstances().get(0); + + // Store the original comment + if (instance.getComment() != null) { + currentComment = instance.getComment(); + } pathLabel.setText(instance.getFilePath()); commentTextArea.setText(instance.getComment()); @@ -74,11 +79,13 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { } /** - * Get the newly added comment + * Get the current comment. + * If the user hit OK, this will be the new comment. + * If the user canceled, this will be the original comment. * @return the comment */ String getComment() { - return comment; + return currentComment; } /** @@ -177,8 +184,8 @@ final class CentralRepoCommentDialog extends javax.swing.JDialog { }//GEN-LAST:event_cancelButtonActionPerformed private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - comment = commentTextArea.getText(); - correlationAttribute.getInstances().get(0).setComment(comment); + currentComment = commentTextArea.getText(); + correlationAttribute.getInstances().get(0).setComment(currentComment); commentUpdated = true; dispose(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index de0c6467c8..15ed21d753 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -120,10 +120,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { OtherOccurrenceNodeData selectedNode = (OtherOccurrenceNodeData) tableModel.getRow(otherCasesTable.getSelectedRow()); AddEditCentralRepoCommentAction action = AddEditCentralRepoCommentAction.createAddEditCommentAction(selectedNode.createCorrelationAttribute()); - String newComment = action.addEditCentralRepoComment(); - if (newComment != null) { - selectedNode.updateComment(newComment); - } + String currentComment = action.addEditCentralRepoComment(); + selectedNode.updateComment(currentComment); otherCasesTable.repaint(); } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error performing Add/Edit Comment action", ex); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index 45bc197283..59df6b7bbc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -1772,7 +1772,7 @@ abstract class AbstractSqlEamDb implements EamDb { } catch (SQLException ex) { throw new EamDbException("Error getting all artifact instances from instances table", ex); } finally { - EamDbUtil.closePreparedStatement(preparedStatement); + EamDbUtil.closeStatement(preparedStatement); EamDbUtil.closeResultSet(resultSet); EamDbUtil.closeConnection(conn); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java index 1154da273d..32de4e7646 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDbSettings.java @@ -221,7 +221,7 @@ public final class PostgresEamDbSettings { LOGGER.log(Level.SEVERE, "Failed to execute database existance query.", ex); // NON-NLS return false; } finally { - EamDbUtil.closePreparedStatement(ps); + EamDbUtil.closeStatement(ps); EamDbUtil.closeResultSet(rs); EamDbUtil.closeConnection(conn); } From bc3737659f47711ec1da955323979216e85bf978 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 15 Jun 2018 11:27:56 -0400 Subject: [PATCH 068/101] Provided missing message. --- .../sleuthkit/autopsy/casemodule/LocalFilesPanel.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 872c647a94..ef985db2dd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -279,7 +279,10 @@ final class LocalFilesPanel extends javax.swing.JPanel { * * @param paths Absolute paths to the selected data source */ - @NbBundle.Messages("LocalFilesPanel.pathValidation.error=WARNING: Exception while gettting opon case.") + @NbBundle.Messages({ + "LocalFilesPanel.pathValidation.dataSourceOnCDriveError=Warning: Path to multi-user data source is on \"C:\" drive", + "LocalFilesPanel.pathValidation.getOpenCase=WARNING: Exception while gettting open case." + }) private void warnIfPathIsInvalid(final List pathsList) { errorLabel.setVisible(false); @@ -289,13 +292,13 @@ final class LocalFilesPanel extends javax.swing.JPanel { for (String currentPath : pathsList) { if (!PathValidator.isValid(currentPath, currentCaseType)) { errorLabel.setVisible(true); - errorLabel.setText(NbBundle.getMessage(this.getClass(), "DataSourceOnCDriveError.text")); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_dataSourceOnCDriveError()); return; } } } catch (NoCurrentCaseException ex) { errorLabel.setVisible(true); - errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_error()); + errorLabel.setText(Bundle.LocalFilesPanel_pathValidation_getOpenCase()); } } From 3418e12ccc97263848c2dfd69529b2dde9f0e842 Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Fri, 15 Jun 2018 11:44:54 -0400 Subject: [PATCH 069/101] added try-catch for exception in jython module loader --- .../sleuthkit/autopsy/python/JythonModuleLoader.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java index b50742f197..cd6140cf0f 100644 --- a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java +++ b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java @@ -70,8 +70,12 @@ public final class JythonModuleLoader { private static List getInterfaceImplementations(LineFilter filter, Class interfaceClass) { List objects = new ArrayList<>(); Set pythonModuleDirs = new HashSet<>(); - PythonInterpreter interpreter = new PythonInterpreter(); - + PythonInterpreter interpreter = null; + try { + interpreter = new PythonInterpreter(); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Failed to load python Intepreter. Cannot load python modules", ex); + } // add python modules from 'autospy/build/cluster/InternalPythonModules' folder // which are copied from 'autopsy/*/release/InternalPythonModules' folders. for (File f : InstalledFileLocator.getDefault().locateAll("InternalPythonModules", "org.sleuthkit.autopsy.core", false)) { //NON-NLS @@ -91,7 +95,9 @@ public final class JythonModuleLoader { if (line.startsWith("class ") && filter.accept(line)) { //NON-NLS String className = line.substring(6, line.indexOf("(")); try { - objects.add(createObjectFromScript(interpreter, script, className, interfaceClass)); + if (interpreter != null) { + objects.add(createObjectFromScript(interpreter, script, className, interfaceClass)); + } } catch (Exception ex) { logger.log(Level.SEVERE, String.format("Failed to load %s from %s", className, script.getAbsolutePath()), ex); //NON-NLS // NOTE: using ex.toString() because the current version is always returning null for ex.getMessage(). From fb20a28bc418321b5ca266ec7055d8ebf40c993f Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 15 Jun 2018 11:57:06 -0400 Subject: [PATCH 070/101] Changed IDs for the four CommonFiles VHD images. --- Core/build.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/build.xml b/Core/build.xml index 6cba803539..1ad7bacc14 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -96,10 +96,10 @@ - - - - + + + +
From 711e8d06e09177d2ebbeadde48e49cebbfe2cc24 Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Fri, 15 Jun 2018 13:27:34 -0400 Subject: [PATCH 071/101] added message notification to exception handing --- .../org/sleuthkit/autopsy/python/JythonModuleLoader.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java index cd6140cf0f..8afd8e8d4f 100644 --- a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java +++ b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java @@ -33,8 +33,10 @@ import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.python.util.PythonInterpreter; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.report.GeneralReportModule; @@ -66,15 +68,19 @@ public final class JythonModuleLoader { public static List getGeneralReportModules() { return getInterfaceImplementations(new GeneralReportModuleDefFilter(), GeneralReportModule.class); } - + @Messages({"JythonModuleLoader.pythonInterpreterError.title=Python Modules", + "JythonModuleLoader.pythonInterpreterError.msg=Failed to load python modules, See log for more details"}) private static List getInterfaceImplementations(LineFilter filter, Class interfaceClass) { List objects = new ArrayList<>(); Set pythonModuleDirs = new HashSet<>(); PythonInterpreter interpreter = null; + // This method has previously thrown unchecked exceptions when it could not load because of non-latin characters. try { interpreter = new PythonInterpreter(); } catch (Exception ex) { logger.log(Level.SEVERE, "Failed to load python Intepreter. Cannot load python modules", ex); + MessageNotifyUtil.Notify.show(Bundle.JythonModuleLoader_pythonInterpreterError_title(),Bundle.JythonModuleLoader_pythonInterpreterError_msg(), MessageNotifyUtil.MessageType.ERROR); + return objects; } // add python modules from 'autospy/build/cluster/InternalPythonModules' folder // which are copied from 'autopsy/*/release/InternalPythonModules' folders. From c4d09ebdb024c71dcc5fde4e6af1b1b44245462f Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 15 Jun 2018 16:01:21 -0400 Subject: [PATCH 072/101] Addressed review comments. --- .../casemodule/services/TagsManager.java | 141 ++---------------- .../datamodel/AbstractContentChildren.java | 2 +- ...y.java => AutopsyTreeChildrenFactory.java} | 22 +-- .../autopsy/datamodel/Bundle.properties | 3 +- .../autopsy/datamodel/Bundle_ja.properties | 1 + .../autopsy/datamodel/DataSourceGrouping.java | 11 +- .../datamodel/DataSourceGroupingChildren.java | 101 ------------- .../datamodel/DataSourceGroupingNode.java | 4 +- .../autopsy/datamodel/DataSourcesNode.java | 4 +- .../autopsy/datamodel/ExtractedContent.java | 8 +- .../DirectoryTreeTopComponent.java | 16 +- .../directorytree/ViewContextAction.java | 2 +- 12 files changed, 43 insertions(+), 272 deletions(-) rename Core/src/org/sleuthkit/autopsy/datamodel/{RootContentChildrenFactory.java => AutopsyTreeChildrenFactory.java} (85%) delete mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 0c5d7727d0..f094d2623b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -173,44 +173,18 @@ public class TagsManager implements Closeable { /** * Selects all of the rows from the tag_names table in the case database for * which there is at least one matching row in the content_tags or - * blackboard_artifact_tags tables, for the given datasource object id. + * blackboard_artifact_tags tables, for the given data source object id. * + * @param dsObjId data source object id + * * @return A list, possibly empty, of TagName data transfer objects (DTOs) * for the rows. * * @throws TskCoreException */ public List getTagNamesInUse(long dsObjId) throws TskCoreException { - - - ArrayList tagNames = new ArrayList<>(); - String queryStr = "SELECT * FROM tag_names " - + "WHERE tag_name_id IN " - + "( SELECT content_tags.tag_name_id as tag_name_id " - + "FROM content_tags as content_tags, tsk_files as tsk_files" - + " WHERE content_tags.obj_id = tsk_files.obj_id" - + " AND tsk_files.data_source_obj_id = " + dsObjId - + " UNION " - + "SELECT artifact_tags.tag_name_id as tag_name_id " - + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " - + " WHERE artifact_tags.artifact_id = arts.artifact_id" - + " AND arts.data_source_obj_id = " + dsObjId - + " )"; - - try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { - ResultSet resultSet = query.getResultSet(); - while (resultSet.next()) { - tagNames.add(new TagName(resultSet.getLong("tag_name_id"), resultSet.getString("display_name"), - resultSet.getString("description"), TagName.HTML_COLOR.getColorByName(resultSet.getString("color")), - TskData.FileKnown.valueOf(resultSet.getByte("knownStatus")))); //NON-NLS - } - return tagNames; - } catch (SQLException | TskCoreException ex) { - throw new TskCoreException("Failed to get tag names in use for data source objID : " + dsObjId, ex); - } - + return caseDb.getTagNamesInUse(dsObjId); } - /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current @@ -461,29 +435,7 @@ public class TagsManager implements Closeable { * the case database. */ public long getContentTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { - - if (tagName.getId() == Tag.ID_NOT_SET) { - throw new TskCoreException("TagName object is invalid, id not set"); - } - - String queryStr = - "SELECT COUNT(*) AS count " - + " FROM content_tags as content_tags, tsk_files as tsk_files " - + " WHERE content_tags.obj_id = tsk_files.obj_id" - + " AND tsk_files.data_source_obj_id = " + dsObjId - + " AND content_tags.tag_name_id = " + tagName.getId(); - - try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { - ResultSet resultSet = query.getResultSet(); - if (resultSet.next()) { - return resultSet.getLong("count"); - } else { - throw new TskCoreException("Error getting content_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId ); - } - } catch (SQLException | TskCoreException ex) { - throw new TskCoreException("Failed to get content_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); - } - + return caseDb.getContentTagsCountByTagName(tagName, dsObjId); } /** @@ -520,7 +472,7 @@ public class TagsManager implements Closeable { * * @param tagName The tag name of interest. * - * @param dsObjId + * @param dsObjId data source object id * * @return A list, possibly empty, of the content tags with the specified * tag name, and for the given data source. @@ -529,32 +481,7 @@ public class TagsManager implements Closeable { * case database. */ public List getContentTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { - - if (tagName.getId() == Tag.ID_NOT_SET) { - throw new TskCoreException("TagName object is invalid, id not set"); - } - - String queryStr = - "SELECT * " - + " FROM content_tags as content_tags, tsk_files as tsk_files " - + " WHERE content_tags.obj_id = tsk_files.obj_id" - + " AND tsk_files.data_source_obj_id = " + dsObjId - + " AND content_tags.tag_name_id = " + tagName.getId(); - - try (CaseDbQuery query = caseDb.executeQuery(queryStr);) { - ResultSet resultSet = query.getResultSet(); - ArrayList tags = new ArrayList<>(); - while (resultSet.next()) { - ContentTag tag = new ContentTag(resultSet.getLong("tag_id"), caseDb.getContentById(resultSet.getLong("obj_id")), - tagName, resultSet.getString("comment"), resultSet.getLong("begin_byte_offset"), resultSet.getLong("end_byte_offset")); //NON-NLS - tags.add(tag); - } - resultSet.close(); - return tags; - } catch (SQLException | TskCoreException ex) { - throw new TskCoreException("Failed to get content_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); - } - + return caseDb.getContentTagsByTagName(tagName, dsObjId); } /** @@ -664,7 +591,7 @@ public class TagsManager implements Closeable { * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. - * @param dsObjId + * @param dsObjId data source object id * * @return A count of the artifact tags with the specified tag name, * for the given data source. @@ -673,28 +600,7 @@ public class TagsManager implements Closeable { * the case database. */ public long getBlackboardArtifactTagsCountByTagName(TagName tagName, long dsObjId) throws TskCoreException { - if (tagName.getId() == Tag.ID_NOT_SET) { - throw new TskCoreException("TagName object is invalid, id not set"); - } - - String queryStr = "SELECT COUNT(*) AS count " - + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " - + " WHERE artifact_tags.artifact_id = arts.artifact_id" - + " AND artifact_tags.tag_name_id = " + tagName.getId() - + " AND arts.data_source_obj_id = " + dsObjId - ; - - try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(queryStr);) { - ResultSet resultSet = query.getResultSet(); - if (resultSet.next()) { - return resultSet.getLong("count"); - } else { - throw new TskCoreException("Error getting blackboard_artifact_tags row count for tag name (tag_name_id = " + tagName.getId() + ")" + " for dsObjId = " + dsObjId); - } - } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { - throw new TskCoreException("Failed to get blackboard_artifact_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); - } - + return caseDb.getBlackboardArtifactTagsCountByTagName(tagName, dsObjId); } /** @@ -734,7 +640,7 @@ public class TagsManager implements Closeable { * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. - * @param dsObjId + * @param dsObjId data source object id * * @return A list, possibly empty, of the artifact tags with the specified * tag name, for the specified data source. @@ -743,32 +649,7 @@ public class TagsManager implements Closeable { * case database. */ public List getBlackboardArtifactTagsByTagName(TagName tagName, long dsObjId) throws TskCoreException { - if (tagName.getId() == Tag.ID_NOT_SET) { - throw new TskCoreException("TagName object is invalid, id not set"); - } - - String queryStr = "SELECT * " - + " FROM blackboard_artifact_tags as artifact_tags, blackboard_artifacts AS arts " - + " WHERE artifact_tags.artifact_id = arts.artifact_id" - + " AND artifact_tags.tag_name_id = " + tagName.getId() - + " AND arts.data_source_obj_id = " + dsObjId - ; - - try (CaseDbQuery query = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(queryStr);) { - ResultSet resultSet = query.getResultSet(); - ArrayList tags = new ArrayList<>(); - while (resultSet.next()) { - BlackboardArtifact artifact = caseDb.getBlackboardArtifact(resultSet.getLong("artifact_id")); //NON-NLS - Content content = caseDb.getContentById(artifact.getObjectID()); - BlackboardArtifactTag tag = new BlackboardArtifactTag(resultSet.getLong("tag_id"), - artifact, content, tagName, resultSet.getString("comment")); //NON-NLS - tags.add(tag); - } - return tags; - } catch (SQLException | TskCoreException | NoCurrentCaseException ex) { - throw new TskCoreException("Failed to get blackboard_artifact_tags row count for tag_name_id = " + tagName.getId() + "data source objID : " + dsObjId, ex); - } - + return caseDb.getBlackboardArtifactTagsByTagName(tagName, dsObjId); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 9d47861786..71705d2725 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -202,7 +202,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(DataSourceGrouping datasourceGrouping) { - return new DataSourceGroupingNode(datasourceGrouping.getgDataSource()); + return new DataSourceGroupingNode(datasourceGrouping.getDataSource()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java similarity index 85% rename from Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java rename to Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java index 276f4c3f17..cc3b8cb8ae 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RootContentChildrenFactory.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyTreeChildrenFactory.java @@ -24,8 +24,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.List; -import java.util.Observable; -import java.util.Observer; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; @@ -39,19 +37,19 @@ import org.sleuthkit.datamodel.TskCoreException; /** - * Child factory to create the top level children of the root of the directory tree + * Child factory to create the top level children of the autopsy tree * */ -public class RootContentChildrenFactory extends ChildFactory.Detachable implements Observer { +public class AutopsyTreeChildrenFactory extends ChildFactory.Detachable { - private static final Logger logger = Logger.getLogger(RootContentChildrenFactory.class.getName()); + private static final Logger logger = Logger.getLogger(AutopsyTreeChildrenFactory.class.getName()); private final SleuthkitCase tskCase; /** - * Constructs the child factory for root nodes + * Constructs the child factory * @param tskCase */ - public RootContentChildrenFactory(SleuthkitCase tskCase) { + public AutopsyTreeChildrenFactory(SleuthkitCase tskCase) { this.tskCase = tskCase; } @@ -81,15 +79,11 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); } - @Override - public void update(Observable observable, Object arg) { - refresh(true); - } - /** * Creates keys for the top level children. * * @param list list of keys created + * @return true, indicating that the key list is complete */ @Override protected boolean createKeys(List list) { @@ -99,7 +93,7 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable List dataSources = tskCase.getDataSources(); List keys = new ArrayList<>(); dataSources.forEach((datasource) -> { - keys.add(new DataSourceGrouping(tskCase, datasource)); + keys.add(new DataSourceGrouping(datasource)); }); list.addAll(keys); @@ -117,7 +111,7 @@ public class RootContentChildrenFactory extends ChildFactory.Detachable } } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, "Error getting datas sources list form the database.", tskCoreException); + logger.log(Level.SEVERE, "Error getting datas sources list from the database.", tskCoreException); } return true; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index f392d406d3..374c96069b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -55,7 +55,8 @@ DataModelActionsFactory.fileInDir.text=View File in Directory DataModelActionsFactory.viewNewWin.text=View in New Window DataModelActionsFactory.openExtViewer.text=Open in External Viewer DataModelActionsFactory.srfFileSameMD5.text=Search for files with the same MD5 hash -DataSourcesNode.name=Data Source Files +DataSourcesNode.name=Data Sources +DataSourcesNode.group_by_datasource.name=Data Source Files DataSourcesNode.createSheet.name.name=Name DataSourcesNode.createSheet.name.displayName=Name DataSourcesNode.createSheet.name.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties index 3d20fded35..979c4d50cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle_ja.properties @@ -57,6 +57,7 @@ DataModelActionsFactory.viewNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30 DataModelActionsFactory.openExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u306b\u8868\u793a DataModelActionsFactory.srfFileSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22 DataSourcesNode.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9 +DataSourcesNode.group_by_datasource.name=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb DataSourcesNode.createSheet.name.name=\u540d\u524d DataSourcesNode.createSheet.name.displayName=\u540d\u524d DataSourcesNode.createSheet.name.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java index 360ee735ce..91ae285c81 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGrouping.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.datamodel; import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.SleuthkitCase; /** * A top level UI grouping of Files, Views, Results, Tags @@ -28,15 +27,13 @@ import org.sleuthkit.datamodel.SleuthkitCase; */ public class DataSourceGrouping implements AutopsyVisitableItem { - private final SleuthkitCase skCase; private final DataSource dataSource; - public DataSourceGrouping(SleuthkitCase skCase, DataSource dataSource) { - this.skCase = skCase; + public DataSourceGrouping(DataSource dataSource) { this.dataSource = dataSource; } - DataSource getgDataSource() { + DataSource getDataSource() { return this.dataSource; } @@ -45,8 +42,4 @@ public class DataSourceGrouping implements AutopsyVisitableItem { return visitor.visit(this); } - public SleuthkitCase getSleuthkitCase() { - return skCase; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java deleted file mode 100644 index 3463d807dc..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingChildren.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 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.datamodel; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.logging.Level; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Child factory for Data Source grouping nodes - * A node is created for each data source in the case - * - */ -public class DataSourceGroupingChildren extends Children.Keys { - - private static final Logger logger = Logger.getLogger(DataSourceGroupingChildren.class.getName()); - private final SleuthkitCase sleuthkitCase; - - - - /** - * Constructs the factory object - * - * @param tskCase - Case DB - */ - public DataSourceGroupingChildren(SleuthkitCase tskCase) { - this.sleuthkitCase = tskCase; - } - - /** - * Listener for handling DATA_SOURCE_ADDED events. - */ - private final PropertyChangeListener pcl = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { - reloadKeys(); - } - } - }; - - private void reloadKeys() { - try { - List keys = sleuthkitCase.getDataSources(); - setKeys(keys); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get Datasources from DB", ex); - } - } - - @Override - protected void addNotify() { - super.addNotify(); - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); - reloadKeys(); - } - - @Override - protected void removeNotify() { - super.removeNotify(); - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl); - setKeys(Collections.emptyList()); - } - - @Override - protected Node[] createNodes(DataSource datasource) { - return new Node[]{createNodeForKey(datasource)}; - } - - protected Node createNodeForKey(DataSource datasource) { - return new DataSourceGroupingNode(datasource); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java index 43054427a3..4c8ee90e7c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourceGroupingNode.java @@ -34,12 +34,12 @@ import org.sleuthkit.datamodel.LocalFilesDataSource; * Data source grouping node - an optional grouping node in the data tree view * */ -public class DataSourceGroupingNode extends DisplayableItemNode { +class DataSourceGroupingNode extends DisplayableItemNode { private static final Logger logger = Logger.getLogger(DataSourceGroupingNode.class.getName()); /** - * Creates the Data source grouping node for the given data source, + * Creates a data source grouping node for the given data source. * * @param dataSource specifies the data source */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 8441b8aaad..d4e0ebf261 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -42,6 +42,7 @@ import org.sleuthkit.datamodel.TskDataException; public class DataSourcesNode extends DisplayableItemNode { public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name"); + private final String displayName; // NOTE: The images passed in via argument will be ignored. @Deprecated @@ -55,12 +56,13 @@ public class DataSourcesNode extends DisplayableItemNode { public DataSourcesNode(long dsObjId) { super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME)); + displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME; init(); } private void init() { setName(NAME); - setDisplayName(NAME); + setDisplayName(displayName); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 409b730345..366aa12979 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -81,7 +81,7 @@ public class ExtractedContent implements AutopsyVisitableItem { public ExtractedContent(SleuthkitCase skCase, long objId) { this.skCase = skCase; this.datasourceObjId = objId; - this.blackboard = new Blackboard(skCase); + this.blackboard = skCase.getBlackboard(); } @Override @@ -292,7 +292,7 @@ public class ExtractedContent implements AutopsyVisitableItem { if (skCase != null) { try { List types = (UserPreferences.groupItemsInTreeByDatasource()) ? - blackboard.getArtifactTypesInUseByDataSource(datasourceObjId) : + blackboard.getArtifactTypesInUse(datasourceObjId) : skCase.getArtifactTypesInUse() ; types.removeAll(doNotShow); @@ -357,7 +357,7 @@ public class ExtractedContent implements AutopsyVisitableItem { // "getBlackboardArtifactCount()" method to skCase try { this.childCount = UserPreferences.groupItemsInTreeByDatasource() ? - blackboard.getBlackboardArtifactsCountByDataSource(type.getTypeID(), datasourceObjId) : + blackboard.getArtifactsCount(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) @@ -482,7 +482,7 @@ public class ExtractedContent implements AutopsyVisitableItem { try { List arts = UserPreferences.groupItemsInTreeByDatasource() ? - blackboard.getBlackboardArtifactsByDataSource(type.getTypeID(), datasourceObjId) : + blackboard.getArtifacts(type.getTypeID(), datasourceObjId) : skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 596552262d..33977d5904 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -73,7 +73,7 @@ import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.ResultsNode; -import org.sleuthkit.autopsy.datamodel.RootContentChildrenFactory; +import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildrenFactory; import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.BINRange; @@ -102,8 +102,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS private static final Logger LOGGER = Logger.getLogger(DirectoryTreeTopComponent.class.getName()); - private RootContentChildrenFactory rootChildrenFactory; - private Children contentChildren; + private AutopsyTreeChildrenFactory autopsyTreeChildrenFactory; + private Children autopsyTreeChildren; /** * the constructor @@ -397,9 +397,9 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // if there's at least one image, load the image and open the top component final SleuthkitCase tskCase = currentCase.getSleuthkitCase(); - rootChildrenFactory = new RootContentChildrenFactory(tskCase); - contentChildren = Children.create(rootChildrenFactory, true); - Node root = new AbstractNode(contentChildren) { + autopsyTreeChildrenFactory = new AutopsyTreeChildrenFactory(tskCase); + autopsyTreeChildren = Children.create(autopsyTreeChildrenFactory, true); + Node root = new AbstractNode(autopsyTreeChildren) { //JIRA-2807: What is the point of these overrides? /** * to override the right click action in the white blank space @@ -512,7 +512,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat @Override public void componentClosed() { //@@@ push the selection node to null? - contentChildren = null; + autopsyTreeChildren = null; } void writeProperties(java.util.Properties p) { @@ -808,7 +808,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private void rebuildTree() { // refresh all children of the root. - rootChildrenFactory.refreshChildren(); + autopsyTreeChildrenFactory.refreshChildren(); // Select the first node and reset the selection history // This should happen on the EDT once the tree has been rebuilt. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 9798216b9b..f4da3a3edf 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -147,7 +147,7 @@ public class ViewContextAction extends AbstractAction { try { // get the objid/name of the datasource of the selected content. skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - long contentDSObjid = skCase.getDataSourceObjectId(content.getId()); + long contentDSObjid = content.getDataSource().getId(); DataSource datasource = skCase.getDataSource(contentDSObjid); dsname = datasource.getName(); From a1a73786d85c547247a8b5603b7352842df4ba43 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 15 Jun 2018 16:12:25 -0400 Subject: [PATCH 073/101] Remove unused imports --- .../sleuthkit/autopsy/casemodule/services/TagsManager.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index f094d2623b..d80feed87a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -20,8 +20,6 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -38,8 +36,6 @@ import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery; -import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; From e5807925f3b2231970e2ce6be8a955f3469bd70e Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 15 Jun 2018 18:40:52 -0400 Subject: [PATCH 074/101] 3848 Create interesting artifact and short circuit archive extraction when zip bomb --- .../SevenZipExtractor.java | 109 +++++++++++++----- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index eb31e7d30a..4cda7f0709 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -161,7 +161,7 @@ class SevenZipExtractor { * * @return true if potential zip bomb, false otherwise */ - private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) { + private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem, ConcurrentHashMap depthMap, String escapedFilePath) { try { final Long archiveItemSize = archiveFileItem.getSize(); @@ -180,20 +180,12 @@ class SevenZipExtractor { int cRatio = (int) (archiveItemSize / archiveItemPackedSize); if (cRatio >= MAX_COMPRESSION_RATIO) { - String itemName = archiveFileItem.getPath(); - logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName); - String path; - try { - path = archiveFile.getUniquePath(); - } catch (TskCoreException ex) { - path = archiveFile.getParentPath() + archiveFile.getName(); - } + Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId()); String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", + cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath); return true; } else { return false; @@ -205,6 +197,36 @@ class SevenZipExtractor { } } + private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) { + rootArchive.flagAsZipBomb(); + logger.log(Level.INFO, details); //NON-NLS + String msg = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), escapedFilePath); + try { + BlackboardArtifact artifact = rootArchive.getArchiveFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, EmbeddedFileExtractorModuleFactory.getModuleName(), + "Possible Zip Bomb")); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, + EmbeddedFileExtractorModuleFactory.getModuleName(), + Bundle.SevenZipExtractor_zipBombArtifactCreation_text(archiveFile.getName()))); + artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, + EmbeddedFileExtractorModuleFactory.getModuleName(), + details)); + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error( + Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName()); + } + services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS + } + services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + } + /** * Check file extension and return appropriate input options for * SevenZip.openInArchive() @@ -476,7 +498,9 @@ class SevenZipExtractor { * * @return true if unpacking is complete */ - @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."}) + @Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search.", + "# {0} - rootArchive", + "SevenZipExtractor.zipBombArtifactCreation.text=Zip Bomb Detected {0}"}) boolean unpack(AbstractFile archiveFile, ConcurrentHashMap depthMap, String password) { boolean unpackSuccessful = true; //initialized to true change to false if any files fail to extract and boolean hasEncrypted = false; @@ -518,18 +542,22 @@ class SevenZipExtractor { } parentAr = depthMap.get(archiveId); if (parentAr == null) { - parentAr = new Archive(archiveId, 0); + parentAr = new Archive(archiveId, 0, archiveId, archiveFile); depthMap.put(archiveId, parentAr); - } else if (parentAr.getDepth() == MAX_DEPTH) { - String msg = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName()); - String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", - parentAr.getDepth(), escapedArchiveFilePath); - //MessageNotifyUtil.Notify.error(msg, details); - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); - unpackSuccessful = false; - return unpackSuccessful; + } else { + Archive rootArchive = depthMap.get(parentAr.getRootArchiveId()); + if (rootArchive.isFlaggedAsZipBomb()) { + //skip this archive as the root archive has already been determined to contain a zip bomb + unpackSuccessful = false; + return unpackSuccessful; + } else if (parentAr.getDepth() == MAX_DEPTH) { + String details = NbBundle.getMessage(SevenZipExtractor.class, + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb", + parentAr.getDepth(), FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); + flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedArchiveFilePath); + unpackSuccessful = false; + return unpackSuccessful; + } } try { stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile)); @@ -580,7 +608,7 @@ class SevenZipExtractor { ++itemNumber; //check if possible zip bomb - if (isZipBombArchiveItemCheck(archiveFile, item)) { + if (isZipBombArchiveItemCheck(archiveFile, item, depthMap, escapedArchiveFilePath)) { unpackSuccessful = false; return unpackSuccessful; } @@ -668,7 +696,7 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - Archive child = new Archive(unpackedFile.getId(), parentAr.getDepth() + 1); + Archive child = new Archive(unpackedFile.getId(), parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); parentAr.addChild(child); depthMap.put(unpackedFile.getId(), child); } @@ -1214,6 +1242,9 @@ class SevenZipExtractor { private final int depth; private final long objectId; private final List children; + private final long rootArchiveId; + private boolean flaggedAsZipBomb = false; + private AbstractFile archiveFile; /** * Create a new Archive object. @@ -1224,10 +1255,12 @@ class SevenZipExtractor { * increase as unpack recurses down through archives * contained within */ - Archive(long objectId, int depth) { + Archive(long objectId, int depth, long rootArchiveId, AbstractFile archiveFile) { this.objectId = objectId; this.children = new ArrayList<>(); this.depth = depth; + this.rootArchiveId = rootArchiveId; + this.archiveFile = archiveFile; } /** @@ -1240,6 +1273,22 @@ class SevenZipExtractor { children.add(child); } + void flagAsZipBomb() { + flaggedAsZipBomb = true; + } + + boolean isFlaggedAsZipBomb() { + return flaggedAsZipBomb; + } + + AbstractFile getArchiveFile() { + return archiveFile; + } + + long getRootArchiveId() { + return rootArchiveId; + } + /** * Get the object id of this archive. * @@ -1285,7 +1334,7 @@ class SevenZipExtractor { /** * Get the AbstractFile contained in this object * - * @return abstractFile - The abstractFile this object wraps + * @return archiveFile - The archiveFile this object wraps */ private AbstractFile getFile() { return abstractFile; From ab83c9940c6dfd7d8312b994e900db02977d816f Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 17 Jun 2018 23:15:23 -0400 Subject: [PATCH 075/101] reduce API --- .../imagegallery/datamodel/DrawableDB.java | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index fa008fe3e9..a5092614a7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -847,35 +847,35 @@ public final class DrawableDB { * * @throws TskCoreException */ - @Deprecated - public long countFiles() throws TskCoreException { - Statement statement = null; - ResultSet rs = null; - dbReadLock(); - try { - statement = con.createStatement(); - rs = statement.executeQuery("SELECT COUNT (*) FROM drawable_files"); //NON-NLS - return rs.getLong(1); - } catch (SQLException e) { - throw new TskCoreException("SQLException thrown when calling 'DrawableDB.countFiles(): ", e); - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing result set after executing countFiles", ex); //NON-NLS - } - } - if (statement != null) { - try { - statement.close(); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing statement after executing countFiles", ex); //NON-NLS - } - } - dbReadUnlock(); - } - } +// @Deprecated +// public long countFiles() throws TskCoreException { +// Statement statement = null; +// ResultSet rs = null; +// dbReadLock(); +// try { +// statement = con.createStatement(); +// rs = statement.executeQuery("SELECT COUNT (*) FROM drawable_files"); //NON-NLS +// return rs.getLong(1); +// } catch (SQLException e) { +// throw new TskCoreException("SQLException thrown when calling 'DrawableDB.countFiles(): ", e); +// } finally { +// if (rs != null) { +// try { +// rs.close(); +// } catch (SQLException ex) { +// LOGGER.log(Level.SEVERE, "Error closing result set after executing countFiles", ex); //NON-NLS +// } +// } +// if (statement != null) { +// try { +// statement.close(); +// } catch (SQLException ex) { +// LOGGER.log(Level.SEVERE, "Error closing statement after executing countFiles", ex); //NON-NLS +// } +// } +// dbReadUnlock(); +// } +// } /** * @@ -952,7 +952,12 @@ public final class DrawableDB { return vals; } - public void insertGroup(final String value, DrawableAttribute groupBy) { + /** + * Insert new group into DB + * @param value Value of the group (unique to the type) + * @param groupBy Type of the grouping (CATEGORY, MAKE, etc.) + */ + private void insertGroup(final String value, DrawableAttribute groupBy) { dbWriteLock(); try { @@ -980,15 +985,15 @@ public final class DrawableDB { * @throws TskCoreException if unable to get a file from the currently open * {@link SleuthkitCase} */ - private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException { - try { - AbstractFile f = tskCase.getAbstractFileById(id); - return DrawableFile.create(f, analyzed, isVideoFile(f)); - } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex); //NON-NLS - return null; - } - } +// private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException { +// try { +// AbstractFile f = tskCase.getAbstractFileById(id); +// return DrawableFile.create(f, analyzed, isVideoFile(f)); +// } catch (IllegalStateException ex) { +// LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex); //NON-NLS +// return null; +// } +// } /** * @param id the obj_id of the file to return From 2d95f87a81b9f3fc654a127a15f5fa172b8e4187 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 08:01:08 -0400 Subject: [PATCH 076/101] Add additional logging for auto ingest dashboard commands --- .../AutoIngestCaseDeletedEvent.java | 9 +++- .../AutoIngestCasePrioritizedEvent.java | 45 ++++++++++++++++++- .../autoingest/AutoIngestJobCancelEvent.java | 14 +++++- .../AutoIngestJobReprocessEvent.java | 16 ++++++- .../autoingest/AutoIngestManager.java | 45 ++++++++++++++++--- .../autoingest/AutoIngestMonitor.java | 20 ++++++--- 6 files changed, 132 insertions(+), 17 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java index 3d3ff8951f..1a7f6314a4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCaseDeletedEvent.java @@ -31,6 +31,7 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa private static final long serialVersionUID = 1L; private final String caseName; private final String nodeName; + private final String userName; /** * Constructs an event that is published when a case is deleted by the @@ -38,11 +39,13 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa * * @param caseName The case name. * @param nodeName The host name of the node that deleted the case. + * @param userName The user that deleted the case */ - AutoIngestCaseDeletedEvent(String caseName, String nodeName) { + AutoIngestCaseDeletedEvent(String caseName, String nodeName, String userName) { super(AutoIngestManager.Event.CASE_DELETED.toString(), null, null); this.caseName = caseName; this.nodeName = nodeName; + this.userName = userName; } String getCaseName() { @@ -52,5 +55,9 @@ final class AutoIngestCaseDeletedEvent extends AutopsyEvent implements Serializa String getNodeName() { return nodeName; } + + String getUserName() { + return userName; + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 5fbf380601..2a586ae244 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -27,9 +27,20 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; */ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { + // Possible event types + enum EventType { + CASE_PRIORITIZED, + CASE_DEPRIORITIZED, + JOB_PRIORITIZED, + JOB_DEPRIORITIZED + } + private static final long serialVersionUID = 1L; private final String caseName; private final String nodeName; + private final String userName; + private final EventType eventType; + private final String dataSource; /** * Constructs an event published when an automated ingest manager @@ -37,11 +48,17 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement * * @param caseName The name of the case. * @param nodeName The host name of the node that prioritized the case. + * @param userName The logged in user + * @param eventType The type of prioritization event + * @pamam dataSource List of data sources that were prioritized/deprioritized (if it wasn't the whole case) */ - public AutoIngestCasePrioritizedEvent(String nodeName, String caseName) { + public AutoIngestCasePrioritizedEvent(String nodeName, String caseName, String userName, EventType eventType, String dataSource) { super(AutoIngestManager.Event.CASE_PRIORITIZED.toString(), null, null); this.caseName = caseName; this.nodeName = nodeName; + this.userName = userName; + this.eventType = eventType; + this.dataSource = dataSource; } /** @@ -61,5 +78,31 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement public String getNodeName() { return nodeName; } + + /** + * Gets the user logged in to the node that prioritized the case. + * + * @return The user name + */ + public String getUserName() { + return userName; + } + /** + * Gets the type of prioritization + * + * @return The type + */ + public EventType getEventType() { + return eventType; + } + + /** + * Gets the list of data sources (if applicable) + * + * @return The data sources + */ + public String getDataSources() { + return dataSource; + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java index a264a202e3..e466bf0354 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobCancelEvent.java @@ -25,8 +25,20 @@ import java.io.Serializable; */ public final class AutoIngestJobCancelEvent extends AutoIngestJobEvent implements Serializable{ private static final long serialVersionUID = 1L; + private final String nodeName; + private final String userName; - public AutoIngestJobCancelEvent(AutoIngestJob job) { + public AutoIngestJobCancelEvent(AutoIngestJob job, String nodeName, String userName) { super(AutoIngestManager.Event.CANCEL_JOB, job); + this.nodeName = nodeName; + this.userName = userName; + } + + String getNodeName() { + return nodeName; + } + + String getUserName() { + return userName; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java index 817e153c7e..a72df908a2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobReprocessEvent.java @@ -24,9 +24,21 @@ import java.io.Serializable; * Event published to reprocess an AutoIngestJob. */ public final class AutoIngestJobReprocessEvent extends AutoIngestJobEvent implements Serializable{ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; + private final String nodeName; + private final String userName; - public AutoIngestJobReprocessEvent(AutoIngestJob job) { + public AutoIngestJobReprocessEvent(AutoIngestJob job, String nodeName, String userName) { super(AutoIngestManager.Event.REPROCESS_JOB, job); + this.nodeName = nodeName; + this.userName = userName; + } + + String getNodeName() { + return nodeName; + } + + String getUserName() { + return userName; } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index e30c795013..79ea4e4bbb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -389,6 +389,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteJobCancelledEvent(AutoIngestJobCancelEvent event) { AutoIngestJob job = event.getJob(); if (job != null && job.getProcessingHostName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) { + sysLogger.log(Level.INFO, "Received cancel job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{job.getManifest().getDataSourceFileName(), job.getManifest().getCaseName(), event.getUserName(), event.getNodeName()}); if (event.getJob().equals(currentJob)) { cancelCurrentJob(); } @@ -403,6 +405,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen private void handleRemoteJobReprocessEvent(AutoIngestJobReprocessEvent event) { synchronized (jobsLock) { AutoIngestJob job = event.getJob(); + sysLogger.log(Level.INFO, "Received reprocess job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{job.getManifest().getDataSourceFileName(), job.getManifest().getCaseName(), event.getUserName(), event.getNodeName()}); if (completedJobs.contains(job)) { // Remove from completed jobs table. completedJobs.remove(job); @@ -423,6 +427,29 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param event A prioritization event from another auto ingest node. */ private void handleRemoteCasePrioritizationEvent(AutoIngestCasePrioritizedEvent event) { + switch (event.getEventType()) { + case CASE_PRIORITIZED: + sysLogger.log(Level.INFO, "Received prioritize case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case CASE_DEPRIORITIZED: + sysLogger.log(Level.INFO, "Received deprioritize case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case JOB_PRIORITIZED: + sysLogger.log(Level.INFO, "Received prioritize job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{event.getDataSources(), event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + case JOB_DEPRIORITIZED: + sysLogger.log(Level.INFO, "Received deprioritize job event for data source {0} in case {1} from user {2} on machine {3}", + new Object[]{event.getDataSources(), event.getCaseName(), event.getUserName(), event.getNodeName()}); + break; + default: + sysLogger.log(Level.WARNING, "Received invalid prioritization event from user {0} on machine {1}", + new Object[]{event.getUserName(), event.getNodeName()}); + break; + } + String hostName = event.getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); scanInputDirsNow(); @@ -437,6 +464,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * @param event A case deleted event from another auto ingest node. */ private void handleRemoteCaseDeletedEvent(AutoIngestCaseDeletedEvent event) { + sysLogger.log(Level.INFO, "Received delete case event for case {0} from user {1} on machine {2}", + new Object[]{event.getCaseName(), event.getUserName(), event.getNodeName()}); String hostName = event.getNodeName(); hostNamesToLastMsgTime.put(hostName, Instant.now()); scanInputDirsNow(); @@ -661,7 +690,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToDeprioritize.isEmpty()) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } } @@ -710,7 +740,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToPrioritize.isEmpty()) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } } @@ -760,8 +791,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (null != jobToDeprioritize) { final String caseName = jobToDeprioritize.getManifest().getCaseName(); + final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } } @@ -816,8 +849,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (null != jobToPrioritize) { final String caseName = jobToPrioritize.getManifest().getCaseName(); + final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } } @@ -970,7 +1005,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen casesToManifests.remove(caseName); } - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, System.getProperty("user.name"))); setChanged(); notifyObservers(Event.CASE_DELETED); return result; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 4e262c738a..26e774c9b7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -386,7 +386,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a deprioritization event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } return jobsSnapshot; @@ -433,7 +434,8 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a prioritization event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } return jobsSnapshot; @@ -482,8 +484,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a deprioritization event. */ final String caseName = job.getManifest().getCaseName(); + final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } @@ -538,8 +542,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a prioritization event. */ final String caseName = job.getManifest().getCaseName(); + final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName)); + eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, + System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } @@ -554,7 +560,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ void cancelJob(AutoIngestJob job) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job)); + eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job, LOCAL_HOST_NAME, System.getProperty("user.name"))); }).start(); } @@ -599,7 +605,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a reprocess event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job)); + eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job, LOCAL_HOST_NAME, System.getProperty("user.name"))); }).start(); } @@ -652,7 +658,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen completedJob.getManifest().getCaseName().equals(caseName)); // Publish a message to update auto ingest nodes. - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME)); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, System.getProperty("user.name"))); } return CaseDeletionResult.FULLY_DELETED; From fdfbcf03accc3bb5c1a8595c354a32ac078fc1d0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 08:05:35 -0400 Subject: [PATCH 077/101] Remove some public modifiers --- .../autoingest/AutoIngestCasePrioritizedEvent.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 2a586ae244..1319ea7072 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -84,7 +84,7 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement * * @return The user name */ - public String getUserName() { + String getUserName() { return userName; } @@ -93,7 +93,7 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement * * @return The type */ - public EventType getEventType() { + EventType getEventType() { return eventType; } @@ -102,7 +102,7 @@ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implement * * @return The data sources */ - public String getDataSources() { + String getDataSources() { return dataSource; } } From 59314836d5180e84c2f1ac1c6e77d31a9352780c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 08:23:36 -0400 Subject: [PATCH 078/101] codacy --- .../autoingest/AutoIngestCasePrioritizedEvent.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java index 1319ea7072..884846db71 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestCasePrioritizedEvent.java @@ -27,7 +27,9 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; */ public final class AutoIngestCasePrioritizedEvent extends AutopsyEvent implements Serializable { - // Possible event types + /** + * Possible event types + */ enum EventType { CASE_PRIORITIZED, CASE_DEPRIORITIZED, From 68831228bd652407d0ddbeb9e83ec91c3a1786a4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 18 Jun 2018 10:28:53 -0400 Subject: [PATCH 079/101] 3848 minor clean up zip bomb improvements --- .../SevenZipExtractor.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 4cda7f0709..3e40049317 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; @@ -156,7 +157,7 @@ class SevenZipExtractor { * * More heuristics to be added here * - * @param archiveName the parent archive + * @param archiveFile the parent archive * @param archiveFileItem the archive item * * @return true if potential zip bomb, false otherwise @@ -182,7 +183,7 @@ class SevenZipExtractor { if (cRatio >= MAX_COMPRESSION_RATIO) { Archive rootArchive = depthMap.get(depthMap.get(archiveFile.getId()).getRootArchiveId()); String details = NbBundle.getMessage(SevenZipExtractor.class, - "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", + "EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, FileUtil.escapeFileName(getArchiveFilePath(rootArchive.getArchiveFile()))); flagRootArchiveAsZipBomb(rootArchive, archiveFile, details, escapedFilePath); @@ -197,6 +198,15 @@ class SevenZipExtractor { } } + /** + * Flag the root archive archive as a zipbomb by creating an interesting + * file artifact and posting a message to the inbox for the user. + * + * @param rootArchive - the Archive which the artifact is to be for + * @param archiveFile - the AbstractFile which for the file which triggered the potential zip bomb to be detected + * @param details - the String which contains the details about how the potential zip bomb was detected + * @param escapedFilePath - the escaped file path for the archiveFile + */ private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) { rootArchive.flagAsZipBomb(); logger.log(Level.INFO, details); //NON-NLS @@ -224,7 +234,7 @@ class SevenZipExtractor { } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS } - services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + services.postMessage(IngestMessage.createMessage(MessageType.INFO, EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); } /** @@ -638,7 +648,6 @@ class SevenZipExtractor { escapedArchiveFilePath, item.getPath()); String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details"); - //MessageNotifyUtil.Notify.error(msg, details); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new String[]{escapedArchiveFilePath, item.getPath()}); //NON-NLS logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS From f22dc0e2cfc42779ea26180426babf9049086e09 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 18 Jun 2018 11:01:15 -0400 Subject: [PATCH 080/101] 3848 change message type for zip bomb detection back to warning --- .../modules/embeddedfileextractor/SevenZipExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 3e40049317..73455a8d9f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -234,7 +234,7 @@ class SevenZipExtractor { } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error creating blackboard artifact for Zip Bomb Detection for file: " + escapedFilePath, ex); //NON-NLS } - services.postMessage(IngestMessage.createMessage(MessageType.INFO, EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); + services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); } /** From 36de8d4daa8eb5743a4c7acafce0bfbdd7615d8b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 11:03:56 -0400 Subject: [PATCH 081/101] Only make at most one "No results found" for an ad hoc keyword list query. --- .../autopsy/keywordsearch/AdHocSearchChildFactory.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index ced485d9c6..accc19cb9a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -121,6 +121,12 @@ class AdHocSearchChildFactory extends ChildFactory { createFlatKeys(queryRequest.getQuery(), toPopulate); } + + // If there were no hits, make a single Node that will display that + // no results were found. + if (toPopulate.isEmpty()) { + toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); + } return true; } @@ -207,9 +213,7 @@ class AdHocSearchChildFactory extends ChildFactory { } - if (hitNumber == 0) { - toPopulate.add(new KeyValue("This KeyValue Is Empty", 0)); - } else { + if (hitNumber != 0) { // Add all the nodes to toPopulate at once. Minimizes node creation // EDT threads, which can slow and/or hang the UI on large queries. toPopulate.addAll(tempList); From 7e26ff6c836930696837cbc9b61e170bf20e3c04 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 18 Jun 2018 11:21:11 -0400 Subject: [PATCH 082/101] 3848 add necessary comments and clean up Archive arguements --- .../embeddedfileextractor/Bundle.properties | 2 +- .../SevenZipExtractor.java | 67 +++++++++++++------ 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties index dfda2e6061..89cf1636b7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/Bundle.properties @@ -13,7 +13,7 @@ EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFileLevel=Content-o EmbeddedFileExtractorIngestModule.ArchiveExtractor.encryptionFull=Full Encryption (Archive File) EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir\: {0}\: {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive\: {0}, item\: {1} -EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping item in {1}. +EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping items in {1}. EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected\: {0} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of {1} EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive\: {0}, will use\: {1} diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 73455a8d9f..4fb1f66e72 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -53,7 +53,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; -import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; @@ -157,8 +156,14 @@ class SevenZipExtractor { * * More heuristics to be added here * - * @param archiveFile the parent archive - * @param archiveFileItem the archive item + * @param archiveFile the AbstractFile for the parent archive which + * which we are checking + * @param archiveFileItem the current item being extracted from the parent + * archive + * @param depthMap a concurrent hashmap which keeps track of the + * depth of all nested archives, key of objectID + * @param escapedFilePath the path to the archiveFileItem which has been + * escaped * * @return true if potential zip bomb, false otherwise */ @@ -203,8 +208,10 @@ class SevenZipExtractor { * file artifact and posting a message to the inbox for the user. * * @param rootArchive - the Archive which the artifact is to be for - * @param archiveFile - the AbstractFile which for the file which triggered the potential zip bomb to be detected - * @param details - the String which contains the details about how the potential zip bomb was detected + * @param archiveFile - the AbstractFile which for the file which + * triggered the potential zip bomb to be detected + * @param details - the String which contains the details about how + * the potential zip bomb was detected * @param escapedFilePath - the escaped file path for the archiveFile */ private void flagRootArchiveAsZipBomb(Archive rootArchive, AbstractFile archiveFile, String details, String escapedFilePath) { @@ -526,7 +533,6 @@ class SevenZipExtractor { SevenZipContentReadStream stream = null; final ProgressHandle progress = ProgressHandle.createHandle(Bundle.EmbeddedFileExtractorIngestModule_ArchiveExtractor_moduleName()); //recursion depth check for zip bomb - final long archiveId = archiveFile.getId(); Archive parentAr; try { blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); @@ -550,10 +556,10 @@ class SevenZipExtractor { unpackSuccessful = false; return unpackSuccessful; } - parentAr = depthMap.get(archiveId); + parentAr = depthMap.get(archiveFile.getId()); if (parentAr == null) { - parentAr = new Archive(archiveId, 0, archiveId, archiveFile); - depthMap.put(archiveId, parentAr); + parentAr = new Archive(0, archiveFile.getId(), archiveFile); + depthMap.put(archiveFile.getId(), parentAr); } else { Archive rootArchive = depthMap.get(parentAr.getRootArchiveId()); if (rootArchive.isFlaggedAsZipBomb()) { @@ -705,7 +711,7 @@ class SevenZipExtractor { continue; } if (isSevenZipExtractionSupported(unpackedFile)) { - Archive child = new Archive(unpackedFile.getId(), parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); + Archive child = new Archive(parentAr.getDepth() + 1, parentAr.getRootArchiveId(), archiveFile); parentAr.addChild(child); depthMap.put(unpackedFile.getId(), child); } @@ -1249,23 +1255,24 @@ class SevenZipExtractor { //depth will be 0 for the root archive unpack was called on, and increase as unpack recurses down through archives contained within private final int depth; - private final long objectId; private final List children; private final long rootArchiveId; private boolean flaggedAsZipBomb = false; - private AbstractFile archiveFile; + private final AbstractFile archiveFile; /** * Create a new Archive object. * - * @param objectId the unique object id of the archive object - * @param depth the depth in the archive structure - 0 will be the - * root archive unpack was called on, and it will - * increase as unpack recurses down through archives - * contained within + * @param depth the depth in the archive structure - 0 will be + * the root archive unpack was called on, and it + * will increase as unpack recurses down through + * archives contained within + * @param rootArchiveId the unique object id of the root parent archive + * of this archive + * @param archiveFile the AbstractFile which this Archive object + * represents */ - Archive(long objectId, int depth, long rootArchiveId, AbstractFile archiveFile) { - this.objectId = objectId; + Archive(int depth, long rootArchiveId, AbstractFile archiveFile) { this.children = new ArrayList<>(); this.depth = depth; this.rootArchiveId = rootArchiveId; @@ -1282,18 +1289,36 @@ class SevenZipExtractor { children.add(child); } + /** + * Set the flag which identifies whether this file has been determined to be a zip bomb to true. + */ void flagAsZipBomb() { flaggedAsZipBomb = true; } + /** + * Gets whether or not this archive has been flagged as a zip bomb. + * + * @return True when flagged as a zip bomb, false if it is not flagged + */ boolean isFlaggedAsZipBomb() { return flaggedAsZipBomb; } + /** + * Get the AbstractFile which this Archive object represents. + * + * @return archiveFile - the AbstractFile which this Archive represents. + */ AbstractFile getArchiveFile() { return archiveFile; } + /** + * Get the object id of the root archive which contained this archive. + * + * @return rootArchiveId - the objectID of the root archive + */ long getRootArchiveId() { return rootArchiveId; } @@ -1301,10 +1326,10 @@ class SevenZipExtractor { /** * Get the object id of this archive. * - * @return objectId - the unique objectId of this archive + * @return the unique objectId of this archive from its AbstractFile */ long getObjectId() { - return objectId; + return archiveFile.getId(); } /** From e08e4c5a65f6bb11718cea98f68ed66a215b2bde Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 18 Jun 2018 12:31:49 -0400 Subject: [PATCH 083/101] 3914: Custom(user-defined) tag name should be listed at the top in Remove Tag --- ...DeleteFileBlackboardArtifactTagAction.java | 18 +++++++++++++++-- .../actions/DeleteFileContentTagAction.java | 20 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java index 008cb419ff..76f3faa8be 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -138,7 +139,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem */ @NbBundle.Messages({"# {0} - artifactID", "DeleteFileBlackboardArtifactTagAction.deleteTags.alert=Unable to untag artifact {0}."}) - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -153,6 +154,8 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem = selectedBlackboardArtifactsList.iterator().next(); Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + List standardTagMenuitems = new ArrayList<>(); try { // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); @@ -182,7 +185,12 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem tagNameItem.addActionListener((ActionEvent e) -> { deleteTag(tagName, artifactTag, artifact.getArtifactID()); }); - add(tagNameItem); + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } } @@ -192,6 +200,12 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem } } + if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){ + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); if (getItemCount() == 0) { setEnabled(false); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java index f336cd888f..c8a8a69fdc 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -136,7 +137,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen * creating or selecting a tag name for a tag and specifying an optional tag * comment. */ - private class TagMenu extends JMenu { + private final class TagMenu extends JMenu { private static final long serialVersionUID = 1L; @@ -150,6 +151,8 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen AbstractFile file = selectedAbstractFilesList.iterator().next(); Map tagNamesMap = null; + List standardTagNames = TagsManager.getStandardTagNames(); + List standardTagMenuitems = new ArrayList<>(); try { // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); @@ -179,7 +182,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen tagNameItem.addActionListener((ActionEvent e) -> { deleteTag(tagName, contentTag, file.getId()); }); - add(tagNameItem); + + // Show custom tags before predefined tags in the menu + if (standardTagNames.contains(tagDisplayName)) { + standardTagMenuitems.add(tagNameItem); + } else { + add(tagNameItem); + } } } } @@ -189,6 +198,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen } } + if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){ + addSeparator(); + } + standardTagMenuitems.forEach((menuItem) -> { + add(menuItem); + }); + if(getItemCount() == 0) { setEnabled(false); } From fcefceae4ade3e86b767ef462bac035b3d8a5b9d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 18 Jun 2018 12:36:08 -0400 Subject: [PATCH 084/101] 3798 synchronize getter and setter for boolean which identifies zip bomb status --- .../modules/embeddedfileextractor/SevenZipExtractor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 4fb1f66e72..ee9415ddca 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -1292,7 +1292,7 @@ class SevenZipExtractor { /** * Set the flag which identifies whether this file has been determined to be a zip bomb to true. */ - void flagAsZipBomb() { + synchronized void flagAsZipBomb() { flaggedAsZipBomb = true; } @@ -1301,7 +1301,7 @@ class SevenZipExtractor { * * @return True when flagged as a zip bomb, false if it is not flagged */ - boolean isFlaggedAsZipBomb() { + synchronized boolean isFlaggedAsZipBomb() { return flaggedAsZipBomb; } From 589b8d976d1f91c562521a178d1d7542ef9dd3c7 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 18 Jun 2018 10:27:34 -0700 Subject: [PATCH 085/101] Suppress local variable warnings, add show case button to enable/disable CR subcomponents functionality. --- .../centralrepository/optionspanel/GlobalSettingsPanel.java | 1 + .../autopsy/centralrepository/optionspanel/ShowCasesDialog.java | 1 + 2 files changed, 2 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 25bf4434a9..2a0467c68b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -613,6 +613,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i organizationPanel.setEnabled(enable && !ingestRunning); organizationTextArea.setEnabled(enable && !ingestRunning); manageOrganizationButton.setEnabled(enable && !ingestRunning); + showCasesButton.setEnabled(enable && !ingestRunning); return true; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index 58e4dccb06..454ee7f962 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; /** * Dialog to display table of CorrelationCase information from the CR tab of options. */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class ShowCasesDialog extends JDialog { private static final long serialVersionUID = 1L; From 50d45e394419a20fc443d47c41970463cefcff7c Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 18 Jun 2018 10:54:08 -0700 Subject: [PATCH 086/101] Adjust other occurances layout, labels to be clearer. Fix scroll bars. --- .../contentviewer/Bundle.properties | 4 +- .../DataContentViewerOtherCases.form | 112 +++++++----------- .../DataContentViewerOtherCases.java | 71 ++++------- .../optionspanel/ShowCasesDialog.form | 11 +- .../optionspanel/ShowCasesDialog.java | 10 +- 5 files changed, 85 insertions(+), 123 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties index 58101b7abf..b3375cda0d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties @@ -3,7 +3,7 @@ DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options. DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency +DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Comment DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date DataContentViewerOtherCases.earliestCaseLabel.toolTipText= -DataContentViewerOtherCases.earliestCaseLabel.text=Earliest Case: -DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Comment +DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date: diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 64ce01aa31..ce0ad7a55c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - + @@ -103,10 +103,10 @@ - + - + @@ -124,43 +124,34 @@ - - + + - - - - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + @@ -200,48 +191,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -259,6 +208,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index cb3ed48f8e..e5b0b3f441 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -462,7 +462,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return ret; } - @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Date Not Available."}) + @Messages({"DataContentViewerOtherCases.earliestCaseNotAvailable= Not Enabled."}) /** * Gets the list of Eam Cases and determines the earliest case creation date. * Sets the label to display the earliest date string to the user. @@ -745,11 +745,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableContainerPanel = new javax.swing.JPanel(); tableScrollPane = new javax.swing.JScrollPane(); otherCasesTable = new javax.swing.JTable(); - tableStatusPanel = new javax.swing.JPanel(); - earliestDatePanel = new javax.swing.JPanel(); - tableStatusPanelLabel = new javax.swing.JLabel(); earliestCaseLabel = new javax.swing.JLabel(); earliestCaseDate = new javax.swing.JLabel(); + tableStatusPanel = new javax.swing.JPanel(); + tableStatusPanelLabel = new javax.swing.JLabel(); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { @@ -793,71 +792,54 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi otherCasesTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); tableScrollPane.setViewportView(otherCasesTable); + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N + earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N + tableStatusPanel.setPreferredSize(new java.awt.Dimension(1500, 16)); javax.swing.GroupLayout tableStatusPanelLayout = new javax.swing.GroupLayout(tableStatusPanel); tableStatusPanel.setLayout(tableStatusPanelLayout); tableStatusPanelLayout.setHorizontalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 1388, Short.MAX_VALUE) + .addGap(0, 0, Short.MAX_VALUE) ); tableStatusPanelLayout.setVerticalGroup( tableStatusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 16, Short.MAX_VALUE) ); - javax.swing.GroupLayout earliestDatePanelLayout = new javax.swing.GroupLayout(earliestDatePanel); - earliestDatePanel.setLayout(earliestDatePanelLayout); - earliestDatePanelLayout.setHorizontalGroup( - earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 206, Short.MAX_VALUE) - ); - earliestDatePanelLayout.setVerticalGroup( - earliestDatePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 16, Short.MAX_VALUE) - ); - tableStatusPanelLabel.setForeground(new java.awt.Color(255, 0, 51)); - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseLabel, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.text")); // NOI18N - earliestCaseLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseLabel.toolTipText")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(earliestCaseDate, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.earliestCaseDate.text")); // NOI18N - javax.swing.GroupLayout tableContainerPanelLayout = new javax.swing.GroupLayout(tableContainerPanel); tableContainerPanel.setLayout(tableContainerPanelLayout); tableContainerPanelLayout.setHorizontalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addContainerGap() .addComponent(earliestCaseLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(earliestCaseDate) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1500, Short.MAX_VALUE) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 780, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 715, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() + .addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1282, Short.MAX_VALUE) + .addGap(218, 218, 218)) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(earliestDatePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(earliestCaseLabel) - .addComponent(earliestCaseDate))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate)) + .addGap(10, 10, 10) + .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); @@ -870,10 +852,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); otherCasesPanelLayout.setVerticalGroup( otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 170, Short.MAX_VALUE) + .addGap(0, 336, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 336, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -885,7 +867,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 336, Short.MAX_VALUE) ); }// //GEN-END:initComponents @@ -906,10 +888,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JFileChooser CSVFileChooser; + private javax.swing.JMenuItem addCommentMenuItem; private javax.swing.JLabel earliestCaseDate; private javax.swing.JLabel earliestCaseLabel; - private javax.swing.JPanel earliestDatePanel; - private javax.swing.JMenuItem addCommentMenuItem; private javax.swing.JMenuItem exportToCSVMenuItem; private javax.swing.JPanel otherCasesPanel; private javax.swing.JTable otherCasesTable; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form index 6ddcf95177..fdddafa805 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form @@ -71,9 +71,9 @@ - + - + @@ -105,15 +105,18 @@ - + - + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index 454ee7f962..5605cc3d23 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -90,6 +90,8 @@ public final class ShowCasesDialog extends JDialog { showCasesScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); showCasesScrollPane.setPreferredSize(new java.awt.Dimension(535, 415)); + innerCaseScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + caseDetailsTable.setAutoCreateRowSorter(true); caseDetailsTable.setModel(tableModel); caseDetailsTable.setToolTipText(org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.caseDetailsTable.toolTipText")); // NOI18N @@ -110,9 +112,9 @@ public final class ShowCasesDialog extends JDialog { ); outCasesPaneLayout.setVerticalGroup( outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 423, Short.MAX_VALUE) + .addGap(0, 400, Short.MAX_VALUE) .addGroup(outCasesPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 423, Short.MAX_VALUE)) + .addComponent(innerCaseScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)) ); showCasesScrollPane.setViewportView(outCasesPane); @@ -127,9 +129,9 @@ public final class ShowCasesDialog extends JDialog { ); showCasesPanelLayout.setVerticalGroup( showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 426, Short.MAX_VALUE) + .addGap(0, 407, Short.MAX_VALUE) .addGroup(showCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(showCasesScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE)) + .addComponent(showCasesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 407, Short.MAX_VALUE)) ); org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(ShowCasesDialog.class, "ShowCasesDialog.closeButton.text")); // NOI18N From d0365adcfdca689eaf6098923794685861f86958 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 13:55:45 -0400 Subject: [PATCH 087/101] Add documentation for OCR feature --- ...configuration-dialog-string-extraction.PNG | Bin 52498 -> 54352 bytes .../images/keyword-search-ocr-image.png | Bin 0 -> 74404 bytes .../keyword-search-ocr-indexed-text.png | Bin 0 -> 16833 bytes docs/doxygen-user/keyword_search.dox | 10 +++++++++- 4 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 docs/doxygen-user/images/keyword-search-ocr-image.png create mode 100644 docs/doxygen-user/images/keyword-search-ocr-indexed-text.png diff --git a/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG b/docs/doxygen-user/images/keyword-search-configuration-dialog-string-extraction.PNG index e5706a4df891006d81f231d3ce383b4fd087ec2e..5dcc5e3d03ad11ab39a235b93fc81ab9ab503746 100644 GIT binary patch delta 35863 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 diff --git a/docs/doxygen-user/images/keyword-search-ocr-image.png b/docs/doxygen-user/images/keyword-search-ocr-image.png new file mode 100644 index 0000000000000000000000000000000000000000..a8fda73cc4b4c35b6d53f4d4f8060882cf1e7db7 GIT binary patch literal 74404 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

?3B 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 aKEDxnO*_KmZ3EZEv68&ni(*;hkN+2mc1U&r literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/keyword_search.dox b/docs/doxygen-user/keyword_search.dox index 6edb313f38..b7509892f8 100644 --- a/docs/doxygen-user/keyword_search.dox +++ b/docs/doxygen-user/keyword_search.dox @@ -36,7 +36,7 @@ Under the Keyword list is the option to send ingest inbox messages for each hit. \image html keyword-search-inbox.PNG ## String Extraction tab {#stringExtractionTab} -The string extraction setting defines how strings are extracted from files from which text cannot be extracted because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. +The string extraction setting defines how strings are extracted from files from which text cannot be extracted normally because their file formats are not supported. This is the case with arbitrary binary files (such as the page file) and chunks of unallocated space that represent deleted files. When we extract strings from binary files we need to interpret sequences of bytes as text differently, depending on the possible text encoding and script/language used. In many cases we don't know in advance what the specific encoding/language the text is encoded in. However, it helps if the investigator is looking for a specific language, because by selecting less languages the indexing performance will be improved and the number of false positives will be reduced. The default setting is to search for English strings only, encoded as either UTF8 or UTF16. This setting has the best performance (shortest ingest time). @@ -44,6 +44,14 @@ The user can also use the String Viewer first and try different script/language \image html keyword-search-configuration-dialog-string-extraction.PNG +There is also a setting to enable Optical Character Recognition (OCR). If enabled, text may be extracted from supported image types. Enabling this feature will make the keyword search module take longer to run, and the results are not perfect. The following shows a sample image containing text: + +\image html keyword-search-ocr-image.png + +The "Indexed Text" tab shows the results when running the keyword search module with the OCR option enabled. If we were to use Keyword Search to look for the word "forensics", this file would be a match. + +\image html keyword-search-ocr-indexed-text.png + ## General Settings tab {#generalSettingsTab} \image html keyword-search-configuration-dialog-general.PNG From 0361ba7a4545a1f80a8eaffadac0b1f62c0d3c21 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 18 Jun 2018 14:02:52 -0400 Subject: [PATCH 088/101] Update Autopsy version numbers to 4.8.0 --- .../org/sleuthkit/autopsy/corecomponents/Bundle.properties | 2 +- docs/doxygen-user/Doxyfile | 4 ++-- docs/doxygen/Doxyfile | 6 +++--- nbproject/project.properties | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index bae8218148..bb98cc90f6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -26,7 +26,7 @@ LBL_Description=

URL_ON_IMG=http://www.sleuthkit.org/ -URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.7.0/ +URL_ON_HELP=http://sleuthkit.org/autopsy/docs/user-docs/4.8.0/ FILE_FOR_LOCAL_HELP=file:/// INDEX_FOR_LOCAL_HELP=/docs/index.html LBL_Close=Close diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 84605ac34f..ee0a85b2dd 100755 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.7.0 +HTML_OUTPUT = 4.8.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 5dbdbf1479..7facd03b19 100755 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,10 +38,10 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.7.0 +PROJECT_NUMBER = 4.8.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a +# for a project that appears a the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "Graphical digital forensics platform for The Sleuth Kit and other tools." @@ -1063,7 +1063,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.7.0/ +HTML_OUTPUT = api-docs/4.8.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index 3cbde84d3e..1ce749b501 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,10 +4,10 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.7.0 +app.version=4.8.0 ### build.type must be one of: DEVELOPMENT, RELEASE -#build.type=RELEASE -build.type=DEVELOPMENT +build.type=RELEASE +#build.type=DEVELOPMENT project.org.netbeans.progress=org-netbeans-api-progress project.org.sleuthkit.autopsy.experimental=Experimental From 40e955e0ab53c86e814e2d91c5f06dc47aa86203 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 18 Jun 2018 11:04:15 -0700 Subject: [PATCH 089/101] removed outdated unmerged common files class. Fixed logger varible name. Removed public access. --- .../DataContentViewerOtherCases.form | 4 +- .../DataContentViewerOtherCases.java | 29 +++++------ .../optionspanel/GlobalSettingsPanel.java | 2 +- .../optionspanel/ShowCasesDialog.form | 2 +- .../optionspanel/ShowCasesDialog.java | 4 +- .../optionspanel/ShowCasesTableModel.java | 14 +++--- .../commonfilesearch/CommonFilesChildren.java | 48 ------------------- 7 files changed, 28 insertions(+), 75 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index ce0ad7a55c..2f24643a21 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - +
@@ -106,7 +106,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index e5b0b3f441..f3a5293fda 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -87,7 +87,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi private static final long serialVersionUID = -1L; - private final static Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName()); + private final static Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName()); private final DataContentViewerOtherCasesTableModel tableModel; private final Collection correlationAttributes; @@ -121,7 +121,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { saveToCSV(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } } else if (jmi.equals(showCommonalityMenuItem)) { showCommonalityDetails(); @@ -180,7 +180,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), DEFAULT_OPTION, PLAIN_MESSAGE); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex); + logger.log(Level.SEVERE, "Error getting commonality details.", ex); JOptionPane.showConfirmDialog(showCommonalityMenuItem, Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(), Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(), @@ -304,7 +304,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } catch (IOException ex) { - LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); + logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex); } } @@ -396,7 +396,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID()); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS + logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS return null; } @@ -442,7 +442,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } else { @@ -455,7 +455,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } } @@ -487,11 +487,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS } catch (ParseException ex) { - LOGGER.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS } // NON-NLS + // NON-NLS } earliestCaseDate.setText(dateStringDisplay); @@ -536,13 +537,13 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi return artifactInstances; } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { // do nothing. // @@@ Review this behavior - LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS + logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS } return new HashMap<>(0); @@ -596,7 +597,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi try { deviceId = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId()).getDeviceId(); } catch (TskDataException | TskCoreException ex) { - LOGGER.log(Level.WARNING, "Error getting data source info: {0}", ex); + logger.log(Level.WARNING, "Error getting data source info: {0}", ex); return; } UniquePathKey uniquePathKey = new UniquePathKey(deviceId, filePath); @@ -689,7 +690,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi newCeArtifact.addInstance(corAttrInstance); tableModel.addEamArtifact(newCeArtifact); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex); + logger.log(Level.SEVERE, "Error creating correlation attribute", ex); } }); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 2a0467c68b..e4d618dee7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -45,7 +45,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteEamDbSettings; public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { private static final long serialVersionUID = 1L; - private static final Logger LOGGER = Logger.getLogger(GlobalSettingsPanel.class.getName()); + private static final Logger logger = Logger.getLogger(GlobalSettingsPanel.class.getName()); private final IngestJobEventPropertyChangeListener ingestJobEventListener; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form index fdddafa805..f34b3e575c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.form @@ -71,7 +71,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index 5605cc3d23..cf32998430 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -32,7 +32,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; * Dialog to display table of CorrelationCase information from the CR tab of options. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public final class ShowCasesDialog extends JDialog { +final class ShowCasesDialog extends JDialog { private static final long serialVersionUID = 1L; @@ -41,7 +41,7 @@ public final class ShowCasesDialog extends JDialog { /** * Creates new form ShowCases Panel */ - public ShowCasesDialog() { + ShowCasesDialog() { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.ShowCasesDialog_title_text(), true); // NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java index 8ed38c4891..def6cb15b6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesTableModel.java @@ -27,7 +27,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; /** * Model for cells to display correlation case information */ -public class ShowCasesTableModel extends AbstractTableModel { +class ShowCasesTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; @@ -61,11 +61,11 @@ public class ShowCasesTableModel extends AbstractTableModel { this.columnWidth = columnWidth; } - public String columnName() { + String columnName() { return columnName; } - public int columnWidth() { + int columnWidth() { return columnWidth; } }; @@ -94,7 +94,7 @@ public class ShowCasesTableModel extends AbstractTableModel { * * @return preferred column width >= 0 */ - public int getColumnPreferredWidth(int colIdx) { + int getColumnPreferredWidth(int colIdx) { return TableColumns.values()[colIdx].columnWidth(); } @@ -117,7 +117,7 @@ public class ShowCasesTableModel extends AbstractTableModel { return mapValueById(rowIdx, TableColumns.values()[colIdx]); } - public Object getRow(int rowIdx) { + Object getRow(int rowIdx) { return eamCases.get(rowIdx); } @@ -172,12 +172,12 @@ public class ShowCasesTableModel extends AbstractTableModel { * @param eamCase central repository case to add to the * table */ - public void addEamCase(CorrelationCase eamCase) { + void addEamCase(CorrelationCase eamCase) { eamCases.add(eamCase); fireTableDataChanged(); } - public void clearTable() { + void clearTable() { eamCases.clear(); fireTableDataChanged(); } diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java deleted file mode 100644 index 858244e4c6..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 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.commonfilesearch; - -import java.util.List; -import org.sleuthkit.datamodel.AbstractFile; -import org.openide.nodes.Children; -import org.openide.nodes.Node; - -/** - * - */ -class CommonFilesChildren extends Children.Keys{ - - CommonFilesChildren(boolean lazy, List fileList){ - super(lazy); - this.setKeys(fileList); - } - - @Override - protected Node[] createNodes(AbstractFile abstractFile) { - -// if(abstractFile.isDir()){ -// //TODO log an error ??? -// -// } else { -// -// } - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - -} From 56848583274b42824ec6e263bc7775dd8ea265ba Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 18 Jun 2018 14:59:33 -0400 Subject: [PATCH 090/101] Remove commented out code --- .../imagegallery/datamodel/DrawableDB.java | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index a5092614a7..51d58c7f1e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -840,43 +840,6 @@ public final class DrawableDB { } } - /** - * Count the total number of files in the database.. - * - * @return Total number of files in the database - * - * @throws TskCoreException - */ -// @Deprecated -// public long countFiles() throws TskCoreException { -// Statement statement = null; -// ResultSet rs = null; -// dbReadLock(); -// try { -// statement = con.createStatement(); -// rs = statement.executeQuery("SELECT COUNT (*) FROM drawable_files"); //NON-NLS -// return rs.getLong(1); -// } catch (SQLException e) { -// throw new TskCoreException("SQLException thrown when calling 'DrawableDB.countFiles(): ", e); -// } finally { -// if (rs != null) { -// try { -// rs.close(); -// } catch (SQLException ex) { -// LOGGER.log(Level.SEVERE, "Error closing result set after executing countFiles", ex); //NON-NLS -// } -// } -// if (statement != null) { -// try { -// statement.close(); -// } catch (SQLException ex) { -// LOGGER.log(Level.SEVERE, "Error closing statement after executing countFiles", ex); //NON-NLS -// } -// } -// dbReadUnlock(); -// } -// } - /** * * @@ -976,25 +939,6 @@ public final class DrawableDB { } } - /** - * @param id the obj_id of the file to return - * @param analyzed the analyzed state of the file - * - * @return a DrawableFile for the given obj_id and analyzed state - * - * @throws TskCoreException if unable to get a file from the currently open - * {@link SleuthkitCase} - */ -// private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException { -// try { -// AbstractFile f = tskCase.getAbstractFileById(id); -// return DrawableFile.create(f, analyzed, isVideoFile(f)); -// } catch (IllegalStateException ex) { -// LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex); //NON-NLS -// return null; -// } -// } - /** * @param id the obj_id of the file to return * From 376268da3877439adbdcc2b7290006762b05dae0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 18 Jun 2018 16:07:50 -0400 Subject: [PATCH 091/101] Update file search by attribute documentation --- docs/doxygen-user/file_search.dox | 10 ++++++---- .../images/file-search-top-component.PNG | Bin 36411 -> 34870 bytes 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/doxygen-user/file_search.dox b/docs/doxygen-user/file_search.dox index 9eb716233f..96387a958c 100644 --- a/docs/doxygen-user/file_search.dox +++ b/docs/doxygen-user/file_search.dox @@ -24,16 +24,18 @@ Note: it doesn't support regular expression and keyword matching. Search for all files and directory whose size matches the pattern given. The pattern can be "equal to", "greater than", and "less than". The unit for the size can be "Byte(s)", "KB", "MB", "GB", and "TB". \li MIME Type: Search for all files with the selected MIME type. Multiple types can be used by holding SHIFT or CTRL while selecting. +\li MD5: +Search for all files with the given MD5 hash. \li Date: Search for all files and directory whose "date property" is within the date range given. The "date properties" are "Modified Date", "Accessed Date", "Changed Date", and "Created Date". You must also specify the timezone for the date given. \li Known Status: Search for all files and directory whose known status is recognized as either Unknown, Known, or Known Bad. For more on Known Status, see the \ref hash_db_page. To use any of these filters, check the box next to the category and click "Search" button to start the search process. The result will show up in the "Result Viewer". -\li MD5 -Search for all files with the given MD5 hash. +\li Data Source: +Search only within the specified data source instead of the entire case. Note that multiple data sources can be selected by using shift+right mouse button or control+right mouse button. -Here's a contrived example where we try to get all the directories and files whose name contains "hello", has a size greater than 1000 Bytes, is in JPEG format, was created between 09/06/2017 and -09/09/2017 (in GMT-5 timezone), is an unknown file, and has a hash of 1127F348BD4303A4C3D1D587C807B49F: +Here's a contrived example where we try to get all the directories and files whose name contains "hello", has a size greater than 1000 Bytes, is in JPEG format, was created between 06/01/2018 and +06/08/2017 (in GMT-5 timezone), is an unknown file, has a hash of 1127F348BD4303A4C3D1D587C807B49F, and appears in data source "image3.vhd": \image html example-of-file-search.PNG */ \ No newline at end of file diff --git a/docs/doxygen-user/images/file-search-top-component.PNG b/docs/doxygen-user/images/file-search-top-component.PNG index d98b73be336fa77b3115aeff5cd32ab79abd71e2..310704a471b6422cc463cec747f3341c162b8687 100644 GIT binary patch literal 34870 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 z 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 a!LsTLJG*V#Dgl+uG zfohzH=jVoE8mk@f&4Or?+GNUGa}&=E@Gu-|6BCoHD(ia}JI8I?i})SStz57F_|XA! zb|wf8QDqLouu98c@q&3ayeVM*^wC*`DZIjiKy{BPr9munq@n`Jj}dCkcv1$>4ltTD zmQ;;P+%P!--|Nw(1!(?<1^mr)w;^Uy5!v@I|Ky<#IKT%On|OMqA)|r3hZ1twUm8Ttx-kwfvO8g|ixpC-^ zAMio0L=BG zKBj9SG+dE1b`ycrg|NFjg~FhbXkCH_OF-=$+PISpO+UCCY64e| zu^GQYal_X))XeWd&Yln9{oi)xp*NjlP5rs1lqU?TZm;|BW&hcNk^ZqjH~_zIOo@Fvg;7=}6HE>a$74Gu{pf<5m&%K`C#HTL%mMPsq+5Cl zGx?nsBTF;*)VjzrfU$%j^$~*3Y-)Rop}CjD<%tim_i3w<`{Mph_W9p#Q&-_CTkwAg zS3{1kP(?F$*uYgKg$T1=rK5agP~xQByAetbK4AYWvH>TqD573nr4R1&-m`WbO7DY~ zw7ASTfZnbN@@)akMNaU$ot}nCcwB07%sQ!n$LftN5_^Ii*LCWwLaCx=sC2vwh=a4Z zFxgL>yVY|8|IcT~{nA{z8XfpW7LYyqVV;SF2@%UngYEd0m~TC9viO;&qwk|k3}>9x}>fue}}*Z@3fSV1*P?N z1ZX0Bh~WV7*%M&~Yk;i=$wj}#!ipU)S@KZ(d!Q{19uyZahzSTUKHEVuZAv(&JeNUF zGCm7h{@~JlCD^&aLn8BcV2L%CjK2@2q(P->chI0Iwimq8myyy*^8{|+G_MIv_5xyQ zTD(6r>E_?imk3a~)hYgtC?dr7VkHDI2$H}k+yJHD!K%lDzg}ex_mmW`!O~dMG^~M3 zH;B%+HJ-5P*3ZTl3Cn;_)GvRgoivh2d5h@};8@}n5CHqWs(McbexGFMQ#9!3gW|a$ zBOQ3+{6_!ic!$FlKE4#U!~wegz(cGW6Y}_*p#?Vwb;wC#O~Y&GV#~ugM)@jAOH+(@HHSwbB5l^k^yPXab=MhSXEY>>)y3n)HKR!gflP~%yIXzHn(S#0)(yg*CHkQOf&k69d|~SDSfCtEU;#<3S1lEdQpHs(etR}*YP}kQ zdBdsUpP__IAYI_gequb8)!JifvZ{!cpD*+bM~L_~ID&SGUj}qYEUp0(NJ+gsO*Dw=HL=BwZ}q?nI+KSp5^!ZJ0? z3A7)r?!wJ`O+iUUj}_<8t{@xd52buMq4N8f z17mVMnB&P*K{R^PbXPVQ_Z8}rJ0)=cG8JW8!^oOt_%36x>L3Mr^TZ<&7FI3wgZD~# zqJ{;}Fc=)2x2L?E!1HlG$V~O$3Y6B#-u3}#=+64(nGf1=ysS;&u-vJunBK`|gFA^N zt@uYA0)rBEv0UkW>Su8qs4cYt;8>fyM*DO%4&!K;LXO}WcxqP_to5xRJ2fWoIt3uf z2a=0K06=?9cKJ{8OE-ko%7fy(%oCbp6QMmmh^r<1&7%Jn_JfqJ=Z&5*p_gvV>^X>- z4<5Y_+0Zn-lAQBoK#TKl4C*k0ff;eY^=ofI{tj|vxI7yn`~ifg+z1$O*~z8@Vu|0g zWks=PdXvJy<2Pnbrw>JCs?)2R%FZ#>0jI=W^h>!50qKKb1AbxQTSjHi#`pVO2hoGB z4d@Wyz1&}Xj5wU}lZ%Xu{I=Ac1hTTS8d988pVXY(m|y^2bn*uSkb&5B$5DW(nIgRA z@u5JCo}ol#^;48cw)b|IUYxTaVe#kFvmRj@#kg`nP9{Ab9!7Q7La23M`sG!^2lhe&FCVV|oNe}H> zAg~;{_sF$(?)1L z;_SR7N=KS-kIA_Zy?o-f*u=!yT^5ez0OZ^p%Y*b4ftfiy-+22ZVqgsKD@@%60arMI zVoIMOcsN?LUf&;>uICZ=J6Cipw4wPOKCo9hnL@U=u-n%2;Cx$g>?I`XSaGeVCuA-Q z8MxGD*|zuhr2yeq0YsK>%X$QB=En2#^`!^&q=MNMJB-rq=_5`0j)1-ss?Q^J#Q1C_ zk7Y~-2b+dw?%4FK8fpLac3Ip0O>Ee<1%Rm1J@~T{b8ByVS1C{odie0W)DX_FPk zx<2r9eifde#GQ<;9MeJo8Vo%HgLgd-C%G`YiUzoWvQ$DfFKvkSR?sFOOJu(W$P%yY zWmgvPOO@9pYHv<86!`YWi@HRK1B0dCfc_kH$OW@!xZgMdD)rspsQ<7Y;EZexrkF@A zW4WD&`2e|Tt^HDHd_AtH)PJ)=dC=)##oV+bIA@4TtdBHXvK!PKvQ46wbp z^bCvCEU5FSq#J{-IHrdd$iFVf^eF<6zkjwFh!;J6dRf3&-vcp#&0pB0a!8pV>P`Pj z2s^(5AzvDl%8^P-#{_QIFfYdpD=(pCK`HY5o83@>0JyM#0_J=RWLQH{QXIx=N4EkR;O|Xi%x%pex?Lcq1#Gsk+o7;5+x1zU=or1^UUAza(KpCBK4S zRbXF((k1~ZGb^QN6xUTm0OoLXTxNI20F@5N{sN!l17EPthI(*NV-AOVl2E{6VTy<( zVB#zgUtIo{b&j~Hp%k!9Y8&ci0^L6+gk=u_jpS3W3=bR1>X?>Kh1g>lC^+?OWPnCy)f$X zOQq`GE7p`zj$nhgoOUL`6oYWCw#;*n@}@eTYCg#yq1|=5xGqGl0!E)xziqYOUs(xn zEs^T@P{K#VikC{;1HbsrE_+?>&q23iy^jEg-IT~YywQP`^I-4)1abq7{0i4H2fHzE zTGY+Kn@OPMyXy&Bw_3l~cHFLgjhA3QjYNzbXi|e*9;8tn_+{brDno_$rYw!QTAK;s zo$jP#52?J69$IAmy599&0C8`8#Ze>!lvD+wGVT9%muMDt}lUY79K z!E=Pr=ScV%CelA%XW`A-M0VBABvNCT4zSavnwav~K~zOCnZO$tiHQVCV&iG^262&? zfE`5D1nk7RsXcFE8+YTj?7{8c8=r%H_@6wD22SG@9&BEOLoG{q-d)DyahYE&%Cuiu z^*ORCXx({0S?^VLGzBQSlc+ytG@Q}2XxV5}JVJoqb;B4Wgi|b49a2+UV;*vGt%FE#T z!flI`ww9nvq`Afua=MspNCz(C61IVay{v>ClJ=gsL)zhEmC(m!@K|*Vo7lxz!uF&c z6t*E7!Y0D9Wkp2SVk-esg+htkG#b040U`|*4{fYLsQ=iwm?oDSnTNavm> zZ4`Fk{Ba%AYmL4Bb)pv|esVMm`@Z|`<8F~(puuYfE{CLzkmw>vI9Ofa0h_Bx+ng94 zu1FiVNl6Ov75=!EbM>!r+V+tj}&=WRy*43A#j@D?AaWi{1Wo;BwKz zc6E&yl1V!%+72GKX$qT&=o;~)O{`JZ2on->6s{ZbOj-8GUg!X{|lIflAHD6%Fe zlbgTvr7tOAZ`-yF9((LDmNr?OLhqmd>SexjWZhWEjL8TB`CRK^-ePD}VrDT42tZeD zHxuFQyEbus?-u%43F11YEW*bj;yPBNxw3lZv&Y2za#~C>jyiOkrWo8GdOuAwY9HJC uoZeEWa38mu6>>1Sn4C Date: Mon, 18 Jun 2018 16:37:41 -0400 Subject: [PATCH 092/101] Update ad hoc keyword list search doc --- docs/doxygen-user/adHocKeywordSearch.dox | 2 +- .../images/keyword-search-list.PNG | Bin 15399 -> 17910 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/adHocKeywordSearch.dox b/docs/doxygen-user/adHocKeywordSearch.dox index f8e4881fb4..8949583a88 100644 --- a/docs/doxygen-user/adHocKeywordSearch.dox +++ b/docs/doxygen-user/adHocKeywordSearch.dox @@ -84,7 +84,7 @@ Results will be opened in a separate Results Viewer for every search executed an In addition to being selected during ingest, keyword lists can also be run through the Keyword Lists button. For information on setting up these keyword lists, see the \ref keywordListsTab section of the ingest module documentation. -Lists created using the Keyword Search Configuration Dialog can be manually searched by the user by pressing on the 'Keyword Lists' button, selecting the check boxes corresponding to the lists to be searched, and pressing the 'Search' button. +Lists created using the Keyword Search Configuration Dialog can be manually searched by the user by pressing on the 'Keyword Lists' button and selecting the check boxes corresponding to the lists to be searched. The search can be restricted to only certain data sources by selecting the checkbox near the bottom and then highlighting the data sources to search within. Multiple data sources can be selected used shift+right click or control+right click. Once everything has been configured, press "Search" to begin the search. \image html keyword-search-list.PNG diff --git a/docs/doxygen-user/images/keyword-search-list.PNG b/docs/doxygen-user/images/keyword-search-list.PNG index 112ff6a7c8292d6de305bb194fc9a09d38cf79e6..bd0b0d42fb125ca8e778a209f2c17b9d8c6dfa07 100644 GIT binary patch literal 17910 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 From d413a754d6df7f4c9d67bd19071346de3d8cc171 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Mon, 18 Jun 2018 14:08:28 -0700 Subject: [PATCH 093/101] Fix layout spacing. Make reset text use bundle, and allow case details to refresh properly after reset. --- .../DataContentViewerOtherCases.form | 40 ++++++++-------- .../DataContentViewerOtherCases.java | 48 +++++++++---------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form index 2f24643a21..60667bae46 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.form @@ -80,7 +80,7 @@ - + @@ -103,10 +103,10 @@ - + - + @@ -124,34 +124,36 @@ - - - - - - - + + + + + + + + - - - - - + + + + + + + + - + - - - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index f3a5293fda..47541cf486 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -315,7 +315,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi // start with empty table tableModel.clearTable(); correlationAttributes.clear(); - earliestCaseDate.setText("None"); + earliestCaseDate.setText(Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable()); } @Override @@ -467,13 +467,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi * Gets the list of Eam Cases and determines the earliest case creation date. * Sets the label to display the earliest date string to the user. */ - private void setEarliestCaseDate() { - LocalDate earliestDate = LocalDate.now(); - DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); + private void setEarliestCaseDate() { String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); - if (EamDb.isEnabled()) { + LocalDate earliestDate = LocalDate.now(); + DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); try { EamDb dbManager = EamDb.getInstance(); List cases = dbManager.getCases(); @@ -703,8 +702,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } else { clearMessageOnTableStatusPanel(); setColumnWidths(); - setEarliestCaseDate(); } + setEarliestCaseDate(); } private void setColumnWidths() { @@ -817,30 +816,31 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi tableContainerPanel.setLayout(tableContainerPanelLayout); tableContainerPanelLayout.setHorizontalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(tableContainerPanelLayout.createSequentialGroup() - .addComponent(earliestCaseLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(earliestCaseDate) - .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() .addComponent(tableStatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1282, Short.MAX_VALUE) .addGap(218, 218, 218)) .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(tableContainerPanelLayout.createSequentialGroup() + .addComponent(earliestCaseLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(earliestCaseDate) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) ); tableContainerPanelLayout.setVerticalGroup( tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, tableContainerPanelLayout.createSequentialGroup() - .addComponent(tableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 120, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(earliestCaseLabel) - .addComponent(earliestCaseDate)) - .addGap(10, 10, 10) + .addComponent(tableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 176, Short.MAX_VALUE) + .addGap(0, 0, 0) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(tableContainerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(earliestCaseLabel) + .addComponent(earliestCaseDate)) + .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(0, 0, 0) .addComponent(tableStatusPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(tableStatusPanelLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(0, 0, 0)) ); javax.swing.GroupLayout otherCasesPanelLayout = new javax.swing.GroupLayout(otherCasesPanel); @@ -853,10 +853,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); otherCasesPanelLayout.setVerticalGroup( otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 336, Short.MAX_VALUE) + .addGap(0, 483, Short.MAX_VALUE) .addGroup(otherCasesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(otherCasesPanelLayout.createSequentialGroup() - .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 336, Short.MAX_VALUE) + .addComponent(tableContainerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) .addGap(0, 0, 0))) ); @@ -868,7 +868,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 336, Short.MAX_VALUE) + .addComponent(otherCasesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 483, Short.MAX_VALUE) ); }// //GEN-END:initComponents From 7e16b36a1dc1586f114c6d96b036660abd6ccd4c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 19 Jun 2018 09:01:38 -0400 Subject: [PATCH 094/101] Updated kw search docs to include data source selection --- docs/doxygen-user/adHocKeywordSearch.dox | 2 +- .../images/keyword-search-bar.PNG | Bin 5453 -> 8586 bytes .../images/keyword-search-list.PNG | Bin 17910 -> 17284 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/adHocKeywordSearch.dox b/docs/doxygen-user/adHocKeywordSearch.dox index 8949583a88..6277bcffae 100644 --- a/docs/doxygen-user/adHocKeywordSearch.dox +++ b/docs/doxygen-user/adHocKeywordSearch.dox @@ -72,7 +72,7 @@ In general all three types of keyword searches will work as expected but the fea \section ad_hoc_kw_search Keyword Search -Individual keyword or regular expressions can quickly be searched using the search text box widget. You can select "Exact Match", "Substring Match" and "Regular Expression" match. See the earlier \ref ad_hoc_kw_types_section section for information on each keyword type. +Individual keyword or regular expressions can quickly be searched using the search text box widget. You can select "Exact Match", "Substring Match" and "Regular Expression" match. See the earlier \ref ad_hoc_kw_types_section section for information on each keyword type. The search can be restricted to only certain data sources by selecting the checkbox near the bottom and then highlighting the data sources to search within. Multiple data sources can be selected used shift+right click or control+right click. \image html keyword-search-bar.PNG diff --git a/docs/doxygen-user/images/keyword-search-bar.PNG b/docs/doxygen-user/images/keyword-search-bar.PNG index dc9cc74111e878b44cfef3991aa3b15df4d69dbd..09fd2b404de3eede2de2c6721fcdc10d52da196d 100644 GIT binary patch literal 8586 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 a`Z}?*{NCS^WI^qk04T|+$d*Hm1O6MiW;MD1 literal 5453 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 diff --git a/docs/doxygen-user/images/keyword-search-list.PNG b/docs/doxygen-user/images/keyword-search-list.PNG index bd0b0d42fb125ca8e778a209f2c17b9d8c6dfa07..56919753d251de1a4a2cda5113a4ae28f12f760b 100644 GIT binary patch literal 17284 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 Date: Tue, 19 Jun 2018 10:10:51 -0400 Subject: [PATCH 095/101] Make a new method to stop codacy from complaining --- .../autoingest/AutoIngestManager.java | 19 ++++++++++++++----- .../autoingest/AutoIngestMonitor.java | 16 ++++++++-------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 79ea4e4bbb..c057b68b2e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -650,6 +650,15 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen } jobProcessingTask.requestResume(); } + + /** + * Get the name of the currently logged in user + * @return + */ + static String getSystemUserNameProperty() { + return System.getProperty("user.name"); + } + /** * Removes the priority (set to zero) of all pending ingest jobs for a @@ -691,7 +700,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToDeprioritize.isEmpty()) { new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } } @@ -741,7 +750,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen if (!jobsToPrioritize.isEmpty()) { new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } } @@ -794,7 +803,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } } @@ -852,7 +861,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); + getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } } @@ -1005,7 +1014,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen casesToManifests.remove(caseName); } - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, getSystemUserNameProperty())); setChanged(); notifyObservers(Event.CASE_DELETED); return result; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 26e774c9b7..b5e587ec81 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -387,7 +387,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_DEPRIORITIZED, "")); }).start(); } return jobsSnapshot; @@ -435,7 +435,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.CASE_PRIORITIZED, "")); }).start(); } return jobsSnapshot; @@ -487,7 +487,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen final String dataSourceName = jobToDeprioritize.getManifest().getDataSourceFileName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_DEPRIORITIZED, dataSourceName)); }).start(); } @@ -545,7 +545,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen final String dataSourceName = jobToPrioritize.getManifest().getDataSourceFileName(); new Thread(() -> { eventPublisher.publishRemotely(new AutoIngestCasePrioritizedEvent(LOCAL_HOST_NAME, caseName, - System.getProperty("user.name"), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); + AutoIngestManager.getSystemUserNameProperty(), AutoIngestCasePrioritizedEvent.EventType.JOB_PRIORITIZED, dataSourceName)); }).start(); } @@ -560,7 +560,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ void cancelJob(AutoIngestJob job) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestJobCancelEvent(job, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } @@ -605,7 +605,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen * Publish a reprocess event. */ new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestJobReprocessEvent(job, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } @@ -658,7 +658,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen completedJob.getManifest().getCaseName().equals(caseName)); // Publish a message to update auto ingest nodes. - eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestCaseDeletedEvent(caseName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); } return CaseDeletionResult.FULLY_DELETED; @@ -672,7 +672,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen */ private void sendControlEventToNode(ControlEventType eventType, String nodeName) { new Thread(() -> { - eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName, LOCAL_HOST_NAME, System.getProperty("user.name"))); + eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName, LOCAL_HOST_NAME, AutoIngestManager.getSystemUserNameProperty())); }).start(); } From ad7243a57cacdf1218b9d622a67c6b57786652a7 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 19 Jun 2018 09:41:13 -0700 Subject: [PATCH 096/101] Added timezone handling by relying on LocalDateTime --- .../contentviewer/DataContentViewerOtherCases.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 47541cf486..9ce9e4f311 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -49,7 +49,8 @@ import javax.swing.JPanel; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; -import org.joda.time.LocalDate; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; @@ -471,20 +472,21 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi String dateStringDisplay = Bundle.DataContentViewerOtherCases_earliestCaseNotAvailable(); if (EamDb.isEnabled()) { - LocalDate earliestDate = LocalDate.now(); + LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC); DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); try { EamDb dbManager = EamDb.getInstance(); List cases = dbManager.getCases(); for (CorrelationCase aCase : cases) { - LocalDate caseDate = LocalDate.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); - + LocalDateTime caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate())); + if (caseDate.isBefore(earliestDate)) { earliestDate = caseDate; dateStringDisplay = aCase.getCreationDate(); } } + } catch (EamDbException ex) { logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS } catch (ParseException ex) { From 124b0cc602cb5389300d11294b8e3ab53f096060 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 19 Jun 2018 09:59:44 -0700 Subject: [PATCH 097/101] Revert "Wrapper Child factory in callable to further delay execution. Still doesn't make Search lazy since DataResultFilterNode will gather all children and do lookups which triggers the children queries." This reverts commit b6d8bf7a1d68e5d170800041e63cd485c2bde831. # Conflicts: # Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java --- .../commonfilesearch/CommonFilesChildren.java | 49 ------------------- .../sleuthkit/autopsy/datamodel/Md5Node.java | 27 ++-------- 2 files changed, 4 insertions(+), 72 deletions(-) delete mode 100644 Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java deleted file mode 100644 index 11cfd5c967..0000000000 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesChildren.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011 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.commonfilesearch; - -import java.util.List; -import org.sleuthkit.datamodel.AbstractFile; -import org.openide.nodes.Children; -import org.openide.nodes.Node; - -/** - * - * @author bsweeney - */ -class CommonFilesChildren extends Children.Keys{ - - CommonFilesChildren(boolean lazy, List fileList){ - super(lazy); - this.setKeys(fileList); - } - - @Override - protected Node[] createNodes(AbstractFile t) { - - if(t.isDir()){ - //TODO log an error ??? - - } else { - - } - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java index 3960c27eee..96de7eba36 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Md5Node.java @@ -22,7 +22,6 @@ package org.sleuthkit.autopsy.datamodel; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -54,34 +53,16 @@ public class Md5Node extends DisplayableItemNode { private final String dataSources; public Md5Node(Md5Metadata data) { - super(Children.createLazy(new Md5ChildCallable(data)), Lookups.singleton(data.getMd5())); + super(Children.create( + new FileInstanceNodeFactory(data), true), + Lookups.singleton(data.getMd5())); + this.commonFileCount = data.size(); this.dataSources = String.join(", ", data.getDataSources()); this.md5Hash = data.getMd5(); this.setDisplayName(this.md5Hash); } - - /** - * Callable wrapper to further delay lazy ChildFactory creation - * and createNodes() call once lazy loading is functional. - */ - private static class Md5ChildCallable implements Callable { - private final Md5Metadata key; - private Md5ChildCallable(Md5Metadata key) { - this.key = key; - } - @Override - public Children call() throws Exception { - //Check that the key has children, - //if it doesn't have children, return a leaf: - if (key.getMetadata().isEmpty()) { - return Children.LEAF; - } else { - return Children.create(new FileInstanceNodeFactory(key), true); - } - } - } int getCommonFileCount() { return this.commonFileCount; From 0bfd64f156fad4e19115c5e67af58240c54b10ac Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 19 Jun 2018 10:24:15 -0700 Subject: [PATCH 098/101] Update progress handler text variable names, remove second text update. --- .../autopsy/commonfilesearch/CommonFilesPanel.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java index 12324b6b90..556b25fa52 100644 --- a/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonfilesearch/CommonFilesPanel.java @@ -166,9 +166,8 @@ public final class CommonFilesPanel extends javax.swing.JPanel { "CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)", "CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)", "CommonFilesPanel.search.results.pathText=Common Files Search Results", - "CommonFilesPanel.search.done.searchProgress1=Gathering Common Files Search Results.", - "CommonFilesPanel.search.done.searchProgress2=Generating Common Files Search Results.", - "CommonFilesPanel.search.done.searchProgress3=Displaying Common Files Search Results.", + "CommonFilesPanel.search.done.searchProgressGathering=Gathering Common Files Search Results.", + "CommonFilesPanel.search.done.searchProgressDisplay=Displaying Common Files Search Results.", "CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.", "CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.", "CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.", @@ -209,7 +208,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { @Override @SuppressWarnings({"BoxedValueEquality", "NumberEquality"}) protected CommonFilesMetadata doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException { - progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgress1()); + progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering()); progress.start(); progress.switchToIndeterminate(); @@ -247,8 +246,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { protected void done() { try { super.done(); - progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgress2()); - CommonFilesMetadata metadata = get(); + CommonFilesMetadata metadata = get(); CommonFilesNode commonFilesNode = new CommonFilesNode(metadata); @@ -260,7 +258,7 @@ public final class CommonFilesPanel extends javax.swing.JPanel { Collection viewers = new ArrayList<>(1); viewers.add(table); - progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgress3()); + progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay()); DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers); progress.finish(); } catch (InterruptedException ex) { From 6ece03f355ee949cacd612471c2001a995c02530 Mon Sep 17 00:00:00 2001 From: Andrew Ziehl Date: Tue, 19 Jun 2018 10:42:26 -0700 Subject: [PATCH 099/101] PR fixes, remove excess NON-NLS, log stack trace, disable UI elements when CR unchecked. --- .../DataContentViewerOtherCases.java | 2 -- .../optionspanel/Bundle.properties | 2 +- .../optionspanel/GlobalSettingsPanel.form | 4 +-- .../optionspanel/GlobalSettingsPanel.java | 26 ++++++++++--------- .../optionspanel/ShowCasesDialog.java | 9 ++++--- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 9ce9e4f311..161d52fab4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -492,8 +492,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi } catch (ParseException ex) { logger.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS } - // NON-NLS - // NON-NLS } earliestCaseDate.setText(dateStringDisplay); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties index 3e090a5640..5514e3316b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties @@ -67,7 +67,6 @@ GlobalSettingsPanel.pnCorrelationProperties.border.title=Correlation Properties GlobalSettingsPanel.organizationPanel.border.title=Organizations GlobalSettingsPanel.casesPanel.border.title=Case Details GlobalSettingsPanel.showCasesButton.text=Show Cases -GlobalSettingsPanel.casesTextArea1.text=Display table that lists central repository case details. ShowCasesDialog.closeButton.AccessibleContext.accessibleName=Close ShowCasesDialog.closeButton.actionCommand=Close ShowCasesDialog.closeButton.text=Close @@ -75,3 +74,4 @@ ShowCasesDialog.caseDetailsTable.toolTipText=Click column name to sort. Right-cl ShowCasesDialog.title=Case Details GlobalSettingsPanel.Case\ Details.AccessibleContext.accessibleName=Cases Details ShowCasesDialog.caseDetailsTable.AccessibleContext.accessibleDescription=Click column name to sort. +GlobalSettingsPanel.casesTextArea.text=Display table that lists central repository case details. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form index 0d647a920f..33c94bea0b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form @@ -471,7 +471,7 @@ - + @@ -484,7 +484,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index e4d618dee7..925edee795 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -127,7 +127,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i casesPanel = new javax.swing.JPanel(); showCasesButton = new javax.swing.JButton(); casesScrollPane = new javax.swing.JScrollPane(); - casesTextArea1 = new javax.swing.JTextArea(); + casesTextArea = new javax.swing.JTextArea(); tbOops = new javax.swing.JTextField(); setName(""); // NOI18N @@ -308,16 +308,16 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i casesScrollPane.setBorder(null); - casesTextArea1.setEditable(false); - casesTextArea1.setBackground(new java.awt.Color(240, 240, 240)); - casesTextArea1.setColumns(20); - casesTextArea1.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N - casesTextArea1.setLineWrap(true); - casesTextArea1.setRows(2); - casesTextArea1.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesTextArea1.text")); // NOI18N - casesTextArea1.setWrapStyleWord(true); - casesTextArea1.setBorder(null); - casesScrollPane.setViewportView(casesTextArea1); + casesTextArea.setEditable(false); + casesTextArea.setBackground(new java.awt.Color(240, 240, 240)); + casesTextArea.setColumns(20); + casesTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + casesTextArea.setLineWrap(true); + casesTextArea.setRows(2); + casesTextArea.setText(org.openide.util.NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.casesTextArea.text")); // NOI18N + casesTextArea.setWrapStyleWord(true); + casesTextArea.setBorder(null); + casesScrollPane.setViewportView(casesTextArea); javax.swing.GroupLayout casesPanelLayout = new javax.swing.GroupLayout(casesPanel); casesPanel.setLayout(casesPanelLayout); @@ -614,6 +614,8 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i organizationTextArea.setEnabled(enable && !ingestRunning); manageOrganizationButton.setEnabled(enable && !ingestRunning); showCasesButton.setEnabled(enable && !ingestRunning); + casesPanel.setEnabled(enable && !ingestRunning); + casesTextArea.setEnabled(enable && !ingestRunning); return true; } @@ -622,7 +624,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private javax.swing.JButton bnManageTypes; private javax.swing.JPanel casesPanel; private javax.swing.JScrollPane casesScrollPane; - private javax.swing.JTextArea casesTextArea1; + private javax.swing.JTextArea casesTextArea; private javax.swing.JCheckBox cbUseCentralRepo; private javax.swing.JScrollPane correlationPropertiesScrollPane; private javax.swing.JTextArea correlationPropertiesTextArea; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java index cf32998430..6dc29ea87f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/ShowCasesDialog.java @@ -19,14 +19,15 @@ package org.sleuthkit.autopsy.centralrepository.optionspanel; import java.util.List; +import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JFrame; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coreutils.Logger; /** * Dialog to display table of CorrelationCase information from the CR tab of options. @@ -35,6 +36,8 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; final class ShowCasesDialog extends JDialog { private static final long serialVersionUID = 1L; + + private final static Logger logger = Logger.getLogger(ShowCasesDialog.class.getName()); private final ShowCasesTableModel tableModel; @Messages({"ShowCasesDialog.title_text=All Cases Details"}) @@ -44,7 +47,7 @@ final class ShowCasesDialog extends JDialog { ShowCasesDialog() { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.ShowCasesDialog_title_text(), - true); // NON-NLS + true); tableModel = new ShowCasesTableModel(); initComponents(); try { @@ -54,7 +57,7 @@ final class ShowCasesDialog extends JDialog { tableModel.addEamCase(eamCase); } } catch (EamDbException ex) { - Exceptions.printStackTrace(ex); + logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS } display(); } From 6eb82224aab9f980f1e8c4dbc30323fce1e81812 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Tue, 19 Jun 2018 14:05:42 -0400 Subject: [PATCH 100/101] Clarify strings about substrings --- .../modules/interestingitems/Bundle.properties | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties index 830901a6d9..35378ab1a3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties @@ -21,7 +21,7 @@ FilesSetRulePanel.extensionRadioButton.text=Extension Only FilesSetRulePanel.pathRegexCheckBox.text=Regex FilesSetRulePanel.pathTextField.text= FilesSetRulePanel.fullNameRadioButton.text=Full Name -FilesSetRulePanel.nameRegexCheckbox.text=Regex +FilesSetRulePanel.nameRegexCheckbox.text=Substring / Regex FilesSetRulePanel.ruleNameTextField.text= FilesSetRulePanel.nameTextField.text= FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional): @@ -36,8 +36,8 @@ FilesIdentifierIngestJobSettingsPanel.border.title=Select interesting files sets FilesSetRulePanel.jLabel1.text=Type: FilesSetRulePanel.interesting.jLabel5.text=Enter information about files that you want to find. FilesSetRulePanel.ingest.jLabel5.text=Enter information about files that you want to run ingest on. -FilesSetRulePanel.nameCheck.text=Name Pattern: -FilesSetRulePanel.pathCheck.text=Path Pattern: +FilesSetRulePanel.nameCheck.text=Name: +FilesSetRulePanel.pathCheck.text=Path Substring: FilesSetRulePanel.filesRadioButton.text=Files FilesSetRulePanel.dirsRadioButton.text=Directories FilesSetDefsPanel.interesting.setsListLabel.text=Rule Sets: @@ -56,18 +56,18 @@ FilesSetDefsPanel.newRuleButton.text=New Rule FilesSetDefsPanel.jLabel8.text=File Size: FilesSetDefsPanel.jLabel7.text=MIME Type: FilesSetDefsPanel.rulePathConditionRegexCheckBox.text=Regex -FilesSetDefsPanel.jLabel4.text=Path Pattern: +FilesSetDefsPanel.jLabel4.text=Path Substring: FilesSetDefsPanel.jLabel1.text=Rule Details FilesSetDefsPanel.dirsRadioButton.text=Directories FilesSetDefsPanel.jLabel2.text=File Type: FilesSetDefsPanel.deleteRuleButton.text=Delete Rule -FilesSetDefsPanel.fileNameRegexCheckbox.text=Regex +FilesSetDefsPanel.fileNameRegexCheckbox.text=Substring / Regex FilesSetDefsPanel.ignoreKnownFilesCheckbox.text=Ignore Known Files FilesSetDefsPanel.rulePathConditionTextField.text= -FilesSetDefsPanel.fileNameRadioButton.text=File Name +FilesSetDefsPanel.fileNameRadioButton.text=Full Name FilesSetDefsPanel.jLabel5.text=Description: FilesSetDefsPanel.fileNameTextField.text= -FilesSetDefsPanel.jLabel3.text=Name Pattern: +FilesSetDefsPanel.jLabel3.text=Name: FilesSetDefsPanel.fileNameExtensionRadioButton.text=Extension Only FilesSetDefsPanel.rulesListLabel.text=Rules: FilesSetDefsPanel.editRuleButton.text=Edit Rule From 0db86a108d31d27089d11f91b5000e00c879937f Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Wed, 20 Jun 2018 09:25:15 -0400 Subject: [PATCH 101/101] added check for running with gui before showing gui --- .../org/sleuthkit/autopsy/python/JythonModuleLoader.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java index 8afd8e8d4f..700aa0cc5c 100644 --- a/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java +++ b/Core/src/org/sleuthkit/autopsy/python/JythonModuleLoader.java @@ -35,6 +35,7 @@ import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.python.util.PythonInterpreter; +import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; @@ -79,7 +80,9 @@ public final class JythonModuleLoader { interpreter = new PythonInterpreter(); } catch (Exception ex) { logger.log(Level.SEVERE, "Failed to load python Intepreter. Cannot load python modules", ex); - MessageNotifyUtil.Notify.show(Bundle.JythonModuleLoader_pythonInterpreterError_title(),Bundle.JythonModuleLoader_pythonInterpreterError_msg(), MessageNotifyUtil.MessageType.ERROR); + if(RuntimeProperties.runningWithGUI()){ + MessageNotifyUtil.Notify.show(Bundle.JythonModuleLoader_pythonInterpreterError_title(),Bundle.JythonModuleLoader_pythonInterpreterError_msg(), MessageNotifyUtil.MessageType.ERROR); + } return objects; } // add python modules from 'autospy/build/cluster/InternalPythonModules' folder @@ -101,9 +104,7 @@ public final class JythonModuleLoader { if (line.startsWith("class ") && filter.accept(line)) { //NON-NLS String className = line.substring(6, line.indexOf("(")); try { - if (interpreter != null) { - objects.add(createObjectFromScript(interpreter, script, className, interfaceClass)); - } + objects.add(createObjectFromScript(interpreter, script, className, interfaceClass)); } catch (Exception ex) { logger.log(Level.SEVERE, String.format("Failed to load %s from %s", className, script.getAbsolutePath()), ex); //NON-NLS // NOTE: using ex.toString() because the current version is always returning null for ex.getMessage().