From 0f9f12ae654ba2f0c47846bdca28fcad30703376 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 2 Sep 2021 20:33:27 -0400 Subject: [PATCH 01/51] changes to support view source --- .../datamodel/BlackboardArtifactNode.java | 44 +++++++-- .../datamodel/Bundle.properties-MERGED | 1 + .../directorytree/Bundle.properties-MERGED | 6 ++ .../directorytree/DataResultFilterNode.java | 93 ++++++++++--------- .../actions/ViewArtifactInTimelineAction.java | 15 ++- 5 files changed, 106 insertions(+), 53 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index e3bad44705..df28377d6c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -43,6 +43,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; @@ -81,6 +82,7 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslationService; import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact.Category; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.Score; /** @@ -229,7 +231,7 @@ public class BlackboardArtifactNode extends AbstractContentNode actionsList = new ArrayList<>(); @@ -447,10 +464,10 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Fri, 3 Sep 2021 08:43:26 -0400 Subject: [PATCH 02/51] updates to navigate to os account --- .../autopsy/datamodel/OsAccounts.java | 18 +++- .../directorytree/DataResultFilterNode.java | 84 +++++++++++++++---- .../DirectoryTreeTopComponent.java | 67 +++++++++++++++ 3 files changed, 151 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java index ade29e2cfa..c488e732f0 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java @@ -63,10 +63,20 @@ public final class OsAccounts implements AutopsyVisitableItem { private static final String ICON_PATH = "org/sleuthkit/autopsy/images/os-account.png"; private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); private static final String REALM_DATA_AVAILABLE_EVENT = "REALM_DATA_AVAILABLE_EVENT"; + private static final String LIST_NAME = Bundle.OsAccount_listNode_name(); private SleuthkitCase skCase; private final long filteringDSObjId; + /** + * Returns the name of the OsAccountListNode to be used for id purposes. + * + * @return The name of the OsAccountListNode to be used for id purposes. + */ + public static String getListName() { + return LIST_NAME; + } + public OsAccounts(SleuthkitCase skCase) { this(skCase, 0); } @@ -94,8 +104,8 @@ public final class OsAccounts implements AutopsyVisitableItem { */ public OsAccountListNode() { super(Children.create(new OsAccountNodeFactory(), true)); - setName(Bundle.OsAccount_listNode_name()); - setDisplayName(Bundle.OsAccount_listNode_name()); + setName(LIST_NAME); + setDisplayName(LIST_NAME); setIconBaseWithExtension("org/sleuthkit/autopsy/images/os-account.png"); } @@ -137,7 +147,7 @@ public final class OsAccounts implements AutopsyVisitableItem { } } }; - + private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(listener, null); @Override @@ -146,7 +156,7 @@ public final class OsAccounts implements AutopsyVisitableItem { Case.removeEventTypeSubscriber(Collections.singleton(Case.Events.OS_ACCOUNTS_ADDED), weakPcl); Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl); } - + @Override protected void addNotify() { Case.addEventTypeSubscriber(EnumSet.of(Case.Events.OS_ACCOUNTS_ADDED, Case.Events.OS_ACCOUNTS_DELETED), listener); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 9d8249ef59..92d0f8ae5d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -257,6 +257,54 @@ public class DataResultFilterNode extends FilterNode { } } + /** + * An action that navigates to an artifact. + */ + private static class ViewArtifactAction extends AbstractAction { + + private final BlackboardArtifact artifact; + + /** + * Main constructor. + * + * @param artifact The artifact to navigate to in the action. + * @param displayName The display name of the menu item. + */ + ViewArtifactAction(BlackboardArtifact artifact, String displayName) { + super(displayName); + this.artifact = artifact; + } + + @Override + public void actionPerformed(ActionEvent e) { + DirectoryTreeTopComponent.findInstance().viewArtifact(artifact); + } + } + + /** + * An action that navigates to an os account. + */ + private static class ViewOsAccountAction extends AbstractAction { + + private final OsAccount osAccount; + + /** + * Main constructor. + * + * @param osAccount The os account to navigate to in the action. + * @param displayName The display name of the menu item. + */ + ViewOsAccountAction(OsAccount osAccount, String displayName) { + super(displayName); + this.osAccount = osAccount; + } + + @Override + public void actionPerformed(ActionEvent e) { + DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount); + } + } + /** * Get the menu action for view source X (file, data artifact, os account) * for an analysis result. @@ -271,22 +319,27 @@ public class DataResultFilterNode extends FilterNode { "DataResultFilterNode_getViewSourceDisplayName_baseMessage=View Source {0}", "DataResultFilterNode_getViewSourceDisplayName_type_File=File", "DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact=Data Artifact", - "DataResultFilterNode_getViewSourceDisplayName_type_OSAccount=OS Account", - "DataResultFilterNode_getViewSourceDisplayName_type_Unknown=Content" + "DataResultFilterNode_getViewSourceDisplayName_type_OSAccount=OS Account" }) - private static ViewContextAction getResultViewContext(Content content) { - String type = Bundle.DataResultFilterNode_getViewSourceDisplayName_type_Unknown(); - if (content instanceof AbstractFile) { - type = Bundle.DataResultFilterNode_getViewSourceDisplayName_type_File(); - } else if (content instanceof DataArtifact) { - type = Bundle.DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact(); + private static Action getContentNavigateAction(Content content) { + if (content instanceof DataArtifact) { + return new ViewArtifactAction( + (DataArtifact) content, + Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( + Bundle.DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact())); } else if (content instanceof OsAccount) { - type = Bundle.DataResultFilterNode_getViewSourceDisplayName_type_OSAccount(); + return new ViewOsAccountAction( + (OsAccount) content, + Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( + Bundle.DataResultFilterNode_getViewSourceDisplayName_type_OSAccount())); + } else if (content instanceof AbstractFile) { + return new ViewContextAction( + Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( + Bundle.DataResultFilterNode_getViewSourceDisplayName_type_File()), + content); + } else { + return null; } - - String menuDisplayName = Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage(type); - - return new ViewContextAction(menuDisplayName, content); } @NbBundle.Messages("DataResultFilterNode.viewSourceArtifact.text=View Source Result") @@ -311,7 +364,10 @@ public class DataResultFilterNode extends FilterNode { } if (ban.getArtifact() instanceof AnalysisResult) { - actionsList.add(getResultViewContext(ban.getSourceContent())); + Action contentNavigateAction = getContentNavigateAction(ban.getSourceContent()); + if (contentNavigateAction != null) { + actionsList.add(contentNavigateAction); + } } else { // if the artifact links to another file, add an action to go to // that file diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 975a659aa7..5550c6fc27 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -83,6 +83,7 @@ import org.sleuthkit.autopsy.datamodel.InterestingHits; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.AutopsyTreeChildFactory; import org.sleuthkit.autopsy.datamodel.DataArtifacts; +import org.sleuthkit.autopsy.datamodel.OsAccounts; import org.sleuthkit.autopsy.datamodel.PersonNode; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.ViewsNode; @@ -95,6 +96,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Host; +import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.Person; import org.sleuthkit.datamodel.TskCoreException; @@ -1220,6 +1222,71 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat .findFirst(); } + /** + * Does depth-first search to find os account list node where the provided os account is a child. + * @param node The node. + * @param osAccount The os account. + * @return The parent list node of the os account if found or empty if not. + */ + private Optional getOsAccountListNode(Node node, OsAccount osAccount) { + if (node == null) { + return Optional.empty(); + } else if (node.getLookup().lookup(Host.class) != null + || node.getLookup().lookup(Person.class) != null + || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class)) + || node.getLookup().lookup(DataSource.class) != null) { + + return Stream.of(node.getChildren().getNodes(true)) + .map(childNode -> getOsAccountListNode(childNode, osAccount)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + + } else if (OsAccounts.getListName().equals(node.getName())) { + boolean isOsAccountParent = Stream.of(node.getChildren().getNodes(true)) + .filter(osAcctNd -> { + OsAccount osAcctOfNd = osAcctNd.getLookup().lookup(OsAccount.class); + return osAcctOfNd != null && osAcctOfNd.getId() == osAccount.getId(); + }) + .findFirst() + .isPresent(); + + return isOsAccountParent ? Optional.of(node) : Optional.empty(); + } else { + return Optional.empty(); + } + } + + /** + * Navigates to the os account if the os account is found in the tree. + * @param osAccount The os account. + */ + void viewOsAccount(OsAccount osAccount) { + Optional osAccountListNodeOpt = Stream.of(em.getRootContext().getChildren().getNodes(true)) + .map(nd -> getOsAccountListNode(nd, osAccount)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + + if (!osAccountListNodeOpt.isPresent()) { + return; + } + + Node osAccountListNode = osAccountListNodeOpt.get(); + + DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) osAccountListNode).getOriginal(); + undecoratedParentNode.setChildNodeSelectionInfo((osAcctNd) -> { + OsAccount osAcctOfNd = osAcctNd.getLookup().lookup(OsAccount.class); + return osAcctOfNd != null && osAcctOfNd.getId() == osAccount.getId(); + }); + getTree().expandNode(osAccountListNode); + try { + em.setExploredContextAndSelection(osAccountListNode, new Node[]{osAccountListNode}); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.WARNING, "Property Veto: ", ex); //NON-NLS + } + } + /** * Attempts to retrieve the artifact type for the given artifact type id. * From a261ff1237d85b885fe8c2c1f479adde1cd60ec1 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 3 Sep 2021 13:42:44 -0400 Subject: [PATCH 03/51] os account navigation fixes --- .../directorytree/Bundle.properties-MERGED | 1 - .../DirectoryTreeTopComponent.java | 55 ++++++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 970a1d04fe..1170f879c4 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -10,7 +10,6 @@ DataResultFilterNode_getViewSourceDisplayName_baseMessage=View Source {0} DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact=Data Artifact DataResultFilterNode_getViewSourceDisplayName_type_File=File DataResultFilterNode_getViewSourceDisplayName_type_OSAccount=OS Account -DataResultFilterNode_getViewSourceDisplayName_type_Unknown=Content # {0} - dataSourceCount DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contains {0} data sources. Would you like to group by data source for faster loading? DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source? diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 5550c6fc27..e333e5f115 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -1223,47 +1224,61 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } /** - * Does depth-first search to find os account list node where the provided os account is a child. - * @param node The node. + * Does depth-first search to find os account list node where the provided + * os account is a child. + * + * @param node The node. * @param osAccount The os account. + * * @return The parent list node of the os account if found or empty if not. */ - private Optional getOsAccountListNode(Node node, OsAccount osAccount) { + private Optional getOsAccountListNode(Node node, OsAccount osAccount, Set hosts) { if (node == null) { return Optional.empty(); - } else if (node.getLookup().lookup(Host.class) != null + } + + Host nodeHost = node.getLookup().lookup(Host.class); + if ((nodeHost != null && hosts != null && hosts.contains(nodeHost)) + || node.getLookup().lookup(DataSource.class) != null || node.getLookup().lookup(Person.class) != null - || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class)) - || node.getLookup().lookup(DataSource.class) != null) { + || PersonNode.getUnknownPersonId().equals(node.getLookup().lookup(String.class))) { return Stream.of(node.getChildren().getNodes(true)) - .map(childNode -> getOsAccountListNode(childNode, osAccount)) + .map(childNode -> getOsAccountListNode(childNode, osAccount, hosts)) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); - } else if (OsAccounts.getListName().equals(node.getName())) { - boolean isOsAccountParent = Stream.of(node.getChildren().getNodes(true)) - .filter(osAcctNd -> { - OsAccount osAcctOfNd = osAcctNd.getLookup().lookup(OsAccount.class); - return osAcctOfNd != null && osAcctOfNd.getId() == osAccount.getId(); - }) - .findFirst() - .isPresent(); - - return isOsAccountParent ? Optional.of(node) : Optional.empty(); - } else { - return Optional.empty(); } + + if (OsAccounts.getListName().equals(node.getName())) { + return Optional.of(node); + } + + return Optional.empty(); } /** * Navigates to the os account if the os account is found in the tree. + * * @param osAccount The os account. */ void viewOsAccount(OsAccount osAccount) { + Set hosts = null; + + if (CasePreferences.getGroupItemsInTreeByDataSource()) { + try { + hosts = new HashSet<>(Case.getCurrentCase().getSleuthkitCase().getOsAccountManager().getHosts(osAccount)); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Unable to get valid hosts for osAccount: " + osAccount, ex); + return; + } + } + + final Set finalHosts = hosts; + Optional osAccountListNodeOpt = Stream.of(em.getRootContext().getChildren().getNodes(true)) - .map(nd -> getOsAccountListNode(nd, osAccount)) + .map(nd -> getOsAccountListNode(nd, osAccount, finalHosts)) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); From 214ee21cf882c9ba14190e03420d79cc989388d6 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 7 Sep 2021 15:58:23 -0400 Subject: [PATCH 04/51] refactor --- .../datamodel/BlackboardArtifactNode.java | 533 +++++++++++++++--- .../directorytree/DataResultFilterNode.java | 266 +-------- .../DirectoryTreeTopComponent.java | 2 +- 3 files changed, 467 insertions(+), 334 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index df28377d6c..925018ecd0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -21,13 +21,17 @@ package org.sleuthkit.autopsy.datamodel; import com.google.common.annotations.Beta; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -37,15 +41,23 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.swing.AbstractAction; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.openide.util.Utilities; import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.AddContentTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction; +import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; @@ -76,14 +88,34 @@ import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.datamodel.utils.IconsUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; import org.sleuthkit.autopsy.texttranslation.TextTranslationService; import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.autopsy.directorytree.ExportCSVAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.ViewContextAction; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact.Category; import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalDirectory; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.Score; +import org.sleuthkit.datamodel.SlackFile; +import org.sleuthkit.datamodel.TskException; +import org.sleuthkit.datamodel.VirtualDirectory; /** * A BlackboardArtifactNode is an AbstractNode implementation that can be used @@ -442,13 +474,94 @@ public class BlackboardArtifactNode extends AbstractContentNode List getNonNull(T...items) { + return Stream.of(items) + .filter(i -> i != null) + .collect(Collectors.toList()); } @NbBundle.Messages({ @@ -456,57 +569,311 @@ public class BlackboardArtifactNode extends AbstractContentNode selectedFilesList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + + final Collection selectedArtifactsList + = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + + super.getActions(context); + + // break + getTimelineArtifactAction(); + getTimelineFileAction(artifact); + getTimelineSrcContentAction(); + + // break + getSrcContentAction(); + getFileAction(); + + // break + getExtractWithPasswordAction(srcContent); + getReportActions(srcContent); + + // break + getSrcContentViewerActions(); + + // break + getExtractExportActions(hasSrcFile); + + // break + getTagActions(); + + // break? + if (n != null) { + actionsList.addAll(ContextMenuExtensionPoint.getActions()); + } + } + + @Messages({ + "# {0} - contentType", + "BlackboardArtifactNode_getSrcContentAction_baseMessage=View Source {0} in Directory", + "BlackboardArtifactNode_getSrcContentAction_type_File=File", + "BlackboardArtifactNode_getSrcContentAction_type_DataArtifact=Data Artifact", + "BlackboardArtifactNode_getSrcContentAction_type_OSAccount=OS Account", + "BlackboardArtifactNode_getSrcContentAction_type_Content=Content", + "BlackboardArtifactNode_getSrcContentAction_viewSrcFile=test" + }) + private Action getSrcContentAction() { + if (srcContent instanceof DataArtifact) { + return new ViewArtifactAction( + (DataArtifact) srcContent, + Bundle.BlackboardArtifactNode_getSrcContentAction_baseMessage( + Bundle.BlackboardArtifactNode_getSrcContentAction_type_DataArtifact())); + } else if (srcContent instanceof OsAccount) { + return new ViewOsAccountAction( + (OsAccount) srcContent, + Bundle.BlackboardArtifactNode_getSrcContentAction_baseMessage( + Bundle.BlackboardArtifactNode_getSrcContentAction_type_OSAccount())); + } else if (srcContent instanceof AbstractFile) { + return new ViewContextAction( + Bundle.BlackboardArtifactNode_getSrcContentAction_baseMessage( + Bundle.BlackboardArtifactNode_getSrcContentAction_type_File()), + srcContent); + } else if (srcContent instanceof Content && artifact instanceof DataArtifact) { + return new ViewContextAction( + Bundle.BlackboardArtifactNode_getSrcContentAction_baseMessage( + Bundle.BlackboardArtifactNode_getSrcContentAction_type_Content()), + srcContent); + } else { + return null; + } + } + + /** + * Returns an action to go to a linked file if a file linked by TSK_PATH_ID + * exists. Otherwise, null is returned. + * + * @param artifact The artifact to search for a TSK_PATH_ID. + * + * @return An action to go to a linked file if a file linked by TSK_PATH_ID + * exists. Otherwise, null is returned. + */ + private Action getFileAction(BlackboardArtifact artifact) { + // if the artifact links to another file, add an action to go to + // that file + Content c = findLinkedContent(artifact); + return (c == null) + ? null + : new ViewContextAction( + NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c); + } + + /** + * Returns a Node representing the file content if the content is indeed + * some sort of file. Otherwise, return null. + * + * @param content The content. + * + * @return The file node or null if not a file. + */ + private Node getParentFileNode(Content content) { + if (content instanceof File) { + return new FileNode((AbstractFile) content); + } else if (content instanceof Directory) { + return new DirectoryNode((Directory) content); + } else if (content instanceof VirtualDirectory) { + return new VirtualDirectoryNode((VirtualDirectory) content); + } else if (content instanceof LocalDirectory) { + return new LocalDirectoryNode((LocalDirectory) content); + } else if (content instanceof LayoutFile) { + return new LayoutFileNode((LayoutFile) content); + } else if (content instanceof LocalFile || content instanceof DerivedFile) { + return new LocalFileNode((AbstractFile) content); + } else if (content instanceof SlackFile) { + return new SlackFileNode((SlackFile) content); + } else { + return null; + } + } + + /** + * Returns report actions if content instanceof report. Otherwise returns an + * empty list. + * + * @param content The content. + * + * @return The list of report actions. + */ + private List getReportActions(Content content) { + return content instanceof Report + ? DataModelActionsFactory.getActions(content, false) + : Collections.emptyList(); + } + + /** + * Returns actions for extracting content from file or null if not possible. + * + * @param srcContent The source content. + * + * @return The action or null if not appropriate source content. + */ + private Action getExtractWithPasswordAction(Content srcContent) { + if (srcContent instanceof LocalFile || srcContent instanceof DerivedFile) { + + if (FileTypeExtensions.getArchiveExtensions() + .contains("." + ((AbstractFile) srcContent).getNameExtension().toLowerCase())) { + try { + if (srcContent.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0) { + return new ExtractArchiveWithPasswordAction((AbstractFile) srcContent); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); + } + } + } + return null; + } + + /** + * Returns tag actions. + * + * + * @param artifact This artifact. + * @param selectedFileCount The count of selected files. + * @param selectedArtifactCount The count of selected artifacts. + * + * @return The tag actions. + */ + private List getTagActions(boolean hasSrcFile, BlackboardArtifact artifact, int selectedFileCount, int selectedArtifactCount) { List actionsList = new ArrayList<>(); - actionsList.addAll(Arrays.asList(super.getActions(context))); - /* - * If the artifact represented by this node has a timestamp, add an - * action to view it in the timeline. - */ - try { - if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact) - && // don't show ViewArtifactInTimelineAction for AnalysisResults. - (!(this.artifact instanceof AnalysisResult))) { - - actionsList.add(new ViewArtifactInTimelineAction(artifact)); + if (hasSrcFile) { + // don't show AddContentTagAction for data artifacts. + if (!(artifact instanceof DataArtifact)) { + actionsList.add(AddContentTagAction.getInstance()); } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting artifact timestamp (artifact objID={0})", artifact.getId()), ex); //NON-NLS + + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + + // don't show DeleteFileContentTagAction for data artifacts. + if ((!(artifact instanceof DataArtifact)) && (selectedFileCount == 1)) { + actionsList.add(DeleteFileContentTagAction.getInstance()); + } + } else { + // There's no specific file associated with the artifact, but + // we can still tag the artifact itself + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); } - /* - * If the artifact represented by this node is linked to a file via a - * TSK_PATH_ID attribute, add an action to view the file in the - * timeline. - */ - try { - AbstractFile linkedFile = findLinked(artifact); - if (linkedFile != null) { - actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedFile)); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file of artifact (artifact objID={0})", artifact.getId()), ex); //NON-NLS - + if (selectedArtifactCount == 1) { + actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); } - /* - * If the source content of the artifact represented by this node is a - * file, add an action to view the file in the data source tree. - */ - if (this.srcContent instanceof AbstractFile) { - actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction((AbstractFile) this.srcContent)); - } else if (this.srcContent instanceof DataArtifact) { + return actionsList; + } + + /** + * Returns actions to extract or export. If srcFileNode is null, returns an + * empty list. + * + * @param hasSrcFile Whether or not src content is some sort of file. + * + * @return The list of actions or an empty list. + */ + private List getExtractExportActions(boolean hasSrcFile) { + return hasSrcFile + ? Arrays.asList(ExtractAction.getInstance(), ExportCSVAction.getInstance()) + : Collections.emptyList(); + } + + /** + * Returns actions to view src content in a different viewer or window. + * + * @param srcFileNode The source file node or null if no source file. + * @param selectedFileCount The number of selected files. + * + * @return The list of actions or an empty list. + */ + @Messages({ + "BlackboardArtifactNode_getSrcContentViewerActions_viewInNewWin=View in New Window", + "BlackboardArtifactNode_getSrcContentViewerActions_openInExtViewer=Open in External Viewer Ctrl+E" + }) + private List getSrcContentViewerActions(Node srcFileNode, int selectedFileCount) { + List actionsList = new ArrayList<>(); + if (srcFileNode != null) { + actionsList.add(new NewWindowViewAction(Bundle.BlackboardArtifactNode_getSrcContentViewerActions_viewInNewWin(), srcFileNode)); + if (selectedFileCount == 1) { + actionsList.add(new ExternalViewerAction(Bundle.BlackboardArtifactNode_getSrcContentViewerActions_openInExtViewer(), srcFileNode)); + } else { + actionsList.add(ExternalViewerShortcutAction.getInstance()); + } + } + return actionsList; + } + + /** + * If the source content of the artifact represented by this node is a file, + * returns an action to view the file in the data source tree. + * + * @param srcContent The src content to navigate to in the timeline action. + * + * @return The src content navigation action or null. + */ + private Action getTimelineSrcContentAction(Content srcContent) { + if (srcContent instanceof AbstractFile) { + return ViewFileInTimelineAction.createViewSourceFileAction((AbstractFile) srcContent); + } else if (srcContent instanceof DataArtifact) { try { - if (ViewArtifactInTimelineAction.hasSupportedTimeStamp((DataArtifact) this.srcContent)) { - actionsList.add(new ViewArtifactInTimelineAction((DataArtifact) this.srcContent, - Bundle.BlackboardArtifactNode_getActions_viewSourceDataArtifact())); + if (ViewArtifactInTimelineAction.hasSupportedTimeStamp((DataArtifact) srcContent)) { + return new ViewArtifactInTimelineAction((DataArtifact) srcContent, + Bundle.BlackboardArtifactNode_getActions_viewSourceDataArtifact()); } } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting source data artifact timestamp (artifact objID={0})", this.srcContent.getId()), ex); //NON-NLS + logger.log(Level.SEVERE, MessageFormat.format("Error getting source data artifact timestamp (artifact objID={0})", srcContent.getId()), ex); //NON-NLS } } - return actionsList.toArray(new Action[actionsList.size()]); + return null; + } + + /** + * If the artifact represented by this node is linked to a file via a + * TSK_PATH_ID attribute, return an action to view the file in the timeline. + * + * @param art The artifact with the potential file for the timeline + * navigation action. + * + * @return Returns the view file in timeline action or null if no linked + * file. + */ + private Action getTimelineFileAction(BlackboardArtifact art) { + try { + AbstractFile linkedFile = findLinked(art); + if (linkedFile != null) { + return ViewFileInTimelineAction.createViewFileAction(linkedFile); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file of artifact (artifact objID={0})", art.getId()), ex); //NON-NLS + + } + + return null; + } + + /** + * If the artifact represented by this node has a timestamp, an action to + * view it in the timeline. + * + * @param art The artifact for timeline navigation action. + * + * @return The action or null if no action should exist. + */ + private Action getTimelineArtifactAction(BlackboardArtifact art) { + try { + if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(art) + && // don't show ViewArtifactInTimelineAction for AnalysisResults. + (!(art instanceof AnalysisResult))) { + + return new ViewArtifactInTimelineAction(art); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting artifact timestamp (artifact objID={0})", art.getId()), ex); //NON-NLS + } + + return null; } /** @@ -617,13 +984,17 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.artifactType.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.artifactType.displayName"), NO_DESCR, associatedArtifact.getDisplayName())); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.artifactDetails.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.artifactDetails.displayName"), NO_DESCR, associatedArtifact.getShortDescription())); } @@ -673,13 +1044,17 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.ext.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.mimeType.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.mimeType.displayName"), NO_DESCR, actualMimeType)); } @@ -699,8 +1074,10 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.filePath.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.filePath.displayName"), NO_DESCR, sourcePath)); } @@ -713,28 +1090,38 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file == null ? "" : TimeZoneUtils.getFormattedTime(file.getMtime()))); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file == null ? "" : TimeZoneUtils.getFormattedTime(file.getCtime()))); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file == null ? "" : TimeZoneUtils.getFormattedTime(file.getAtime()))); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file == null ? "" : TimeZoneUtils.getFormattedTime(file.getCrtime()))); sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "ContentTagNode.createSheet.fileSize.displayName"), "", file == null ? "" : file.getSize())); sheetSet.put(new NodeProperty<>( @@ -759,8 +1146,10 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.dataSrc.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.dataSrc.displayName"), NO_DESCR, dataSourceStr)); } @@ -784,14 +1173,18 @@ public class BlackboardArtifactNode extends AbstractContentNode( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.fileSize.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.fileSize.displayName"), NO_DESCR, size)); sheetSet .put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.path.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, + "BlackboardArtifactNode.createSheet.path.displayName"), NO_DESCR, path)); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 92d0f8ae5d..d8b2436179 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -22,8 +22,7 @@ import java.awt.event.ActionEvent; import java.beans.PropertyVetoException; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; +import java.util.Arrays; import java.util.List; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -34,54 +33,25 @@ import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.nodes.Sheet; 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.DeleteFileBlackboardArtifactTagAction; -import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; -import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; -import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; -import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceCountNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceCaseNode; import org.sleuthkit.autopsy.commonpropertiessearch.InstanceDataSourceNode; import org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributeValueNode; import org.sleuthkit.autopsy.commonpropertiessearch.CentralRepoCommonAttributeInstanceNode; -import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; -import org.sleuthkit.autopsy.datamodel.LocalDirectoryNode; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.commonpropertiessearch.CaseDBCommonAttributeInstanceNode; -import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; -import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DerivedFile; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.File; -import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.LocalFile; -import org.sleuthkit.datamodel.LocalDirectory; -import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.TskException; -import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; -import org.sleuthkit.datamodel.DataArtifact; -import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; /** @@ -257,91 +227,6 @@ public class DataResultFilterNode extends FilterNode { } } - /** - * An action that navigates to an artifact. - */ - private static class ViewArtifactAction extends AbstractAction { - - private final BlackboardArtifact artifact; - - /** - * Main constructor. - * - * @param artifact The artifact to navigate to in the action. - * @param displayName The display name of the menu item. - */ - ViewArtifactAction(BlackboardArtifact artifact, String displayName) { - super(displayName); - this.artifact = artifact; - } - - @Override - public void actionPerformed(ActionEvent e) { - DirectoryTreeTopComponent.findInstance().viewArtifact(artifact); - } - } - - /** - * An action that navigates to an os account. - */ - private static class ViewOsAccountAction extends AbstractAction { - - private final OsAccount osAccount; - - /** - * Main constructor. - * - * @param osAccount The os account to navigate to in the action. - * @param displayName The display name of the menu item. - */ - ViewOsAccountAction(OsAccount osAccount, String displayName) { - super(displayName); - this.osAccount = osAccount; - } - - @Override - public void actionPerformed(ActionEvent e) { - DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount); - } - } - - /** - * Get the menu action for view source X (file, data artifact, os account) - * for an analysis result. - * - * @param content The content to navigate to. - * - * @return The ViewContextAction that will navigate to the specified content - * in the results / tree view to be used with an analysis result. - */ - @NbBundle.Messages({ - "# {0} - contentType", - "DataResultFilterNode_getViewSourceDisplayName_baseMessage=View Source {0}", - "DataResultFilterNode_getViewSourceDisplayName_type_File=File", - "DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact=Data Artifact", - "DataResultFilterNode_getViewSourceDisplayName_type_OSAccount=OS Account" - }) - private static Action getContentNavigateAction(Content content) { - if (content instanceof DataArtifact) { - return new ViewArtifactAction( - (DataArtifact) content, - Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( - Bundle.DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact())); - } else if (content instanceof OsAccount) { - return new ViewOsAccountAction( - (OsAccount) content, - Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( - Bundle.DataResultFilterNode_getViewSourceDisplayName_type_OSAccount())); - } else if (content instanceof AbstractFile) { - return new ViewContextAction( - Bundle.DataResultFilterNode_getViewSourceDisplayName_baseMessage( - Bundle.DataResultFilterNode_getViewSourceDisplayName_type_File()), - content); - } else { - return null; - } - } - @NbBundle.Messages("DataResultFilterNode.viewSourceArtifact.text=View Source Result") /** * Uses the default nodes actions per node, adds some custom ones and @@ -351,116 +236,7 @@ public class DataResultFilterNode extends FilterNode { @Override public List visit(BlackboardArtifactNode ban) { - //set up actions for artifact node based on its Content object - //TODO all actions need to be consolidated in single place! - //they should be set in individual Node subclass and using a utility to get Actions per Content sub-type - // TODO UPDATE: There is now a DataModelActionsFactory utility; - - List actionsList = new ArrayList<>(); - - //merge predefined specific node actions if bban subclasses have their own - for (Action a : ban.getActions(true)) { - actionsList.add(a); - } - - if (ban.getArtifact() instanceof AnalysisResult) { - Action contentNavigateAction = getContentNavigateAction(ban.getSourceContent()); - if (contentNavigateAction != null) { - actionsList.add(contentNavigateAction); - } - } else { - // if the artifact links to another file, add an action to go to - // that file - Content c = findLinked(ban); - if (c != null) { - actionsList.add(new ViewContextAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); - } - // action to go to the source file of the artifact - Content fileContent = ban.getLookup().lookup(AbstractFile.class); - if (fileContent == null) { - Content content = ban.getLookup().lookup(Content.class); - actionsList.add(new ViewContextAction("View Source Content in Directory", content)); - } else { - actionsList.add(new ViewContextAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); - } - } - Content c = ban.getLookup().lookup(File.class); - Node n = null; - if (c != null) { - n = new FileNode((AbstractFile) c); - } else if ((c = ban.getLookup().lookup(Directory.class)) != null) { - n = new DirectoryNode((Directory) c); - } else if ((c = ban.getLookup().lookup(VirtualDirectory.class)) != null) { - n = new VirtualDirectoryNode((VirtualDirectory) c); - } else if ((c = ban.getLookup().lookup(LocalDirectory.class)) != null) { - n = new LocalDirectoryNode((LocalDirectory) c); - } else if ((c = ban.getLookup().lookup(LayoutFile.class)) != null) { - n = new LayoutFileNode((LayoutFile) c); - } else if ((c = ban.getLookup().lookup(LocalFile.class)) != null - || (c = ban.getLookup().lookup(DerivedFile.class)) != null) { - n = new LocalFileNode((AbstractFile) c); - if (FileTypeExtensions.getArchiveExtensions().contains("." + ((AbstractFile) c).getNameExtension().toLowerCase())) { - try { - if (c.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED).size() > 0) { - actionsList.add(new ExtractArchiveWithPasswordAction((AbstractFile) c)); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); - } - } - } else if ((c = ban.getLookup().lookup(SlackFile.class)) != null) { - n = new SlackFileNode((SlackFile) c); - } else if ((c = ban.getLookup().lookup(Report.class)) != null) { - actionsList.addAll(DataModelActionsFactory.getActions(c, false)); - } - if (n != null) { - final Collection selectedFilesList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - actionsList.add(null); // creates a menu separator - actionsList.add(new NewWindowViewAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInNewWin.text"), n)); - if (selectedFilesList.size() == 1) { - actionsList.add(new ExternalViewerAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.openInExtViewer.text"), n)); - } else { - actionsList.add(ExternalViewerShortcutAction.getInstance()); - } - actionsList.add(null); // creates a menu separator - actionsList.add(ExtractAction.getInstance()); - actionsList.add(ExportCSVAction.getInstance()); - actionsList.add(null); // creates a menu separator - - // don't show AddContentTagAction for data artifacts. - if (!(ban.getArtifact() instanceof DataArtifact)) { - actionsList.add(AddContentTagAction.getInstance()); - } - - actionsList.add(AddBlackboardArtifactTagAction.getInstance()); - - // don't show DeleteFileContentTagAction for data artifacts. - if ((!(ban.getArtifact() instanceof DataArtifact)) && (selectedFilesList.size() == 1)) { - actionsList.add(DeleteFileContentTagAction.getInstance()); - } - } else { - // There's no specific file associated with the artifact, but - // we can still tag the artifact itself - actionsList.add(null); - actionsList.add(AddBlackboardArtifactTagAction.getInstance()); - } - - final Collection selectedArtifactsList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); - if (selectedArtifactsList.size() == 1) { - actionsList.add(DeleteFileBlackboardArtifactTagAction.getInstance()); - } - - if (n != null) { - actionsList.addAll(ContextMenuExtensionPoint.getActions()); - } - - return actionsList; + return Arrays.asList(ban.getActions(true)); } @Override @@ -476,44 +252,8 @@ public class DataResultFilterNode extends FilterNode { @Override protected List defaultVisit(DisplayableItemNode ditem) { - //preserve the default node's actions - List actions = new ArrayList<>(); - - for (Action action : ditem.getActions(true)) { - actions.add(action); - } - - return actions; + return Arrays.asList(ditem.getActions(true)); } - - private Content findLinked(BlackboardArtifactNode ba) { - BlackboardArtifact art = ba.getLookup().lookup(BlackboardArtifact.class); - Content c = null; - try { - for (BlackboardAttribute attr : art.getAttributes()) { - if (attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID()) { - switch (attr.getAttributeType().getValueType()) { - case INTEGER: - int i = attr.getValueInt(); - if (i != -1) { - c = art.getSleuthkitCase().getContentById(i); - } - break; - case LONG: - long l = attr.getValueLong(); - if (l != -1) { - c = art.getSleuthkitCase().getContentById(l); - } - break; - } - } - } - } catch (TskException ex) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Error getting linked file", ex); //NON-NLS - } - return c; - } - } /* diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index e333e5f115..8b2421dda7 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -1263,7 +1263,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * * @param osAccount The os account. */ - void viewOsAccount(OsAccount osAccount) { + public void viewOsAccount(OsAccount osAccount) { Set hosts = null; if (CasePreferences.getGroupItemsInTreeByDataSource()) { From 92b245d418440899362efe0b6f0a0c5f2278d523 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 7 Sep 2021 16:48:44 -0400 Subject: [PATCH 05/51] cleanup --- .../datamodel/BlackboardArtifactNode.java | 111 ++++++++++-------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 925018ecd0..b6dd46a300 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -28,10 +28,8 @@ import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.EnumSet; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -557,56 +555,85 @@ public class BlackboardArtifactNode extends AbstractContentNode List getNonNull(T...items) { - return Stream.of(items) + + private List getNonNull(Stream items) { + return items .filter(i -> i != null) .collect(Collectors.toList()); } - @NbBundle.Messages({ + private List getNonNull(T... items) { + return getNonNull(Stream.of(items)); + } + + private List getNonNull(List items) { + return getNonNull(items.stream()); + } + + @Messages({ "BlackboardArtifactNode_getActions_viewSourceDataArtifact=View Source Data Artifact in Timeline... " }) @Override public Action[] getActions(boolean context) { Node parentFileNode = getParentFileNode(srcContent); + boolean hasFileContent = parentFileNode != null; - final Collection selectedFilesList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + int selectedFileCount = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size(); + int selectedArtifactCount = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).size(); - final Collection selectedArtifactsList - = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + List> actionsLists = new ArrayList<>(); - super.getActions(context); + actionsLists.add(getNonNull(super.getActions(context))); // break - getTimelineArtifactAction(); - getTimelineFileAction(artifact); - getTimelineSrcContentAction(); + actionsLists.add(getNonNull( + getTimelineArtifactAction(this.artifact), + getTimelineFileAction(this.artifact), + getTimelineSrcContentAction(this.srcContent) + )); // break - getSrcContentAction(); - getFileAction(); + actionsLists.add(getNonNull( + getViewSrcContentAction(this.srcContent), + getViewFileAction(this.artifact), + getExtractWithPasswordAction(this.srcContent) + )); // break - getExtractWithPasswordAction(srcContent); - getReportActions(srcContent); + actionsLists.add(getNonNull( + this.srcContent instanceof Report + ? DataModelActionsFactory.getActions(content, false) + : Collections.emptyList() + )); // break - getSrcContentViewerActions(); - + actionsLists.add(getNonNull( + getSrcContentViewerActions(parentFileNode, selectedFileCount) + )); + // break - getExtractExportActions(hasSrcFile); + actionsLists.add(getNonNull(getExtractExportActions(hasFileContent))); // break - getTagActions(); + actionsLists.add(getNonNull(getTagActions(hasFileContent, this.artifact, selectedFileCount, selectedArtifactCount))); // break? - if (n != null) { - actionsList.addAll(ContextMenuExtensionPoint.getActions()); - } + actionsLists.add(getNonNull(ContextMenuExtensionPoint.getActions())); + + // add in null between each list group. + return actionsLists.stream() + .filter((lst) -> lst != null && !lst.isEmpty()) + .flatMap(lst -> Stream.concat(lst.stream(), Stream.of(null))) + .toArray(sz -> new Action[sz]); } + /** + * Creates an action to navigate to src content in tree hierarchy. + * + * @param content The content. + * + * @return The action or null if no action derived. + */ @Messages({ "# {0} - contentType", "BlackboardArtifactNode_getSrcContentAction_baseMessage=View Source {0} in Directory", @@ -616,27 +643,27 @@ public class BlackboardArtifactNode extends AbstractContentNode getReportActions(Content content) { - return content instanceof Report - ? DataModelActionsFactory.getActions(content, false) - : Collections.emptyList(); - } - /** * Returns actions for extracting content from file or null if not possible. * From fb0406b2e19aa5012819d16d1900c238e9c5ad94 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 7 Sep 2021 18:53:28 -0400 Subject: [PATCH 06/51] bundle changes and fixes --- .../datamodel/BlackboardArtifactNode.java | 66 +++++++++---------- .../datamodel/Bundle.properties-MERGED | 9 +++ .../directorytree/Bundle.properties-MERGED | 5 -- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index b6dd46a300..71a10788d9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -556,19 +556,19 @@ public class BlackboardArtifactNode extends AbstractContentNode List getNonNull(Stream items) { - return items + + + /** + * Returns a list of non null actions from the given possibly null options. + * @param items The items to purge of null items. + * @return The list of non-null actions. + */ + private List getNonNull(Action... items) { + return Stream.of(items) .filter(i -> i != null) .collect(Collectors.toList()); } - private List getNonNull(T... items) { - return getNonNull(Stream.of(items)); - } - - private List getNonNull(List items) { - return getNonNull(items.stream()); - } @Messages({ "BlackboardArtifactNode_getActions_viewSourceDataArtifact=View Source Data Artifact in Timeline... " @@ -583,47 +583,47 @@ public class BlackboardArtifactNode extends AbstractContentNode> actionsLists = new ArrayList<>(); - actionsLists.add(getNonNull(super.getActions(context))); - - // break actionsLists.add(getNonNull( - getTimelineArtifactAction(this.artifact), - getTimelineFileAction(this.artifact), - getTimelineSrcContentAction(this.srcContent) + getTimelineArtifactAction(this.artifact) )); - // break actionsLists.add(getNonNull( - getViewSrcContentAction(this.srcContent), - getViewFileAction(this.artifact), + getTimelineFileAction(this.artifact), + getViewFileAction(this.artifact) + )); + + actionsLists.add(getNonNull( + getTimelineSrcContentAction(this.srcContent), + getViewSrcContentAction(this.srcContent) + )); + + actionsLists.add(getNonNull( getExtractWithPasswordAction(this.srcContent) )); - // break - actionsLists.add(getNonNull( + actionsLists.add( this.srcContent instanceof Report ? DataModelActionsFactory.getActions(content, false) : Collections.emptyList() - )); + ); - // break - actionsLists.add(getNonNull( - getSrcContentViewerActions(parentFileNode, selectedFileCount) - )); + actionsLists.add(getSrcContentViewerActions(parentFileNode, selectedFileCount)); + + actionsLists.add(getExtractExportActions(hasFileContent)); - // break - actionsLists.add(getNonNull(getExtractExportActions(hasFileContent))); + actionsLists.add(getTagActions(hasFileContent, this.artifact, selectedFileCount, selectedArtifactCount)); - // break - actionsLists.add(getNonNull(getTagActions(hasFileContent, this.artifact, selectedFileCount, selectedArtifactCount))); + actionsLists.add(ContextMenuExtensionPoint.getActions()); - // break? - actionsLists.add(getNonNull(ContextMenuExtensionPoint.getActions())); + actionsLists.add(Arrays.asList(super.getActions(context))); - // add in null between each list group. return actionsLists.stream() + // remove any empty lists .filter((lst) -> lst != null && !lst.isEmpty()) - .flatMap(lst -> Stream.concat(lst.stream(), Stream.of(null))) + // add in null between each list group + .flatMap(lst -> Stream.concat(Stream.of((Action) null), lst.stream())) + // skip the first null + .skip(1) .toArray(sz -> new Action[sz]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index d91c934022..51b567451d 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -79,6 +79,15 @@ BlackboardArtifactNode.createSheet.srcFile.origName=Original Name BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged. BlackboardArtifactNode.createSheet.tags.displayName=Tags BlackboardArtifactNode_getActions_viewSourceDataArtifact=View Source Data Artifact in Timeline... +# {0} - contentType +BlackboardArtifactNode_getSrcContentAction_baseMessage=View Source {0} in Directory +BlackboardArtifactNode_getSrcContentAction_type_Content=Content +BlackboardArtifactNode_getSrcContentAction_type_DataArtifact=Data Artifact +BlackboardArtifactNode_getSrcContentAction_type_File=File +BlackboardArtifactNode_getSrcContentAction_type_OSAccount=OS Account +BlackboardArtifactNode_getSrcContentAction_viewSrcFile=test +BlackboardArtifactNode_getSrcContentViewerActions_openInExtViewer=Open in External Viewer Ctrl+E +BlackboardArtifactNode_getSrcContentViewerActions_viewInNewWin=View in New Window BlackboardArtifactTagNode.createSheet.userName.text=User Name BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result Category.five=CAT-5: Non-pertinent diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index 1170f879c4..0ab883cbd4 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -5,11 +5,6 @@ CSVWriter.progress.cancelling=Cancelling CSVWriter.progress.extracting=Exporting to CSV file CTL_DirectoryTreeTopComponent=Directory Tree DataResultFilterNode.viewSourceArtifact.text=View Source Result -# {0} - contentType -DataResultFilterNode_getViewSourceDisplayName_baseMessage=View Source {0} -DataResultFilterNode_getViewSourceDisplayName_type_DataArtifact=Data Artifact -DataResultFilterNode_getViewSourceDisplayName_type_File=File -DataResultFilterNode_getViewSourceDisplayName_type_OSAccount=OS Account # {0} - dataSourceCount DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contains {0} data sources. Would you like to group by data source for faster loading? DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source? From 5fde7109a2301ab1e050a751268bcd0639e77714 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 8 Sep 2021 10:55:37 -0400 Subject: [PATCH 07/51] bug fixes and fixes based on comments --- .../datamodel/BlackboardArtifactNode.java | 99 +++++-------------- .../datamodel/Bundle.properties-MERGED | 3 +- 2 files changed, 27 insertions(+), 75 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 71a10788d9..daa75782ba 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.actions.ViewArtifactAction; +import org.sleuthkit.autopsy.actions.ViewOsAccountAction; import com.google.common.annotations.Beta; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -471,53 +473,7 @@ public class BlackboardArtifactNode extends AbstractContentNode getNonNull(Action... items) { @@ -569,7 +525,6 @@ public class BlackboardArtifactNode extends AbstractContentNode> actionsLists = new ArrayList<>(); actionsLists.add(getNonNull( - getTimelineArtifactAction(this.artifact) + getViewFileAction(this.artifact), + getViewSrcContentAction(this.srcContent) )); actionsLists.add(getNonNull( + getTimelineArtifactAction(this.artifact), getTimelineFileAction(this.artifact), - getViewFileAction(this.artifact) + getTimelineSrcContentAction(this.srcContent) + )); - - actionsLists.add(getNonNull( - getTimelineSrcContentAction(this.srcContent), - getViewSrcContentAction(this.srcContent) - )); - + actionsLists.add(getNonNull( getExtractWithPasswordAction(this.srcContent) )); - actionsLists.add( - this.srcContent instanceof Report - ? DataModelActionsFactory.getActions(content, false) - : Collections.emptyList() - ); + if (this.srcContent instanceof Report) { + actionsLists.add(DataModelActionsFactory.getActions(this.srcContent, false)); + } actionsLists.add(getSrcContentViewerActions(parentFileNode, selectedFileCount)); - + actionsLists.add(getExtractExportActions(hasFileContent)); actionsLists.add(getTagActions(hasFileContent, this.artifact, selectedFileCount, selectedArtifactCount)); @@ -616,7 +567,7 @@ public class BlackboardArtifactNode extends AbstractContentNode lst != null && !lst.isEmpty()) @@ -646,7 +597,7 @@ public class BlackboardArtifactNode extends AbstractContentNode getSrcContentViewerActions(Node srcFileNode, int selectedFileCount) { @@ -830,8 +783,8 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Wed, 8 Sep 2021 10:55:44 -0400 Subject: [PATCH 08/51] bug fixes and fixes based on comments --- .../autopsy/actions/ViewArtifactAction.java | 50 +++++++++++++++++++ .../autopsy/actions/ViewOsAccountAction.java | 50 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java new file mode 100644 index 0000000000..d875a4e58c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java @@ -0,0 +1,50 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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 javax.swing.AbstractAction; +import javax.swing.SwingUtilities; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * An action that navigates to an artifact. + */ +public class ViewArtifactAction extends AbstractAction { + + private final BlackboardArtifact artifact; + + /** + * Main constructor. + * + * @param artifact The artifact to navigate to in the action. + * @param displayName The display name of the menu item. + */ + public ViewArtifactAction(BlackboardArtifact artifact, String displayName) { + super(displayName); + this.artifact = artifact; + } + + @Override + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(() -> DirectoryTreeTopComponent.findInstance().viewArtifact(artifact)); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java new file mode 100644 index 0000000000..d7dbcec57a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java @@ -0,0 +1,50 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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 javax.swing.AbstractAction; +import javax.swing.SwingUtilities; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.OsAccount; + +/** + * An action that navigates to an os account. + */ +public class ViewOsAccountAction extends AbstractAction { + + private final OsAccount osAccount; + + /** + * Main constructor. + * + * @param osAccount The os account to navigate to in the action. + * @param displayName The display name of the menu item. + */ + public ViewOsAccountAction(OsAccount osAccount, String displayName) { + super(displayName); + this.osAccount = osAccount; + } + + @Override + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(() -> DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount)); + } + +} From 4ca5493b9ccd005fc33421af59d3a537db9f9b00 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 8 Sep 2021 12:33:03 -0400 Subject: [PATCH 09/51] fixes --- .../datamodel/BlackboardArtifactNode.java | 189 ++++++------------ .../datamodel/Bundle.properties-MERGED | 4 +- .../actions/ViewFileInTimelineAction.java | 7 +- 3 files changed, 67 insertions(+), 133 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 5ee355320e..ed87920c78 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -266,7 +266,7 @@ public class BlackboardArtifactNode extends AbstractContentNode> actionsLists = new ArrayList<>(); actionsLists.add(getNonNull( - getViewFileAction(this.artifact), - getViewSrcContentAction(this.srcContent) + getTimelineArtifactAction(this.artifact) )); + AbstractFile linkedFile = getLinkedFile(this.artifact); + if (linkedFile != null) { + actionsLists.add(Arrays.asList( + new ViewContextAction(Bundle.BlackboardArtifactNode_getActions_viewLinkedFileAction(), linkedFile), + new ViewFileInTimelineAction(linkedFile, Bundle.BlackboardArtifactNode_getActions_viewLinkedFileInTimelineAction()) + )); + } + + //actionsList.add(new ViewSourceArtifactAction(DataResultFilterNode_viewSourceArtifact_text(), ba)); + actionsLists.add(getNonNull( - getTimelineArtifactAction(this.artifact), - getTimelineFileAction(this.artifact), + getViewSrcContentAction(this.artifact, this.srcContent), getTimelineSrcContentAction(this.srcContent) - )); actionsLists.add(getNonNull( @@ -587,11 +552,17 @@ public class BlackboardArtifactNode extends AbstractContentNode new Action[sz]); } + /** + * Returns a linked file or null if no linked file found. Also logs any + * exception and returns null. + * + * @param artifact The artifact whose linked file will be identified. + * + * @return The linked file to the artifact using TSK_PATH_ID + */ + private AbstractFile getLinkedFile(BlackboardArtifact artifact) { + try { + return findLinked(artifact); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file of artifact (artifact objID={0})", artifact.getId()), ex); //NON-NLS + } + return null; + } + /** * Creates an action to navigate to src content in tree hierarchy. * - * @param content The content. + * @param artifact The artifact. + * @param content The content. * * @return The action or null if no action derived. */ @@ -620,10 +609,9 @@ public class BlackboardArtifactNode extends AbstractContentNode 0) { - return new ExtractArchiveWithPasswordAction((AbstractFile) srcContent); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); + if ((srcContent instanceof LocalFile || srcContent instanceof DerivedFile) + && FileTypeExtensions.getArchiveExtensions() + .contains("." + ((AbstractFile) srcContent).getNameExtension().toLowerCase())) { + try { + if (srcContent.getArtifacts(BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED.getTypeID()).size() > 0) { + return new ExtractArchiveWithPasswordAction((AbstractFile) srcContent); } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to add unzip with password action to context menus", ex); } } + return null; } /** * Returns tag actions. * - * + * @param hasSrcFile Whether or not the artifact has a source + * file. * @param artifact This artifact. * @param selectedFileCount The count of selected files. * @param selectedArtifactCount The count of selected artifacts. @@ -760,20 +727,6 @@ public class BlackboardArtifactNode extends AbstractContentNode getExtractExportActions(boolean hasSrcFile) { - return hasSrcFile - ? Arrays.asList(ExtractAction.getInstance(), ExportCSVAction.getInstance()) - : Collections.emptyList(); - } - /** * Returns actions to view src content in a different viewer or window. * @@ -824,30 +777,6 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Wed, 8 Sep 2021 17:50:46 -0400 Subject: [PATCH 10/51] updates --- .../datamodel/BlackboardArtifactNode.java | 34 ++++++------------- .../datamodel/Bundle.properties-MERGED | 12 +++---- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index ed87920c78..7c071d6fca 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -517,8 +517,8 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Wed, 8 Sep 2021 19:50:40 -0400 Subject: [PATCH 11/51] tag action refactor --- .../datamodel/BlackboardArtifactNode.java | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 7c071d6fca..7230146e24 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -536,9 +536,8 @@ public class BlackboardArtifactNode extends AbstractContentNode getTagActions(boolean hasSrcFile, BlackboardArtifact artifact, int selectedFileCount, int selectedArtifactCount) { List actionsList = new ArrayList<>(); - if (hasSrcFile) { - // don't show AddContentTagAction for data artifacts. - if (!(artifact instanceof DataArtifact)) { - actionsList.add(AddContentTagAction.getInstance()); - } + // don't show AddContentTagAction for data artifacts. + if (hasSrcFile && !(artifact instanceof DataArtifact)) { + actionsList.add(AddContentTagAction.getInstance()); + } - actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + actionsList.add(AddBlackboardArtifactTagAction.getInstance()); - // don't show DeleteFileContentTagAction for data artifacts. - if ((!(artifact instanceof DataArtifact)) && (selectedFileCount == 1)) { - actionsList.add(DeleteFileContentTagAction.getInstance()); - } - } else { - // There's no specific file associated with the artifact, but - // we can still tag the artifact itself - actionsList.add(AddBlackboardArtifactTagAction.getInstance()); + // don't show DeleteFileContentTagAction for data artifacts. + if (hasSrcFile && (!(artifact instanceof DataArtifact)) && (selectedFileCount == 1)) { + actionsList.add(DeleteFileContentTagAction.getInstance()); } if (selectedArtifactCount == 1) { From a2025d246bd70b1879843f7a3f777fff52560727 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 9 Sep 2021 10:56:17 -0400 Subject: [PATCH 12/51] updates based on comments --- .../datamodel/BlackboardArtifactNode.java | 99 +++++++++++++++---- .../datamodel/Bundle.properties-MERGED | 21 +++- 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 7230146e24..32518ee40a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -23,14 +23,12 @@ import org.sleuthkit.autopsy.actions.ViewOsAccountAction; import com.google.common.annotations.Beta; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; @@ -42,7 +40,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.swing.AbstractAction; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -93,7 +90,6 @@ import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; import org.sleuthkit.autopsy.texttranslation.TextTranslationService; import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.autopsy.directorytree.ExportCSVAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction; @@ -114,7 +110,6 @@ import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.SlackFile; -import org.sleuthkit.datamodel.TskException; import org.sleuthkit.datamodel.VirtualDirectory; /** @@ -516,9 +511,10 @@ public class BlackboardArtifactNode extends AbstractContentNode new Action[sz]); } + /** + * Returns the name of the artifact based on the artifact type to be used + * with the associated file string in a right click menu. + * + * @param artifactType The artifact type. + * + * @return The artifact type name. + */ + @Messages({ + "BlackboardArtifactNode_getAssociatedTypeStr_webCache=Cached File", + "BlackboardArtifactNode_getAssociatedTypeStr_webDownload=Downloaded File", + "BlackboardArtifactNode_getAssociatedTypeStr_associated=Associated File",}) + private String getAssociatedTypeStr(BlackboardArtifact.Type artifactType) { + if (BlackboardArtifact.Type.TSK_WEB_CACHE.equals(artifactType)) { + return Bundle.BlackboardArtifactNode_getAssociatedTypeStr_webCache(); + } else if (BlackboardArtifact.Type.TSK_WEB_DOWNLOAD.equals(artifactType)) { + return Bundle.BlackboardArtifactNode_getAssociatedTypeStr_webDownload(); + } else { + return Bundle.BlackboardArtifactNode_getAssociatedTypeStr_associated(); + } + } + + /** + * Returns the name to represent the type of the content (file, data + * artifact, os account, item). + * + * @param content The content. + * + * @return The name of the type of content. + */ + @Messages({ + "BlackboardArtifactNode_getViewSrcContentAction_type_File=File", + "BlackboardArtifactNode_getViewSrcContentAction_type_DataArtifact=Data Artifact", + "BlackboardArtifactNode_getViewSrcContentAction_type_OSAccount=OS Account", + "BlackboardArtifactNode_getViewSrcContentAction_type_unknown=Item" + }) + private String getContentTypeStr(Content content) { + if (content instanceof AbstractFile) { + return Bundle.BlackboardArtifactNode_getViewSrcContentAction_type_File(); + } else if (content instanceof DataArtifact) { + return Bundle.BlackboardArtifactNode_getViewSrcContentAction_type_DataArtifact(); + } else if (content instanceof OsAccount) { + return Bundle.BlackboardArtifactNode_getViewSrcContentAction_type_OSAccount(); + } else { + return Bundle.BlackboardArtifactNode_getViewSrcContentAction_type_unknown(); + } + } + /** * Returns a linked file or null if no linked file found. Also logs any * exception and returns null. @@ -603,20 +651,24 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Thu, 9 Sep 2021 11:34:44 -0400 Subject: [PATCH 13/51] commenting and refactoring --- .../datamodel/BlackboardArtifactNode.java | 67 +++++++++++-------- .../datamodel/Bundle.properties-MERGED | 4 +- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index b368ca118c..00f06ec01e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -29,6 +29,7 @@ import java.lang.ref.WeakReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; @@ -514,43 +515,31 @@ public class BlackboardArtifactNode extends AbstractContentNode> actionsLists = new ArrayList<>(); + // view artifact in timeline actionsLists.add(getNonNull( getTimelineArtifactAction(this.artifact) )); - AbstractFile linkedFile = getLinkedFile(this.artifact); - if (linkedFile != null) { - actionsLists.add(Arrays.asList( - new ViewContextAction( - Bundle.BlackboardArtifactNode_getActions_viewAssociatedFileAction( - getAssociatedTypeStr(this.artifactType)), - linkedFile), - new ViewFileInTimelineAction(linkedFile, - Bundle.BlackboardArtifactNode_getActions_viewAssociatedFileInTimelineAction( - getAssociatedTypeStr(this.artifactType))) - )); - } - + // view associated file (TSK_PATH_ID attr) in directory and timeline + actionsLists.add(getAssociatedFileActions(this.artifact, this.artifactType)); + + // view source content in directory and timeline actionsLists.add(getNonNull( getViewSrcContentAction(this.artifact, this.srcContent), getTimelineSrcContentAction(this.srcContent) )); + // extract with password from encrypted file actionsLists.add(getNonNull( getExtractWithPasswordAction(this.srcContent) )); + // menu options for artifact with report parent if (this.srcContent instanceof Report) { actionsLists.add(DataModelActionsFactory.getActions(this.srcContent, false)); } @@ -559,16 +548,21 @@ public class BlackboardArtifactNode extends AbstractContentNode getAssociatedFileActions(BlackboardArtifact artifact, BlackboardArtifact.Type artifactType) { try { - return findLinked(artifact); + AbstractFile associatedFile = findLinked(artifact); + if (associatedFile != null) { + return Arrays.asList( + new ViewContextAction( + Bundle.BlackboardArtifactNode_getAssociatedFileActions_viewAssociatedFileAction( + getAssociatedTypeStr(artifactType)), + associatedFile), + new ViewFileInTimelineAction(associatedFile, + Bundle.BlackboardArtifactNode_getAssociatedFileActions_viewAssociatedFileInTimelineAction( + getAssociatedTypeStr(artifactType))) + ); + } + } catch (TskCoreException ex) { logger.log(Level.SEVERE, MessageFormat.format("Error getting linked file of artifact (artifact objID={0})", artifact.getId()), ex); //NON-NLS } - return null; + return Collections.emptyList(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index ed402eb45a..f2acf05185 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -79,9 +79,9 @@ BlackboardArtifactNode.createSheet.srcFile.origName=Original Name BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged. BlackboardArtifactNode.createSheet.tags.displayName=Tags # {0} - type -BlackboardArtifactNode_getActions_viewAssociatedFileAction=View {0} in Directory +BlackboardArtifactNode_getAssociatedFileActions_viewAssociatedFileAction=View {0} in Directory # {0} - type -BlackboardArtifactNode_getActions_viewAssociatedFileInTimelineAction=View {0} in Timeline... +BlackboardArtifactNode_getAssociatedFileActions_viewAssociatedFileInTimelineAction=View {0} in Timeline... BlackboardArtifactNode_getAssociatedTypeStr_associated=Associated File BlackboardArtifactNode_getAssociatedTypeStr_webCache=Cached File BlackboardArtifactNode_getAssociatedTypeStr_webDownload=Downloaded File From 8631c944ec0ee53aacf8e8b303dfb89b96c563c0 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 9 Sep 2021 13:45:42 -0400 Subject: [PATCH 14/51] fixes for tag actions --- .../actions/DeleteFileBlackboardArtifactTagAction.java | 9 +++++++-- .../autopsy/datamodel/BlackboardArtifactNode.java | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java index 8c7c77652e..70b037e017 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileBlackboardArtifactTagAction.java @@ -21,12 +21,12 @@ 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; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Collectors; import javafx.application.Platform; import javafx.scene.control.Alert; import javax.swing.AbstractAction; @@ -40,6 +40,7 @@ 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.autopsy.datamodel.BlackboardArtifactItem; import org.sleuthkit.autopsy.tags.TagUtils; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -158,7 +159,11 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem private static final long serialVersionUID = 1L; TagMenu() { - this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class))); + this(Utilities.actionsGlobalContext() + .lookupAll(BlackboardArtifactItem.class) + .stream() + .map((bai) -> (BlackboardArtifact) bai.getTskContent()) + .collect(Collectors.toSet())); } TagMenu(Collection selectedBlackboardArtifactsList) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 00f06ec01e..efd6031eac 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -546,7 +546,7 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Thu, 9 Sep 2021 14:00:18 -0400 Subject: [PATCH 15/51] correct plural and single determination for tagging --- .../autopsy/actions/AddBlackboardArtifactTagAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 06a3e2e39e..79a0d715b2 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -71,7 +71,7 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { "AddBlackboardArtifactTagAction.singularTagResult"); String pluralTagResult = NbBundle.getMessage(this.getClass(), "AddBlackboardArtifactTagAction.pluralTagResult"); - return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).size() > 1 ? pluralTagResult : singularTagResult; + return Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactItem.class).size() > 1 ? pluralTagResult : singularTagResult; } @Override From 9e39dcff3ec0200ab0d08a8d10e5e7c866ef80ed Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 10 Sep 2021 18:44:28 -0400 Subject: [PATCH 16/51] 7918 delete OO viewer code to use caseDB --- .../application/NodeData.java | 12 +-------- .../application/OtherOccurrences.java | 13 +-------- .../application/UniquePathKey.java | 1 - .../contentviewer/OtherOccurrencesPanel.java | 27 ++++++------------- 4 files changed, 10 insertions(+), 43 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java index bae27ab680..0f2ada5220 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/NodeData.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2018-2019 Basis Technology Corp. + * Copyright 2018-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -123,16 +123,6 @@ public class NodeData { comment = newComment; } - /** - * Check if this is a central repo node. - * - * @return true if this node was created from a central repo instance, false - * otherwise - */ - public boolean isCentralRepoNode() { - return (originalCorrelationInstance != null); - } - /** * Get the case name * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java index 643f5d5de7..817d7068a7 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -263,13 +263,6 @@ public final class OtherOccurrences { nodeDataMap.put(uniquePathKey, newNode); } } - if (file != null && corAttr.getCorrelationType().getDisplayName().equals("Files")) { - List caseDbFiles = getCaseDbMatches(corAttr, openCase, file); - - for (AbstractFile caseDbFile : caseDbFiles) { - addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile); - } - } return nodeDataMap; } catch (CentralRepoException ex) { logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS @@ -277,11 +270,7 @@ public final class OtherOccurrences { logger.log(Level.INFO, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { 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 - } + } return new HashMap<>( 0); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java index 6de3ff2799..0ba064c58e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/UniquePathKey.java @@ -22,7 +22,6 @@ import java.util.Objects; import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesPanel; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.coreutils.Logger; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java index 9e962b1a1b..2bf709cfb8 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java @@ -329,8 +329,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { } casesTableModel.clearTable(); - - OtherOccurrenceOneTypeWorker.OneTypeData data = get(); + + OtherOccurrenceOneTypeWorker.OneTypeData data = get(); correlationAttributes.addAll(data.getCorrelationAttributesToAdd()); for (CorrelationCase corCase : data.getCaseNames().values()) { casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase)); @@ -447,12 +447,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { for (NodeData nodeData : correlatedNodeDataMap.values()) { for (int selectedRow : selectedCaseIndexes) { try { - if (nodeData.isCentralRepoNode()) { - if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null - && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { - dataSourcesTableModel.addNodeData(nodeData); - } - } else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) { + if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null + && casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) { dataSourcesTableModel.addNodeData(nodeData); } } catch (CentralRepoException ex) { @@ -505,15 +501,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { for (int selectedDataSourceRow : selectedDataSources) { int rowModelIndex = dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow); try { - if (nodeData.isCentralRepoNode()) { - if (dataSourcesTableModel.getCaseUUIDForRow(rowModelIndex).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) - && dataSourcesTableModel.getDeviceIdForRow(rowModelIndex).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } - } else { - if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) { - filesTableModel.addNodeData(nodeData); - } + if (dataSourcesTableModel.getCaseUUIDForRow(rowModelIndex).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID()) + && dataSourcesTableModel.getDeviceIdForRow(rowModelIndex).equals(nodeData.getDeviceID())) { + filesTableModel.addNodeData(nodeData); } } catch (CentralRepoException ex) { logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex); @@ -876,8 +866,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { int rowIndex = filesTable.getSelectedRow(); List selectedFile = filesTableModel.getListOfNodesForFile(rowIndex); if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof NodeData) { - NodeData instanceData = selectedFile.get(0); - enableCentralRepoActions = instanceData.isCentralRepoNode(); + enableCentralRepoActions = true; } } showCaseDetailsMenuItem.setVisible(enableCentralRepoActions); From 25612ae6da67151a016ca1ae5aab69628ffb3aff Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 10 Sep 2021 18:59:57 -0400 Subject: [PATCH 17/51] 7918 second batch of cutting out case DB code from OO viewer --- .../application/OtherOccurrences.java | 56 +------------------ .../contentviewer/OccurrencePanel.java | 13 ++--- .../OtherOccurrenceOneTypeWorker.java | 21 ++----- .../OtherOccurrencesNodeWorker.java | 23 ++------ 4 files changed, 17 insertions(+), 96 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java index 817d7068a7..6f945553a9 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -55,7 +55,6 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.OsAccountInstance; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -86,7 +85,7 @@ public final class OtherOccurrences { if (osAccountAddr.isPresent()) { try { - for (OsAccountInstance instance : osAccount.getOsAccountInstances()) { + for (OsAccountInstance instance : osAccount.getOsAccountInstances()) { CorrelationAttributeInstance correlationAttributeInstance = CorrelationAttributeUtil.makeCorrAttr(instance.getOsAccount(), instance.getDataSource()); if (correlationAttributeInstance != null) { ret.add(correlationAttributeInstance); @@ -148,25 +147,6 @@ public final class OtherOccurrences { } catch (CentralRepoException | TskCoreException ex) { logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS } - // If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled. - } else if (file != null && file.getSize() > 0) { - String md5 = file.getMd5Hash(); - if (md5 != null && !md5.isEmpty()) { - try { - final CorrelationAttributeInstance.Type fileAttributeType - = CorrelationAttributeInstance.getDefaultCorrelationTypes() - .stream() - .filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) - .findAny() - .get(); - //The Central Repository is not enabled - ret.add(new CorrelationAttributeInstance(fileAttributeType, md5, null, null, "", "", TskData.FileKnown.UNKNOWN, file.getId())); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.INFO, String.format("Unable to create CorrelationAttributeInstance for value %s", md5), ex); // NON-NLS - } - } } return ret; } @@ -270,44 +250,12 @@ public final class OtherOccurrences { logger.log(Level.INFO, "Error getting artifact instances from database.", ex); // NON-NLS } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS - } + } 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 - * @param file The current file. - * - * @return List of matching AbstractFile objects - * - * @throws NoCurrentCaseException - * @throws TskCoreException - * @throws CentralRepoException - */ - public static List getCaseDbMatches(CorrelationAttributeInstance corAttr, Case openCase, AbstractFile file) throws NoCurrentCaseException, TskCoreException, CentralRepoException { - List caseDbArtifactInstances = new ArrayList<>(); - if (file != null) { - String md5 = corAttr.getCorrelationValue(); - SleuthkitCase tsk = openCase.getSleuthkitCase(); - List matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5})); - - for (AbstractFile fileMatch : matches) { - if (file.equals(fileMatch)) { - continue; // If this is the file the user clicked on - } - caseDbArtifactInstances.add(fileMatch); - } - } - return caseDbArtifactInstances; - - } - /** * Adds the file to the nodeDataMap map if it does not already exist * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java index 4fb8ccff35..897259ecc0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OccurrencePanel.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2019 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.coreutils.Logger; @@ -202,13 +201,9 @@ final class OccurrencePanel extends javax.swing.JPanel { } String caseDate = ""; try { - if (occurrence.isCentralRepoNode()) { - if (CentralRepository.isEnabled()) { - CorrelationCase partialCase = occurrence.getCorrelationAttributeInstance().getCorrelationCase(); - caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); - } - } else { - caseDate = Case.getCurrentCase().getCreatedDate(); + if (CentralRepository.isEnabled()) { + CorrelationCase partialCase = occurrence.getCorrelationAttributeInstance().getCorrelationCase(); + caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate(); } } catch (CentralRepoException ex) { LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java index 9fd7b7fa51..69efdb6cdf 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrenceOneTypeWorker.java @@ -29,7 +29,6 @@ import java.util.logging.Level; import javax.swing.SwingWorker; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.application.NodeData; import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.centralrepository.application.UniquePathKey; @@ -116,21 +115,11 @@ class OtherOccurrenceOneTypeWorker extends SwingWorker { if (isCancelled()) { break; } - - if (nodeData.isCentralRepoNode()) { - try { - dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); - } catch (CentralRepoException ex) { - logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); - } - } else { - try { - dataSources.add(OtherOccurrences.makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName())); - } catch (NoCurrentCaseException ex) { - logger.log(Level.WARNING, "No current case open for other occurrences", ex); - } + try { + dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); + caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); } totalCount++; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index ab8821931a..54e009f1b3 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -28,7 +28,6 @@ import java.util.logging.Level; import javax.swing.SwingWorker; import org.openide.nodes.Node; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.application.NodeData; import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData; @@ -85,7 +84,7 @@ class OtherOccurrencesNodeWorker extends SwingWorker } Collection correlationAttributes = new ArrayList<>(); if (osAccount != null) { - correlationAttributes = OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount); + correlationAttributes = OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount); } else { correlationAttributes = OtherOccurrences.getCorrelationAttributesFromNode(node, file); } @@ -93,23 +92,13 @@ class OtherOccurrencesNodeWorker extends SwingWorker Set dataSources = new HashSet<>(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { for (NodeData nodeData : OtherOccurrences.getCorrelatedInstances(file, deviceId, dataSourceName, corAttr).values()) { - if (nodeData.isCentralRepoNode()) { - try { - dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); - } catch (CentralRepoException ex) { - logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); - } - } else { - try { - dataSources.add(OtherOccurrences.makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName())); - } catch (NoCurrentCaseException ex) { - logger.log(Level.WARNING, "No current case open for other occurrences", ex); - } + try { + dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); + caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); } totalCount++; - if (isCancelled()) { break; } From 933dbdefe9938cbedaaa20b0e80135e190f91f31 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 10 Sep 2021 19:08:42 -0400 Subject: [PATCH 18/51] 7918 change is supported method --- .../DataContentViewerOtherCases.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index f04e507f28..d45bc3b690 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -90,19 +90,13 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon @Override public boolean isSupported(Node node) { - // Is supported if one of the following is true: - // - The central repo is enabled and the node is not null - // - The central repo is disabled and the backing file has a valid MD5 hash + // Is supported if: + // The central repo is enabled and the node is not null // And the node has information which could be correlated on. - if (CentralRepository.isEnabled() && node != null) { - return OtherOccurrences.getAbstractFileFromNode(node) != null || OtherOccurrences.getBlackboardArtifactFromNode(node) != null || node.getLookup().lookup(OsAccount.class) != null; - } else if (node != null) { - AbstractFile file = OtherOccurrences.getAbstractFileFromNode(node); - return file != null - && file.getSize() > 0 - && ((file.getMd5Hash() != null) && (!file.getMd5Hash().isEmpty())); - } - return false; + return CentralRepository.isEnabled() && node != null + && (OtherOccurrences.getAbstractFileFromNode(node) != null + || OtherOccurrences.getBlackboardArtifactFromNode(node) != null + || node.getLookup().lookup(OsAccount.class) != null); } @Override From 1629e21a24cbc96b12d9e6b23bbbf312f3b70183 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 13 Sep 2021 20:15:21 -0400 Subject: [PATCH 19/51] swing worker for new actions --- .../autopsy/actions/ViewArtifactAction.java | 32 ++++++++++++++++-- .../autopsy/actions/ViewOsAccountAction.java | 33 +++++++++++++++++-- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java index d875a4e58c..6da2b55862 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewArtifactAction.java @@ -18,9 +18,14 @@ */ package org.sleuthkit.autopsy.actions; +import java.awt.Cursor; import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import javax.swing.AbstractAction; -import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -29,6 +34,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact; */ public class ViewArtifactAction extends AbstractAction { + private static final Logger logger = Logger.getLogger(ViewArtifactAction.class.getName()); private final BlackboardArtifact artifact; /** @@ -44,7 +50,27 @@ public class ViewArtifactAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - SwingUtilities.invokeLater(() -> DirectoryTreeTopComponent.findInstance().viewArtifact(artifact)); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + DirectoryTreeTopComponent.findInstance().viewArtifact(artifact); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Unexpected interrupt while navigating to artifact.", ex); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Error navigating to artifact.", ex); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); } - + } diff --git a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java index d7dbcec57a..200759fbf5 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ViewOsAccountAction.java @@ -18,17 +18,24 @@ */ package org.sleuthkit.autopsy.actions; +import java.awt.Cursor; import java.awt.event.ActionEvent; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; import javax.swing.AbstractAction; -import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.datamodel.OsAccount; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; /** * An action that navigates to an os account. */ public class ViewOsAccountAction extends AbstractAction { + private static final Logger logger = Logger.getLogger(ViewOsAccountAction.class.getName()); + private final OsAccount osAccount; /** @@ -44,7 +51,27 @@ public class ViewOsAccountAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - SwingUtilities.invokeLater(() -> DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount)); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + DirectoryTreeTopComponent.findInstance().viewOsAccount(osAccount); + return null; + } + + @Override + protected void done() { + try { + get(); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Unexpected interrupt while navigating to OS Account.", ex); + } catch (ExecutionException ex) { + logger.log(Level.SEVERE, "Error navigating to OS Account.", ex); + } + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); } - + } From 3dc2cb80e2ed63b333c5150fd86ed98b2df5ab37 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 13 Sep 2021 23:24:43 -0400 Subject: [PATCH 20/51] 7918 mostly working OO viewer changes --- .../application/OtherOccurrences.java | 126 ++---------------- .../DataContentViewerOtherCases.java | 6 +- .../OtherOccurrencesNodeWorker.java | 95 +++++++------ .../contentviewer/OtherOccurrencesPanel.java | 18 +-- 4 files changed, 74 insertions(+), 171 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java index 6f945553a9..6e8ecfbecf 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -46,12 +46,8 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeIns import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.coreutils.Logger; 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.OsAccount; import org.sleuthkit.datamodel.OsAccountInstance; @@ -99,113 +95,10 @@ public final class OtherOccurrences { return ret; } - /** - * Determine what attributes can be used for correlation based on the node. - * If EamDB is not enabled, get the default Files correlation. - * - * @param node The node to correlate. - * @param file The file to correlate. - * - * @return A list of attributes that can be used for correlation - */ - public static Collection getCorrelationAttributesFromNode(Node node, AbstractFile file) { - Collection ret = new ArrayList<>(); - - // correlate on blackboard artifact attributes if they exist and supported - BlackboardArtifact bbArtifact = getBlackboardArtifactFromNode(node); - if (bbArtifact != null && CentralRepository.isEnabled()) { - ret.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(bbArtifact)); - } - - // we can correlate based on the MD5 if it is enabled - if (file != null && CentralRepository.isEnabled() && file.getSize() > 0) { - try { - - List artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); - String md5 = file.getMd5Hash(); - if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) { - for (CorrelationAttributeInstance.Type aType : artifactTypes) { - if (aType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) { - CorrelationCase corCase = CentralRepository.getInstance().getCase(Case.getCurrentCase()); - try { - ret.add(new CorrelationAttributeInstance( - aType, - md5, - corCase, - CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()), - file.getParentPath() + file.getName(), - "", - file.getKnown(), - file.getId())); - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex); - } - break; - } - } - } - } catch (CentralRepoException | TskCoreException ex) { - logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS - } - } - return ret; - } - - /** - * Get the associated BlackboardArtifact from a node, if it exists. - * - * @param node The node - * - * @return The associated BlackboardArtifact, or null - */ - public static BlackboardArtifact getBlackboardArtifactFromNode(Node node) { - BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); - BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class); - - if (nodeBbArtifactTag != null) { - return nodeBbArtifactTag.getArtifact(); - } else if (nodeBbArtifact != null) { - return nodeBbArtifact; - } - - return null; - - } - - /** - * Get the associated AbstractFile from a node, if it exists. - * - * @param node The node - * - * @return The associated AbstractFile, or null - */ - public static AbstractFile getAbstractFileFromNode(Node node) { - BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); - ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class); - AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class); - - if (nodeBbArtifactTag != null) { - Content content = nodeBbArtifactTag.getContent(); - if (content instanceof AbstractFile) { - return (AbstractFile) content; - } - } else if (nodeContentTag != null) { - Content content = nodeContentTag.getContent(); - if (content instanceof AbstractFile) { - return (AbstractFile) content; - } - } else if (nodeAbstractFile != null) { - return nodeAbstractFile; - } - - return null; - } - /** * 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. + * artifact. * * @param file The current file. * @param deviceId The device ID for the current data source. @@ -214,7 +107,7 @@ public final class OtherOccurrences { * * @return A collection of correlated artifact instances */ - public static Map getCorrelatedInstances(AbstractFile file, String deviceId, String dataSourceName, CorrelationAttributeInstance corAttr) { + public static Map getCorrelatedInstances(String deviceId, String dataSourceName, CorrelationAttributeInstance corAttr) { // @@@ Check exception try { final Case openCase = Case.getCurrentCaseThrows(); @@ -231,12 +124,15 @@ public final class OtherOccurrences { // - the case UUID is different // - the data source name is different // - the data source device ID is different - // - the file path is different + // - the object id for the underlying file is different if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID) && (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName)) - && (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId)) - && (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) { - continue; + && (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))) { + Long foundObjectId = artifactInstance.getFileObjectId(); + Long currentObjectId = corAttr.getFileObjectId(); + if (foundObjectId != null && currentObjectId != null && foundObjectId.equals(currentObjectId)) { + continue; + } } NodeData newNode = new NodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue()); UniquePathKey uniquePathKey = new UniquePathKey(newNode); @@ -360,7 +256,7 @@ public final class OtherOccurrences { * * @throws IOException */ - public static void writeOtherOccurrencesToFileAsCSV(File destFile, AbstractFile abstractFile, Collection correlationAttList, String dataSourceName, String deviceId) throws IOException { + public static void writeOtherOccurrencesToFileAsCSV(File destFile, Collection correlationAttList, String dataSourceName, String deviceId) throws IOException { try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) { //write headers StringBuilder headers = new StringBuilder("\""); @@ -377,7 +273,7 @@ public final class OtherOccurrences { for (CorrelationAttributeInstance corAttr : correlationAttList) { Map correlatedNodeDataMap = new HashMap<>(0); // get correlation and reference set instances from DB - correlatedNodeDataMap.putAll(getCorrelatedInstances(abstractFile, deviceId, dataSourceName, corAttr)); + correlatedNodeDataMap.putAll(getCorrelatedInstances(deviceId, dataSourceName, corAttr)); for (NodeData nodeData : correlatedNodeDataMap.values()) { writer.write(nodeData.toCsvString()); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index d45bc3b690..484f393f24 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -27,11 +27,11 @@ import javax.swing.JPanel; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; -import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; import org.sleuthkit.datamodel.OsAccount; /** @@ -94,8 +94,8 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon // The central repo is enabled and the node is not null // And the node has information which could be correlated on. return CentralRepository.isEnabled() && node != null - && (OtherOccurrences.getAbstractFileFromNode(node) != null - || OtherOccurrences.getBlackboardArtifactFromNode(node) != null + && (node.getLookup().lookup(AbstractFile.class) != null + || node.getLookup().lookup(BlackboardArtifactItem.class) != null || node.getLookup().lookup(OsAccount.class) != null); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index 54e009f1b3..e4cb1015fd 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -32,9 +32,12 @@ import org.sleuthkit.autopsy.centralrepository.application.NodeData; import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences; import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; +import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.TskContentItem; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.OsAccount; @@ -61,54 +64,62 @@ class OtherOccurrencesNodeWorker extends SwingWorker @Override protected OtherOccurrencesData doInBackground() throws Exception { - OsAccount osAccount = node.getLookup().lookup(OsAccount.class); - AbstractFile file = OtherOccurrences.getAbstractFileFromNode(node); - if (osAccount != null) { - file = node.getLookup().lookup(AbstractFile.class); - } - String deviceId = ""; - String dataSourceName = ""; - Map caseNames = new HashMap<>(); - Case currentCase = Case.getCurrentCaseThrows(); OtherOccurrencesData data = null; - try { - if (file != null) { - Content dataSource = file.getDataSource(); - deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); - dataSourceName = dataSource.getName(); - } - } catch (TskException ex) { - // do nothing. - // @@@ Review this behavior - return null; - } - Collection correlationAttributes = new ArrayList<>(); - if (osAccount != null) { - correlationAttributes = OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount); - } else { - correlationAttributes = OtherOccurrences.getCorrelationAttributesFromNode(node, file); - } - int totalCount = 0; - Set dataSources = new HashSet<>(); - for (CorrelationAttributeInstance corAttr : correlationAttributes) { - for (NodeData nodeData : OtherOccurrences.getCorrelatedInstances(file, deviceId, dataSourceName, corAttr).values()) { - try { - dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); - caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); - } catch (CentralRepoException ex) { - logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); + if (CentralRepository.isEnabled()) { + OsAccount osAccount = node.getLookup().lookup(OsAccount.class); + + String deviceId = ""; + String dataSourceName = ""; + Map caseNames = new HashMap<>(); + Case currentCase = Case.getCurrentCaseThrows(); + + //the file is used for determining a correlation instance is not the selected instance + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + try { + if (file != null) { + Content dataSource = file.getDataSource(); + deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); + dataSourceName = dataSource.getName(); } - totalCount++; - if (isCancelled()) { - break; + } catch (TskException ex) { + logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); + return data; + } + Collection correlationAttributes = new ArrayList<>(); + if (osAccount != null) { + correlationAttributes.addAll(OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount)); + } else { + TskContentItem contentItem = node.getLookup().lookup(TskContentItem.class); + Content content = null; + if (contentItem != null) { + content = contentItem.getTskContent(); + } + if (content != null) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(content)); + } + } + int totalCount = 0; + Set dataSources = new HashSet<>(); + for (CorrelationAttributeInstance corAttr : correlationAttributes) { + for (NodeData nodeData : OtherOccurrences.getCorrelatedInstances(deviceId, dataSourceName, corAttr).values()) { + try { + dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName())); + caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase()); + } catch (CentralRepoException ex) { + logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex); + } + totalCount++; + if (isCancelled()) { + break; + } } } - } - if (!isCancelled()) { - data = new OtherOccurrencesData(correlationAttributes, file, dataSourceName, deviceId, caseNames, totalCount, dataSources.size(), OtherOccurrences.getEarliestCaseDate()); - } + if (!isCancelled()) { + data = new OtherOccurrencesData(correlationAttributes, file, dataSourceName, deviceId, caseNames, totalCount, dataSources.size(), OtherOccurrences.getEarliestCaseDate()); + } + } return data; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java index 2bf709cfb8..d24f6347e9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java @@ -278,7 +278,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS } - CSVWorker csvWorker = new CSVWorker(selectedFile, file, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes)); + CSVWorker csvWorker = new CSVWorker(selectedFile, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes)); csvWorker.execute(); } } @@ -426,7 +426,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - worker = new SelectionWorker(correlationAttributes, file, deviceId, dataSourceName) { + worker = new SelectionWorker(correlationAttributes, deviceId, dataSourceName) { @Override public void done() { if (isCancelled()) { @@ -487,7 +487,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { final int[] selectedDataSources = dataSourcesTable.getSelectedRows(); filesTableModel.clearTable(); - worker = new SelectionWorker(correlationAttributes, file, deviceId, dataSourceName) { + worker = new SelectionWorker(correlationAttributes, deviceId, dataSourceName) { @Override public void done() { if (isCancelled()) { @@ -608,7 +608,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { private class SelectionWorker extends SwingWorker, Void> { private final Collection coAtInstances; - private final AbstractFile abstractFile; private final String deviceIdStr; private final String dataSourceNameStr; @@ -620,9 +619,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { * @param deviceIdStr * @param dataSourceNameStr */ - SelectionWorker(Collection coAtInstances, AbstractFile abstractFile, String deviceIdStr, String dataSourceNameStr) { + SelectionWorker(Collection coAtInstances, String deviceIdStr, String dataSourceNameStr) { this.coAtInstances = coAtInstances; - this.abstractFile = abstractFile; this.dataSourceNameStr = dataSourceNameStr; this.deviceIdStr = deviceIdStr; } @@ -631,7 +629,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { protected Map doInBackground() throws Exception { Map correlatedNodeDataMap = new HashMap<>(); for (CorrelationAttributeInstance corAttr : coAtInstances) { - correlatedNodeDataMap.putAll(OtherOccurrences.getCorrelatedInstances(abstractFile, deviceIdStr, dataSourceNameStr, corAttr)); + correlatedNodeDataMap.putAll(OtherOccurrences.getCorrelatedInstances(deviceIdStr, dataSourceNameStr, corAttr)); if (isCancelled()) { return new HashMap<>(); @@ -651,7 +649,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { private final String dataSourceName; private final String deviceId; private final File destFile; - private final AbstractFile abstractFile; /** * Construct a CSVWorker @@ -662,9 +659,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { * @param deviceId Id of the selected device. * @param correlationAttList */ - CSVWorker(File destFile, AbstractFile sourceFile, String dataSourceName, String deviceId, Collection correlationAttList) { + CSVWorker(File destFile, String dataSourceName, String deviceId, Collection correlationAttList) { this.destFile = destFile; - this.abstractFile = sourceFile; this.dataSourceName = dataSourceName; this.deviceId = deviceId; this.correlationAttList = correlationAttList; @@ -672,7 +668,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { @Override protected Void doInBackground() throws Exception { - OtherOccurrences.writeOtherOccurrencesToFileAsCSV(this.destFile, this.abstractFile, this.correlationAttList, this.dataSourceName, this.deviceId); + OtherOccurrences.writeOtherOccurrencesToFileAsCSV(this.destFile, this.correlationAttList, this.dataSourceName, this.deviceId); return null; } From 487cd5681b8c8b6603757a115314710771a5f68f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 00:19:30 -0400 Subject: [PATCH 21/51] 7918 fix code regarding OO viewer and tags --- .../OtherOccurrencesNodeWorker.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index e4cb1015fd..c4ad7a2663 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -37,9 +37,12 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeIns import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactTagNode; import org.sleuthkit.autopsy.datamodel.TskContentItem; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.TskException; @@ -72,7 +75,6 @@ class OtherOccurrencesNodeWorker extends SwingWorker String dataSourceName = ""; Map caseNames = new HashMap<>(); Case currentCase = Case.getCurrentCaseThrows(); - //the file is used for determining a correlation instance is not the selected instance AbstractFile file = node.getLookup().lookup(AbstractFile.class); try { @@ -82,10 +84,12 @@ class OtherOccurrencesNodeWorker extends SwingWorker dataSourceName = dataSource.getName(); } } catch (TskException ex) { - logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); + // do nothing. + // @@@ Review this behavior return data; } Collection correlationAttributes = new ArrayList<>(); + if (osAccount != null) { correlationAttributes.addAll(OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount)); } else { @@ -93,11 +97,20 @@ class OtherOccurrencesNodeWorker extends SwingWorker Content content = null; if (contentItem != null) { content = contentItem.getTskContent(); + } else { //fallback and check ContentTags + ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class); + BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); + if (nodeBbArtifactTag != null) { + content = nodeBbArtifactTag.getArtifact(); + } else if (nodeContentTag != null) { + content = nodeContentTag.getContent(); + } } if (content != null) { correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(content)); } } + int totalCount = 0; Set dataSources = new HashSet<>(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { From 4623bdc4306eb0350af9618ec9409506058629c0 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 13:26:43 -0400 Subject: [PATCH 22/51] 7918 remove unused import --- .../contentviewer/OtherOccurrencesNodeWorker.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index c4ad7a2663..97ee89c016 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -37,7 +37,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeIns import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.BlackboardArtifactTagNode; import org.sleuthkit.autopsy.datamodel.TskContentItem; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifactTag; From 867b041bc86fcc2920798248ec7958ca230478e3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 13:40:29 -0400 Subject: [PATCH 23/51] 7918 change to logging for failure to get ds info --- .../OtherOccurrencesNodeWorker.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index 97ee89c016..2a7de81ce0 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -74,33 +74,21 @@ class OtherOccurrencesNodeWorker extends SwingWorker String dataSourceName = ""; Map caseNames = new HashMap<>(); Case currentCase = Case.getCurrentCaseThrows(); - //the file is used for determining a correlation instance is not the selected instance - AbstractFile file = node.getLookup().lookup(AbstractFile.class); - try { - if (file != null) { - Content dataSource = file.getDataSource(); - deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); - dataSourceName = dataSource.getName(); - } - } catch (TskException ex) { - // do nothing. - // @@@ Review this behavior - return data; - } - Collection correlationAttributes = new ArrayList<>(); + Collection correlationAttributes = new ArrayList<>(); + Content content = null; if (osAccount != null) { + content = osAccount; correlationAttributes.addAll(OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount)); } else { TskContentItem contentItem = node.getLookup().lookup(TskContentItem.class); - Content content = null; if (contentItem != null) { content = contentItem.getTskContent(); } else { //fallback and check ContentTags ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class); BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); if (nodeBbArtifactTag != null) { - content = nodeBbArtifactTag.getArtifact(); + content = nodeBbArtifactTag.getContent(); } else if (nodeContentTag != null) { content = nodeContentTag.getContent(); } @@ -109,7 +97,16 @@ class OtherOccurrencesNodeWorker extends SwingWorker correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(content)); } } - + try { + if (content != null) { + Content dataSource = file.getDataSource(); + deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); + dataSourceName = dataSource.getName(); + } + } catch (TskException ex) { + logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); + return data; + } int totalCount = 0; Set dataSources = new HashSet<>(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { From c6804b1b110a16daf3560b5eddb5af8d61eaa6ca Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 14:06:49 -0400 Subject: [PATCH 24/51] 7918 comment and formatting --- .../OtherOccurrencesNodeWorker.java | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index 2a7de81ce0..eef101d7eb 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -69,19 +69,29 @@ class OtherOccurrencesNodeWorker extends SwingWorker OtherOccurrencesData data = null; if (CentralRepository.isEnabled()) { OsAccount osAccount = node.getLookup().lookup(OsAccount.class); - String deviceId = ""; String dataSourceName = ""; Map caseNames = new HashMap<>(); Case currentCase = Case.getCurrentCaseThrows(); - + //the file is currently being used for determining a correlation instance is not the selected instance + // for the purposes of ignoring the currently selected item + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + try { + if (file != null) { + Content dataSource = file.getDataSource(); + deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); + dataSourceName = dataSource.getName(); + } + } catch (TskException ex) { + logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); + return data; + } Collection correlationAttributes = new ArrayList<>(); - Content content = null; if (osAccount != null) { - content = osAccount; correlationAttributes.addAll(OtherOccurrences.getCorrelationAttributeFromOsAccount(node, osAccount)); } else { TskContentItem contentItem = node.getLookup().lookup(TskContentItem.class); + Content content = null; if (contentItem != null) { content = contentItem.getTskContent(); } else { //fallback and check ContentTags @@ -97,16 +107,7 @@ class OtherOccurrencesNodeWorker extends SwingWorker correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(content)); } } - try { - if (content != null) { - Content dataSource = file.getDataSource(); - deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId(); - dataSourceName = dataSource.getName(); - } - } catch (TskException ex) { - logger.log(Level.WARNING, "Exception occurred while trying to get the data source, current case, and device id for an AbstractFile in the other occurrences viewer", ex); - return data; - } + int totalCount = 0; Set dataSources = new HashSet<>(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { @@ -123,11 +124,9 @@ class OtherOccurrencesNodeWorker extends SwingWorker } } } - if (!isCancelled()) { data = new OtherOccurrencesData(correlationAttributes, file, dataSourceName, deviceId, caseNames, totalCount, dataSources.size(), OtherOccurrences.getEarliestCaseDate()); } - } return data; } From d4751ca17da7f4bf696c52c01b8ed560f42812bd Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 15:03:44 -0400 Subject: [PATCH 25/51] 7918 change back to getArtifact from getContent --- .../contentviewer/OtherOccurrencesNodeWorker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index eef101d7eb..6c3ab8722d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -98,7 +98,7 @@ class OtherOccurrencesNodeWorker extends SwingWorker ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class); BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class); if (nodeBbArtifactTag != null) { - content = nodeBbArtifactTag.getContent(); + content = nodeBbArtifactTag.getArtifact(); } else if (nodeContentTag != null) { content = nodeContentTag.getContent(); } From b766c823d86ea67eeb4db3eeb801ee3387f7e885 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 16:30:29 -0400 Subject: [PATCH 26/51] 7918 formatting, comments, and isSupported changes --- .../DataContentViewerOtherCases.java | 39 +++++++++++++++---- .../datamodel/CentralRepository.java | 2 +- .../datamodel/RdbmsCentralRepo.java | 2 +- .../datamodel/SqliteCentralRepo.java | 39 ++++++++++--------- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index 484f393f24..e38d8b957a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -24,14 +24,18 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JPanel; +import org.apache.commons.lang.StringUtils; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactTagNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.OsAccount; /** @@ -89,14 +93,35 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon @Override public boolean isSupported(Node node) { - + //Ideally we would want to attempt to create correlation attributes for the node contents + //and if none could be created determine that it was not supported. + //However that winds up being more work than we really want to be performing in this method so we perform a quicker check. + //The result of this is that the Other Occurrences viewer could be enabled but without a correltion attributes in some situations. // Is supported if: // The central repo is enabled and the node is not null - // And the node has information which could be correlated on. - return CentralRepository.isEnabled() && node != null - && (node.getLookup().lookup(AbstractFile.class) != null - || node.getLookup().lookup(BlackboardArtifactItem.class) != null - || node.getLookup().lookup(OsAccount.class) != null); + if (CentralRepository.isEnabled() && node != null) { + // And the node has information which could be correlated on. + if (node.getLookup().lookup(OsAccount.class) != null) { + //the node has an associated OsAccount to correlate on + return true; + } + if (node.getLookup().lookup(BlackboardArtifactItem.class) != null) { + //it has a blackboard artifact which might have a correlation attribute + return true; + } + if (node.getLookup().lookup(BlackboardArtifactTag.class) != null) { + //Blackboard artifact tags may have their underlying artifact correlated on + return true; + } + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + //the AbstractFile lookup will handle the usecase for file tags as well + if (file != null && !StringUtils.isBlank(file.getMd5Hash())) { + //there is an abstractFile lookup and it has an MD5 so could be correlated on + return true; + } + } + return false; + } @Override diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index a0cd06c26e..144d1830ca 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index 149a5fc398..6783377f2f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java index 688266486b..ce7f4705fc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepo.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2020 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,8 +58,8 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { * * @return the singleton instance of SqliteEamDb * - * @throws CentralRepoException if one or more default correlation type(s) have an - * invalid db table name. + * @throws CentralRepoException if one or more default correlation type(s) + * have an invalid db table name. */ public synchronized static SqliteCentralRepo getInstance() throws CentralRepoException { if (instance == null) { @@ -71,9 +71,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { /** * - * @throws CentralRepoException if the AbstractSqlEamDb class has one or more - * default correlation type(s) having an invalid db - * table name. + * @throws CentralRepoException if the AbstractSqlEamDb class has one or + * more default correlation type(s) having an + * invalid db table name. */ private SqliteCentralRepo() throws CentralRepoException { dbSettings = new SqliteCentralRepoSettings(); @@ -144,7 +144,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { CentralRepoDbUtil.closeConnection(conn); } - RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettings); + RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettings); centralRepoSchemaFactory.insertDefaultDatabaseContent(); } finally { releaseExclusiveLock(); @@ -229,8 +229,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { @Override protected Connection getEphemeralConnection() { - return this.dbSettings.getEphemeralConnection(); - } + return this.dbSettings.getEphemeralConnection(); + } + /** * Add a new name/value pair in the db_info table. * @@ -624,10 +625,10 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { releaseSharedLock(); } } - - @Override + + @Override public Long getCountCasesWithOtherInstances(CorrelationAttributeInstance instance) throws CentralRepoException, CorrelationAttributeNormalizationException { - try { + try { acquireSharedLock(); return super.getCountCasesWithOtherInstances(instance); } finally { @@ -840,9 +841,9 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { super.processSelectClause(selectClause, instanceTableCallback); } finally { releaseSharedLock(); - } - } - + } + } + @Override public void executeCommand(String sql, List params) throws CentralRepoException { try { @@ -852,7 +853,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { releaseExclusiveLock(); } } - + @Override public void executeQuery(String sql, List params, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException { try { @@ -862,7 +863,7 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { releaseSharedLock(); } } - + /** * Check whether a reference set with the given name/version is in the * central repo. Used to check for name collisions when creating reference @@ -1224,8 +1225,8 @@ final class SqliteCentralRepo extends RdbmsCentralRepo { * * @return the lock, or null if locking is not supported * - * @throws CentralRepoException if the coordination service is running but we fail - * to get the lock + * @throws CentralRepoException if the coordination service is running but + * we fail to get the lock */ @Override public CoordinationService.Lock getExclusiveMultiUserDbLock() throws CentralRepoException { From e73ceb51ef23aeb92a9bf340c511d42192c0eb46 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 14 Sep 2021 16:35:17 -0400 Subject: [PATCH 27/51] 7918 remove unused imports --- .../contentviewer/DataContentViewerOtherCases.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java index e38d8b957a..fb85ffbf2b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -32,10 +32,8 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; -import org.sleuthkit.autopsy.datamodel.BlackboardArtifactTagNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifactTag; -import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.OsAccount; /** From ba622eaa22d72d3efd6cdfce59d486586246f86f Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 15 Sep 2021 10:37:43 -0400 Subject: [PATCH 28/51] Update SummaryHelpers.java Make returned message more meaning full --- .../sleuthkit/autopsy/discovery/search/SummaryHelpers.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SummaryHelpers.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SummaryHelpers.java index c258801f70..6a21ef94ba 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/SummaryHelpers.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SummaryHelpers.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.logging.Level; import org.apache.commons.lang.StringUtils; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.ImageUtils; @@ -194,6 +195,7 @@ class SummaryHelpers { } } + @NbBundle.Messages({"SummaryHelper.documentSummary.unable.to.read=Unable to extract text from file."}) /** * Get the beginning of text from the specified AbstractFile. * @@ -217,7 +219,7 @@ class SummaryHelpers { } catch (IOException ex) { return Bundle.FileSearch_documentSummary_noBytes(); } catch (TextExtractor.InitReaderException ex) { - return Bundle.FileSearch_documentSummary_noPreview(); + return Bundle.SummaryHelper_documentSummary_unable_to_read(); } } From b2782c0278eb754b7e8aa500abce8584f35eca5a Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 15 Sep 2021 11:12:38 -0400 Subject: [PATCH 29/51] annotations viewer with listeners --- .../annotations/AnnotationsContentViewer.java | 158 ++++++++++++++++-- 1 file changed, 140 insertions(+), 18 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java index faed21e1b1..ec3652c6fa 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java @@ -18,7 +18,12 @@ */ package org.sleuthkit.autopsy.contentviewers.annotations; +import com.google.common.collect.ImmutableSet; import java.awt.Component; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.EnumSet; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.SwingWorker; @@ -29,8 +34,15 @@ import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.jsoup.nodes.Document; +import org.openide.util.WeakListeners; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler.Refresher; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; /** * Annotations view of file contents. @@ -47,7 +59,49 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName()); - private AnnotationWorker worker; + private static 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, + Case.Events.CONTENT_TAG_DELETED, + Case.Events.CR_COMMENT_CHANGED); + + private static final Set ARTIFACT_TYPES_OF_INTEREST = ImmutableSet.of( + BlackboardArtifact.Type.TSK_HASHSET_HIT, + BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT + ); + + /** + * Refresher used with refresh throttler to listen for artifact events. + */ + private final Refresher refresher = new Refresher() { + + @Override + public void refresh() { + AnnotationsContentViewer.this.refresh(); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + if (IngestManager.IngestModuleEvent.DATA_ADDED.toString().equals(evt.getPropertyName()) && evt.getOldValue() instanceof ModuleDataEvent) { + ModuleDataEvent moduleDataEvent = (ModuleDataEvent) evt.getOldValue(); + if (ARTIFACT_TYPES_OF_INTEREST.contains(moduleDataEvent.getBlackboardArtifactType())) { + return true; + } + } + return false; + } + }; + + private final RefreshThrottler refreshThrottler = new RefreshThrottler(refresher); + + private final PropertyChangeListener caseEventListener = WeakListeners.propertyChange((pcl) -> refresh(), null); + + private final Object updateLock = new Object(); + + private AnnotationWorker worker = null; + + private Node node; /** * Creates an instance of AnnotationsContentViewer. @@ -55,23 +109,74 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data public AnnotationsContentViewer() { initComponents(); ContentViewerHtmlStyles.setupHtmlJTextPane(textPanel); + registerListeners(); + } + + @Override + protected void finalize() throws Throwable { + unregisterListeners(); + } + + /** + * Registers case event and ingest event listeners. + */ + private void registerListeners() { + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, caseEventListener); + refreshThrottler.registerForIngestModuleEvents();; + } + + /** + * Unregisters case event and ingest event listeners. + */ + private void unregisterListeners() { + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, caseEventListener); + refreshThrottler.unregisterEventListener(); } @Override public void setNode(Node node) { - resetComponent(); + this.node = node; + updateData(this.node, true); + } - if (worker != null) { - worker.cancel(true); - worker = null; - } + /** + * Refreshes the data displayed. + */ + private void refresh() { + updateData(this.node, false); + } + /** + * Updates data displayed in the viewer. + * + * @param node The node to use for data. + * @param forceReset If true, forces a reset cancelling the previous worker + * if one exists and clearing data in the component. If + * false, only submits a worker if no previous worker is + * running. + */ + private void updateData(Node node, boolean forceReset) { if (node == null) { return; } - worker = new AnnotationWorker(node); - worker.execute(); + if (forceReset) { + resetComponent(); + } + + synchronized (updateLock) { + if (worker != null) { + if (forceReset) { + worker.cancel(true); + worker = null; + } else { + return; + } + } + + worker = new AnnotationWorker(node, forceReset); + worker.execute(); + } } /** @@ -151,9 +256,18 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data private class AnnotationWorker extends SwingWorker { private final Node node; + private final boolean resetCaretPosition; - AnnotationWorker(Node node) { + /** + * Main constructor. + * + * @param node The node for which data will be fetched. + * @param resetCaretPosition Whether or not to reset the caret position + * when finished. + */ + AnnotationWorker(Node node, boolean resetCaretPosition) { this.node = node; + this.resetCaretPosition = resetCaretPosition; } @Override @@ -173,17 +287,25 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data @Override public void done() { - if (isCancelled()) { - return; + if (!isCancelled()) { + try { + String text = get(); + ContentViewerHtmlStyles.setStyles(textPanel); + textPanel.setText(text); + + if (resetCaretPosition) { + textPanel.setCaretPosition(0); + } + + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Failed to get annotation information for node", ex); + } } - try { - String text = get(); - ContentViewerHtmlStyles.setStyles(textPanel); - textPanel.setText(text); - textPanel.setCaretPosition(0); - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Failed to get annotation information for node", ex); + synchronized (updateLock) { + if (worker == this) { + worker = null; + } } } From 8b95fec5b4d1c4b0e21ba44eba6df2ade9a461d4 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 15 Sep 2021 12:26:44 -0400 Subject: [PATCH 30/51] bug fixes --- .../annotations/AnnotationsContentViewer.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java index ec3652c6fa..9ed0bb6992 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java @@ -95,8 +95,10 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data private final RefreshThrottler refreshThrottler = new RefreshThrottler(refresher); - private final PropertyChangeListener caseEventListener = WeakListeners.propertyChange((pcl) -> refresh(), null); - + + private final PropertyChangeListener caseEventListener = (pcl) -> refresh(); + private final PropertyChangeListener weakCaseEventListener = WeakListeners.propertyChange(caseEventListener, null); + private final Object updateLock = new Object(); private AnnotationWorker worker = null; @@ -121,8 +123,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data * Registers case event and ingest event listeners. */ private void registerListeners() { - Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, caseEventListener); - refreshThrottler.registerForIngestModuleEvents();; + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakCaseEventListener); + refreshThrottler.registerForIngestModuleEvents(); } /** @@ -143,7 +145,9 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data * Refreshes the data displayed. */ private void refresh() { - updateData(this.node, false); + if (this.isVisible()) { + updateData(this.node, false); + } } /** From 90364e87d73bad8aa2858000fe475578df423470 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 15 Sep 2021 12:27:33 -0400 Subject: [PATCH 31/51] formatting --- .../annotations/AnnotationsContentViewer.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java index 9ed0bb6992..82fcbdb568 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java @@ -95,14 +95,12 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data private final RefreshThrottler refreshThrottler = new RefreshThrottler(refresher); - private final PropertyChangeListener caseEventListener = (pcl) -> refresh(); private final PropertyChangeListener weakCaseEventListener = WeakListeners.propertyChange(caseEventListener, null); - + private final Object updateLock = new Object(); private AnnotationWorker worker = null; - private Node node; /** @@ -146,7 +144,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data */ private void refresh() { if (this.isVisible()) { - updateData(this.node, false); + updateData(this.node, false); } } From ac220d2562f5aae8622773566c2b2104b77b7247 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 15 Sep 2021 14:44:52 -0400 Subject: [PATCH 32/51] fixes for more precise events --- .../annotations/AnnotationsContentViewer.java | 130 +++++++++++++++--- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java index 82fcbdb568..0436c83368 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.SwingWorker; +import org.apache.commons.lang3.tuple.Pair; import static org.openide.util.NbBundle.Messages; import org.openide.nodes.Node; @@ -36,10 +37,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.jsoup.nodes.Document; import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; -import org.sleuthkit.autopsy.guiutils.RefreshThrottler; -import org.sleuthkit.autopsy.guiutils.RefreshThrottler.Refresher; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -71,37 +75,59 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT ); - /** - * Refresher used with refresh throttler to listen for artifact events. - */ - private final Refresher refresher = new Refresher() { + private final PropertyChangeListener ingestEventListener = (evt) -> { + Long curArtifactId = AnnotationsContentViewer.this.curArtifactId; + Long curContentId = AnnotationsContentViewer.this.curContentId; - @Override - public void refresh() { - AnnotationsContentViewer.this.refresh(); + if (curArtifactId == null && curContentId == null) { + return; } - @Override - public boolean isRefreshRequired(PropertyChangeEvent evt) { - if (IngestManager.IngestModuleEvent.DATA_ADDED.toString().equals(evt.getPropertyName()) && evt.getOldValue() instanceof ModuleDataEvent) { - ModuleDataEvent moduleDataEvent = (ModuleDataEvent) evt.getOldValue(); - if (ARTIFACT_TYPES_OF_INTEREST.contains(moduleDataEvent.getBlackboardArtifactType())) { - return true; + Pair artifactContentId = getIdsFromEvent(evt); + Long artifactId = artifactContentId.getLeft(); + Long contentId = artifactContentId.getRight(); + + if ((curArtifactId != null && curArtifactId == artifactId) || (curContentId != null && curContentId == contentId)) { + refresh(); + } + }; + + private final PropertyChangeListener weakIngestEventListener = WeakListeners.propertyChange(ingestEventListener, null); + + private final PropertyChangeListener caseEventListener = (evt) -> { + Long curArtifactId = AnnotationsContentViewer.this.curArtifactId; + Long curContentId = AnnotationsContentViewer.this.curContentId; + + if (curArtifactId == null && curContentId == null) { + return; + } + + // if it is a module data event + if (IngestManager.IngestModuleEvent.DATA_ADDED.toString().equals(evt.getPropertyName()) + && evt.getOldValue() instanceof ModuleDataEvent) { + + ModuleDataEvent moduleDataEvent = (ModuleDataEvent) evt.getOldValue(); + + // if an artifact is relevant, refresh + if (ARTIFACT_TYPES_OF_INTEREST.contains(moduleDataEvent.getBlackboardArtifactType())) { + for (BlackboardArtifact artifact : moduleDataEvent.getArtifacts()) { + if (artifact.getArtifactID() == curArtifactId || artifact.getObjectID() == curContentId) { + refresh(); + return; + } } } - return false; } }; - private final RefreshThrottler refreshThrottler = new RefreshThrottler(refresher); - - private final PropertyChangeListener caseEventListener = (pcl) -> refresh(); private final PropertyChangeListener weakCaseEventListener = WeakListeners.propertyChange(caseEventListener, null); private final Object updateLock = new Object(); private AnnotationWorker worker = null; private Node node; + private Long curArtifactId; + private Long curContentId; /** * Creates an instance of AnnotationsContentViewer. @@ -122,15 +148,15 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data */ private void registerListeners() { Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakCaseEventListener); - refreshThrottler.registerForIngestModuleEvents(); + IngestManager.getInstance().addIngestJobEventListener(weakIngestEventListener); } /** * Unregisters case event and ingest event listeners. */ private void unregisterListeners() { - Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, caseEventListener); - refreshThrottler.unregisterEventListener(); + Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakCaseEventListener); + IngestManager.getInstance().removeIngestJobEventListener(weakIngestEventListener); } @Override @@ -139,6 +165,65 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data updateData(this.node, true); } + /** + * Returns a pair of the artifact id (or null) and the content id (or null) + * for the case event. + * + * @param evt The case event. + * + * @return A pair of the artifact id (or null) and the content id (or null) + * for the case event. + */ + private static Pair getIdsFromEvent(PropertyChangeEvent evt) { + Case.Events eventType = null; + try { + eventType = Case.Events.valueOf(evt.getPropertyName()); + } catch (IllegalArgumentException ex) { + logger.log(Level.SEVERE, "Unknown event type: " + evt.getPropertyName(), ex); + return Pair.of(null, null); + } + + Long artifactId = null; + Long contentId = null; + + switch (eventType) { + case BLACKBOARD_ARTIFACT_TAG_ADDED: + if (evt instanceof BlackBoardArtifactTagAddedEvent) { + BlackboardArtifact art = ((BlackBoardArtifactTagAddedEvent) evt).getAddedTag().getArtifact(); + artifactId = art.getArtifactID(); + contentId = art.getObjectID(); + } + break; + case BLACKBOARD_ARTIFACT_TAG_DELETED: + if (evt instanceof BlackBoardArtifactTagDeletedEvent) { + artifactId = ((BlackBoardArtifactTagDeletedEvent) evt).getDeletedTagInfo().getArtifactID(); + contentId = ((BlackBoardArtifactTagDeletedEvent) evt).getDeletedTagInfo().getContentID(); + } + break; + case CONTENT_TAG_ADDED: + if (evt instanceof ContentTagAddedEvent) { + contentId = ((ContentTagAddedEvent) evt).getAddedTag().getContent().getId(); + } + break; + case CONTENT_TAG_DELETED: + if (evt instanceof ContentTagDeletedEvent) { + contentId = ((ContentTagDeletedEvent) evt).getDeletedTagInfo().getContentID(); + } + break; + case CR_COMMENT_CHANGED: + if (evt instanceof CommentChangedEvent) { + long commentObjId = ((CommentChangedEvent) evt).getContentID(); + artifactId = commentObjId; + contentId = commentObjId; + } + break; + default: + break; + }; + + return Pair.of(artifactId, contentId); + } + /** * Refreshes the data displayed. */ @@ -249,6 +334,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data @Override public void resetComponent() { textPanel.setText(""); + } /** From 9f820c4f86e39b24c517fbb699449e8da51c3964 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 20 Sep 2021 11:46:25 -0400 Subject: [PATCH 33/51] fixes --- .../annotations/AnnotationUtils.java | 49 +++++++++++++-- .../annotations/AnnotationsContentViewer.java | 62 +++++++++++-------- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java index 97cf7d7e6d..a3320d2e02 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java @@ -135,7 +135,7 @@ public class AnnotationUtils { * @return The pair of artifact (or null if not present) and content (either * artifact parent content, the node content, or null). */ - private static Pair getDisplayContent(Node node) { + static DisplayTskItems getDisplayContent(Node node) { BlackboardArtifactItem artItem = node.getLookup().lookup(BlackboardArtifactItem.class); BlackboardArtifact artifact = artItem == null ? null : artItem.getTskContent(); @@ -143,16 +143,18 @@ public class AnnotationUtils { ? artItem.getSourceContent() : node.getLookup().lookup(AbstractFile.class); - return Pair.of(artifact, content); + return new DisplayTskItems(artifact, content); } /** * Returns whether or not the node is supported by the annotation viewer. + * * @param node The node to display. + * * @return True if the node is supported. */ public static boolean isSupported(Node node) { - return getDisplayContent(node).getRight() != null; + return getDisplayContent(node).getContent() != null; } /** @@ -168,9 +170,9 @@ public class AnnotationUtils { Document html = Jsoup.parse(EMPTY_HTML); Element body = html.getElementsByTag("body").first(); - Pair displayPair = getDisplayContent(node); - BlackboardArtifact artifact = displayPair.getLeft(); - Content srcContent = displayPair.getRight(); + DisplayTskItems displayItems = getDisplayContent(node); + BlackboardArtifact artifact = displayItems.getArtifact(); + Content srcContent = displayItems.getContent(); boolean somethingWasRendered = false; if (artifact != null) { @@ -677,4 +679,39 @@ public class AnnotationUtils { } } + /** + * The TSK items that are being displayed as deciphered from the netbeans + * node. + */ + static class DisplayTskItems { + + private final BlackboardArtifact artifact; + private final Content content; + + /** + * Main constructor. + * + * @param artifact The artifact being displayed or null. + * @param content The parent content or source file being displayed or + * null. + */ + DisplayTskItems(BlackboardArtifact artifact, Content content) { + this.artifact = artifact; + this.content = content; + } + + /** + * @return The selected artifact or null if no selected artifact. + */ + BlackboardArtifact getArtifact() { + return artifact; + } + + /** + * @return The parent content or source file being displayed or null. + */ + Content getContent() { + return content; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java index 0436c83368..c01548026b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationsContentViewer.java @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.contentviewers.annotations.AnnotationUtils.DisplayTskItems; import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles; import org.sleuthkit.autopsy.contentviewers.utils.ViewerPriority; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -70,6 +71,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED); + private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED); + private static final Set ARTIFACT_TYPES_OF_INTEREST = ImmutableSet.of( BlackboardArtifact.Type.TSK_HASHSET_HIT, BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT @@ -83,25 +86,6 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data return; } - Pair artifactContentId = getIdsFromEvent(evt); - Long artifactId = artifactContentId.getLeft(); - Long contentId = artifactContentId.getRight(); - - if ((curArtifactId != null && curArtifactId == artifactId) || (curContentId != null && curContentId == contentId)) { - refresh(); - } - }; - - private final PropertyChangeListener weakIngestEventListener = WeakListeners.propertyChange(ingestEventListener, null); - - private final PropertyChangeListener caseEventListener = (evt) -> { - Long curArtifactId = AnnotationsContentViewer.this.curArtifactId; - Long curContentId = AnnotationsContentViewer.this.curContentId; - - if (curArtifactId == null && curContentId == null) { - return; - } - // if it is a module data event if (IngestManager.IngestModuleEvent.DATA_ADDED.toString().equals(evt.getPropertyName()) && evt.getOldValue() instanceof ModuleDataEvent) { @@ -111,7 +95,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data // if an artifact is relevant, refresh if (ARTIFACT_TYPES_OF_INTEREST.contains(moduleDataEvent.getBlackboardArtifactType())) { for (BlackboardArtifact artifact : moduleDataEvent.getArtifacts()) { - if (artifact.getArtifactID() == curArtifactId || artifact.getObjectID() == curContentId) { + if ((curArtifactId != null && artifact.getArtifactID() == curArtifactId) + || (curContentId != null && artifact.getObjectID() == curContentId)) { refresh(); return; } @@ -120,6 +105,26 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data } }; + private final PropertyChangeListener weakIngestEventListener = WeakListeners.propertyChange(ingestEventListener, null); + + private final PropertyChangeListener caseEventListener = (evt) -> { + Long curArtifactId = AnnotationsContentViewer.this.curArtifactId; + Long curContentId = AnnotationsContentViewer.this.curContentId; + + if (curArtifactId == null && curContentId == null) { + return; + } + + Pair artifactContentId = getIdsFromEvent(evt); + Long artifactId = artifactContentId.getLeft(); + Long contentId = artifactContentId.getRight(); + + // if there is a match of content id or artifact id and the event, refresh + if ((curArtifactId != null && curArtifactId.equals(artifactId)) || (curContentId != null && curContentId.equals(contentId))) { + refresh(); + } + }; + private final PropertyChangeListener weakCaseEventListener = WeakListeners.propertyChange(caseEventListener, null); private final Object updateLock = new Object(); @@ -138,17 +143,17 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data registerListeners(); } - @Override - protected void finalize() throws Throwable { - unregisterListeners(); - } - /** * Registers case event and ingest event listeners. */ private void registerListeners() { Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakCaseEventListener); - IngestManager.getInstance().addIngestJobEventListener(weakIngestEventListener); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakIngestEventListener); + } + + @Override + protected void finalize() throws Throwable { + unregisterListeners(); } /** @@ -156,12 +161,15 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data */ private void unregisterListeners() { Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakCaseEventListener); - IngestManager.getInstance().removeIngestJobEventListener(weakIngestEventListener); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakIngestEventListener); } @Override public void setNode(Node node) { this.node = node; + DisplayTskItems displayItems = AnnotationUtils.getDisplayContent(node); + this.curArtifactId = displayItems.getArtifact() == null ? null : displayItems.getArtifact().getArtifactID(); + this.curContentId = displayItems.getContent() == null ? null : displayItems.getContent().getId(); updateData(this.node, true); } From b87c8b876c17e5756c3b13b2120aaccb4bcdba14 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 20 Sep 2021 16:08:59 -0400 Subject: [PATCH 34/51] Fix for regex patterns that have boundary characters --- .../org/sleuthkit/autopsy/keywordsearch/RegexQuery.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index eaac4f12b4..31334f852e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -364,6 +364,15 @@ final class RegexQuery implements KeywordSearchQuery { } offset = hitMatcher.end(); + if (offset > 1) { + /* NOTE: some of our regex patterns look for boundary characters immediately before and + * after the keyword hit (e.g. PHONE_NUMBER_REGEX, IP_ADDRESS_REGEX). After a match, Java + * pattern mather re-starts at the first character not matched by the previous match. This + * basically requires two boundary characters to be present between each pattern match. + * To mitigate this we are resetting the offest one character back. + */ + offset--; + } final ATTRIBUTE_TYPE artifactAttributeType = originalKeyword.getArtifactAttributeType(); // We attempt to reduce false positives for phone numbers and IP address hits From f780b92af5891e8b601fe3b0f9caa2402619a572 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Mon, 20 Sep 2021 17:10:29 -0400 Subject: [PATCH 35/51] Limited the changes to IP and email regexes only --- .../autopsy/keywordsearch/RegexQuery.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 31334f852e..61ddc44921 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -364,15 +364,6 @@ final class RegexQuery implements KeywordSearchQuery { } offset = hitMatcher.end(); - if (offset > 1) { - /* NOTE: some of our regex patterns look for boundary characters immediately before and - * after the keyword hit (e.g. PHONE_NUMBER_REGEX, IP_ADDRESS_REGEX). After a match, Java - * pattern mather re-starts at the first character not matched by the previous match. This - * basically requires two boundary characters to be present between each pattern match. - * To mitigate this we are resetting the offest one character back. - */ - offset--; - } final ATTRIBUTE_TYPE artifactAttributeType = originalKeyword.getArtifactAttributeType(); // We attempt to reduce false positives for phone numbers and IP address hits @@ -393,6 +384,20 @@ final class RegexQuery implements KeywordSearchQuery { } // Replace all non numeric at the end of the hit. hit = hit.replaceAll("[^0-9]$", ""); + + if (offset > 1) { + /* + * NOTE: our IP and phone number regex patterns look for + * boundary characters immediately before and after + * the keyword hit. After a match, Java pattern + * mather re-starts at the first character not + * matched by the previous match. This basically + * requires two boundary characters to be present + * between each pattern match. To mitigate this we + * are resetting the offest one character back. + */ + offset--; + } } /** From 867c7dfdeeea07c0b272cefa10cf9e850b6eedf9 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 20 Sep 2021 17:11:59 -0400 Subject: [PATCH 36/51] Changed the BlackboardArtifactNode first column display name --- .../datamodel/BlackboardArtifactNode.java | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index fabb83bac0..b275ceec16 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -132,7 +132,6 @@ public class BlackboardArtifactNode extends AbstractContentNode( + Bundle.BlackboardArtifactNode_createSheet_srcFile_name(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_displayName(), + NO_DESCR, + getDisplayName())); boolean scoHasBeenAdded = false; if (artifact instanceof AnalysisResult @@ -578,20 +583,8 @@ public class BlackboardArtifactNode extends AbstractContentNode( - Bundle.BlackboardArtifactNode_createSheet_srcFile_name(), - Bundle.BlackboardArtifactNode_createSheet_srcFile_displayName(), - NO_DESCR, - getDisplayName())); - } - + } + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { /* * If machine translation is configured, add the original name of @@ -1097,13 +1090,7 @@ public class BlackboardArtifactNode extends AbstractContentNode( - Bundle.BlackboardArtifactNode_analysisSheet_soureName_name(), - Bundle.BlackboardArtifactNode_analysisSheet_soureName_name(), - NO_DESCR, - srcContentShortDescription)); - + private void updateSheetForAnalysisResult(AnalysisResult result, Sheet.Set sheetSet) { addSCOColumns(sheetSet); sheetSet.put(new NodeProperty<>( @@ -1224,6 +1211,28 @@ public class BlackboardArtifactNode extends AbstractContentNode - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties deleted file mode 100644 index afb1fd207c..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties +++ /dev/null @@ -1,15 +0,0 @@ -OpenIDE-Module-Name=stixModule -STIXReportModule.getName.text=STIX -STIXReportModule.getDesc.text=Generate a report by running a collection of STIX (Structured Threat Information eXpression) files against the data sources. Also creates artifacts under Interesting Files. -STIXReportModule.progress.readSTIX=Parsing STIX files -STIXReportModuleConfigPanel.jLabel2.text=Select a STIX file or directory of STIX files -STIXReportModuleConfigPanel.jButton1.text=Choose file -STIXReportModuleConfigPanel.jCheckBox1.text=Include results for false indicators in output file -STIXReportModule.notifyMsg.unableToOpenReportFile=Unable to complete STIX report. -STIXReportModule.progress.completedWithErrors=Completed with errors -STIXReportModule.notifyMsg.unableToOpenFileDir=Unable to open STIX file/directory {0} -STIXReportModule.progress.couldNotOpenFileDir=Could not open file/directory {0} -STIXReportModule.notifyMsg.tooManyArtifactsgt1000=Too many STIX-related artifacts generated for "{0}". Only saving first 1000. -STIXReportModule.notifyErr.noFildDirProvided=No STIX file/directory provided -STIXReportModule.progress.noFildDirProvided=No STIX file/directory provided -STIXReportModuleConfigPanel.jStixFileTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties-MERGED deleted file mode 100755 index 17df690225..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle.properties-MERGED +++ /dev/null @@ -1,18 +0,0 @@ -OpenIDE-Module-Name=stixModule -StixArtifactData.indexError.message=Failed to index STIX interesting file hit artifact for keyword search. -StixArtifactData.noOpenCase.errMsg=No open case available. -STIXReportModule.getName.text=STIX -STIXReportModule.getDesc.text=Generate a report by running a collection of STIX (Structured Threat Information eXpression) files against the data sources. Also creates artifacts under Interesting Files. -STIXReportModule.progress.readSTIX=Parsing STIX files -STIXReportModule.srcModuleName.text=STIX Report -STIXReportModuleConfigPanel.jLabel2.text=Select a STIX file or directory of STIX files -STIXReportModuleConfigPanel.jButton1.text=Choose file -STIXReportModuleConfigPanel.jCheckBox1.text=Include results for false indicators in output file -STIXReportModule.notifyMsg.unableToOpenReportFile=Unable to complete STIX report. -STIXReportModule.progress.completedWithErrors=Completed with errors -STIXReportModule.notifyMsg.unableToOpenFileDir=Unable to open STIX file/directory {0} -STIXReportModule.progress.couldNotOpenFileDir=Could not open file/directory {0} -STIXReportModule.notifyMsg.tooManyArtifactsgt1000=Too many STIX-related artifacts generated for "{0}". Only saving first 1000. -STIXReportModule.notifyErr.noFildDirProvided=No STIX file/directory provided -STIXReportModule.progress.noFildDirProvided=No STIX file/directory provided -STIXReportModuleConfigPanel.jStixFileTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle_ja.properties deleted file mode 100644 index 55895784e6..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/Bundle_ja.properties +++ /dev/null @@ -1,18 +0,0 @@ -#Mon Jul 12 13:22:00 UTC 2021 -OpenIDE-Module-Name=stix\u30e2\u30b8\u30e5\u30fc\u30eb -STIXReportModule.getDesc.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u5bfe\u3057\u3066\u5e7e\u3064\u304b\u306eSTIX\uff08Structured Threat Information eXpression\uff1b\u8105\u5a01\u60c5\u5831\u69cb\u9020\u5316\u8a18\u8ff0\u5f62\u5f0f\uff09\u30d5\u30a1\u30a4\u30eb\u3092\u5b9f\u884c\u3057\u3001\u30ec\u30dd\u30fc\u30c8\u3092\u751f\u6210\u3057\u307e\u3059\u3002\u307e\u305f\u3001\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u5185\u306b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u4f5c\u6210\u3002 -STIXReportModule.getName.text=STIX -STIXReportModule.notifyErr.noFildDirProvided=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093 -STIXReportModule.notifyMsg.tooManyArtifactsgt1000="{0}"\u7528\u306b\u751f\u6210\u3055\u308c\u305fSTIX\u95a2\u9023\u306e\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u6700\u521d\u306e1000\u306e\u307f\u4fdd\u5b58\u3055\u308c\u307e\u3059\u3002 -STIXReportModule.notifyMsg.unableToOpenFileDir=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{0}\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f -STIXReportModule.notifyMsg.unableToOpenReportFile=STIX\u30ec\u30dd\u30fc\u30c8\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093\u3002 -STIXReportModule.progress.completedWithErrors=\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u304c\u3001\u5b8c\u4e86\u3057\u307e\u3057\u305f -STIXReportModule.progress.couldNotOpenFileDir=\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea{0}\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f -STIXReportModule.progress.noFildDirProvided=STIX\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u304c\u63d0\u4f9b\u3055\u308c\u3066\u3044\u307e\u305b\u3093 -STIXReportModule.progress.readSTIX=STIX\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30fc\u30b9\u4e2d -STIXReportModule.srcModuleName.text=STIX\u30ec\u30dd\u30fc\u30c8 -STIXReportModuleConfigPanel.jButton1.text=\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e -STIXReportModuleConfigPanel.jCheckBox1.text=\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30d5\u30a1\u30a4\u30eb\u306e\u8aa4\u3063\u305f\u30a4\u30f3\u30b8\u30b1\u30fc\u30bf\u30fc\u306e\u7d50\u679c\u3082\u542b\u3080 -STIXReportModuleConfigPanel.jLabel2.text=STIX\u30d5\u30a1\u30a4\u30eb\u307e\u305f\u306fSTIX\u30d5\u30a1\u30a4\u30eb\u306e\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u9078\u629e -StixArtifactData.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u7528\u306eSTIX\u8208\u5473\u6df1\u3044\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002 -StixArtifactData.noOpenCase.errMsg=\u30aa\u30fc\u30d7\u30f3\u30b1\u30fc\u30b9\u306f\u3042\u308a\u307e\u305b\u3093\u3002 diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAccountObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAccountObj.java deleted file mode 100644 index e52d28d943..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAccountObj.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; - -import org.mitre.cybox.objects.AccountObjectType; -import org.mitre.cybox.objects.UserAccountObjectType; -import org.mitre.cybox.objects.WindowsUserAccount; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalAccountObj extends EvaluatableObject { - - private final AccountObjectType obj; - - EvalAccountObj(AccountObjectType a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - @SuppressWarnings( "deprecation" ) - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - // Fields we can search for: - // UserAccount: Home_Directory, Username - // WinUserAccount: SID - if (!(obj instanceof UserAccountObjectType)) { - return new ObservableResult(id, "AccountObject: Can not process \"Account\" - need a User_Account or Windows_User_Account", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // For displaying what we were looking for in the results - String searchString = ""; - - // Check which fields are present and record them - boolean haveHomeDir = false; - boolean haveUsername = false; - boolean haveSID = false; - - UserAccountObjectType userAccountObj = (UserAccountObjectType) obj; - if (userAccountObj.getHomeDirectory() != null) { - haveHomeDir = true; - searchString = "HomeDir \"" + userAccountObj.getHomeDirectory().getValue().toString() + "\""; //NON-NLS - } - if (userAccountObj.getUsername() != null) { - haveUsername = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Username \"" + userAccountObj.getUsername().getValue().toString() + "\""; //NON-NLS - } - - WindowsUserAccount winUserObj = null; - if (obj instanceof WindowsUserAccount) { - winUserObj = (WindowsUserAccount) obj; - - if (winUserObj.getSecurityID() != null) { - haveSID = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "SID \"" + winUserObj.getSecurityID().getValue().toString() + "\""; //NON-NLS - } - } - - if (!(haveHomeDir || haveUsername || haveSID)) { - return new ObservableResult(id, "AccountObject: No evaluatable fields found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // Set warnings for any unsupported fields - setUnsupportedFieldWarnings(); - - // The assumption here is that there aren't going to be too many network shares, so we - // can cycle through all of them. - try { - List finalHits = new ArrayList<>(); - - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT); - - for (BlackboardArtifact art : artList) { - boolean foundHomeDirMatch = false; - boolean foundUsernameMatch = false; - boolean foundSIDMatch = false; - - for (BlackboardAttribute attr : art.getAttributes()) { - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID()) - && (haveHomeDir)) { - foundHomeDirMatch = compareStringObject(userAccountObj.getHomeDirectory(), attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME.getTypeID()) - && (haveUsername)) { - foundUsernameMatch = compareStringObject(userAccountObj.getUsername(), attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID.getTypeID()) - && (haveSID) && (winUserObj != null)) { - foundSIDMatch = compareStringObject(winUserObj.getSecurityID(), attr.getValueString()); - } - } - - if (((!haveHomeDir) || foundHomeDirMatch) - && ((!haveUsername) || foundUsernameMatch) - && ((!haveSID) || foundSIDMatch)) { - finalHits.add(art); - } - - } - - // Check if we found any matches - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "Account")); //NON-NLS - } - return new ObservableResult(id, "AccountObject: Found a match for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Didn't find any matches - return new ObservableResult(id, "AccountObject: No matches found for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } catch (TskCoreException | NoCurrentCaseException ex) { - return new ObservableResult(id, "AccountObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } - - /** - * Set up the warning for any fields in the object that aren't supported. - */ - private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList(); - - if (obj.getDescription() != null) { - fieldNames.add("Description"); //NON-NLS - } - if (obj.getDomain() != null) { - fieldNames.add("Domain"); //NON-NLS - } - if (obj.getAuthentications() != null) { - fieldNames.add("Authentication"); //NON-NLS - } - if (obj.getCreationDate() != null) { - fieldNames.add("Creation_Date"); //NON-NLS - } - if (obj.getModifiedDate() != null) { - fieldNames.add("Modified_Date"); //NON-NLS - } - if (obj.getLastAccessedTime() != null) { - fieldNames.add("Last_Accessed_Time"); //NON-NLS - } - - if (obj instanceof UserAccountObjectType) { - UserAccountObjectType userAccountObj = (UserAccountObjectType) obj; - if (userAccountObj.getFullName() != null) { - fieldNames.add("Full_Name"); //NON-NLS - } - if (userAccountObj.getGroupList() != null) { - fieldNames.add("Group_List"); //NON-NLS - } - if (userAccountObj.getLastLogin() != null) { - fieldNames.add("Last_Login"); //NON-NLS - } - if (userAccountObj.getPrivilegeList() != null) { - fieldNames.add("Privilege_List"); //NON-NLS - } - if (userAccountObj.getScriptPath() != null) { - fieldNames.add("Script_Path"); //NON-NLS - } - if (userAccountObj.getUserPasswordAge() != null) { - fieldNames.add("User_Password_Age"); //NON-NLS - } - } - - if (obj instanceof WindowsUserAccount) { - WindowsUserAccount winUserObj = (WindowsUserAccount) obj; - - if (winUserObj.getSecurityType() != null) { - fieldNames.add("Security_Type"); //NON-NLS - } - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported field(s): " + warningStr); //NON-NLS - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAddressObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAddressObj.java deleted file mode 100644 index e8ec9b3d79..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalAddressObj.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; -import org.mitre.cybox.common_2.ConditionApplicationEnum; -import org.mitre.cybox.common_2.ConditionTypeEnum; - -import org.mitre.cybox.objects.Address; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalAddressObj extends EvaluatableObject { - - private final Address obj; - - public EvalAddressObj(Address a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - if (obj.getAddressValue() == null) { - return new ObservableResult(id, "AddressObject: No address value field found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - Case case1; - try { - case1 = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - return new ObservableResult(id, "Exception while getting open case.", //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - - String origAddressStr = obj.getAddressValue().getValue().toString(); - - // For now, we don't support "NONE" because it honestly doesn't seem like it - // would ever appear in practice. - if (((obj.getAddressValue().getApplyCondition() != null) - && (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.NONE))) { - return new ObservableResult(id, "AddressObject: Can not process apply condition " + obj.getAddressValue().getApplyCondition().toString() //NON-NLS - + " on Address object", spacing, ObservableResult.ObservableState.INDETERMINATE, null); //NON-NLS - } - - // Set warnings for any unsupported fields - setUnsupportedFieldWarnings(); - - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - - try { - // Need to check that every part of the string had at least one match - // in the AND case - boolean everyPartMatched = true; - List combinedArts = new ArrayList(); - String searchString = ""; - String[] parts = origAddressStr.split("##comma##"); //NON-NLS - - for (String addressStr : parts) { - - // Update the string to show in the results - if (!searchString.isEmpty()) { - - if ((obj.getAddressValue().getApplyCondition() != null) - && (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.ALL)) { - searchString += " AND "; //NON-NLS - } else { - searchString += " OR "; //NON-NLS - } - } - searchString += addressStr; - - if ((obj.getAddressValue().getCondition() == null) - || (obj.getAddressValue().getCondition() == ConditionTypeEnum.EQUALS)) { - List arts = sleuthkitCase.getBlackboardArtifacts( - BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, - addressStr); - - if (arts.isEmpty()) { - everyPartMatched = false; - } else { - combinedArts.addAll(arts); - } - - } else { - // This is inefficient, but the easiest way to do it. - - List finalHits = new ArrayList(); - - // Get all the URL artifacts - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT); - - for (BlackboardArtifact art : artList) { - - for (BlackboardAttribute attr : art.getAttributes()) { - if (attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { - if (compareStringObject(addressStr, obj.getAddressValue().getCondition(), - obj.getAddressValue().getApplyCondition(), attr.getValueString())) { - finalHits.add(art); - } - } - } - } - - if (finalHits.isEmpty()) { - everyPartMatched = false; - } else { - combinedArts.addAll(finalHits); - } - } - } - - // If we're in the ALL case, make sure every piece matched - if ((obj.getAddressValue().getApplyCondition() != null) - && (obj.getAddressValue().getApplyCondition() == ConditionApplicationEnum.ALL) - && (!everyPartMatched)) { - return new ObservableResult(id, "AddressObject: No matches for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - - if (!combinedArts.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : combinedArts) { - artData.add(new StixArtifactData(a.getObjectID(), id, "AddressObject")); //NON-NLS - } - return new ObservableResult(id, "AddressObject: Found a match for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - return new ObservableResult(id, "AddressObject: Found no matches for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - - } catch (TskCoreException ex) { - return new ObservableResult(id, "AddressObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } - - /** - * Set up the warning for any fields in the object that aren't supported. - */ - private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList(); - - if (obj.getVLANName() != null) { - fieldNames.add("VLAN_Name"); //NON-NLS - } - if (obj.getVLANName() != null) { - fieldNames.add("VLAN_Num"); //NON-NLS - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported field(s): " + warningStr); //NON-NLS - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalDomainObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalDomainObj.java deleted file mode 100644 index 203355c7d0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalDomainObj.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import java.util.ArrayList; -import java.util.List; -import org.mitre.cybox.common_2.ConditionApplicationEnum; -import org.mitre.cybox.common_2.ConditionTypeEnum; -import org.mitre.cybox.objects.DomainName; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.datamodel.SleuthkitCase; - -/** - * - */ -class EvalDomainObj extends EvaluatableObject { - - private final DomainName obj; - - public EvalDomainObj(DomainName a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - if (obj.getValue() == null) { - return new ObservableResult(id, "DomainObject: No domain value field found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - Case case1; - try { - case1 = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - return new ObservableResult(id, "Exception while getting open case.", //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - // Since we have single URL artifacts, ALL and NONE conditions probably don't make sense to test - if (!((obj.getValue().getApplyCondition() == null) - || (obj.getValue().getApplyCondition() == ConditionApplicationEnum.ANY))) { - return new ObservableResult(id, "DomainObject: Can not process apply condition " + obj.getValue().getApplyCondition().toString() //NON-NLS - + " on Domain object", spacing, ObservableResult.ObservableState.INDETERMINATE, null); //NON-NLS - } - - // If the condition is not "CONTAINS", add a warning that it's being ignored - if ((obj.getValue().getCondition() != null) - && (obj.getValue().getCondition() != ConditionTypeEnum.CONTAINS)) { - addWarning("Warning: Ignoring condition " + obj.getValue().getCondition().toString() //NON-NLS - + " on DomainName - using substring comparison"); //NON-NLS - } - - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - - try { - // Set up the list of matching artifacts - List finalHits = new ArrayList(); - - // Get all the URL artifacts - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT); - - for (BlackboardArtifact art : artList) { - - for (BlackboardAttribute attr : art.getAttributes()) { - if (attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { - String url = attr.getValueString(); - - // Check whether the domain name is a substring of the URL (regardless - // of the condition on the domain name object) - if (compareStringObject(obj.getValue().getValue().toString(), ConditionTypeEnum.CONTAINS, - obj.getValue().getApplyCondition(), url)) { - finalHits.add(art); - } - } - } - } - - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "DomainNameObject")); //NON-NLS - } - return new ObservableResult(id, "DomainNameObject: Found a match for " + obj.getValue().getValue().toString() //NON-NLS - + " " + getPrintableWarnings(), - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - return new ObservableResult(id, "DomainNameObject: Found no matches for " + obj.getValue().getValue().toString() //NON-NLS - + " " + getPrintableWarnings(), - spacing, ObservableResult.ObservableState.FALSE, null); - } catch (TskCoreException ex) { - return new ObservableResult(id, "DomainNameObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalEmailObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalEmailObj.java deleted file mode 100644 index 78344eb4de..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalEmailObj.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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.report.modules.stix; - -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; - -import org.mitre.cybox.objects.EmailMessage; -import org.mitre.cybox.objects.Address; - -/** - * - */ -class EvalEmailObj extends EvaluatableObject { - - private final EmailMessage obj; - - private List finalHits; - - public EvalEmailObj(EmailMessage a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - - finalHits = null; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - List toHits = null; - boolean hadToFields = false; - List ccHits = null; - boolean hadCcFields = false; - List fromHits = null; - boolean hadFromField = false; - List subjectHits = null; - boolean hadSubjectField = false; - - if (obj.getHeader() != null) { - if ((obj.getHeader().getTo() != null) - && (obj.getHeader().getTo().getRecipients() != null) - && (!obj.getHeader().getTo().getRecipients().isEmpty())) { - for (Address addr : obj.getHeader().getTo().getRecipients()) { - if (addr.getAddressValue() != null) { - - hadToFields = true; - - try { - toHits = findArtifactsBySubstring(addr.getAddressValue(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - } - } - - if ((obj.getHeader().getCC() != null) - && (obj.getHeader().getCC().getRecipients() != null) - && (!obj.getHeader().getCC().getRecipients().isEmpty())) { - for (Address addr : obj.getHeader().getCC().getRecipients()) { - if (addr.getAddressValue() != null) { - - hadCcFields = true; - - try { - ccHits = findArtifactsBySubstring(addr.getAddressValue(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_CC); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - } - } - - if ((obj.getHeader().getFrom() != null) - && (obj.getHeader().getFrom().getAddressValue() != null)) { - - hadFromField = true; - - try { - fromHits = findArtifactsBySubstring(obj.getHeader().getFrom().getAddressValue(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if ((obj.getHeader().getSubject() != null) - && (obj.getHeader().getSubject().getValue() != null)) { - - hadSubjectField = true; - - try { - subjectHits = findArtifactsBySubstring(obj.getHeader().getSubject(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - } - - // Make sure at least one test had some data - if ((!hadToFields) && (!hadFromField) && (!hadCcFields) && (!hadSubjectField)) { - return new ObservableResult(id, "EmailMessage: Could not find any parsable EmailMessage fields " //NON-NLS - + getPrintableWarnings(), - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // Check if there were more fields that aren't currently supported - String fieldNames = getListOfUnsupportedFields(); - if (fieldNames.length() > 0) { - addWarning("Unsupported field(s) found: " + fieldNames); //NON-NLS - } - - // Find the artifacts that matched all of the fields - finalHits = null; - boolean finalHitsStarted = false; - - if (hadToFields) { - combineHits(toHits, finalHitsStarted); - finalHitsStarted = true; - } - if (hadCcFields) { - combineHits(ccHits, finalHitsStarted); - finalHitsStarted = true; - } - if (hadFromField) { - combineHits(fromHits, finalHitsStarted); - finalHitsStarted = true; - } - if (hadSubjectField) { - combineHits(subjectHits, finalHitsStarted); - finalHitsStarted = true; - } - - if (!finalHitsStarted) { - // We didn't find any fields that could be evaluated - return new ObservableResult(id, "EmailMessage: EmailObj parsing incomplete " + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // If there are any artifacts left in finalHits, we have a match - if (finalHits.size() > 0) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "EmailMessage")); //NON-NLS - } - return new ObservableResult(id, "EmailMessage: " + finalHits.size() + " matching artifacts found " + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } else { - return new ObservableResult(id, "EmailMessage: No matching artifacts found " + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - } - - /** - * Add a set of hits to the final set of hits. Removes any artifacts that - * aren't found in the new set. The final list is the artifacts found in all - * sets. - * - * @param newHits The new hits to add to the list - * @param finalHitsStarted Whether we've started the list or not - */ - private void combineHits(List newHits, boolean finalHitsStarted) { - if (finalHitsStarted && (finalHits != null)) { - finalHits.retainAll(newHits); - } else { - finalHits = newHits; - } - } - - /** - * Test to see if the Email Object has any fields set that we don't support - * right now. - * - * @return a list of unsupported fields found. - */ - private String getListOfUnsupportedFields() { - String fieldNames = ""; - if (obj.getHeader() != null) { - if (obj.getHeader().getReceivedLines() != null) { - fieldNames += "Received_Lines "; //NON-NLS - } - if (obj.getHeader().getBCC() != null) { - fieldNames += "BCC "; //NON-NLS - } - if (obj.getHeader().getInReplyTo() != null) { - fieldNames += "In_Reply_To "; //NON-NLS - } - if (obj.getHeader().getDate() != null) { - fieldNames += "Date "; //NON-NLS - } - if (obj.getHeader().getMessageID() != null) { - fieldNames += "Message_ID "; //NON-NLS - } - if (obj.getHeader().getSender() != null) { - fieldNames += "Sender "; //NON-NLS - } - if (obj.getHeader().getReplyTo() != null) { - fieldNames += "Reply_To "; //NON-NLS - } - if (obj.getHeader().getErrorsTo() != null) { - fieldNames += "Errors_To "; //NON-NLS - } - if (obj.getHeader().getBoundary() != null) { - fieldNames += "Boundary "; //NON-NLS - } - if (obj.getHeader().getContentType() != null) { - fieldNames += "Content_Type "; //NON-NLS - } - if (obj.getHeader().getMIMEVersion() != null) { - fieldNames += "MIME_Version "; //NON-NLS - } - if (obj.getHeader().getPrecedence() != null) { - fieldNames += "Precedence "; //NON-NLS - } - if (obj.getHeader().getUserAgent() != null) { - fieldNames += "User_Agent "; //NON-NLS - } - if (obj.getHeader().getXMailer() != null) { - fieldNames += "X_Mailer "; //NON-NLS - } - if (obj.getHeader().getXOriginatingIP() != null) { - fieldNames += "X_Originiating_IP "; //NON-NLS - } - if (obj.getHeader().getXPriority() != null) { - fieldNames += "X_Priority "; //NON-NLS - } - - } - if (obj.getEmailServer() != null) { - fieldNames += "Email_Server "; //NON-NLS - } - if (obj.getRawBody() != null) { - fieldNames += "Raw_Body "; //NON-NLS - } - if (obj.getRawHeader() != null) { - fieldNames += "Raw_Header "; //NON-NLS - } - if (obj.getAttachments() != null) { - fieldNames += "Attachments "; //NON-NLS - } - if (obj.getLinks() != null) { - fieldNames += "Links "; //NON-NLS - } - - return fieldNames; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalFileObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalFileObj.java deleted file mode 100644 index 842cfe5efb..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalFileObj.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; -import java.util.Date; -import java.util.TimeZone; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import org.mitre.cybox.common_2.ConditionApplicationEnum; - -import org.mitre.cybox.objects.FileObjectType; -import org.mitre.cybox.objects.WindowsExecutableFileObjectType; -import org.mitre.cybox.common_2.ConditionTypeEnum; -import org.mitre.cybox.common_2.DatatypeEnum; -import org.mitre.cybox.common_2.HashType; -import org.mitre.cybox.common_2.DateTimeObjectPropertyType; -import org.mitre.cybox.common_2.StringObjectPropertyType; -import org.mitre.cybox.common_2.UnsignedLongObjectPropertyType; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalFileObj extends EvaluatableObject { - - private final FileObjectType obj; - - public EvalFileObj(FileObjectType a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - @SuppressWarnings("deprecation") - public synchronized ObservableResult evaluate() { - - Case case1; - try { - case1 = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - return new ObservableResult(id, "Exception while getting open case.", //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - - setWarnings(""); - String whereClause = ""; - - if (obj.getSizeInBytes() != null) { - try { - String newClause = processULongObject(obj.getSizeInBytes(), "size"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getFileName() != null) { - try { - String newClause = processStringObject(obj.getFileName(), "name"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getFileExtension() != null) { - if ((obj.getFileExtension().getCondition() == null) - || (obj.getFileExtension().getCondition() == ConditionTypeEnum.EQUALS)) { - String newClause = "LOWER(name) LIKE LOWER(\'%" + obj.getFileExtension().getValue() + "\')"; //NON-NLS - whereClause = addClause(whereClause, newClause); - } else { - addWarning( - "Could not process condition " + obj.getFileExtension().getCondition().value() + " on file extension"); //NON-NLS - } - } - - if (obj.getFilePath() != null) { - try { - - String[] parts = obj.getFilePath().getValue().toString().split("##comma##"); //NON-NLS - String finalPathStr = ""; - - for (String filePath : parts) { - // First, we need to normalize the path - String currentFilePath = filePath; - - // Remove the drive letter - if (currentFilePath.matches("^[A-Za-z]:.*")) { - currentFilePath = currentFilePath.substring(2); - } - - // Change any backslashes to forward slashes - currentFilePath = currentFilePath.replace("\\", "/"); - - // The path needs to start with a slash - if (!currentFilePath.startsWith("/")) { - currentFilePath = "/" + currentFilePath; - } - - // If the path does not end in a slash, the final part should be the file name. - if (!currentFilePath.endsWith("/")) { - int lastSlash = currentFilePath.lastIndexOf('/'); - if (lastSlash >= 0) { - currentFilePath = currentFilePath.substring(0, lastSlash + 1); - } - } - - // Reconstruct the path string (which may be multi-part) - if (!finalPathStr.isEmpty()) { - finalPathStr += "##comma##"; //NON-NLS - } - finalPathStr += currentFilePath; - } - - String newClause = processStringObject(finalPathStr, obj.getFilePath().getCondition(), - obj.getFilePath().getApplyCondition(), "parent_path"); //NON-NLS - - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getCreatedTime() != null) { - try { - String newClause = processTimestampObject(obj.getCreatedTime(), "crtime"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getModifiedTime() != null) { - try { - String newClause = processTimestampObject(obj.getModifiedTime(), "mtime"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getAccessedTime() != null) { - try { - String newClause = processTimestampObject(obj.getAccessedTime(), "atime"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - - if (obj.getHashes() != null) { - for (HashType h : obj.getHashes().getHashes()) { - if (h.getSimpleHashValue() != null) { - if (h.getType().getValue().equals("MD5")) { //NON-NLS - String newClause = ""; - if (h.getSimpleHashValue().getValue().toString().toLowerCase().contains("##comma##")) { //NON-NLS - String[] parts = h.getSimpleHashValue().getValue().toString().toLowerCase().split("##comma##"); //NON-NLS - String hashList = ""; - for (String s : parts) { - if (!hashList.isEmpty()) { - hashList += ", "; - } - hashList += "\'" + s + "\'"; - } - newClause = "md5 IN (" + hashList + ")"; //NON-NLS - } else { - newClause = "md5=\'" + h.getSimpleHashValue().getValue().toString().toLowerCase() + "\'"; //NON-NLS - } - whereClause = addClause(whereClause, newClause); - } else { - addWarning("Could not process hash type " + h.getType().getValue().toString()); //NON-NLS - } - } else { - addWarning("Could not process non-simple hash value"); //NON-NLS - } - } - } - - if (obj instanceof WindowsExecutableFileObjectType) { - WindowsExecutableFileObjectType winExe = (WindowsExecutableFileObjectType) obj; - if (winExe.getHeaders() != null) { - if (winExe.getHeaders().getFileHeader() != null) { - if (winExe.getHeaders().getFileHeader().getTimeDateStamp() != null) { - try { - String result = convertTimestampString(winExe.getHeaders().getFileHeader().getTimeDateStamp().getValue().toString()); - String newClause = processNumericFields(result, - winExe.getHeaders().getFileHeader().getTimeDateStamp().getCondition(), - winExe.getHeaders().getFileHeader().getTimeDateStamp().getApplyCondition(), - "crtime"); //NON-NLS - whereClause = addClause(whereClause, newClause); - } catch (TskCoreException ex) { - addWarning(ex.getLocalizedMessage()); - } - } - } - } - } - - String unsupportedFields = listUnsupportedFields(); - if (!unsupportedFields.isEmpty()) { - addWarning("Unsupported fields: " + unsupportedFields); //NON-NLS - } - - if (whereClause.length() > 0) { - try { - List matchingFiles = sleuthkitCase.findAllFilesWhere(whereClause); - - if (!matchingFiles.isEmpty()) { - - if (listSecondaryFields().isEmpty()) { - - List artData = new ArrayList(); - for (AbstractFile a : matchingFiles) { - artData.add(new StixArtifactData(a, id, "FileObject")); //NON-NLS - } - - return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } else { - - // We need to tag the matching files in Autopsy, so keep track of them - List secondaryHits = new ArrayList(); - - for (AbstractFile file : matchingFiles) { - boolean passedTests = true; - - if (obj.isIsMasqueraded() != null) { - List arts = file.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED); - boolean isMasq = false; - if (!arts.isEmpty()) { - isMasq = true; - } - - if (obj.isIsMasqueraded() != isMasq) { - passedTests = false; - } - - } - - if (obj.getFileFormat() != null) { - - String formatsFound = file.getMIMEType(); - if (formatsFound != null) { - if (!(formatsFound.equalsIgnoreCase(obj.getFileFormat().getValue().toString()))) { - addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString() //NON-NLS - + " against " + formatsFound); //NON-NLS - } - } else { - addWarning("Warning: Did not match File_Format field " + obj.getFileFormat().getValue().toString() //NON-NLS - + " (no file formats found)"); //NON-NLS - } - // It looks like the STIX file formats can be different than what Autopsy stores - // (mime vs. unix file), so don't kill a file based on this field not matching. - //if (!foundMatch) { - // passedTests = false; - //} - } - if (passedTests) { - secondaryHits.add(file); - } - } - - if (secondaryHits.isEmpty()) { - - return new ObservableResult(id, "FileObject: Found " + matchingFiles.size() + " matches for " + whereClause //NON-NLS - + " but none for secondary tests on " + listSecondaryFields() + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } else { - List artData = new ArrayList(); - for (AbstractFile a : secondaryHits) { - artData.add(new StixArtifactData(a, id, "FileObject")); //NON-NLS - } - return new ObservableResult(id, "FileObject: Found " + secondaryHits.size() + " matches for " + whereClause //NON-NLS - + " and secondary tests on " + listSecondaryFields() + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - } - } else { - return new ObservableResult(id, "FileObject: Found no matches for " + whereClause + getPrintableWarnings(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - } catch (TskCoreException ex) { - return new ObservableResult(id, "FileObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } else { - - } - - return new ObservableResult(id, "FileObject: No evaluatable fields " + getPrintableWarnings(), spacing, //NON-NLS - ObservableResult.ObservableState.INDETERMINATE, null); - } - - /** - * Create a list of secondary fields. These are the ones that we only test - * on the matches for the primary fields. - * - * @return List of secondary fields - */ - private String listSecondaryFields() { - String secondaryFields = ""; - - if (obj.isIsMasqueraded() != null) { - secondaryFields += "is_masqueraded "; //NON-NLS - } - - if (obj.getFileFormat() != null) { - secondaryFields += "File_Format "; //NON-NLS - } - - return secondaryFields; - } - - /** - * List unsupported fields found in the object. - * - * @return List of unsupported fields - */ - private String listUnsupportedFields() { - String unsupportedFields = ""; - - if (obj.isIsPacked() != null) { - unsupportedFields += "is_packed "; //NON-NLS - } - if (obj.getDevicePath() != null) { - unsupportedFields += "Device_Path "; //NON-NLS - } - if (obj.getFullPath() != null) { - unsupportedFields += "Full_Path "; //NON-NLS - } - if (obj.getMagicNumber() != null) { - unsupportedFields += "Magic_Number "; //NON-NLS - } - if (obj.getDigitalSignatures() != null) { - unsupportedFields += "Digital_Signatures "; //NON-NLS - } - if (obj.getFileAttributesList() != null) { - unsupportedFields += "File_Attributes_List "; //NON-NLS - } - if (obj.getPermissions() != null) { - unsupportedFields += "Permissions "; //NON-NLS - } - if (obj.getUserOwner() != null) { - unsupportedFields += "User_Owner "; //NON-NLS - } - if (obj.getPackerList() != null) { - unsupportedFields += "Packer_List "; //NON-NLS - } - if (obj.getPeakEntropy() != null) { - unsupportedFields += "Peak_Entropy "; //NON-NLS - } - if (obj.getSymLinks() != null) { - unsupportedFields += "Sym_Links "; //NON-NLS - } - if (obj.getByteRuns() != null) { - unsupportedFields += "Bytes_Runs "; //NON-NLS - } - if (obj.getExtractedFeatures() != null) { - unsupportedFields += "Extracted_Features "; //NON-NLS - } - if (obj.getEncryptionAlgorithm() != null) { - unsupportedFields += "Encryption_Algorithm "; //NON-NLS - } - if (obj.getDecryptionKey() != null) { - unsupportedFields += "Decryption_Key "; //NON-NLS - } - if (obj.getCompressionMethod() != null) { - unsupportedFields += "Compression_Method "; //NON-NLS - } - if (obj.getCompressionVersion() != null) { - unsupportedFields += "Compression_Version "; //NON-NLS - } - if (obj.getCompressionComment() != null) { - unsupportedFields += "Compression_Comment "; //NON-NLS - } - - return unsupportedFields; - } - - /** - * Convert timestamp string into a long. - * - * @param timeStr - * - * @return - * - * @throws ParseException - */ - private static long convertTimestamp(String timeStr) throws ParseException { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //NON-NLS - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); //NON-NLS - Date parsedDate = dateFormat.parse(timeStr); - - Long unixTime = parsedDate.getTime() / 1000; - - return unixTime; - } - - /** - * Return the SQL clause for an unsigned long object. Splits into fields and - * call the more generic version of the function. - * - * @param longObj The Cybox UnsignedLong object - * @param fieldName Name of the field to test against - * - * @return SQL clause - * - * @throws TskCoreException - */ - private static String processULongObject(UnsignedLongObjectPropertyType longObj, String fieldName) - throws TskCoreException { - - return processNumericFields(longObj.getValue().toString(), longObj.getCondition(), - longObj.getApplyCondition(), fieldName); - } - - /** - * Return the SQL clause for a numeric object. - * - * @param valueStr Value (as string) - * @param typeCondition Cybox condition - * @param applyCondition Cybox apply_condition - * @param fieldName Name of the field to test against - * - * @return SQL clause - * - * @throws TskCoreException - */ - private static String processNumericFields(String valueStr, ConditionTypeEnum typeCondition, - ConditionApplicationEnum applyCondition, String fieldName) - throws TskCoreException { - - if ((typeCondition == null) - || ((typeCondition != ConditionTypeEnum.INCLUSIVE_BETWEEN) - && (typeCondition != ConditionTypeEnum.EXCLUSIVE_BETWEEN))) { - - String fullClause = ""; - - if (valueStr.isEmpty()) { - throw new TskCoreException("Empty value field"); //NON-NLS - } - - String[] parts = valueStr.split("##comma##"); //NON-NLS - - for (String valuePart : parts) { - String partialClause; - - if ((typeCondition == null) - || (typeCondition == ConditionTypeEnum.EQUALS)) { - - partialClause = fieldName + "=" + valuePart; - } else if (typeCondition == ConditionTypeEnum.DOES_NOT_EQUAL) { - partialClause = fieldName + "!=" + valuePart; - } else if (typeCondition == ConditionTypeEnum.GREATER_THAN) { - partialClause = fieldName + ">" + valuePart; - } else if (typeCondition == ConditionTypeEnum.GREATER_THAN_OR_EQUAL) { - partialClause = fieldName + ">=" + valuePart; - } else if (typeCondition == ConditionTypeEnum.LESS_THAN) { - partialClause = fieldName + "<" + valuePart; - } else if (typeCondition == ConditionTypeEnum.LESS_THAN_OR_EQUAL) { - partialClause = fieldName + "<=" + valuePart; - } else { - throw new TskCoreException("Could not process condition " + typeCondition.value() + " on " + fieldName); //NON-NLS - } - - if (fullClause.isEmpty()) { - - if (parts.length > 1) { - fullClause += "( "; - } - if (applyCondition == ConditionApplicationEnum.NONE) { - fullClause += " NOT "; //NON-NLS - } - fullClause += partialClause; - } else { - if (applyCondition == ConditionApplicationEnum.ALL) { - fullClause += " AND " + partialClause; //NON-NLS - } else if (applyCondition == ConditionApplicationEnum.NONE) { - fullClause += " AND NOT " + partialClause; //NON-NLS - } else { - fullClause += " OR " + partialClause; //NON-NLS - } - } - } - - if (parts.length > 1) { - fullClause += " )"; - } - - return fullClause; - } else { - // I don't think apply conditions make sense for these two. - if (typeCondition == ConditionTypeEnum.INCLUSIVE_BETWEEN) { - String[] parts = valueStr.split("##comma##"); //NON-NLS - if (parts.length != 2) { - throw new TskCoreException("Unexpected number of arguments in INCLUSIVE_BETWEEN on " + fieldName //NON-NLS - + "(" + valueStr + ")"); - } - return (fieldName + ">=" + parts[0] + " AND " + fieldName + "<=" + parts[1]); //NON-NLS - } else { - String[] parts = valueStr.split("##comma##"); //NON-NLS - if (parts.length != 2) { - throw new TskCoreException("Unexpected number of arguments in EXCLUSIVE_BETWEEN on " + fieldName //NON-NLS - + "(" + valueStr + ")"); - } - return (fieldName + ">" + parts[0] + " AND " + fieldName + "<" + parts[1]); //NON-NLS - } - } - } - - /** - * Return the SQL clause for a String object - * - * @param stringObj The full Cybox String object - * @param fieldName Name of the field we're testing against - * - * @return SQL clause - * - * @throws TskCoreException - */ - private static String processStringObject(StringObjectPropertyType stringObj, String fieldName) - throws TskCoreException { - - return processStringObject(stringObj.getValue().toString(), stringObj.getCondition(), - stringObj.getApplyCondition(), fieldName); - } - - /** - * Return the SQL clause for a String object - * - * @param valueStr Value as a string - * @param condition Cybox condition - * @param applyCondition Cybox apply_condition - * @param fieldName Name of the field we're testing against - * - * @return SQL clause - * - * @throws TskCoreException - */ - public static String processStringObject(String valueStr, ConditionTypeEnum condition, - ConditionApplicationEnum applyCondition, String fieldName) - throws TskCoreException { - - String fullClause = ""; - String lowerFieldName = "lower(" + fieldName + ")"; //NON-NLS - - if (valueStr.isEmpty()) { - throw new TskCoreException("Empty value field"); //NON-NLS - } - - String[] parts = valueStr.split("##comma##"); //NON-NLS - - for (String value : parts) { - String lowerValue = value.toLowerCase(); - String partialClause; - if ((condition == null) - || (condition == ConditionTypeEnum.EQUALS)) { - partialClause = lowerFieldName + "=\'" + lowerValue + "\'"; - } else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) { - partialClause = lowerFieldName + " !=\'%" + lowerValue + "%\'"; - } else if (condition == ConditionTypeEnum.CONTAINS) { - partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "%\'"; //NON-NLS - } else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) { - partialClause = lowerFieldName + " NOT LIKE \'%" + lowerValue + "%\'"; //NON-NLS - } else if (condition == ConditionTypeEnum.STARTS_WITH) { - partialClause = lowerFieldName + " LIKE \'" + lowerValue + "%\'"; //NON-NLS - } else if (condition == ConditionTypeEnum.ENDS_WITH) { - partialClause = lowerFieldName + " LIKE \'%" + lowerValue + "\'"; //NON-NLS - } else { - throw new TskCoreException("Could not process condition " + condition.value() + " on " + fieldName); //NON-NLS - } - - if (fullClause.isEmpty()) { - - if (parts.length > 1) { - fullClause += "( "; - } - if (applyCondition == ConditionApplicationEnum.NONE) { - fullClause += " NOT "; //NON-NLS - } - fullClause += partialClause; - } else { - if (applyCondition == ConditionApplicationEnum.ALL) { - fullClause += " AND " + partialClause; //NON-NLS - } else if (applyCondition == ConditionApplicationEnum.NONE) { - fullClause += " AND NOT " + partialClause; //NON-NLS - } else { - fullClause += " OR " + partialClause; //NON-NLS - } - } - } - - if (parts.length > 1) { - fullClause += " )"; - } - - return fullClause; - } - - /** - * Create the SQL clause for a timestamp object. Converts the time into a - * numeric field and then creates the clause from that. - * - * @param dateObj Cybox DateTimeObject - * @param fieldName Name of the field we're testing against - * - * @return SQL clause - * - * @throws TskCoreException - */ - private static String processTimestampObject(DateTimeObjectPropertyType dateObj, String fieldName) - throws TskCoreException { - - if (DatatypeEnum.DATE_TIME == dateObj.getDatatype()) { - - // Change the string into unix timestamps - String result = convertTimestampString(dateObj.getValue().toString()); - return processNumericFields(result, dateObj.getCondition(), dateObj.getApplyCondition(), fieldName); - - } else { - throw new TskCoreException("Found non DATE_TIME field on " + fieldName); //NON-NLS - } - } - - /** - * Convert a timestamp string into a numeric one. Leave it as a string since - * that's what we get from other object types. - * - * @param timestampStr - * - * @return String version with timestamps replaced by numeric values - * - * @throws TskCoreException - */ - private static String convertTimestampString(String timestampStr) - throws TskCoreException { - try { - String result = ""; - if (timestampStr.length() > 0) { - String[] parts = timestampStr.split("##comma##"); //NON-NLS - - for (int i = 0; i < parts.length - 1; i++) { - long unixTime = convertTimestamp(parts[i]); - result += unixTime + "##comma##"; //NON-NLS - } - result += convertTimestamp(parts[parts.length - 1]); - } - return result; - } catch (java.text.ParseException ex) { - throw new TskCoreException("Error parsing timestamp string " + timestampStr); //NON-NLS - } - - } - - /** - * Add a new clause to the existing clause - * - * @param a_clause Current clause - * @param a_newClause New clause - * - * @return Full clause - */ - private static String addClause(String a_clause, String a_newClause) { - - if ((a_clause == null) || a_clause.isEmpty()) { - return a_newClause; - } - - return (a_clause + " AND " + a_newClause); //NON-NLS - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalNetworkShareObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalNetworkShareObj.java deleted file mode 100644 index b145120eeb..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalNetworkShareObj.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import java.util.ArrayList; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import org.mitre.cybox.common_2.ConditionApplicationEnum; - -import org.mitre.cybox.objects.WindowsNetworkShare; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalNetworkShareObj extends EvaluatableObject { - - private final WindowsNetworkShare obj; - - public EvalNetworkShareObj(WindowsNetworkShare a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - if ((obj.getNetname() == null) && (obj.getLocalPath() == null)) { - return new ObservableResult(id, "NetworkShareObjet: No remote name or local path found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // For displaying what we were looking for in the results - String searchString = ""; - if (obj.getNetname() != null) { - searchString += "Netname \"" + obj.getNetname().getValue() + "\""; //NON-NLS - - // The apply conditions ALL or NONE probably won't work correctly. Neither seems - // all that likely to come up in practice, so just give a warning. - if ((obj.getNetname().getApplyCondition() != null) - && (obj.getNetname().getApplyCondition() != ConditionApplicationEnum.ANY)) { - addWarning("Apply condition " + obj.getNetname().getApplyCondition().value() //NON-NLS - + " may not work correctly"); //NON-NLS - } - } - if (obj.getLocalPath() != null) { - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "LocalPath \"" + obj.getLocalPath().getValue() + "\""; //NON-NLS - - // Same as above - the apply conditions ALL or NONE probably won't work correctly. Neither seems - // all that likely to come up in practice, so just give a warning. - if ((obj.getLocalPath().getApplyCondition() != null) - && (obj.getLocalPath().getApplyCondition() != ConditionApplicationEnum.ANY)) { - addWarning("Apply condition " + obj.getLocalPath().getApplyCondition().value() //NON-NLS - + " may not work correctly"); //NON-NLS - } - } - - setUnsupportedFieldWarnings(); - - // The assumption here is that there aren't going to be too many network shares, so we - // can cycle through all of them. - try { - List finalHits = new ArrayList(); - - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_REMOTE_DRIVE); - - for (BlackboardArtifact art : artList) { - boolean foundRemotePathMatch = false; - boolean foundLocalPathMatch = false; - - for (BlackboardAttribute attr : art.getAttributes()) { - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH.getTypeID()) - && (obj.getNetname() != null)) { - foundRemotePathMatch = compareStringObject(obj.getNetname(), attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCAL_PATH.getTypeID()) - && (obj.getLocalPath() != null)) { - foundLocalPathMatch = compareStringObject(obj.getLocalPath(), attr.getValueString()); - } - } - - // Check whether we found everything we were looking for - if (((foundRemotePathMatch) || (obj.getNetname() == null)) - && ((foundLocalPathMatch) || (obj.getLocalPath() == null))) { - finalHits.add(art); - } - } - - // Check if we found any matches - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "NetworkShare")); //NON-NLS - } - return new ObservableResult(id, "NetworkShareObject: Found a match for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Didn't find any matches - return new ObservableResult(id, "NetworkObject: No matches found for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } catch (TskCoreException | NoCurrentCaseException ex) { - return new ObservableResult(id, "NetworkObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } - - private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList(); - - if (obj.getCurrentUses() != null) { - fieldNames.add("Current_Uses"); //NON-NLS - } - if (obj.getMaxUses() != null) { - fieldNames.add("Max_Uses"); //NON-NLS - } - if (obj.getType() != null) { - fieldNames.add("Type"); //NON-NLS - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported field(s): " + warningStr); //NON-NLS - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalRegistryObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalRegistryObj.java deleted file mode 100644 index 8cf6e522f6..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalRegistryObj.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.report.modules.stix; - -import com.williballenthin.rejistry.RegistryHiveFile; -import com.williballenthin.rejistry.RegistryKey; -import com.williballenthin.rejistry.RegistryParseException; -import com.williballenthin.rejistry.RegistryValue; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.datamodel.AbstractFile; -import java.util.List; -import java.util.ArrayList; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.io.File; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import org.mitre.cybox.objects.WindowsRegistryKey; -import org.mitre.cybox.common_2.ConditionTypeEnum; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalRegistryObj extends EvaluatableObject { - - private final WindowsRegistryKey obj; - private final List regFiles = new ArrayList<>(); - - EvalRegistryObj(WindowsRegistryKey a_obj, String a_id, String a_spacing, List a_regFiles) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - regFiles.addAll(a_regFiles); - } - - private EvalRegistryObj() { - obj = null; - id = null; - spacing = ""; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - // Key name is required - if (obj.getKey() == null) { - return new ObservableResult(id, "RegistryObject: No key found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // For now, only support a full string match - if (!((obj.getKey().getCondition() == null) - || (obj.getKey().getCondition() == ConditionTypeEnum.EQUALS))) { - return new ObservableResult(id, "RegistryObject: Can not support condition " + obj.getKey().getCondition() //NON-NLS - + " on Key field", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - setUnsupportedFieldWarnings(); - - // Make a list of hives to test - List hiveList = new ArrayList<>(); - if (obj.getHive() == null) { - // If the hive field is missing, add everything - hiveList.addAll(regFiles); - } else if (obj.getHive().getValue().toString().startsWith("HKEY")) { //NON-NLS - // If the hive name is HKEY_LOCAL_MACHINE, add the ones from the config directory. - // Otherwise, add the others - for (RegistryFileInfo regFile : regFiles) { - if (regFile.getAbstractFile().getParentPath() != null) { - Pattern pattern = Pattern.compile("system32", Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(regFile.getAbstractFile().getParentPath()); - if (matcher.find()) { - // Looking for system files and found one, so add it to the list - if (obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) { //NON-NLS - hiveList.add(regFile); - } - } else { - // Looking for non-system files and found one, so add it to the list - if (!obj.getHive().getValue().toString().equalsIgnoreCase("HKEY_LOCAL_MACHINE")) { //NON-NLS - hiveList.add(regFile); - } - } - } - } - } else { - // Otherwise, try to match the name - String stixHiveName = obj.getHive().getValue().toString(); - - // The temp files will end \Temp\STIX\(hive)_(number) - Pattern pattern = Pattern.compile("Temp.STIX." + stixHiveName, Pattern.CASE_INSENSITIVE); - - for (RegistryFileInfo hive : regFiles) { - Matcher matcher = pattern.matcher(hive.getTempFileName()); - if (matcher.find()) { - hiveList.add(hive); - } - } - - // If nothing matched, add all the files - if (hiveList.isEmpty()) { - hiveList.addAll(regFiles); - } - } - - // This is unlikely to happen unless we have no registry files to test against - if (hiveList.isEmpty()) { - return new ObservableResult(id, "RegistryObject: No matching registry hives found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - for (RegistryFileInfo hive : hiveList) { - try { - ObservableResult result = testRegistryFile(hive); - if (result.isTrue()) { - return result; - } - } catch (Exception ex) { - // The registry parser seems to throw lots of different types of exceptions, - // so make sure to catch them all by this point. Malformed registry files - // in particular cause problems. - addWarning("Error processing registry file " + hive); //NON-NLS - } - } - - if (obj.getHive() == null) { - return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue() //NON-NLS - + " in hive " + obj.getHive().getValue(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - - } - - /** - * Test the Registry object against one registry file. - * - * @param a_regInfo The registry file - * - * @return Result of the test - */ - private ObservableResult testRegistryFile(RegistryFileInfo a_regInfo) { - try { - RegistryKey root = openRegistry(a_regInfo.getTempFileName()); - RegistryKey result = findKey(root, obj.getKey().getValue().toString()); - - if (result == null) { - - // Take another shot looking for the key minus the first part of the path (sometimes the - // hive file name is here). This should only happen if the hive name started - // with "HKEY" - if ((obj.getHive() != null) - && obj.getHive().getValue().toString().startsWith("HKEY")) { //NON-NLS - String[] parts = obj.getKey().getValue().toString().split("\\\\"); - String newKey = ""; - for (int i = 1; i < parts.length; i++) { - if (newKey.length() > 0) { - newKey += "\\"; - } - newKey += parts[i]; - } - result = findKey(root, newKey); - } - - if (result == null) { - return new ObservableResult(id, "RegistryObject: Could not find key " + obj.getKey().getValue(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - } - - if ((obj.getValues() == null) || (obj.getValues().getValues().isEmpty())) { - // No values to test - List artData = new ArrayList<>(); - artData.add(new StixArtifactData(a_regInfo.getAbstractFile().getId(), id, "Registry")); //NON-NLS - return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue(), //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Test all the values - for (org.mitre.cybox.objects.RegistryValueType stixRegValue : obj.getValues().getValues()) { - try { - for (RegistryValue valFromFile : result.getValueList()) { - - // Test if the name field matches (if present) - boolean nameSuccess = true; // True if the name matches or isn't present - if (stixRegValue.getName() != null) { - try { - nameSuccess = compareStringObject(stixRegValue.getName(), valFromFile.getName()); - } catch (UnsupportedEncodingException ex) { - nameSuccess = false; - } - } - - boolean valueSuccess = true; - if (nameSuccess && (stixRegValue.getData() != null)) { - switch (valFromFile.getValueType()) { - case REG_SZ: - case REG_EXPAND_SZ: - - try { - valueSuccess = compareStringObject(stixRegValue.getData(), - valFromFile.getValue().getAsString()); - } catch (UnsupportedEncodingException ex) { - valueSuccess = false; - } - break; - case REG_DWORD: - case REG_BIG_ENDIAN: - case REG_QWORD: - - // Only support "equals" for now. - if ((stixRegValue.getData().getCondition() == null) - || (stixRegValue.getData().getCondition() == ConditionTypeEnum.EQUALS)) { - - // Try to convert the STIX string to a long - try { - long stixValue = Long.decode(stixRegValue.getData().getValue().toString()); - - try { - valueSuccess = (stixValue == valFromFile.getValue().getAsNumber()); - } catch (UnsupportedEncodingException ex) { - valueSuccess = false; - } - } catch (NumberFormatException ex) { - // We probably weren't looking at a numeric field to begin with, - // so getting this exception isn't really an error. - valueSuccess = false; - } - } else { - valueSuccess = false; - } - - break; - default: - // Nothing to do here. These are the types we don't handle: - // REG_BIN, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK, REG_MULTI_SZ, REG_NONE, - // REG_RESOURCE_LIST, REG_RESOURCE_REQUIREMENTS_LIST - } - } - - if (nameSuccess && valueSuccess) { - // Found a match for all values - List artData = new ArrayList<>(); - artData.add(new StixArtifactData(a_regInfo.getAbstractFile().getId(), id, "Registry")); //NON-NLS - return new ObservableResult(id, "RegistryObject: Found key " + obj.getKey().getValue() //NON-NLS - + " and value " + stixRegValue.getName().getValue().toString() //NON-NLS - + " = " + stixRegValue.getData().getValue().toString(), - spacing, ObservableResult.ObservableState.TRUE, artData); - } - } - } catch (Exception ex) { - // Broad catch here becase the registry parser can create all kinds of exceptions beyond what it reports. - return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } - } catch (TskCoreException ex) { - return new ObservableResult(id, "RegistryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - return new ObservableResult(id, "RegistryObject: Not done", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - public RegistryKey openRegistry(String hive) throws TskCoreException { - - try { - RegistryHiveFile regFile = new RegistryHiveFile(new File(hive)); - RegistryKey root = regFile.getRoot(); - return root; - } catch (IOException ex) { - throw new TskCoreException("Error opening registry file - " + ex.getLocalizedMessage()); //NON-NLS - } catch (RegistryParseException ex) { - throw new TskCoreException("Error opening root node of registry - " + ex.getLocalizedMessage()); //NON-NLS - } - } - - /** - * Go down the registry tree to find a key with the given name. - * - * @param root Root of the registry hive - * @param name Name of the subkey to seach for - * - * @return The matching subkey or null if not found - */ - public RegistryKey findKey(RegistryKey root, String name) { - - RegistryKey currentKey = root; - - // Split the key name into parts - String[] parts = name.split("\\\\"); - for (String part : parts) { - - if (part.length() > 0) { - try { - currentKey = currentKey.getSubkey(part); - } catch (Exception ex) { - // We get an exception if the value wasn't found (not a RegistryParseException). - // There doesn't seem to be a cleaner way to test for existance without cycling though - // everything ourselves. (Broad catch because things other than RegistryParseException - // can happen) - return null; - } - } - } - - // If we make it this far, we've found it - return currentKey; - } - - /** - * Copy all registry hives to the temp directory and return the list of - * created files. - * - * @return Paths to copied registry files. - */ - public static List copyRegistryFiles() throws TskCoreException { - - // First get all the abstract files - List regFilesAbstract = findRegistryFiles(); - - // List to hold all the extracted file names plus their abstract file - List regFilesLocal = new ArrayList<>(); - - // Make the temp directory - String tmpDir; - try { - tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator + "STIX"; //NON-NLS - } catch (NoCurrentCaseException ex) { - throw new TskCoreException(ex.getLocalizedMessage()); - } - File dir = new File(tmpDir); - if (dir.exists() == false) { - dir.mkdirs(); - } - - long index = 1; - for (AbstractFile regFile : regFilesAbstract) { - String regFileName = regFile.getName(); - String regFileNameLocal = tmpDir + File.separator + regFileName + "_" + index; - File regFileNameLocalFile = new File(regFileNameLocal); - try { - // Don't save any unallocated versions - if (regFile.getMetaFlagsAsString().contains("Allocated")) { //NON-NLS - ContentUtils.writeToFile(regFile, regFileNameLocalFile); - regFilesLocal.add(new EvalRegistryObj().new RegistryFileInfo(regFile, regFileNameLocal)); - } - } catch (IOException ex) { - throw new TskCoreException(ex.getLocalizedMessage()); - } - index++; - } - - return regFilesLocal; - } - - /** - * Search for the registry hives on the system. Mostly copied from - * RecentActivity - */ - private static List findRegistryFiles() throws TskCoreException { - List registryFiles = new ArrayList<>(); - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - throw new TskCoreException(ex.getLocalizedMessage()); - } - org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = openCase.getServices().getFileManager(); - - for (Content ds : openCase.getDataSources()) { - - // find the user-specific ntuser-dat files - registryFiles.addAll(fileManager.findFiles(ds, "ntuser.dat")); //NON-NLS - - // find the system hives - String[] regFileNames = new String[]{"system", "software", "security", "sam"}; //NON-NLS - for (String regFileName : regFileNames) { - List allRegistryFiles = fileManager.findFiles(ds, regFileName, "/system32/config"); //NON-NLS - for (AbstractFile regFile : allRegistryFiles) { - // Don't want anything from regback - if (!regFile.getParentPath().contains("RegBack")) { //NON-NLS - registryFiles.add(regFile); - } - } - } - } - - return registryFiles; - } - - private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList<>(); - - if (obj.getNumberValues() != null) { - fieldNames.add("Number_Values"); //NON-NLS - } - if (obj.getModifiedTime() != null) { - fieldNames.add("Modified_Time"); //NON-NLS - } - if (obj.getCreatorUsername() != null) { - fieldNames.add("Creator_Username"); //NON-NLS - } - if (obj.getHandleList() != null) { - fieldNames.add("Handle_List"); //NON-NLS - } - if (obj.getNumberSubkeys() != null) { - fieldNames.add("Number_Subkeys"); //NON-NLS - } - if (obj.getSubkeys() != null) { - fieldNames.add("Subkeys"); //NON-NLS - } - if (obj.getByteRuns() != null) { - fieldNames.add("Byte_Runs"); //NON-NLS - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported field(s): " + warningStr); //NON-NLS - } - - /** - * Class to keep track of the abstract file and temp file that goes with - * each registry hive. - */ - public class RegistryFileInfo { - - private final AbstractFile abstractFile; - private final String tempFileName; - - public RegistryFileInfo(AbstractFile a_abstractFile, String a_tempFileName) { - abstractFile = a_abstractFile; - tempFileName = a_tempFileName; - } - - /** - * Get the AbstractFile for this RegistryFileInfo - * - * @return the abstractFile - */ - AbstractFile getAbstractFile() { - return abstractFile; - } - - /** - * Get the Temporary file name for this RegistryFileInfo - * - * @return the tempFileName - */ - String getTempFileName() { - return tempFileName; - } - - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalSystemObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalSystemObj.java deleted file mode 100644 index 956f82fa02..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalSystemObj.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.OSInfo; -import org.sleuthkit.datamodel.OSUtility; - -import java.util.List; -import java.util.ArrayList; - -import org.mitre.cybox.objects.SystemObjectType; -import org.mitre.cybox.objects.WindowsSystem; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalSystemObj extends EvaluatableObject { - - private final SystemObjectType obj; - - public EvalSystemObj(SystemObjectType a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - // For displaying what we were looking for in the results - String searchString = ""; - - // Check which fields are present and record them - boolean haveHostname = false; - // boolean haveDomain = false; - boolean haveProcArch = false; - boolean haveTempDir = false; - boolean haveProductName = false; - boolean haveSystemRoot = false; - boolean haveProductID = false; - boolean haveOwner = false; - boolean haveOrganization = false; - - if (obj.getHostname() != null) { - haveHostname = true; - searchString = "Hostname \"" + obj.getHostname().getValue().toString() + "\""; //NON-NLS - } - if (obj.getProcessorArchitecture() != null) { - haveProcArch = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Processor architecture \"" + obj.getProcessorArchitecture().getValue().toString() + "\""; //NON-NLS - } - - WindowsSystem winSysObj = null; - if (obj instanceof WindowsSystem) { - winSysObj = (WindowsSystem) obj; - - if (winSysObj.getProductID() != null) { - haveProductID = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Product ID \"" + winSysObj.getProductID().getValue().toString() + "\""; //NON-NLS - } - if (winSysObj.getProductName() != null) { - haveProductName = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Product Name \"" + winSysObj.getProductName().getValue().toString() + "\""; //NON-NLS - } - if (winSysObj.getRegisteredOrganization() != null) { - haveOrganization = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Registered Org \"" + winSysObj.getRegisteredOrganization().getValue().toString() + "\""; //NON-NLS - } - if (winSysObj.getRegisteredOwner() != null) { - haveOwner = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Registered Owner \"" + winSysObj.getRegisteredOwner().getValue().toString() + "\""; //NON-NLS - } - if (winSysObj.getWindowsSystemDirectory() != null) { - haveSystemRoot = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "System root \"" + winSysObj.getWindowsSystemDirectory().getValue().toString() + "\""; //NON-NLS - } - if (winSysObj.getWindowsTempDirectory() != null) { - haveTempDir = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Temp dir \"" + winSysObj.getWindowsTempDirectory().getValue().toString() + "\""; //NON-NLS - } - } - - // Return if we have nothing to search for - if (!(haveHostname || haveProcArch - || haveTempDir || haveProductName || haveSystemRoot || haveProductID - || haveOwner || haveOrganization)) { - return new ObservableResult(id, "SystemObject: No evaluatable fields found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - setUnsupportedFieldWarnings(); - - try { - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - List osInfoList = OSUtility.getOSInfo(sleuthkitCase); - - List finalHits = new ArrayList(); - - if (!osInfoList.isEmpty()) { - for (OSInfo info : osInfoList) { - - boolean foundHostnameMatch = false; - //boolean foundDomainMatch = false; - boolean foundProcArchMatch = false; - boolean foundTempDirMatch = false; - boolean foundProductNameMatch = false; - boolean foundSystemRootMatch = false; - boolean foundProductIDMatch = false; - boolean foundOwnerMatch = false; - boolean foundOrganizationMatch = false; - - if (haveHostname) { - foundHostnameMatch = compareStringObject(obj.getHostname(), info.getCompName()); - } - if (haveProcArch) { - foundProcArchMatch = compareStringObject(obj.getProcessorArchitecture().getValue().toString(), - obj.getProcessorArchitecture().getCondition(), - obj.getProcessorArchitecture().getApplyCondition(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE)); - } - if (haveTempDir && (winSysObj != null)) { - foundTempDirMatch = compareStringObject(winSysObj.getWindowsTempDirectory(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEMP_DIR)); - } - if (haveProductName && (winSysObj != null)) { - foundProductNameMatch = compareStringObject(winSysObj.getProductName(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)); - } - if (haveSystemRoot && (winSysObj != null)) { - foundSystemRootMatch = compareStringObject(winSysObj.getWindowsSystemDirectory(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)); - } - if (haveProductID && (winSysObj != null)) { - foundProductIDMatch = compareStringObject(winSysObj.getProductID(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PRODUCT_ID)); - } - if (haveOwner && (winSysObj != null)) { - foundOwnerMatch = compareStringObject(winSysObj.getRegisteredOwner(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_OWNER)); - } - if (haveOrganization && (winSysObj != null)) { - foundOrganizationMatch = compareStringObject(winSysObj.getRegisteredOrganization(), - info.getAttributeValue(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ORGANIZATION)); - } - - if (((!haveHostname) || foundHostnameMatch) - && ((!haveProcArch) || foundProcArchMatch) - && ((!haveTempDir) || foundTempDirMatch) - && ((!haveProductName) || foundProductNameMatch) - && ((!haveSystemRoot) || foundSystemRootMatch) - && ((!haveProductID) || foundProductIDMatch) - && ((!haveOwner) || foundOwnerMatch) - && ((!haveOrganization) || foundOrganizationMatch)) { - - finalHits.addAll(info.getArtifacts()); - } - } - - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "System")); //NON-NLS - } - return new ObservableResult(id, "SystemObject: Found a match for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Didn't find any matches - return new ObservableResult(id, "SystemObject: No matches found for " + searchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } else { - return new ObservableResult(id, "SystemObject: No OS artifacts found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } catch (TskCoreException | NoCurrentCaseException ex) { - return new ObservableResult(id, "SystemObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - } - - /** - * Set up the warning for any fields in the object that aren't supported. - */ - private void setUnsupportedFieldWarnings() { - List fieldNames = new ArrayList(); - - if (obj.getAvailablePhysicalMemory() != null) { - fieldNames.add("Available_Physical_Memory"); //NON-NLS - } - if (obj.getBIOSInfo() != null) { - fieldNames.add("BIOS_Info"); //NON-NLS - } - if (obj.getDate() != null) { - fieldNames.add("Date"); //NON-NLS - } - if (obj.getLocalTime() != null) { - fieldNames.add("Local_Time"); //NON-NLS - } - if (obj.getNetworkInterfaceList() != null) { - fieldNames.add("Network_Interface_List"); //NON-NLS - } - if (obj.getOS() != null) { - fieldNames.add("OS"); //NON-NLS - } - if (obj.getProcessor() != null) { - fieldNames.add("Processor"); //NON-NLS - } - if (obj.getSystemTime() != null) { - fieldNames.add("System_Time"); //NON-NLS - } - if (obj.getTimezoneDST() != null) { - fieldNames.add("Timezone_DST"); //NON-NLS - } - if (obj.getTimezoneStandard() != null) { - fieldNames.add("Timezone_Standard"); //NON-NLS - } - if (obj.getTotalPhysicalMemory() != null) { - fieldNames.add("Total_Physical_Memory"); //NON-NLS - } - if (obj.getUptime() != null) { - fieldNames.add("Uptime"); //NON-NLS - } - if (obj.getUsername() != null) { - fieldNames.add("Username"); //NON-NLS - } - - if (obj instanceof WindowsSystem) { - WindowsSystem winSysObj = (WindowsSystem) obj; - - if (winSysObj.getDomains() != null) { - fieldNames.add("Domain"); //NON-NLS - } - if (winSysObj.getGlobalFlagList() != null) { - fieldNames.add("Global_Flag_List"); //NON-NLS - } - if (winSysObj.getNetBIOSName() != null) { - fieldNames.add("NetBIOS_Name"); //NON-NLS - } - if (winSysObj.getOpenHandleList() != null) { - fieldNames.add("Open_Handle_List"); //NON-NLS - } - if (winSysObj.getWindowsDirectory() != null) { - fieldNames.add("Windows_Directory"); //NON-NLS - } - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported field(s): " + warningStr); //NON-NLS - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURIObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURIObj.java deleted file mode 100644 index 49aa3f2e48..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURIObj.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; -import org.mitre.cybox.common_2.ConditionApplicationEnum; - -import org.mitre.cybox.objects.URIObjectType; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalURIObj extends EvaluatableObject { - - private final URIObjectType obj; - - public EvalURIObj(URIObjectType a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - if (obj.getValue() == null) { - return new ObservableResult(id, "URIObject: No URI value field found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - Case case1; - try { - case1 = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - return new ObservableResult(id, "Exception while getting open case: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } - - String addressStr = obj.getValue().getValue().toString(); - - // Strip off http:// or https:// - String modifiedAddressStr = addressStr.toLowerCase(); - modifiedAddressStr = modifiedAddressStr.replaceAll("http(s)?://", ""); //NON-NLS - - // Since we have single URL artifacts, ALL and NONE conditions probably don't make sense to test - if (!((obj.getValue().getApplyCondition() == null) - || (obj.getValue().getApplyCondition() == ConditionApplicationEnum.ANY))) { - return new ObservableResult(id, "URIObject: Can not process apply condition " + obj.getValue().getApplyCondition().toString() //NON-NLS - + " on URI object", spacing, ObservableResult.ObservableState.INDETERMINATE, null); //NON-NLS - } - - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - - try { - /* - * if ((obj.getValue().getCondition() == null) || - * (obj.getValue().getCondition() == ConditionTypeEnum.EQUALS)) { - * - * // Old version - uses a database query but only works on full - * strings. // It will be faster to use this in the "equals" case - * String[] parts = addressStr.split("##comma##"); - * List arts = new - * ArrayList(); for (String part : parts) { - * arts.addAll(sleuthkitCase.getBlackboardArtifacts( - * BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT, - * BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, part)); } - * - * if (!arts.isEmpty()) { - * - * List artData = new - * ArrayList(); for (BlackboardArtifact a : arts) - * { artData.add(new StixArtifactData(a.getObjectID(), id, - * "URIObject")); } - * - * return new ObservableResult(id, "URIObject: Found " + arts.size() - * + " matches for address = \"" + addressStr + "\"", spacing, - * ObservableResult.ObservableState.TRUE, artData); - * - * } else { return new ObservableResult(id, "URIObject: Found no - * matches for address = \"" + addressStr + "\"", spacing, - * ObservableResult.ObservableState.FALSE, null); } } else { - */ - - // This is inefficient, but the easiest way to do it. - List finalHits = new ArrayList(); - - // Get all the URL artifacts - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT); - - for (BlackboardArtifact art : artList) { - - for (BlackboardAttribute attr : art.getAttributes()) { - if (attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { - - String modifiedAttrString = attr.getValueString(); - if (modifiedAttrString != null) { - modifiedAttrString = modifiedAttrString.toLowerCase(); - modifiedAttrString = modifiedAttrString.replaceAll("http(s)?://", ""); //NON-NLS - } - - if (compareStringObject(modifiedAddressStr, obj.getValue().getCondition(), - obj.getValue().getApplyCondition(), modifiedAttrString)) { - finalHits.add(art); - } - } - } - } - - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "UriObject")); //NON-NLS - } - return new ObservableResult(id, "UriObject: Found a match for " + addressStr, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - return new ObservableResult(id, "URIObject: Found no matches for " + addressStr, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - /* - * } - */ - - } catch (TskCoreException ex) { - return new ObservableResult(id, "URIObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURLHistoryObj.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURLHistoryObj.java deleted file mode 100644 index d952761e23..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvalURLHistoryObj.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -import java.util.List; -import java.util.ArrayList; - -import org.mitre.cybox.common_2.AnyURIObjectPropertyType; -import org.mitre.cybox.objects.URLHistory; -import org.mitre.cybox.objects.URLHistoryEntryType; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; - -/** - * - */ -class EvalURLHistoryObj extends EvaluatableObject { - - private final URLHistory obj; - - public EvalURLHistoryObj(URLHistory a_obj, String a_id, String a_spacing) { - obj = a_obj; - id = a_id; - spacing = a_spacing; - } - - @Override - public synchronized ObservableResult evaluate() { - - setWarnings(""); - - if ((obj.getBrowserInformation() == null) && (obj.getURLHistoryEntries() == null)) { - return new ObservableResult(id, "URLHistoryObject: No browser info or history entries found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // For displaying what we were looking for in the results - String baseSearchString = ""; - String finalResultsStr = ""; - - // The browser info is the same for each entry - boolean haveBrowserName = false; - if (obj.getBrowserInformation() != null) { - if (obj.getBrowserInformation().getName() != null) { - haveBrowserName = true; - } - baseSearchString = "Browser \"" + obj.getBrowserInformation().getName() + "\""; //NON-NLS - } - - // Matching artifacts - List finalHits = new ArrayList(); - - if (obj.getURLHistoryEntries() != null) { - - for (URLHistoryEntryType entry : obj.getURLHistoryEntries()) { - - boolean haveURL = false; - boolean haveHostname = false; - boolean haveReferrer = false; - boolean havePageTitle = false; - boolean haveUserProfile = false; - - setUnsupportedEntryFieldWarnings(entry); - - // At present, the search string doesn't get reported (because there could be different ones - // for multiple URL History Entries) but it's good for debugging. - String searchString = baseSearchString; - - if ((entry.getURL() != null) && (entry.getURL().getValue() != null)) { - haveURL = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "URL \"" + entry.getURL().getValue().getValue() + "\""; //NON-NLS - } - - if ((entry.getReferrerURL() != null) && (entry.getReferrerURL().getValue() != null)) { - haveReferrer = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Referrer \"" + entry.getReferrerURL().getValue().getValue() + "\""; //NON-NLS - } - - if (entry.getUserProfileName() != null) { - haveUserProfile = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "UserProfile \"" + entry.getUserProfileName().getValue() + "\""; //NON-NLS - } - - if (entry.getPageTitle() != null) { - havePageTitle = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Page title \"" + entry.getPageTitle().getValue() + "\""; //NON-NLS - } - - if ((entry.getHostname() != null) && (entry.getHostname().getHostnameValue() != null)) { - haveHostname = true; - if (!searchString.isEmpty()) { - searchString += " and "; //NON-NLS - } - searchString += "Hostname \"" + entry.getHostname().getHostnameValue().getValue() + "\""; //NON-NLS - } - - if (!finalResultsStr.isEmpty()) { - finalResultsStr += ", "; - } - finalResultsStr += searchString; - - if (!(haveURL || haveHostname || haveReferrer - || havePageTitle || haveUserProfile || haveBrowserName)) { - return new ObservableResult(id, "URLHistoryObject: No evaluatable fields found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - try { - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); - - for (BlackboardArtifact art : artList) { - boolean foundURLMatch = false; - boolean foundHostnameMatch = false; - boolean foundReferrerMatch = false; - boolean foundPageTitleMatch = false; - boolean foundUserProfileMatch = false; - boolean foundBrowserNameMatch = false; - - for (BlackboardAttribute attr : art.getAttributes()) { - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL.getTypeID()) - && (haveURL)) { - if (entry.getURL().getValue() instanceof AnyURIObjectPropertyType) { - foundURLMatch = compareStringObject(entry.getURL().getValue().getValue().toString(), - entry.getURL().getValue().getCondition(), - entry.getURL().getValue().getApplyCondition(), - attr.getValueString()); - } else { - addWarning("Non-AnyURIObjectPropertyType found in URL value field"); //NON-NLS - } - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID()) - && (haveHostname)) { - foundHostnameMatch = compareStringObject(entry.getHostname().getHostnameValue(), - attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REFERRER.getTypeID()) - && (haveReferrer)) { - if (entry.getReferrerURL().getValue() instanceof AnyURIObjectPropertyType) { - foundReferrerMatch = compareStringObject(entry.getReferrerURL().getValue().getValue().toString(), - entry.getURL().getValue().getCondition(), - entry.getURL().getValue().getApplyCondition(), - attr.getValueString()); - } else { - addWarning("Non-AnyURIObjectPropertyType found in URL value field"); //NON-NLS - } - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE.getTypeID()) - && (havePageTitle)) { - foundPageTitleMatch = compareStringObject(entry.getPageTitle(), - attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME.getTypeID()) - && (haveUserProfile)) { - foundUserProfileMatch = compareStringObject(entry.getUserProfileName(), - attr.getValueString()); - } - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()) - && (haveBrowserName)) { - foundBrowserNameMatch = compareStringObject(obj.getBrowserInformation().getName(), - null, null, attr.getValueString()); - } - } - - if (((!haveURL) || foundURLMatch) - && ((!haveHostname) || foundHostnameMatch) - && ((!haveReferrer) || foundReferrerMatch) - && ((!havePageTitle) || foundPageTitleMatch) - && ((!haveUserProfile) || foundUserProfileMatch) - && ((!haveBrowserName) || foundBrowserNameMatch)) { - finalHits.add(art); - } - } - - } catch (TskCoreException | NoCurrentCaseException ex) { - return new ObservableResult(id, "URLHistoryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } - - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "URLHistory")); //NON-NLS - } - return new ObservableResult(id, "URLHistoryObject: Found at least one match for " + finalResultsStr, //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Didn't find any matches - return new ObservableResult(id, "URLHistoryObject: No matches found for " + finalResultsStr, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - - } else if (haveBrowserName) { - - // It doesn't seem too useful, but we can just search for the browser name - // if there aren't any URL entries - try { - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - List artList - = sleuthkitCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); - - for (BlackboardArtifact art : artList) { - boolean foundBrowserNameMatch = false; - - for (BlackboardAttribute attr : art.getAttributes()) { - if ((attr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()) - && (haveBrowserName)) { - foundBrowserNameMatch = compareStringObject(obj.getBrowserInformation().getName(), - null, null, attr.getValueString()); - } - } - - if (foundBrowserNameMatch) { - finalHits.add(art); - } - } - - if (!finalHits.isEmpty()) { - List artData = new ArrayList(); - for (BlackboardArtifact a : finalHits) { - artData.add(new StixArtifactData(a.getObjectID(), id, "URLHistory")); //NON-NLS - } - return new ObservableResult(id, "URLHistoryObject: Found at least one match", //NON-NLS - spacing, ObservableResult.ObservableState.TRUE, artData); - } - - // Didn't find any matches - return new ObservableResult(id, "URLHistoryObject: No matches found for " + baseSearchString, //NON-NLS - spacing, ObservableResult.ObservableState.FALSE, null); - } catch (TskCoreException | NoCurrentCaseException ex) { - return new ObservableResult(id, "URLHistoryObject: Exception during evaluation: " + ex.getLocalizedMessage(), //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } else { - // Nothing to search for - return new ObservableResult(id, "URLHistoryObject: No evaluatable fields found", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - } - - /** - * Set up the warning for any fields in the URL_History_Entry object that - * aren't supported. - */ - private void setUnsupportedEntryFieldWarnings(URLHistoryEntryType entry) { - List fieldNames = new ArrayList(); - - if (entry.getUserProfileName() != null) { - fieldNames.add("User_Profile_Name"); //NON-NLS - } - if (entry.getVisitCount() != null) { - fieldNames.add("Visit_Count"); //NON-NLS - } - if (entry.getManuallyEnteredCount() != null) { - fieldNames.add("Manually_Entered_Count"); //NON-NLS - } - if (entry.getModificationDateTime() != null) { - fieldNames.add("Modification_DateTime"); //NON-NLS - } - if (entry.getExpirationDateTime() != null) { - fieldNames.add("Expiration_DateTime"); //NON-NLS - } - if (entry.getFirstVisitDateTime() != null) { - fieldNames.add("First_Visit_DateTime"); //NON-NLS - } - if (entry.getLastVisitDateTime() != null) { - fieldNames.add("Last_Visit_DateTime"); //NON-NLS - } - - String warningStr = ""; - for (String name : fieldNames) { - if (!warningStr.isEmpty()) { - warningStr += ", "; - } - warningStr += name; - } - - addWarning("Unsupported URL_History_Entry field(s): " + warningStr); //NON-NLS - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvaluatableObject.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvaluatableObject.java deleted file mode 100644 index 2f48c65ac5..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/EvaluatableObject.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-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.report.modules.stix; - -import java.util.ArrayList; -import java.util.List; -import org.mitre.cybox.common_2.ConditionApplicationEnum; -import org.mitre.cybox.common_2.ConditionTypeEnum; -import org.mitre.cybox.common_2.StringObjectPropertyType; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - */ -abstract class EvaluatableObject { - - private String warnings; - protected String id; - protected String spacing; - - abstract public ObservableResult evaluate(); - - /** - * Set the warnings string to the given value. - * - * @param a_warnings - */ - public void setWarnings(String a_warnings) { - warnings = a_warnings; - } - - /** - * Get the warnings string. This should not be used to print the final - * version of the warnings. - * - * @return - */ - public String getWarnings() { - return warnings; - } - - /** - * Add to the warnings string. - * - * @param a_newWarning - */ - public void addWarning(String a_newWarning) { - if ((warnings == null) || warnings.isEmpty()) { - warnings = a_newWarning; - return; - } - warnings = warnings + ", " + a_newWarning; - } - - /** - * Find a list of artifacts with the given attribute type that contain the - * String Object. All comparisons will look for substrings of the Blackboard - * artifacts that match the String Object. - * - * @param item - * @param attrType - * - * @return - * - * @throws TskCoreException - */ - public List findArtifactsBySubstring(StringObjectPropertyType item, - BlackboardAttribute.ATTRIBUTE_TYPE attrType) throws TskCoreException { - - if (item.getValue() == null) { - throw new TskCoreException("Error: Value field is null"); //NON-NLS - } - - if (item.getCondition() == null) { - addWarning("Warning: No condition given for " + attrType.getDisplayName() + " field, using substring comparison"); //NON-NLS - } else if (item.getCondition() != ConditionTypeEnum.CONTAINS) { - addWarning("Warning: Ignoring condition " + item.getCondition() + " for " //NON-NLS - + attrType.getDisplayName() + " field and doing substring comparison"); //NON-NLS - } - - List hits = null; - try { - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - - String[] parts = item.getValue().toString().split("##comma##"); //NON-NLS - - if ((item.getApplyCondition() == null) - || (item.getApplyCondition() == ConditionApplicationEnum.ANY)) { - - for (String part : parts) { - if (hits == null) { - // Note that this searches for artifacts with "part" as a substring - hits = sleuthkitCase.getBlackboardArtifacts( - attrType, - part, false); - } else { - hits.addAll(sleuthkitCase.getBlackboardArtifacts( - attrType, - part, false)); - } - } - } else if ((item.getApplyCondition() != null) - || (item.getApplyCondition() == ConditionApplicationEnum.ALL)) { - - boolean firstRound = true; - for (String part : parts) { - if (firstRound) { - hits = sleuthkitCase.getBlackboardArtifacts( - attrType, - part, false); - firstRound = false; - } else if (hits != null) { - hits.retainAll(sleuthkitCase.getBlackboardArtifacts( - attrType, - part, false)); - } else { - // After first round; hits is still null - // I don't think this should happen but if it does we're done - return new ArrayList(); - } - } - } else { - throw new TskCoreException("Error: Can not apply NONE condition in search"); //NON-NLS - } - } catch (TskCoreException | NoCurrentCaseException ex) { - addWarning(ex.getLocalizedMessage()); - } - - return hits; - } - - /** - * Compare a CybOX String Object with a given string. - * - * @param stringObj The CybOX String Object - * @param strField The string to compare against - * - * @return true if strField is a match for the CybOX object - * - * @throws TskCoreException - */ - public static boolean compareStringObject(StringObjectPropertyType stringObj, String strField) - throws TskCoreException { - if (stringObj.getValue() == null) { - throw new TskCoreException("Error: Value field is null"); //NON-NLS - } - - String valueStr = stringObj.getValue().toString(); - ConditionTypeEnum condition = stringObj.getCondition(); - ConditionApplicationEnum applyCondition = stringObj.getApplyCondition(); - - return compareStringObject(valueStr, condition, applyCondition, strField); - } - - /** - * Compare a string with CybOX conditions to a given string. - * - * @param valueStr The CybOX string - * @param condition EQUALS, CONTAINS, STARTS_WITH, etc - * @param applyCondition ANY, ALL, NONE - * @param strField The string to compare against - * - * @return true if strField is a match for the CybOX valueStr and conditions - * - * @throws TskCoreException - */ - public static boolean compareStringObject(String valueStr, ConditionTypeEnum condition, - ConditionApplicationEnum applyCondition, String strField) - throws TskCoreException { - - if (valueStr == null) { - throw new TskCoreException("Error: Value field is null"); //NON-NLS - } - - String[] parts = valueStr.split("##comma##"); //NON-NLS - String lowerFieldName = strField.toLowerCase(); - - for (String value : parts) { - boolean partialResult; - if ((condition == null) - || (condition == ConditionTypeEnum.EQUALS)) { - partialResult = value.equalsIgnoreCase(strField); - } else if (condition == ConditionTypeEnum.DOES_NOT_EQUAL) { - partialResult = !value.equalsIgnoreCase(strField); - } else if (condition == ConditionTypeEnum.CONTAINS) { - partialResult = lowerFieldName.contains(value.toLowerCase()); - } else if (condition == ConditionTypeEnum.DOES_NOT_CONTAIN) { - partialResult = !lowerFieldName.contains(value.toLowerCase()); - } else if (condition == ConditionTypeEnum.STARTS_WITH) { - partialResult = lowerFieldName.startsWith(value.toLowerCase()); - } else if (condition == ConditionTypeEnum.ENDS_WITH) { - partialResult = lowerFieldName.endsWith(value.toLowerCase()); - } else { - throw new TskCoreException("Could not process condition " + condition.value() + " on " + value); //NON-NLS - } - - // Do all the short-circuiting - if (applyCondition == ConditionApplicationEnum.NONE) { - if (partialResult == true) { - // Failed - return false; - } - } else if (applyCondition == ConditionApplicationEnum.ALL) { - if (partialResult == false) { - // Failed - return false; - } - } else { - // Default is "any" - if (partialResult == true) { - return true; - } - } - } - - // At this point we're done and didn't short-circuit, so ALL or NONE conditions were true, - // and ANY was false - if ((applyCondition == ConditionApplicationEnum.NONE) - || (applyCondition == ConditionApplicationEnum.ALL)) { - return true; - } - return false; - } - - /** - * Format the warnings that will be printed. Basically, just put parentheses - * around them if the string isn't empty. - * - * @return - */ - public String getPrintableWarnings() { - String warningsToPrint = ""; - if ((getWarnings() != null) - && (!getWarnings().isEmpty())) { - warningsToPrint = " (" + getWarnings() + ")"; - } - return warningsToPrint; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/ObservableResult.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/ObservableResult.java deleted file mode 100644 index ecfc179e90..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/ObservableResult.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 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.report.modules.stix; - -import java.util.List; -import java.util.ArrayList; - -import org.mitre.cybox.cybox_2.OperatorTypeEnum; - -/** - * - */ -class ObservableResult { - - public enum ObservableState { - - TRUE("true "), //NON-NLS - FALSE("false "), //NON-NLS - INDETERMINATE("indeterminate"); //NON-NLS - - private final String label; - - private ObservableState(String s) { - label = s; - } - - @Override - public String toString() { - return label; - } - } - - private ObservableState state = null; - private String description = ""; - private List artifacts; - - public ObservableResult(String a_id, String a_desc, String a_spacing, - ObservableState a_state, List a_artifacts) { - state = a_state; - description = a_spacing + a_id + "\t" + a_state + "\t" + a_desc + "\r\n"; - artifacts = a_artifacts; - } - - public ObservableResult(OperatorTypeEnum a_operator, String a_spacing) { - state = ObservableState.INDETERMINATE; - description = a_spacing + a_operator + "\r\n"; - artifacts = new ArrayList(); - } - - public ObservableState getState() { - return state; - } - - /** - * Returns true if the ObservableResult is currently true. Note: A false - * result here does not mean the state is false; it could also be - * indeterminate. - * - * @return true if the ObservableResult is true, false if it is false or - * indeterminate - */ - public boolean isTrue() { - return (state == ObservableState.TRUE); - } - - /** - * Returns true if the ObservableResult is currently false. Note: A false - * result here does not mean the state is true; it could also be - * indeterminate. - * - * @return true if the ObservableResult is false, false if it is true or - * indeterminate - */ - public boolean isFalse() { - return (state == ObservableState.FALSE); - } - - public String getDescription() { - return description; - } - - public List getArtifacts() { - return artifacts; - } - - /** - * Add a new result to the current state - * - * @param a_result The new result to add - * @param a_operator AND or OR - */ - public void addResult(ObservableResult a_result, OperatorTypeEnum a_operator) { - addResult(a_result.getDescription(), a_result.getState(), - a_result.getArtifacts(), a_operator); - } - - /** - * Add a new result to the current state. - * - * @param a_description Description of the observable and testing done - * @param a_state State of what we're adding (true, false, or - * indeterminate) - * @param a_operator AND or OR - */ - private void addResult(String a_description, ObservableState a_state, - List a_artifacts, OperatorTypeEnum a_operator) { - - addToDesc(a_description); - - if (a_operator == OperatorTypeEnum.AND) { - - if (a_state == ObservableState.FALSE) { - // If we now have a false, the whole thing is false regardless of previous state. - // Clear out any existing artifacts. - state = ObservableState.FALSE; - artifacts.clear(); - } else if (a_state == ObservableState.INDETERMINATE) { - // Don't change the current state, and don't save the new artifacts - // (though there probably wouldn't be any) - } else { - if (state == ObservableState.FALSE) { - // Previous state false + new state true => stay false - } else if (state == ObservableState.TRUE) { - // Previous state true + new state true => stay true and add artifacts - if ((artifacts == null) && (a_artifacts != null)) { - artifacts = new ArrayList(); - } - if (a_artifacts != null) { - artifacts.addAll(a_artifacts); - } - } else { - // If the previous state was indeterminate, change it to true and add artifacts - state = ObservableState.TRUE; - if ((artifacts == null) && (a_artifacts != null)) { - artifacts = new ArrayList(); - } - if (a_artifacts != null) { - artifacts.addAll(a_artifacts); - } - } - } - } else { - if (a_state == ObservableState.TRUE) { - // If we now have a true, the whole thing is true regardless of previous state. - // Add the new artifacts. - state = ObservableState.TRUE; - if ((artifacts == null) && (a_artifacts != null)) { - artifacts = new ArrayList(); - } - if (a_artifacts != null) { - artifacts.addAll(a_artifacts); - } - } else if (a_state == ObservableState.INDETERMINATE) { - // Don't change the current state and don't record it to the - // description string (later we should save these in some way) - } else { - if (state == ObservableState.FALSE) { - // Previous state false + new state false => stay false - } else if (state == ObservableState.TRUE) { - // Previous state true + new state false => stay true - } else { - // Previous state indeterminate + new state false => change to false - state = ObservableState.FALSE; - } - } - } - - } - - /** - * Add to the description string. Mostly just to make things cleaner by not - * testing for null all over the place. - * - * @param a_desc New part of the description to add - */ - private void addToDesc(String a_desc) { - if (description == null) { - description = a_desc; - } else { - description += a_desc; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModule.java deleted file mode 100644 index 5935dbf89e..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModule.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 - 2020 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.report.modules.stix; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import javax.swing.JPanel; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import javax.xml.namespace.QName; -import org.mitre.cybox.cybox_2.ObjectType; -import org.mitre.cybox.cybox_2.Observable; -import org.mitre.cybox.cybox_2.ObservableCompositionType; -import org.mitre.cybox.cybox_2.OperatorTypeEnum; -import org.mitre.cybox.objects.AccountObjectType; -import org.mitre.cybox.objects.Address; -import org.mitre.cybox.objects.DomainName; -import org.mitre.cybox.objects.EmailMessage; -import org.mitre.cybox.objects.FileObjectType; -import org.mitre.cybox.objects.SystemObjectType; -import org.mitre.cybox.objects.URIObjectType; -import org.mitre.cybox.objects.URLHistory; -import org.mitre.cybox.objects.WindowsNetworkShare; -import org.mitre.cybox.objects.WindowsRegistryKey; -import org.mitre.stix.common_1.IndicatorBaseType; -import org.mitre.stix.indicator_2.Indicator; -import org.mitre.stix.stix_1.STIXPackage; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.report.GeneralReportModule; -import org.sleuthkit.autopsy.report.GeneralReportSettings; -import org.sleuthkit.autopsy.report.NoReportModuleSettings; -import org.sleuthkit.autopsy.report.ReportModuleSettings; -import org.sleuthkit.autopsy.report.ReportProgressPanel; -import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - */ -public class STIXReportModule implements GeneralReportModule { - - private static final Logger logger = Logger.getLogger(STIXReportModule.class.getName()); - private STIXReportModuleConfigPanel configPanel; - private static STIXReportModule instance = null; - private String reportPath; - private boolean reportAllResults; - - private Map idToObjectMap = new HashMap<>(); - private Map idToResult = new HashMap<>(); - - private List registryFileData = null; - - private final boolean skipShortCircuit = true; - - // Hidden constructor for the report - private STIXReportModule() { - } - - // Get the default implementation of this report - public static synchronized STIXReportModule getDefault() { - if (instance == null) { - instance = new STIXReportModule(); - } - return instance; - } - - /** - * @param settings Report settings. - * @param progressPanel panel to update the report's progress - */ - @Override - @Messages({"STIXReportModule.srcModuleName.text=STIX Report"}) - public void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel) { - // Start the progress bar and setup the report - progressPanel.setIndeterminate(false); - progressPanel.start(); - String baseReportDir = settings.getReportDirectoryPath(); - progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.readSTIX")); - reportPath = baseReportDir + getRelativeFilePath(); - File reportFile = new File(reportPath); - // Check if the user wants to display all output or just hits - reportAllResults = configPanel.getShowAllResults(); - - // Keep track of whether any errors occur during processing - boolean hadErrors = false; - - // Process the file/directory name entry - String stixFileName = configPanel.getStixFile(); - - if (stixFileName == null) { - logger.log(Level.SEVERE, "STIXReportModuleConfigPanel.stixFile not initialized "); //NON-NLS - progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided")); - new File(baseReportDir).delete(); - return; - } - if (stixFileName.isEmpty()) { - logger.log(Level.SEVERE, "No STIX file/directory provided "); //NON-NLS - progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.noFildDirProvided")); - new File(baseReportDir).delete(); - return; - } - File stixFile = new File(stixFileName); - - if (!stixFile.exists()) { - logger.log(Level.SEVERE, String.format("Unable to open STIX file/directory %s", stixFileName)); //NON-NLS - progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.couldNotOpenFileDir", stixFileName)); - new File(baseReportDir).delete(); - return; - } - - try (BufferedWriter output = new BufferedWriter(new FileWriter(reportFile))) { - - // Create array of stix file(s) - File[] stixFiles; - if (stixFile.isFile()) { - stixFiles = new File[1]; - stixFiles[0] = stixFile; - } else { - stixFiles = stixFile.listFiles(); - } - - // Set the length of the progress bar - we increment twice for each file - progressPanel.setMaximumProgress(stixFiles.length * 2 + 1); - - // Process each STIX file - for (File file : stixFiles) { - if (progressPanel.getStatus() == ReportStatus.CANCELED) { - return; - } - try { - processFile(file.getAbsolutePath(), progressPanel, output); - } catch (TskCoreException | JAXBException ex) { - String errMsg = String.format("Unable to process STIX file %s", file); - logger.log(Level.SEVERE, errMsg, ex); //NON-NLS - progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), errMsg)); - hadErrors = true; - break; - } - // Clear out the ID maps before loading the next file - idToObjectMap = new HashMap<>(); - idToResult = new HashMap<>(); - } - - // Set the progress bar to done. If any errors occurred along the way, modify - // the "complete" message to indicate this. - Case.getCurrentCaseThrows().addReport(reportPath, Bundle.STIXReportModule_srcModuleName_text(), ""); - if (hadErrors) { - progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors")); - } else { - progressPanel.complete(ReportStatus.COMPLETE); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, "Unable to complete STIX report.", ex); //NON-NLS - progressPanel.complete(ReportStatus.ERROR, NbBundle.getMessage(this.getClass(), "STIXReportModule.progress.completedWithErrors")); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Unable to add report to database.", ex); - } - } - - /** - * Process a STIX file. - * - * @param stixFile - Name of the file - * @param progressPanel - Progress panel (for updating) - * @param output - * - * @throws JAXBException - * @throws TskCoreException - */ - private void processFile(String stixFile, ReportProgressPanel progressPanel, BufferedWriter output) throws - JAXBException, TskCoreException { - - // Load the STIX file - STIXPackage stix; - stix = loadSTIXFile(stixFile); - - printFileHeader(stixFile, output); - - // Save any observables listed up front - processObservables(stix); - progressPanel.increment(); - - // Make copies of the registry files - registryFileData = EvalRegistryObj.copyRegistryFiles(); - - // Process the indicators - processIndicators(stix, output, progressPanel); - progressPanel.increment(); - - } - - /** - * Load a STIX-formatted XML file into a STIXPackage object. - * - * @param stixFileName Name of the STIX file to unmarshal - * - * @return Unmarshalled file contents - * - * @throws JAXBException - */ - private STIXPackage loadSTIXFile(String stixFileName) throws JAXBException { - // Create STIXPackage object from xml. - // See JIRA-6958 for details about class loading and jaxb. - ClassLoader original = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(STIXReportModule.class.getClassLoader()); - File file = new File(stixFileName); - JAXBContext jaxbContext = JAXBContext.newInstance("org.mitre.stix.stix_1:org.mitre.stix.common_1:org.mitre.stix.indicator_2:" //NON-NLS - + "org.mitre.cybox.objects:org.mitre.cybox.cybox_2:org.mitre.cybox.common_2"); //NON-NLS - Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); - STIXPackage stix = (STIXPackage) jaxbUnmarshaller.unmarshal(file); - return stix; - } finally { - Thread.currentThread().setContextClassLoader(original); - } - } - - /** - * Do the initial processing of the list of observables. For each - * observable, save it in a map using the ID as key. - * - * @param stix STIXPackage - */ - private void processObservables(STIXPackage stix) { - if (stix.getObservables() != null) { - List obs = stix.getObservables().getObservables(); - for (Observable o : obs) { - if (o.getId() != null) { - saveToObjectMap(o); - } - } - } - } - - /** - * Process all STIX indicators and save results to output file and create - * artifacts. - * - * @param stix STIXPackage - * @param output - * @param progressPanel - */ - private void processIndicators(STIXPackage stix, BufferedWriter output, ReportProgressPanel progressPanel) throws TskCoreException { - if (stix.getIndicators() != null) { - List s = stix.getIndicators().getIndicators(); - for (IndicatorBaseType t : s) { - if (t instanceof Indicator) { - Indicator ind = (Indicator) t; - if (ind.getObservable() != null) { - if (ind.getObservable().getObject() != null) { - ObservableResult result = evaluateSingleObservable(ind.getObservable(), ""); - if (result.isTrue() || reportAllResults) { - writeResultsToFile(ind, result.getDescription(), result.isTrue(), output); - } - if (result.isTrue()) { - saveResultsAsArtifacts(ind, result, progressPanel); - } - } else if (ind.getObservable().getObservableComposition() != null) { - ObservableResult result = evaluateObservableComposition(ind.getObservable().getObservableComposition(), " "); - - if (result.isTrue() || reportAllResults) { - writeResultsToFile(ind, result.getDescription(), result.isTrue(), output); - } - if (result.isTrue()) { - saveResultsAsArtifacts(ind, result, progressPanel); - } - } - } - } - } - } - } - - /** - * Create the artifacts saved in the observable result. - * - * @param ind - * @param result - * @param progressPanel - * - * @throws TskCoreException - */ - private void saveResultsAsArtifacts(Indicator ind, ObservableResult result, ReportProgressPanel progressPanel) throws TskCoreException { - - if (result.getArtifacts() == null) { - return; - } - - // Count of how many artifacts have been created for this indicator. - int count = 0; - - for (StixArtifactData s : result.getArtifacts()) { - - // Figure out what name to use for this indicator. If it has a title, - // use that. Otherwise use the ID. If both are missing, use a - // generic heading. - if (ind.getTitle() != null) { - s.createArtifact(ind.getTitle()); - } else if (ind.getId() != null) { - s.createArtifact(ind.getId().toString()); - } else { - s.createArtifact("Unnamed indicator(s)"); //NON-NLS - } - - // Trying to protect against the case where we end up with tons of artifacts - // for a single observable because the condition was not restrictive enough - count++; - if (count > 1000) { - progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), - "STIXReportModule.notifyMsg.tooManyArtifactsgt1000")); - break; - } - } - - } - - /** - * Write the full results string to the output file. - * - * @param ind - Used to get the title, ID, and description of the - * indicator - * @param resultStr - Full results for this indicator - * @param found - true if the indicator was found in datasource(s) - * @param output - */ - private void writeResultsToFile(Indicator ind, String resultStr, boolean found, BufferedWriter output) { - if (output != null) { - try { - if (found) { - output.write("----------------\r\n" - + "Found indicator:\r\n"); //NON-NLS - } else { - output.write("-----------------------\r\n" - + "Did not find indicator:\r\n"); //NON-NLS - } - if (ind.getTitle() != null) { - output.write("Title: " + ind.getTitle() + "\r\n"); //NON-NLS - } else { - output.write("\r\n"); - } - if (ind.getId() != null) { - output.write("ID: " + ind.getId() + "\r\n"); //NON-NLS - } - - if (ind.getDescription() != null) { - String desc = ind.getDescription().getValue(); - desc = desc.trim(); - output.write("Description: " + desc + "\r\n"); //NON-NLS - } - output.write("\r\nObservable results:\r\n" + resultStr + "\r\n\r\n"); //NON-NLS - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS - } - } - } - - /** - * Write the a header for the current file to the output file. - * - * @param a_fileName - * @param output - */ - private void printFileHeader(String a_fileName, BufferedWriter output) { - if (output != null) { - try { - char[] chars = new char[a_fileName.length() + 8]; - Arrays.fill(chars, '#'); - String header = new String(chars); - output.write("\r\n" + header); - output.write("\r\n"); - output.write("### " + a_fileName + " ###\r\n"); - output.write(header + "\r\n\r\n"); - } catch (IOException ex) { - logger.log(Level.SEVERE, String.format("Error writing to STIX report file %s", reportPath), ex); //NON-NLS - } - - } - - } - - /** - * Use the ID or ID ref to create a key into the observable map. - * - * @param obs - * - * @return - */ - private String makeMapKey(Observable obs) { - QName idQ; - if (obs.getId() != null) { - idQ = obs.getId(); - } else if (obs.getIdref() != null) { - idQ = obs.getIdref(); - } else { - return ""; - } - - return idQ.getLocalPart(); - } - - /** - * Save an observable in the object map. - * - * @param obs - */ - private void saveToObjectMap(Observable obs) { - - if (obs.getObject() != null) { - idToObjectMap.put(makeMapKey(obs), obs.getObject()); - } - } - - /** - * Evaluate an observable composition. Can be called recursively. - * - * @param comp The observable composition object to evaluate - * @param spacing Used to formatting the output - * - * @return The status of the composition - * - * @throws TskCoreException - */ - private ObservableResult evaluateObservableComposition(ObservableCompositionType comp, String spacing) throws TskCoreException { - if (comp.getOperator() == null) { - throw new TskCoreException("No operator found in composition"); //NON-NLS - } - - if (comp.getObservables() != null) { - List obsList = comp.getObservables(); - - // Split based on the type of composition (AND vs OR) - if (comp.getOperator() == OperatorTypeEnum.AND) { - ObservableResult result = new ObservableResult(OperatorTypeEnum.AND, spacing); - for (Observable o : obsList) { - - ObservableResult newResult; // The combined result for the composition - if (o.getObservableComposition() != null) { - newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " "); - if (result == null) { - result = newResult; - } else { - result.addResult(newResult, OperatorTypeEnum.AND); - } - } else { - newResult = evaluateSingleObservable(o, spacing + " "); - if (result == null) { - result = newResult; - } else { - result.addResult(newResult, OperatorTypeEnum.AND); - } - } - - if ((!skipShortCircuit) && !result.isFalse()) { - // For testing purposes (and maybe in general), may not want to short-circuit - return result; - } - } - // At this point, all comparisions should have been true (or indeterminate) - if (result == null) { - // This really shouldn't happen, but if we have an empty composition, - // indeterminate seems like a reasonable result - return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - return result; - - } else { - ObservableResult result = new ObservableResult(OperatorTypeEnum.OR, spacing); - for (Observable o : obsList) { - - ObservableResult newResult;// The combined result for the composition - - if (o.getObservableComposition() != null) { - newResult = evaluateObservableComposition(o.getObservableComposition(), spacing + " "); - if (result == null) { - result = newResult; - } else { - result.addResult(newResult, OperatorTypeEnum.OR); - } - } else { - newResult = evaluateSingleObservable(o, spacing + " "); - if (result == null) { - result = newResult; - } else { - result.addResult(newResult, OperatorTypeEnum.OR); - } - } - - if ((!skipShortCircuit) && result.isTrue()) { - // For testing (and maybe in general), may not want to short-circuit - return result; - } - } - // At this point, all comparisions were false (or indeterminate) - if (result == null) { - // This really shouldn't happen, but if we have an empty composition, - // indeterminate seems like a reasonable result - return new ObservableResult("", "", spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - return result; - } - } else { - throw new TskCoreException("No observables found in list"); //NON-NLS - } - } - - /** - * Evaluate one observable and return the result. This is at the end of the - * observable composition tree and will not be called recursively. - * - * @param obs The observable object to evaluate - * @param spacing For formatting the output - * - * @return The status of the observable - * - * @throws TskCoreException - */ - private ObservableResult evaluateSingleObservable(Observable obs, String spacing) throws TskCoreException { - - // If we've already calculated this one, return the saved value - if (idToResult.containsKey(makeMapKey(obs))) { - return idToResult.get(makeMapKey(obs)); - } - - if (obs.getIdref() == null) { - - // We should have the object data right here (as opposed to elsewhere in the STIX file). - // Save it to the map. - if (obs.getId() != null) { - saveToObjectMap(obs); - } - - if (obs.getObject() != null) { - - ObservableResult result = evaluateObject(obs.getObject(), spacing, makeMapKey(obs)); - idToResult.put(makeMapKey(obs), result); - return result; - } - } - - if (idToObjectMap.containsKey(makeMapKey(obs))) { - ObservableResult result = evaluateObject(idToObjectMap.get(makeMapKey(obs)), spacing, makeMapKey(obs)); - idToResult.put(makeMapKey(obs), result); - return result; - } - - throw new TskCoreException("Error loading/finding object for observable " + obs.getIdref()); //NON-NLS - } - - /** - * Evaluate a STIX object. - * - * - * @param obj The object to evaluate against the datasource(s) - * @param spacing For formatting the output - * @param id - * - * @return - */ - private ObservableResult evaluateObject(ObjectType obj, String spacing, String id) { - - EvaluatableObject evalObj; - - if (obj.getProperties() instanceof FileObjectType) { - evalObj = new EvalFileObj((FileObjectType) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof Address) { - evalObj = new EvalAddressObj((Address) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof URIObjectType) { - evalObj = new EvalURIObj((URIObjectType) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof EmailMessage) { - evalObj = new EvalEmailObj((EmailMessage) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof WindowsNetworkShare) { - evalObj = new EvalNetworkShareObj((WindowsNetworkShare) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof AccountObjectType) { - evalObj = new EvalAccountObj((AccountObjectType) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof SystemObjectType) { - evalObj = new EvalSystemObj((SystemObjectType) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof URLHistory) { - evalObj = new EvalURLHistoryObj((URLHistory) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof DomainName) { - evalObj = new EvalDomainObj((DomainName) obj.getProperties(), id, spacing); - } else if (obj.getProperties() instanceof WindowsRegistryKey) { - evalObj = new EvalRegistryObj((WindowsRegistryKey) obj.getProperties(), id, spacing, registryFileData); - } else { - // Try to get the object type as a string - String type = obj.getProperties().toString(); - type = type.substring(0, type.indexOf("@")); - if ((type.lastIndexOf(".") + 1) < type.length()) { - type = type.substring(type.lastIndexOf(".") + 1); - } - return new ObservableResult(id, type + " not supported", //NON-NLS - spacing, ObservableResult.ObservableState.INDETERMINATE, null); - } - - // Evalutate the object - return evalObj.evaluate(); - } - - @Override - public String getName() { - String name = NbBundle.getMessage(this.getClass(), "STIXReportModule.getName.text"); - return name; - } - - @Override - public String getRelativeFilePath() { - return "stix.txt"; //NON-NLS - } - - @Override - public String getDescription() { - String desc = NbBundle.getMessage(this.getClass(), "STIXReportModule.getDesc.text"); - return desc; - } - - @Override - public JPanel getConfigurationPanel() { - initializePanel(); - return configPanel; - } - - private void initializePanel() { - if (configPanel == null) { - configPanel = new STIXReportModuleConfigPanel(); - } - } - - /** - * Get default configuration for this report module. - * - * @return Object which contains default report module settings. - */ - @Override - public ReportModuleSettings getDefaultConfiguration() { - return new STIXReportModuleSettings(); - } - - /** - * Get current configuration for this report module. - * - * @return Object which contains current report module settings. - */ - @Override - public ReportModuleSettings getConfiguration() { - initializePanel(); - return configPanel.getConfiguration(); - } - - /** - * Set report module configuration. - * - * @param settings Object which contains report module settings. - */ - @Override - public void setConfiguration(ReportModuleSettings settings) { - initializePanel(); - if (settings == null || settings instanceof NoReportModuleSettings) { - configPanel.setConfiguration((STIXReportModuleSettings) getDefaultConfiguration()); - return; - } - - if (settings instanceof STIXReportModuleSettings) { - configPanel.setConfiguration((STIXReportModuleSettings) settings); - return; - } - - throw new IllegalArgumentException("Expected settings argument to be an instance of STIXReportModuleSettings"); - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.form b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.form deleted file mode 100644 index d65bb2be2e..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.form +++ /dev/null @@ -1,101 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java deleted file mode 100644 index e32d3a0d0d..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleConfigPanel.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-2021 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.report.modules.stix; - -import java.io.File; -import javax.swing.JFileChooser; -import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; - -/** - * Configuration panel for STIX report generation. - */ -@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public class STIXReportModuleConfigPanel extends javax.swing.JPanel { - - String stixFile = null; - boolean showAllResults; - private final JFileChooserFactory chooserHelper; - - /** - * Creates new form STIXReportModuleConfigPanel - */ - public STIXReportModuleConfigPanel() { - initComponents(); - showAllResults = false; - jCheckBox1.setSelected(false); - chooserHelper = new JFileChooserFactory(); - } - - void setConfiguration(STIXReportModuleSettings settings) { - jStixFileTextField.setText(settings.getStixFile()); - showAllResults = settings.isShowAllResults(); - jCheckBox1.setSelected(settings.isShowAllResults()); - } - - STIXReportModuleSettings getConfiguration() { - return new STIXReportModuleSettings(jStixFileTextField.getText(), jCheckBox1.isSelected()); - } - - String getStixFile() { - return stixFile; - } - - boolean getShowAllResults() { - return showAllResults; - } - - /** - * 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() { - - jLabel2 = new javax.swing.JLabel(); - jStixFileTextField = new javax.swing.JTextField(); - jButton1 = new javax.swing.JButton(); - jCheckBox1 = new javax.swing.JCheckBox(); - - jLabel2.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jLabel2.text")); // NOI18N - - jStixFileTextField.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jStixFileTextField.text")); // NOI18N - jStixFileTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jStixFileTextFieldActionPerformed(evt); - } - }); - jStixFileTextField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - jStixFileTextFieldKeyReleased(evt); - } - public void keyTyped(java.awt.event.KeyEvent evt) { - jStixFileTextFieldKeyTyped(evt); - } - }); - - jButton1.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jButton1.text")); // NOI18N - jButton1.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButton1ActionPerformed(evt); - } - }); - - jCheckBox1.setText(org.openide.util.NbBundle.getMessage(STIXReportModuleConfigPanel.class, "STIXReportModuleConfigPanel.jCheckBox1.text")); // NOI18N - jCheckBox1.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jCheckBox1ActionPerformed(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel2) - .addGroup(layout.createSequentialGroup() - .addComponent(jStixFileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 292, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jButton1)) - .addComponent(jCheckBox1)) - .addContainerGap(73, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jStixFileTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jButton1)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jCheckBox1) - .addContainerGap(225, Short.MAX_VALUE)) - ); - }// //GEN-END:initComponents - - private void jStixFileTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jStixFileTextFieldActionPerformed - - }//GEN-LAST:event_jStixFileTextFieldActionPerformed - - private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed - - JFileChooser fileChooser = chooserHelper.getChooser(); - fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - - File currentSelection = new File(jStixFileTextField.getText()); - if (currentSelection.exists()) { - fileChooser.setCurrentDirectory(currentSelection); - } - - int result = fileChooser.showOpenDialog(this); - - if (result == JFileChooser.APPROVE_OPTION) { - stixFile = fileChooser.getSelectedFile().getAbsolutePath(); - jStixFileTextField.setText(stixFile); - } - - }//GEN-LAST:event_jButton1ActionPerformed - - private void jStixFileTextFieldKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jStixFileTextFieldKeyTyped - - }//GEN-LAST:event_jStixFileTextFieldKeyTyped - - private void jStixFileTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jStixFileTextFieldKeyReleased - stixFile = jStixFileTextField.getText(); - }//GEN-LAST:event_jStixFileTextFieldKeyReleased - - private void jCheckBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jCheckBox1ActionPerformed - showAllResults = jCheckBox1.isSelected(); - }//GEN-LAST:event_jCheckBox1ActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton jButton1; - private javax.swing.JCheckBox jCheckBox1; - private javax.swing.JLabel jLabel2; - private javax.swing.JTextField jStixFileTextField; - // End of variables declaration//GEN-END:variables -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleSettings.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleSettings.java deleted file mode 100755 index 25ade99380..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/STIXReportModuleSettings.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.report.modules.stix; - -import org.sleuthkit.autopsy.report.ReportModuleSettings; - -/** - * Settings for the STIX report module. - */ -class STIXReportModuleSettings implements ReportModuleSettings { - - private static final long serialVersionUID = 1L; - - private final String stixFile; - private final boolean showAllResults; - - STIXReportModuleSettings() { - stixFile = null; - showAllResults = false; - } - - STIXReportModuleSettings(String stixFile, boolean showAllResults) { - this.stixFile = stixFile; - this.showAllResults = showAllResults; - } - - @Override - public long getVersionNumber() { - return serialVersionUID; - } - - /** - * @return the stixFile - */ - String getStixFile() { - return stixFile; - } - - /** - * @return the showAllResults - */ - boolean isShowAllResults() { - return showAllResults; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/stix/StixArtifactData.java b/Core/src/org/sleuthkit/autopsy/report/modules/stix/StixArtifactData.java deleted file mode 100644 index 3d354663b0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/stix/StixArtifactData.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.report.modules.stix; - -import java.util.Arrays; -import java.util.Collection; -import java.util.logging.Level; -import org.apache.commons.lang3.StringUtils; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT; -import org.sleuthkit.datamodel.BlackboardAttribute; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; -import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE; -import org.sleuthkit.datamodel.Score; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - */ -class StixArtifactData { - private static final String MODULE_NAME = "Stix"; - - private AbstractFile file; - private final String observableId; - private final String objType; - private static final Logger logger = Logger.getLogger(StixArtifactData.class.getName()); - - StixArtifactData(AbstractFile a_file, String a_observableId, String a_objType) { - file = a_file; - observableId = a_observableId; - objType = a_objType; - } - - StixArtifactData(long a_objId, String a_observableId, String a_objType) { - try { - Case case1 = Case.getCurrentCaseThrows(); - SleuthkitCase sleuthkitCase = case1.getSleuthkitCase(); - file = sleuthkitCase.getAbstractFileById(a_objId); - } catch (TskCoreException | NoCurrentCaseException ex) { - file = null; - } - observableId = a_observableId; - objType = a_objType; - } - - @Messages({"StixArtifactData.indexError.message=Failed to index STIX interesting file hit artifact for keyword search.", - "StixArtifactData.noOpenCase.errMsg=No open case available."}) - void createArtifact(String a_title) throws TskCoreException { - Blackboard blackboard; - try { - blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - - String setName = "STIX Indicator - " + StringUtils.defaultIfBlank(a_title, "(no title)"); //NON-NLS - - Collection attributes = Arrays.asList( - new BlackboardAttribute(TSK_SET_NAME, MODULE_NAME, setName), - new BlackboardAttribute(TSK_TITLE, MODULE_NAME, observableId), - new BlackboardAttribute(TSK_CATEGORY, MODULE_NAME, objType)); - - // Create artifact if it doesn't already exist. - if (!blackboard.artifactExists(file, TSK_INTERESTING_FILE_HIT, attributes)) { - BlackboardArtifact bba = file.newAnalysisResult( - BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE, - null, setName, null, - attributes) - .getAnalysisResult(); - - try { - /* - * post the artifact which will index the artifact for keyword - * search, and fire an event to notify UI of this new artifact - */ - blackboard.postArtifact(bba, MODULE_NAME); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS - } - } - } -} diff --git a/docs/doxygen-user/images/reports_case.png b/docs/doxygen-user/images/reports_case.png index 8e128f51e786148fb0d6b31ddc6fffa84284186e..c0585ea66284cdd086e0ff1520a5abec596b5a96 100644 GIT binary patch delta 12576 zcmZX31ymf-66FMf1SdEI2qCz;LvVL@hv4qgxVyW%ySsaEmn66c4>GWm|9AJSoat$) z>3Q#UP2F3!YJN_>TfTl5M+#uUc&cbPD;c_dws*8Mv#>V(?CfE0`q|Xo!VCm*U$4&8 zbS34B3EpXs(1W6|*FYI{vV`k?dF_j{9#6?So_N)5Ng{!T19d?Qn=Ze-nrFWK>cy01 zQgoDLBbx;3M@|UrBD!BI4{IB*UijWz%)#d;3ob0b&#MpF{@~Z3Is%<~y1O0T+xEnR zcZa7XoBUV%%X8;}UZH~G%Un~Vl8#&YE4p1SDl2#7O9{Ct_H=zQ+Jh{~8+KzW*B9AG zz0RD_We1%R~dq{`Z)Nt$-P~r**xmrWg$4?u`4q0V++?8wafglM^s}E zZU#dw< z$%wFSQBP!eIo}fDy9yD_*o5a!)&Akn&7F|1pGHYwIj_sk{Z(;eVoHFs7sa8m*R(K~1cM5D ze+f*<6Tcpep7F7Y$OTHCY$Y39*0xz4+%i;4gl)8Tjeqz9K1}g_R?tYI#!JFD7=X$= zSI5!aG`9&(Jp9J<>uw!uX3yi)4~?mX5pRN@J5vWOHEVj?{4mUzL@+x28O?_RT5d?5 z{MZI6sgpj^j*DIkGB?R8nD9a}oEC~BLt`wzWK~D7%H?)m2Em@kMha&)KH7$>1RrQUtJW&t=SR`yO**Sr6RC~;*eZvmJ@3l_s;sbssHn%IG52L8g;_l(v&hIfs{>Z^ z7EktIcXFx>0+wvED9n-lF6)Q*&L2}?)$ORH-igdN<%6h_i#JlIkVTt(uqAqnT^@?eSmhV2_T_Y2%)$>tFOn;M@*&7A_R?F^b?b9dhYg07K zbRF)?qIEO~tv9UG`PSQk#W_&(@oZD^JDXN|PM>Rl#1!q;aOg0=4V@x~0*Xh@-I!;nxo?Z3={q zrZB1{cfv)^s7XUu&e4r_eRb8$%Ek-p+o4-~E;#uVbzY@N9}Z>x8=CkJY8L3`2YMaP zd03eN7e{A)3JIF@y8N)|YwrB*`U?aX&4Hbhp zK=eWht>J8IesX<$Y0oOLfq4K?7EWVbaukCwzM)7@HkAKRN4g)FAz}#-3Su~tD zK!{Ger0zi$et`a^HAGh|<3ed{!pW5+2GW2flujqH=|Zp`Y8Zst_+V1qt=>kjE-m;` z8cj>EF%BgayB{hve4Ur zVAT56=|WE_!Wk2O@8X+{Zcm)-S6WR%tk=w#{ldeHZ<7^{*jB5?hmz4aN4Z*M$cqE`8UQ)wklYqoZ2!0&tqR+OV*&LfhG68SFpRE?aAc~{H8R^!gT zo?L7`)mawP*!~iY_AJ|{`34@J93p=L_w&Yu1&WXt)7o!ar@nUG8E(qpbD8PR@>kcA zgDn++NBPFI!b8O~PMZhMHg<2uyKEPBTh2A*wUg`PCaoh1LF6d_)7YOzwGuF3PAVwb zu%^IM&i+jVA0*r=k=wMa12Tq5^e<|UyBr_8w=COjppN+Vdo~Cq-Aq(V*IV!k=(*9An6lmD2)Fo z=*YpJy$YfAu9*h2=~RpMMe@Zllv7j!?!%8tOM|t(S}Q{!%K2o!sONZx`AXOzi87nz zJZP@(EoNRvOyp+-GZJEvfr3#6iUI9}O83c-j$hR{w_(ONxKEnreZel?2Yq^@Vo?rW z;~I{Nj{itWGTXxso|WySYmX= z;~$PAUmPwmlsBv3OITkG!AB@m%~uTplb}>(Sr5trZPGux`^;!;CAxp7vpR8$xWrw` zT%i%<4%j(u;I!SlwpTmPLm0lmqnp<_1k@boDT4VeJdkXtokm{^BPm3i8 zoE7)o8eiY5USpwnJtPoXWx6h}d^E-PS9t>+)3>$C=9VQzS$5ha zZ5(fRpb4K{^TdE7uUaF}j;z0amJ({P-am}jv&h_l0#$j}Packmwqq_0w?|C@%la1? z_3wj%UqkeMtHirLM`ioUC%X(99Cv@NOCzZl9D`!v2I%-Ypu;{JXm0g`$Ca4F>k`)ez|-Pd zNg+d&+Z50Xu^N=R`B3Nyo}af+{D3Z3W@QODa=?_zP<4`C?@X)9TstmqXA5)9Pw&(w zxL5@+bhQ{V7;d|oID^53)!3R;BKxUk&nw0u8zQ{f=vXa14)(VEN> z7_JNnuP;P$5_(ZMAT-1vG-T?lf-i8Y2-L6oq$(qbF0^IWTKp5X5N-5#_os1J<9y-%7 zASLpmPc@g?1R+#Z9IW8gC8+CKq@0~uZENw9Qct7rcn-ID#An`UjsiJA40)=hl2-i> z4$dR-xF(6a`S{`YbtFu_u+P;YtTFg&6c&^dMPC&whou_WNR$x>MZ;7?Y?ZC8fP&qQ z98-nVGLsfQEpi30cvJx9*8;0o!M8%8IM1ZNGp9l%VZQG{@ZcE9O#1$v;HYvoDycTF zf<}velwkcBCsJ{#9X=(K_!UMwS^H%%u1jOiIMgNec|@6X*7h@&cHzra%lM_Yob$t^9b#fGb#H#%efp zP>!NiwbF>QlAU{DV}O(to_@7`@dbofhzEl|j}F$2 z_}*WRu=(oqfnPn3>n4E)_%0xG7E3UGv<_ZqqLx$1XXG%A%u7J2fXiNXO|!J)YiUAM z2F2jI_>y;e;{pm|zI~GP8(83VJC^;?dW3#jIEKoto<#>xB3>gznb2^We{IPm zqlf>992y@a{7v4aBa~wb!vPeJdFfZJ7z_{N7j{Q@ph2`LDtK}Qz$6!;rmm`0+$qL1 zx(8Y+X_O?)cEXF&eZTZNBb5t_u*F0`yX80In@pFOV&^NW`5WnJxbuD+ACW5mCOJFc zq%8?+Dqaez@{~}C55w^CHb-OnTbPWqXdmx!p&u*HG&%mqli+?#tFZ|Ynv&q-4|JjV za7v)MyQ*koR5Xp=y zGb^IvRKSspbjMqE8i-sM*w$D3vwbjj7HYRoC8kxm552dp2GDv~{&W>pVyzXd!)rTS zVebB#dS+~?T3T(zq1DZ{{bpl}X_T)K!wQE2w#79dLWo-U=|lXeh$G0{KFN>nIZ@NpB^4AxM5-bJPftAo;$FKL z6OHONBs74kjz3t%bTvQoH&yXVeL>p{ClKP%dJls#186G_q2RJOPKTMeH?~JXiEto=D9oT5 z!yCqzBPU6>v$aLiY(sch>9*q`z@061w&^eoq_S{Bf#>(Nd@KnH*6UOh?HK-tU^XaN zm;7ss9l&wqa&wULgNNA+hiRKCTR7qK0@vol;a^QuImNJU+$Q7-<`ywiGw0jN>$8=E zS*IEl#M9%0tvKgz5eQevsdOl$!K{OIbq?3ZY5mprOEwFg^pm-?t6pFoV8-K(DrCP8ZeRG#nU8Hokumh`Z0 zb6&E6H-RM*Gnv9XrxU~GJWa?x3Llp3K&it*@dh`iOAcXhMrmhL&AZ7TjihI}IWfL1 zZ@{|e`9hNgrd|^Zu<=K<^ES=cDPeQYwQV`$_Ulvu)d$j|7tXuqsooeT{W1@|AkFZUgeFSjF0xezNxM=LcGit`q zmhP!ja5GuRaBZ^Mz+iN{>{k5f)x3qfn71TZG1t&+SINnT+-3Uhk@E$9BY%5^(3`E*a zJN3T#v>ucm^KbP~5|uxhN}jYBKTyxp4TmHp?o~ZC|6=4xMA#OM6@E2?U9RNbV7?-E zm)i3eOi-hK;ic5BG?w(jrv zt$1WRbn^|@!7p)vh4!$7XlwF50H`~`VLl>N&ssQ7SbL!PP+K!YcWLnaSjhQET> z1CnYeLOeqRtWLPQ^CK*eBwd6AKq3i>o#C+Vgpd4nkL=9D!nf!fvWLfi)`79r{2kuG z&e>TCf#g4Vg;^~c}}=bt0<6rNaP&@4I{x? z-{)yUe7IEYaq-}c_yTv(k1_&%dh?n3dOwPohaHuV1&;1bbKnH?nlcw-sg_Asd5Ixn z2!}9}2HTW^uSm$DphJy!$Om!Ir$HY5?|ePNy`5p(w5s%ZMwRah&~yC6f^a|tD?WEr zD8@JT8m87&;3sLQdUa?f^pdM}*#YQ#<}Yt% zdpOXC{aEw8GYb`?eSD+S+5LB&vYSp9zEN7-;s^GQ}8ki zID@0|Z9a-A%eR!|2*b4CZ}sV(=So$WDorDm7u%=ihPp+?wcE4oTG5{X!NrO(zD9$FZ|TB$5se zm^z3kz$>j9p-z^Ii#^u+equBZ4hm5ms;kbT*)JnJF<$PMUiue3&_SN*t|zJ_%o>J3>$!W!*%!l9|34}tx5fIXqMcf z6@4wOJ$QH5k3S233k65D3;tT3iJ5_V1O~U78qALWHpY0SlE7 z-522^r(QBu6!1CpAOd{)55#w4ZNE!cP%C~9DssCK7F0O2WMTOj)~jLlLxtUC?)Yr^ zg4Qz~y_=~qty1b-HZ40{HO(YQMs_Jgweq6DyyX)cJ{raN{s??XczElSKkN6BE~bUg zbS7xQ7nk5UziEbd=rGTZWtNk#-n~0N9yW-dq5 zn4w4E;m!a?Ey2^vYyaWdv{~!!vE9kBB2Mc#ED{yE;nU5xRD;ONP6|NX}RdJrfr1xd2R zB7^&?ox`0W$U8q*$>~K1$@JLzqn%!T7}_ykU%a z@=K&?%1jPlC3&p)VwKkjIr#0_2BhUZFQ= zNxuy*wm|ZD+q{kI)8>3}TQMXiCPvM`p`NU&+OfPiNThoANYbE#kcH`sYJ0B#2@+33MUBN~*&Xz6R6P-SDH({p zqN*Zc=aRTRvC3$vxpngJrW~Bt#8mrI12Z3fowPsEqsYDL*2SMI59i`M&`$sY`Js8C zvazyT_6NX7NdqFKGu{@2L1) zz33^KM5;sy5vNi~7|eH{OPf@zC)mXjBDU8=2mzI#srC?r$owDUjI+94XBafH#(Cs=Imx%FFU} za{ShC6e?BI=E}0cSid@uxm6g8zHI803pWGw#l=zHXQbyVw@KxBV{8iTd`FflH}3Z^ zAkeq4Z0>3s8!0I%rRSx}3=X##qXN1N44gubMq@S#dbRKv_q+S2jIME zUZ1FY628h8o~Y(W`-~d?pj5Isy7twykjfeJO~jcjMzCqZsaR(XRNY9q+1>cBBP~U> zX&XkwtmJg64rW@Y>1QgAj=qbEi__50a6fr#Sj=W==r5&6>XP-{CQ&i$#<4HnsdGgp zvu#HU#||DQ{!r)szV4?qzgGe96-^vmU9K=Xs+`=Ecl%_=te^K=p!*2Eq&!1l_IXGy z`|n?Ok8VA#k7o$C_mu49Qi_U-4H~qNjd|MdU@E7YM!klTpWpX|u(W7gRlDdl9W1H$&HDFR1vQ6k*AC_+ zLk5Hc&DLi#vX&O$d!DZ5a7AU;+Rh*FT`kMsr}`N>)e7Yr=Ck`uxS#x#JHJ2S(Qo_# zb1xK`;x!ustXGT&Ujyk)9d%pN_u{#k{ll&S21`8 z0x@EPg{m;C#yWK9+jZ!Dg&-)!B@ZA0B}$tz06`l6m~N$He`V$g>eyozxCdOY;l-M@>J#%m0Dgc9wryJs+j8ql?PS{gMqwfd9wjs2TP70ua#fhgg1 z1<1c%+rA1lBPoXOnC$@L?ugTM)eM%R3A}lAFWveRK!+dA`?4+02ga`0)ALGARn;b* zCE$bx5~fXv^$-Hpsx$DPpC_IxXURQTzax;6kcb=n6}Lk?YkufkDNf|0f{0D4AZ)z* z-ph@Ou2|sDL0J>);X%QHnxSMj%8*uC=_Qw}4>Y7D5Wu zUl**EG%u#9Yb?~PGU?P!bLt2Gd>iIu)CzX@219-+G^5gQ(oT*4=+m;NtgUS=gU7t4 zfEIhYW#q*FYv@Oe-*@||`V$cPO*uGlc(B6d(mJe}73SQ zm}Fa}ekBc5nq(|dPf;eE8q`^8sq5562UCPLx2|B(NGBK;RTz|NV~NYdKJ$90U|nPn zKHxRf`rGrG;0xLlN3U}!5N_uQ!zgPI!u}(Xn40ElSxHGr%U(~_n}ZoenI=)E-$tx< zu2ly^!_;%=wcC2V2?+^{>&xur%rNA)Iz3D-)t zURg+_=3_mgrr7_&^D_DN)|rssQKX)U>3>mTZO>C7ts=IjnII-8X2Jsg4lIt>*@T{{j$YFg6&kh$)jgH!}NG%>fBoAy+EGFkccKh7b;`o z2Z?j&NjnD}2pED^J1Lo&l|0(pA_ z_^0YeNB3=rr#JOU)DYB6bm>!d=y3!{Aaf&O^rL|iuhcOTWz-Q9FVwOBucNXl1*Gjr z0(oO6#%N$A$||BK@+p!ePOC|XKGbSHI37Z@_a6+<)7f@+rXWNbX8nKq9GC1LF{}PR z*&+4#J;a>mk@!u5{C|!Z@qfhlDt)Ky`N;(a%D>KI3LPCi zB0Od}!c-bBS2E^@+xFAWQvqq-wjUn9A<4k(pcCosQ{0OA8Bwk8I&Lk!XmZzN8+E1SmsRVpoL#;zNRA z9zr#<2?CQ%U7c)5$~=uUI<+|80ko>SIOQh$n73^GOL8rzD{?0=;AJG=SbV(2h+Dfo zpN_jNGxpl9I)l*wM9_mgXsc12jEoGr)h%=C`St2TV9QfnkFxY8vdTircOh%By7kzl zI-8&p4tA@h;^gI2EAysf1`evwj^pI|=auaJs#vG&o;Xvvg2(U9hBa#ryB$8JW+!0;nrUwwT`2lB)A|sQyuP?%){?XAzsqLnf}(lo zn~+xX_h!_E`woWA4Of|SySjo)fpiqnD#*ihe0{>8<=LtEl`@U(Mdh(>@~0l=x{KSSyfdu%9{kvpT3JF~wdztAgB_}7{E
krlxn1}O{l?;)yjwEQBj&+RkvbWt7yW6r>+h-^yUVKed3%;$l ze6R&16G~t3OuHRR?9NS}1kag#v;|f&s^@bBo+9+taC90TZEEkUm`yS$nn|y??%1qu zHuTQrUfY*Vx(iqWpV~a89*mI)uQpHhm(>cvqZ^pDuxu@`xfB+strj*XQ|^VbVK9Ch zv$q#Sgtiik6Vt)#{90wVIrFdcvBCot$_c=pMvr9hr0{@6wD_8pbp@Bb#sq_k;SOTh znx_`rtFDrEbU>tDn|bo6gHw&7uWS93?dh6qcoh_l-dH1<-&IHff2nO3_4TV!iIWZeq9e!i^exf$mzCzOm{zX}!jo@_W~ z?W=6n+EbpzaS@0{aNTKUz)MIY#`lm_Dcss!5HuZ3?TqVVml8|WZ9jecrYj6_b+}q& zz>?BFj^*<@lka#eWA^8}U~96bB_tKGfO2-hYMW~A+GMTsPp`k5?DXsAGXZYl5)#fv z^^;u){!W4XuK5xg%h~w(kvIpLMEQildYwx3ih<92o%efq{@-Sf2t zd-aj>QjX7;(K+LMu^lfb zfmI^L#gsM;)Z=GIiuWp>K)s0mvz=GKMoiPRI$wql+(;3{1ar+Dq?NWbH^+3vbbO|E znGm{nbfho}KWf$T(wH;tA)9;8LF*xr>ymMz+aR6E!m~JMIOfb-MAp4^!t18-w%jQ# zaBPKY3&rRbw zuv_-A)XJ!(O`?){pPTAGX84j{UJJDf3Ey+BEY4SfOA1cROB)3*?dX_Gp4#C|#O86s zP4D{l;Yj7z-#6UeQ%)jUHWXYk>Wn_guJX*30igxNcZ6K#mt=A^NmYA zvSm9t+hq%23HZSZeI4peI5&OYKN;F0y|ehhpf@*Z*8J*_ue)(eckT<+%ixh(v$zu{ zdTj2x@HiPwhbAW9AM1fD_O(47=6>$7F?1Cl=J3}nQQ+UzMn?SvXM>3+5~g|{tH4-4 z^UuM?m1LsaS1sC+LOYx~?$QE@T;I2zC*TirAP6f3E(1eHmOg-fvKQ5C)eHy zU%RQv7hr=0E?X8-h0p8Tu4+lMupBIVL2 zBJF_R-J}BS{|It${>kPP3pi<$YYmAiY?bX%6gEiQ{+DA$i6vr@ZxT=f*n`3k;0Z#oN={tD|KBIe-I)y5baFEz3BrT2}l`KB8k?W%-pHTwkt6XA1IjbLz{Qhtu<3M=* zW$^O!(R@CceRkUJ4)qd1wp~cvS-B!cxiDrHSg`OQigCU`&cNV#d03Qb4~Rhj&?=TYT?9aEEZC5 zLlH`f2&zPJ^*)udRQDjN|Hb|B$fhzqOwj+Gdv8|nG1 z(6*c@Lgp$Ki@FlteF|R>5x5F|eU7JB9|qBQFz{mb*PFs2%ITZ?dsdj<5VlXm5AHIj zsNAw7)b&9c04PlfQsq>hW*8Y8JAb z&I0DCMi`GCX;48#B&g@Lc^@*x=;847!_qQgzJ10W9tjl*d0s0*57R-1*TY5f54uBT zd3pKOsMukn_-V6+0QCBsHfi*#DLuwng4g#d9R#QnZlU%|1gM@UKS?*?~wPl_I2J0BJoMlw^`g t#^!VnNkA+HBaE!SgPS2|e#fV8&@tUl1yt|uLm}57Nl`hG8X<##{{iNJ_M`v+ delta 7630 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 literal 24983 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 b-{%m#3%#&t{UHMQ%K}l;zFT<5`uTqWQf2tR diff --git a/docs/doxygen-user/images/reports_select.png b/docs/doxygen-user/images/reports_select.png index 97e440adeb26758c799a187952b2964d56b7f8c9..21fbc0dce92be14521079595633cfef36188b6f6 100644 GIT binary patch literal 24890 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 WyrYlZryxfaho-vjFQwP+KK(Cyo1eV^ literal 21175 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 Thm67E00000NkvXXu0mjfx+7^K diff --git a/docs/doxygen-user/reporting.dox b/docs/doxygen-user/reporting.dox index dbdf63a4c8..57f996108e 100644 --- a/docs/doxygen-user/reporting.dox +++ b/docs/doxygen-user/reporting.dox @@ -101,11 +101,6 @@ This report module generates a KML file from any GPS data in the case. This file This report module generates a new Autopsy case that includes tagged and/or interesting items. See the \ref portable_case_page page for additional information. -\subsection report_stix STIX - -The STIX module allows you to generate a report and Interesting File artifacts by running a STIX file (or files) against the data sources in the case. -For more information see the \ref stix_page page. - \subsection report_body_file TSK Body File This module generates a TSK Body File from the files in your case, which looks similar to the following: diff --git a/docs/doxygen-user/stix.dox b/docs/doxygen-user/stix.dox deleted file mode 100644 index ef91e6e180..0000000000 --- a/docs/doxygen-user/stix.dox +++ /dev/null @@ -1,104 +0,0 @@ -/*! \page stix_page STIX - -[TOC] - - -Overview -======== -This document outlines the use of the STIX feature of Autopsy. This feature allows one or more Structured Threat Information Exchange (STIX) files to be run against a data source, reporting which indicators were found in the data source. More information about STIX can be found at https://stix.mitre.org/. -This document assumes basic familiarity with Autopsy. - -Quick Start -=========== --# Create a case as normal and add a disk image (or folder of files) as a data source. To get the most out of the STIX module, ensure that the following ingest modules are selected: - - Recent Activity - - Hash Lookup (Check box to calculate MD5 hashes even with no database selected) - - File Type Identification - - Keyword Search (URL, IP, and Email addresses) - - Email Parser - - Extension Mismatch Detector --# After the image has been added and ingest is complete, click the Report button then select STIX. Next choose either a single STIX file or a directory of STIX files to run against the image. It is possible to do this while ingest is running but the results will be incomplete. --# Once the STIX report module is complete, there will be two sets of results: - - Entries will be created under Interesting Items in the Autopsy tree, under a subheading for each indicator. - - A log of which indicators/observables were found is generated by the report module (Follow the link on the Report Generation Progress window) - - -Supported CybOX Objects -======================= - -- Address Object - - Address_Value - -- Domain Name Object - - Value - -- Email Message Object - - To - - CC - - From - - Subject - -- File Object - - Size_In_Bytes - - File_Name - - File_Path - - File_Extension - - Modified_Time - - Accessed_Time - - Created_Time - - Hashes (MD5 only) - - File_Format - - is_masqueraded - -- URI Object - - Value - -- URL History Object - - Browser_Information (Name) - - URL - - Hostname - - Referrer_URL - - Page_Title - - User_Profile_Name - -- User Account Object - - Home_Directory - - Username - -- Win Executable File Object - - Time_Date_Stamp - -- Windows Network Share Object - - Local_Path - - Netname - -- Win Registry Key Object - - Key (Required) - - Hive - - Values - -- System Object - - Hostname - - Processor_Architecture - -- Win System Object - - Product_ID - - Product_Name - - Registered_Owner - - Registered_Organization - - Windows_System_Directory - - Windows_Temp_Directory - -- Win User Account Object - - SID - -See http://cybox.mitre.org for more information on CybOX Objects. - -Limitations -=========== -- As shown in the list above, not all CybOX objects/fields are currently supported. When an unsupported object/field is found in an observable, its status is set to "indeterminate" instead of true or false. These indeterminate fields will not change the result of the observable composition (i.e., if the rest is true, the overall result will stay as true). -- Not all ConditionTypeEnum values are supported. It varies by field, but generally on String fields the following work: EQUALS, DOES_NOT_EQUAL, CONTAINS, DOES_NOT_CONTAIN, STARTS_WITH, ENDS_WITH. If a condition type is not supported there will be a warning in the log file. -- Related objects are not processed - - -*/ From 66632bcb52d518e81bde44cb73882fc34bd2be76 Mon Sep 17 00:00:00 2001 From: apriestman Date: Tue, 21 Sep 2021 12:49:27 -0400 Subject: [PATCH 38/51] Fix doxygen warnings. Remove missed stix reference. --- .../datamodel/CorrelationAttributeUtil.java | 15 +++++++++------ .../eventlisteners/IngestEventsListener.java | 7 +++++++ .../autopsy/datamodel/BlackboardArtifactNode.java | 6 +++--- .../autopsy/datamodel/DisplayableItemNode.java | 1 - .../datasourcesummary/datamodel/TypesSummary.java | 2 +- .../autopsy/discovery/search/DomainSearch.java | 1 + docs/doxygen-user/main.dox | 1 - 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index c8dfa81f7c..e08bed19c0 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -133,7 +133,7 @@ public class CorrelationAttributeUtil { * whether receiving a null return value is an error or not, plus null * checking is easy to forget, while catching exceptions is enforced. * - * @param Content A Content object. + * @param content A Content object. * * @return A list, possibly empty, of correlation attribute instances for * the content. @@ -298,6 +298,7 @@ public class CorrelationAttributeUtil { * * @param corrAttrInstances Correlation attributes will be added to this. * @param artifact An artifact with a phone number attribute. + * @param attributes List of attributes. * * @throws TskCoreException If there is an error * querying the case @@ -393,11 +394,12 @@ public class CorrelationAttributeUtil { * * @param corrAttrInstances A list of correlation attribute instances. * @param artifact An artifact. - * @param artAttrType The type of the atrribute of the artifact that - * is to be made into a correlatin attribute + * @param artAttrType The type of the attribute of the artifact that + * is to be made into a correlation attribute * instance. * @param typeId The type ID for the desired correlation * attribute instance. + * @param attributes List of attributes. * @param sourceContent The source content object. * @param dataSource The data source content object. * @@ -427,11 +429,12 @@ public class CorrelationAttributeUtil { * * @param corrAttrInstances A list of correlation attribute instances. * @param artifact An artifact. - * @param artAttrType The type of the atrribute of the artifact that - * is to be made into a correlatin attribute + * @param artAttrType The type of the attribute of the artifact that + * is to be made into a correlation attribute * instance. * @param typeId The type ID for the desired correlation * attribute instance. + * @param attributes List of attributes. * * @throws CentralRepoException If there is an error querying the central * repository. @@ -555,7 +558,7 @@ public class CorrelationAttributeUtil { * account. Checks address if it is null, or one of the ones always present * on a windows system and thus not unique. * - * @param osAccoun The OS account. + * @param osAccount The OS account. * @param dataSource The data source content object. * * @return The correlation attribute instance or null, if an error occurred. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index cd9e03c9aa..270f436a71 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -229,6 +229,8 @@ public class IngestEventsListener { * @param originalArtifact Original artifact that we want to flag * @param caseDisplayNames List of case names artifact was previously seen * in + * @param aType The correlation type. + * @param value The correlation value. */ @NbBundle.Messages({"IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)", "IngestEventsListener.prevCaseComment.text=Previous Case: "}) @@ -259,6 +261,8 @@ public class IngestEventsListener { * @param originalArtifact the artifact to create the "previously seen" item for * @param caseDisplayNames the case names the artifact was previously seen * in + * @param aType The correlation type. + * @param value The correlation value. */ @NbBundle.Messages({"IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)", "# {0} - typeName", @@ -303,6 +307,8 @@ public class IngestEventsListener { * * @param originalArtifact the artifact to create the "previously unseen" item * for + * @param aType The correlation type. + * @param value The correlation value. */ static private void makeAndPostPreviouslyUnseenArtifact(BlackboardArtifact originalArtifact, CorrelationAttributeInstance.Type aType, String value) { Collection attributesForNewArtifact = Arrays.asList( @@ -319,6 +325,7 @@ public class IngestEventsListener { /** * Make an artifact to flag the passed in artifact. * + * @param newArtifactType Type of artifact to create. * @param originalArtifact Artifact in current case we want to flag * @param attributesForNewArtifact Attributes to assign to the new artifact * @param configuration The configuration to be specified for the new artifact hit diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 64cece2956..cc32ae029f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -834,8 +834,8 @@ public class BlackboardArtifactNode extends AbstractContentNode The return type. * @param visitor The visitor, where the type parameter of the visitor is * the type of the object that will be returned as the result * of the visit operation. diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TypesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TypesSummary.java index b13e852b95..7eb06e3f68 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TypesSummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TypesSummary.java @@ -170,7 +170,7 @@ public class TypesSummary { * Constructor that accepts FileTypeCategory. * * @param label The label for this slice. - * @param mimeTypes The mime types associated with this slice. + * @param fileCategory The category associated with this slice. * @param color The color associated with this slice. */ public FileTypeCategoryData(String label, FileTypeUtils.FileTypeCategory fileCategory, Color color) { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 0557808758..bfa1f34a0b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -126,6 +126,7 @@ public class DomainSearch { * @param caseDb The case database. * @param centralRepoDb The central repository database. Can be null * if not needed. + * @param context The search context. * * @return A LinkedHashMap grouped and sorted according to the parameters. * diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index c01e5ea76d..a31367ff6c 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -71,7 +71,6 @@ The following topics are available here: - \subpage ui_quick_search - \subpage file_search_page - \subpage ad_hoc_keyword_search_page - - \subpage stix_page - \subpage common_properties_page - \subpage search_all_cases_page From 1957bb13e3f8a6e1f95c9d7b6ef25b8ea4f46ec7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 21 Sep 2021 18:53:49 -0400 Subject: [PATCH 39/51] 7978-redo the API to support OS accounts --- .../application/OtherOccurrences.java | 11 +- .../OtherOccurrencesNodeWorker.java | 11 +- .../datamodel/CorrelationAttributeUtil.java | 277 ++++++++++-------- .../eventlisteners/CaseEventListener.java | 72 ++--- .../eventlisteners/IngestEventsListener.java | 7 +- .../annotations/AnnotationUtils.java | 13 +- .../autopsy/datamodel/GetSCOTask.java | 18 +- 7 files changed, 238 insertions(+), 171 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java index 6e8ecfbecf..85f0fcd6de 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -76,23 +76,20 @@ public final class OtherOccurrences { * @return A list of attributes that can be used for correlation */ public static Collection getCorrelationAttributeFromOsAccount(Node node, OsAccount osAccount) { - Collection ret = new ArrayList<>(); Optional osAccountAddr = osAccount.getAddr(); - if (osAccountAddr.isPresent()) { try { for (OsAccountInstance instance : osAccount.getOsAccountInstances()) { - CorrelationAttributeInstance correlationAttributeInstance = CorrelationAttributeUtil.makeCorrAttr(instance.getOsAccount(), instance.getDataSource()); - if (correlationAttributeInstance != null) { - ret.add(correlationAttributeInstance); + List correlationAttributeInstances = CorrelationAttributeUtil.makeCorrAttrsForSearch(instance); + if (!correlationAttributeInstances.isEmpty()) { + return correlationAttributeInstances; } } } catch (TskCoreException ex) { logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for osAccount %s.", osAccountAddr.get()), ex); } } - - return ret; + return new ArrayList<>(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java index 6c3ab8722d..dcc3375d4d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesNodeWorker.java @@ -39,9 +39,11 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.TskContentItem; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.TskException; @@ -104,10 +106,15 @@ class OtherOccurrencesNodeWorker extends SwingWorker } } if (content != null) { - correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(content)); + if (content instanceof AbstractFile) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) content)); + } else if (content instanceof AnalysisResult) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) content)); + } else if (content instanceof DataArtifact) { + correlationAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) content)); + } } } - int totalCount = 0; Set dataSources = new HashSet<>(); for (CorrelationAttributeInstance corAttr : correlationAttributes) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index e08bed19c0..ffd9c96399 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -39,9 +39,11 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataArtifact; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.InvalidAccountIDException; import org.sleuthkit.datamodel.OsAccount; +import org.sleuthkit.datamodel.OsAccountInstance; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -78,7 +80,7 @@ public class CorrelationAttributeUtil { return Bundle.CorrelationAttributeUtil_emailaddresses_text(); } - private static List makeCorrAttrsToSave(DataArtifact artifact) { + public static List makeCorrAttrsToSave(DataArtifact artifact) { int artifactTypeID = artifact.getArtifactTypeID(); //The account fields in these types are expected to be saved in a TSK_ACCOUNT artifact, which will be processed if (artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() @@ -102,71 +104,80 @@ public class CorrelationAttributeUtil { * @return A list, possibly empty, of correlation attribute instances for * the content. */ - public static List makeCorrAttrsToSave(Content content) { - if (content instanceof DataArtifact) { - return makeCorrAttrsToSave((DataArtifact) content); - } else if (content instanceof AnalysisResult) { - //AnalysisResults should already have the correlation attributes they are correlating on saved - //This check replaces the check explicitly excluding keyword hits and interesting items that existed prior to the AnalysisResult designation - return new ArrayList<>(); - } else { - return makeCorrAttrsForSearch(content); - } + public static List makeCorrAttrsToSave(AbstractFile file) { + return makeCorrAttrsForSearch(file); + } + + public static List makeCorrAttrsToSave(AnalysisResult file) { + return new ArrayList<>(); + } + + public static List makeCorrAttrsToSave(OsAccountInstance osAccountInstance) { + return makeCorrAttrsForSearch(osAccountInstance); } /** * Makes zero to many correlation attribute instances from the attributes of - * content that have correlatable data. The intention of this method is to - * use the results to correlate with, not to save. If you want to save, - * please use makeCorrAttrsToSave. An artifact that can have correlatable - * data != An artifact that should be the source of data in the CR, so + * AnalysisResult that have correlatable data. The intention of this method + * is to use the results to correlate with, not to save. If you want to + * save, please use makeCorrAttrsToSave. An artifact that can have data to + * search for != An artifact that should be the source of data in the CR, so * results may be too lenient. * * IMPORTANT: The correlation attribute instances are NOT added to the * central repository by this method. * - * TODO (Jira-6088): The methods in this low-level, utility class should - * throw exceptions instead of logging them. The reason for this is that the - * clients of the utility class, not the utility class itself, should be in - * charge of error handling policy, per the Autopsy Coding Standard. Note - * that clients of several of these methods currently cannot determine - * whether receiving a null return value is an error or not, plus null - * checking is easy to forget, while catching exceptions is enforced. + * JIRA-TODO (Jira-6088) * - * @param content A Content object. + * @param analysisResult An AnalysisResult object. * * @return A list, possibly empty, of correlation attribute instances for - * the content. + * the AnalysisResult. */ - public static List makeCorrAttrsForSearch(Content content) { - if (content instanceof DataArtifact) { - return makeCorrAttrsForSearch((DataArtifact) content); - } else if (content instanceof AnalysisResult) { - return makeCorrAttrsForSearch((AnalysisResult) content); - } else if (content instanceof AbstractFile) { - return makeCorrAttrsForSearch((AbstractFile) content); - } else { - return new ArrayList<>(); - } - } - - private static List makeCorrAttrsForSearch(AnalysisResult analysisResult) { + public static List makeCorrAttrsForSearch(AnalysisResult analysisResult) { List correlationAttrs = new ArrayList<>(); try { int artifactTypeID = analysisResult.getArtifactTypeID(); - if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - BlackboardAttribute setNameAttr = analysisResult.getAttribute(BlackboardAttribute.Type.TSK_SET_NAME); - if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) { - makeCorrAttrFromArtifactAttr(correlationAttrs, analysisResult, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID, analysisResult.getAttributes()); - } - } else if (artifactTypeID == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { + if (artifactTypeID == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { BlackboardAttribute assocArtifactAttr = analysisResult.getAttribute(BlackboardAttribute.Type.TSK_ASSOCIATED_ARTIFACT); if (assocArtifactAttr != null) { BlackboardArtifact sourceArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(assocArtifactAttr.getValueLong()); - return CorrelationAttributeUtil.makeCorrAttrsForSearch(sourceArtifact); + if (sourceArtifact instanceof DataArtifact) { + correlationAttrs.addAll((CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) sourceArtifact))); + } else if (sourceArtifact instanceof AnalysisResult) { + correlationAttrs.addAll((CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) sourceArtifact))); + } else { + String sourceName = sourceArtifact != null ? "SourceArtifact display name: " + sourceArtifact.getDisplayName() : "SourceArtifact was null"; + logger.log(Level.WARNING, "Source artifact found through TSK_ASSOCIATED_ARTIFACT attribute was not a DataArtifact or " + + "an Analysis Result. AssociateArtifactAttr Value: {0} {1}", + new Object[]{assocArtifactAttr.getValueString(), sourceName}); + } + + } + } else { + if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { + BlackboardAttribute setNameAttr = analysisResult.getAttribute(BlackboardAttribute.Type.TSK_SET_NAME); + if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) { + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(analysisResult, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID, analysisResult.getAttributes())); + } + } + Content parent = analysisResult.getParent(); + if (parent instanceof AbstractFile) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) parent)); + } else if (parent instanceof AnalysisResult) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) parent)); + } else if (parent instanceof DataArtifact) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) parent)); + } else if (parent instanceof OsAccount) { + for (OsAccountInstance osAccountInst : ((OsAccount) parent).getOsAccountInstances()) { + if (osAccountInst.getDataSource().equals(analysisResult.getDataSource())) { + correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(osAccountInst)); + break; + } + } } } - correlationAttrs.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch(analysisResult.getParent())); + } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to get information regarding correlation attributes from AnalysisResult", ex); } catch (NoCurrentCaseException ex) { @@ -177,7 +188,25 @@ public class CorrelationAttributeUtil { return correlationAttrs; } - private static List makeCorrAttrsForSearch(DataArtifact artifact) { + /** + * Makes zero to many correlation attribute instances from the attributes of + * a DataArtifact that have correlatable data. The intention of this method + * is to use the results to correlate with, not to save. If you want to + * save, please use makeCorrAttrsToSave. An artifact that can have data to + * search for != An artifact that should be the source of data in the CR, so + * results may be too lenient. + * + * IMPORTANT: The correlation attribute instances are NOT added to the + * central repository by this method. + * + * JIRA-TODO (Jira-6088) + * + * @param artifact A DataArtifact object. + * + * @return A list, possibly empty, of correlation attribute instances for + * the DataArtifact. + */ + public static List makeCorrAttrsForSearch(DataArtifact artifact) { List correlationAttrs = new ArrayList<>(); try { @@ -188,52 +217,50 @@ public class CorrelationAttributeUtil { BlackboardAttribute domainAttr = getAttribute(attributes, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN)); if ((domainAttr != null) && !domainsToSkip.contains(domainAttr.getValueString())) { - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID, attributes); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID, attributes)); } } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) { // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); Content dataSource = sourceContent.getDataSource(); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID, - attributes, sourceContent, dataSource); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, - attributes, sourceContent, dataSource); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID, attributes); - + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID, attributes)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, attributes); - + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID, attributes)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID()) { // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); Content dataSource = sourceContent.getDataSource(); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID, - attributes, sourceContent, dataSource); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, - attributes, sourceContent, dataSource); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, - attributes, sourceContent, dataSource); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID()) { // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); Content dataSource = sourceContent.getDataSource(); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, - attributes, sourceContent, dataSource); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, - attributes, sourceContent, dataSource); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) { // prefetch all the information as we will be calling makeCorrAttrFromArtifactAttr() multiple times Content sourceContent = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(artifact.getObjectID()); Content dataSource = sourceContent.getDataSource(); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID, - attributes, sourceContent, dataSource); - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID, - attributes, sourceContent, dataSource); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID, + attributes, sourceContent, dataSource)); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID, + attributes, sourceContent, dataSource)); } else if (artifactTypeID == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { makeCorrAttrFromAcctArtifact(correlationAttrs, artifact); @@ -245,14 +272,14 @@ public class CorrelationAttributeUtil { pathAttrString = setNameAttr.getValueString(); } if (pathAttrString != null && !pathAttrString.isEmpty()) { - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes)); } else { - makeCorrAttrFromArtifactAttr(correlationAttrs, artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes); + correlationAttrs.addAll(makeCorrAttrFromArtifactAttr(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, CorrelationAttributeInstance.INSTALLED_PROGS_TYPE_ID, attributes)); } } else if (artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) { - makeCorrAttrsFromCommunicationArtifacts(correlationAttrs, artifact, attributes); + correlationAttrs.addAll(makeCorrAttrsFromCommunicationArtifact(artifact, attributes)); } } catch (CorrelationAttributeNormalizationException ex) { logger.log(Level.WARNING, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS @@ -310,8 +337,9 @@ public class CorrelationAttributeUtil { * in normalizing the * attribute. */ - private static void makeCorrAttrsFromCommunicationArtifacts(List corrAttrInstances, BlackboardArtifact artifact, + private static List makeCorrAttrsFromCommunicationArtifact(BlackboardArtifact artifact, List attributes) throws TskCoreException, CentralRepoException, CorrelationAttributeNormalizationException { + /* * Extract the phone number from the artifact attribute. */ @@ -326,6 +354,7 @@ public class CorrelationAttributeUtil { /* * Normalize the phone number. */ + List corrAttrInstances = new ArrayList<>(); if (value != null && CorrelationAttributeNormalizer.isValidPhoneNumber(value)) { value = CorrelationAttributeNormalizer.normalizePhone(value); @@ -334,6 +363,7 @@ public class CorrelationAttributeUtil { corrAttrInstances.add(corrAttr); } } + return corrAttrInstances; } /** @@ -408,9 +438,9 @@ public class CorrelationAttributeUtil { * @throws TskCoreException If there is an error querying the case * database. */ - private static void makeCorrAttrFromArtifactAttr(List corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, + private static List makeCorrAttrFromArtifactAttr(BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, List attributes, Content sourceContent, Content dataSource) throws CentralRepoException, TskCoreException { - + List corrAttrInstances = new ArrayList<>(); BlackboardAttribute attribute = getAttribute(attributes, new BlackboardAttribute.Type(artAttrType)); if (attribute != null) { String value = attribute.getValueString(); @@ -421,6 +451,7 @@ public class CorrelationAttributeUtil { } } } + return corrAttrInstances; } /** @@ -441,10 +472,10 @@ public class CorrelationAttributeUtil { * @throws TskCoreException If there is an error querying the case * database. */ - private static void makeCorrAttrFromArtifactAttr(List corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, + private static List makeCorrAttrFromArtifactAttr(BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId, List attributes) throws CentralRepoException, TskCoreException { - makeCorrAttrFromArtifactAttr(corrAttrInstances, artifact, artAttrType, typeId, attributes, null, null); + return makeCorrAttrFromArtifactAttr(artifact, artAttrType, typeId, attributes, null, null); } /** @@ -553,51 +584,6 @@ public class CorrelationAttributeUtil { } } - /** - * Makes a correlation attribute instance of a given type from an OS - * account. Checks address if it is null, or one of the ones always present - * on a windows system and thus not unique. - * - * @param osAccount The OS account. - * @param dataSource The data source content object. - * - * @return The correlation attribute instance or null, if an error occurred. - */ - public static CorrelationAttributeInstance makeCorrAttr(OsAccount osAccount, Content dataSource) { - - Optional accountAddr = osAccount.getAddr(); - // Check address if it is null or one of the ones below we want to ignore it since they will always be one a windows system - // and they are not unique - if (!accountAddr.isPresent() || accountAddr.get().equals("S-1-5-18") || accountAddr.get().equals("S-1-5-19") || accountAddr.get().equals("S-1-5-20")) { - return null; - } - try { - - CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows()); - CorrelationAttributeInstance correlationAttributeInstance = new CorrelationAttributeInstance( - CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID), - accountAddr.get(), - correlationCase, - CorrelationDataSource.fromTSKDataSource(correlationCase, dataSource), - "", - "", - TskData.FileKnown.KNOWN, - osAccount.getId()); - - return correlationAttributeInstance; - - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, String.format("Cannot get central repository for OsAccount: %s.", accountAddr.get()), ex); //NON-NLS - return null; - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return null; - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.SEVERE, "Exception with Correlation Attribute Normalization.", ex); //NON-NLS - return null; - } - } - // @@@ BC: This seems like it should go into a DB-specific class because it is // much different from the other methods in this class. It is going to the DB for data. /** @@ -697,9 +683,10 @@ public class CorrelationAttributeUtil { * * @param file The file. * - * @return The correlation attribute instance or null, if an error occurred. + * @return The correlation attribute instance in a list, or an empty list if + * an error occurred. */ - private static List makeCorrAttrsForSearch(AbstractFile file) { + public static List makeCorrAttrsForSearch(AbstractFile file) { List fileTypeList = new ArrayList<>(); // will be an empty or single element list as was decided in 7852 if (!isSupportedAbstractFileType(file)) { return fileTypeList; @@ -768,6 +755,48 @@ public class CorrelationAttributeUtil { } } + public static List makeCorrAttrsForSearch(OsAccountInstance osAccountInst) { + List correlationAttrs = new ArrayList<>(); + OsAccount account = null; + DataSource dataSource = null; + if (osAccountInst != null) { + try { + account = osAccountInst.getOsAccount(); + dataSource = osAccountInst.getDataSource(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting information from OsAccountInstance.", ex); + } + } + if (account != null && dataSource != null) { + Optional accountAddr = account.getAddr(); + // Check address if it is null or one of the ones below we want to ignore it since they will always be one a windows system + // and they are not unique + if (accountAddr.isPresent() && !accountAddr.get().equals("S-1-5-18") && !accountAddr.get().equals("S-1-5-19") && !accountAddr.get().equals("S-1-5-20")) { + try { + + CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows()); + CorrelationAttributeInstance correlationAttributeInstance = new CorrelationAttributeInstance( + CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID), + accountAddr.get(), + correlationCase, + CorrelationDataSource.fromTSKDataSource(correlationCase, dataSource), + "", + "", + TskData.FileKnown.KNOWN, + account.getId()); + correlationAttrs.add(correlationAttributeInstance); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, String.format("Cannot get central repository for OsAccount: %s.", accountAddr.get()), ex); //NON-NLS + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.SEVERE, "Exception with Correlation Attribute Normalization.", ex); //NON-NLS + } + } + } + return correlationAttrs; + } + /** * Prevent instantiation of this utility class. */ diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index 39d93bd76a..4b9ab561b1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.eventlisteners; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; @@ -64,12 +65,14 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNor import org.sleuthkit.datamodel.Tag; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardAttribute; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_OTHER_CASES; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CORRELATION_TYPE; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CORRELATION_VALUE; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.OsAccountInstance; import org.sleuthkit.datamodel.Score; @@ -212,6 +215,28 @@ public final class CaseEventListener implements PropertyChangeListener { .isPresent(); } + /** + * Sets the known status of a blackboard artifact in the central repository. + * + * @param bbArtifact The blackboard artifact to set known status. + * @param knownStatus The new known status. + */ + private static void setArtifactKnownStatus(CentralRepository dbManager, BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus) { + List convertedArtifacts = new ArrayList<>(); + if (bbArtifact instanceof DataArtifact) { + convertedArtifacts.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) bbArtifact)); + } else if (bbArtifact instanceof AnalysisResult) { + convertedArtifacts.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) bbArtifact)); + } + for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { + try { + dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS + } + } + } + private final class ContentTagTask implements Runnable { private final CentralRepository dbManager; @@ -430,9 +455,9 @@ public final class CaseEventListener implements PropertyChangeListener { TagsManager tagsManager = openCase.getServices().getTagsManager(); List tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact); if (hasNotableTag(tags)) { - setArtifactKnownStatus(bbArtifact, TskData.FileKnown.BAD); + setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.BAD); } else { - setArtifactKnownStatus(bbArtifact, TskData.FileKnown.UNKNOWN); + setArtifactKnownStatus(dbManager, bbArtifact, TskData.FileKnown.UNKNOWN); } } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex); @@ -450,24 +475,6 @@ public final class CaseEventListener implements PropertyChangeListener { return ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)); } - /** - * Sets the known status of a blackboard artifact in the central - * repository. - * - * @param bbArtifact The blackboard artifact to set known status. - * @param knownStatus The new known status. - */ - private void setArtifactKnownStatus(BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus) { - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForSearch(bbArtifact); - for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - try { - dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS - } - } - } - } private final class TagDefinitionChangeTask implements Runnable { @@ -528,12 +535,7 @@ public final class CaseEventListener implements PropertyChangeListener { } //if the Correlation Attribute will have no tags with a status which would prevent the current status from being changed if (!hasTagWithConflictingKnownStatus) { - //Get the correlation atttributes that correspond to the current BlackboardArtifactTag if their status should be changed - //with the initial set of correlation attributes this should be a single correlation attribute - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForSearch(bbTag.getArtifact()); - for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus()); - } + setArtifactKnownStatus(CentralRepository.getInstance(), bbTag.getArtifact(), tagName.getKnownStatus()); } } // Next update the files @@ -568,7 +570,7 @@ public final class CaseEventListener implements PropertyChangeListener { if (!hasTagWithConflictingKnownStatus) { Content taggedContent = contentTag.getContent(); if (taggedContent instanceof AbstractFile) { - final List eamArtifact = CorrelationAttributeUtil.makeCorrAttrsForSearch(taggedContent); + final List eamArtifact = CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) taggedContent); if (!eamArtifact.isEmpty()) { //for an abstract file the 'list' of attributes will be a single attribute or empty and is returning a list for consistency with other makeCorrAttrsForSearch methods per 7852 CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact.get(0), tagName.getKnownStatus()); @@ -691,8 +693,8 @@ public final class CaseEventListener implements PropertyChangeListener { for (OsAccountInstance osAccountInstance : addedOsAccountNew) { try { OsAccount osAccount = osAccountInstance.getOsAccount(); - CorrelationAttributeInstance correlationAttributeInstance = CorrelationAttributeUtil.makeCorrAttr(osAccount, osAccountInstance.getDataSource()); - if (correlationAttributeInstance == null) { + List correlationAttributeInstances = CorrelationAttributeUtil.makeCorrAttrsForSearch(osAccountInstance); + if (correlationAttributeInstances.isEmpty()) { return; } @@ -700,19 +702,21 @@ public final class CaseEventListener implements PropertyChangeListener { try { // Save to the database if requested if (IngestEventsListener.shouldCreateCrProperties()) { - dbManager.addArtifactInstance(correlationAttributeInstance); + for (CorrelationAttributeInstance correlationAttributeInstance : correlationAttributeInstances) { + dbManager.addArtifactInstance(correlationAttributeInstance); + } } // Look up and create artifacts for previously seen accounts if requested if (IngestEventsListener.isFlagSeenDevices()) { CorrelationAttributeInstance.Type osAcctType = CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.OSACCOUNT_TYPE_ID); - List previousOccurences = dbManager.getArtifactInstancesByTypeValue(osAcctType, correlationAttributeInstance.getCorrelationValue()); + List previousOccurences = dbManager.getArtifactInstancesByTypeValue(osAcctType, correlationAttributeInstances.get(0).getCorrelationValue()); for (CorrelationAttributeInstance instance : previousOccurences) { - if (!instance.getCorrelationCase().getCaseUUID().equals(correlationAttributeInstance.getCorrelationCase().getCaseUUID())) { + if (!instance.getCorrelationCase().getCaseUUID().equals(correlationAttributeInstances.get(0).getCorrelationCase().getCaseUUID())) { SleuthkitCase tskCase = osAccount.getSleuthkitCase(); Blackboard blackboard = tskCase.getBlackboard(); - List caseDisplayNames = dbManager.getListCasesHavingArtifactInstances(osAcctType, correlationAttributeInstance.getCorrelationValue()); + List caseDisplayNames = dbManager.getListCasesHavingArtifactInstances(osAcctType, correlationAttributeInstances.get(0).getCorrelationValue()); // calculate score Score score; @@ -737,7 +741,7 @@ public final class CaseEventListener implements PropertyChangeListener { osAcctType.getDisplayName()), new BlackboardAttribute( TSK_CORRELATION_VALUE, MODULE_NAME, - correlationAttributeInstance.getCorrelationValue()), + correlationAttributeInstances.get(0).getCorrelationValue()), new BlackboardAttribute( TSK_OTHER_CASES, MODULE_NAME, prevCases)); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index 270f436a71..7f5ca06761 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -62,6 +62,8 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.TskData; @@ -547,7 +549,10 @@ public class IngestEventsListener { for (BlackboardArtifact bbArtifact : bbArtifacts) { // makeCorrAttrToSave will filter out artifacts which should not be sources of CR data. - List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsToSave(bbArtifact); + List convertedArtifacts = new ArrayList<>(); + if (bbArtifact instanceof DataArtifact){ + convertedArtifacts.addAll(CorrelationAttributeUtil.makeCorrAttrsToSave((DataArtifact)bbArtifact)); + } for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { try { // Only do something with this artifact if it's unique within the job diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java index 97cf7d7e6d..ed153a50cb 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/annotations/AnnotationUtils.java @@ -42,11 +42,13 @@ import org.sleuthkit.autopsy.contentviewers.layout.ContentViewerHtmlStyles; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactItem; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; @@ -148,7 +150,9 @@ public class AnnotationUtils { /** * Returns whether or not the node is supported by the annotation viewer. + * * @param node The node to display. + * * @return True if the node is supported. */ public static boolean isSupported(Node node) { @@ -380,9 +384,14 @@ public class AnnotationUtils { if (artifact == null) { return new ArrayList<>(); } + List instances = new ArrayList<>(); + if (artifact instanceof DataArtifact) { + instances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) artifact)); + } else if (artifact instanceof AnalysisResult) { + instances.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) artifact)); + } - List> lookupKeys = CorrelationAttributeUtil.makeCorrAttrsForSearch(artifact) - .stream() + List> lookupKeys = instances.stream() .map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue())) .collect(Collectors.toList()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java index c721fe9536..60ad65c83f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/GetSCOTask.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; @@ -29,6 +30,10 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataArtifact; /** * Background task to get Score, Comment and Occurrences values for an Abstract @@ -62,7 +67,18 @@ class GetSCOTask implements Runnable { //getting the correlation attribute and setting the comment column is done before the eamdb isEnabled check //because the Comment column will reflect the presence of comments in the CR when the CR is enabled, but reflect tag comments regardless String description = Bundle.GetSCOTask_occurrences_defaultDescription(); - List listOfPossibleAttributes = CorrelationAttributeUtil.makeCorrAttrsForSearch(contentNode.getContent()); + + List listOfPossibleAttributes = new ArrayList<>(); + Content contentFromNode = contentNode.getContent(); + if (contentFromNode instanceof AbstractFile) { + listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AbstractFile) contentFromNode)); + } else if (contentFromNode instanceof AnalysisResult) { + listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((AnalysisResult) contentFromNode)); + } else if (contentFromNode instanceof DataArtifact) { + listOfPossibleAttributes.addAll(CorrelationAttributeUtil.makeCorrAttrsForSearch((DataArtifact) contentFromNode)); + } else { + //JIRA-TODO : add code for Jira-7938 OsAccounts + } scoData.setComment(contentNode.getCommentProperty(tags, listOfPossibleAttributes)); CorrelationAttributeInstance corInstance = null; if (CentralRepository.isEnabled()) { From 9a516d88ff1cd7651d9292b97130c360ada765a8 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Sep 2021 11:03:41 -0400 Subject: [PATCH 40/51] 7978 remove unused import --- .../centralrepository/eventlisteners/IngestEventsListener.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index 7f5ca06761..a036d22073 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -62,7 +62,6 @@ import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.Score; import org.sleuthkit.datamodel.TskData; From 8c0bc0bc5a2a1b4f32ea374c5db7b202518da6a9 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Sep 2021 12:13:07 -0400 Subject: [PATCH 41/51] 7918 fix spelling --- .../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 fb85ffbf2b..51c0791a11 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/DataContentViewerOtherCases.java @@ -94,7 +94,7 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon //Ideally we would want to attempt to create correlation attributes for the node contents //and if none could be created determine that it was not supported. //However that winds up being more work than we really want to be performing in this method so we perform a quicker check. - //The result of this is that the Other Occurrences viewer could be enabled but without a correltion attributes in some situations. + //The result of this is that the Other Occurrences viewer could be enabled but without any correlation attributes in some situations. // Is supported if: // The central repo is enabled and the node is not null if (CentralRepository.isEnabled() && node != null) { From c3f0e1a935f4c435a87c92e8999e9c6053bb4e5f Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 22 Sep 2021 15:05:19 -0400 Subject: [PATCH 42/51] changes to text in Past Cases tab --- .../autopsy/datasourcesummary/ui/Bundle.properties-MERGED | 2 +- .../sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java | 2 +- .../modules/datasourcesummaryexport/Bundle.properties-MERGED | 2 +- .../report/modules/datasourcesummaryexport/ExportPastCases.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index a7a4e5870f..59bcaa6ff4 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -66,7 +66,7 @@ GeolocationPanel_onNoCrIngest_message=No results will be shown because the GPX P GeolocationPanel_unknownRow_title=Unknown PastCasesPanel_caseColumn_title=Case PastCasesPanel_countColumn_title=Count -PastCasesPanel_notableFileTable_tabName=Cases with Common Notable +PastCasesPanel_notableFileTable_tabName=Cases with Common Notable Items at Time Of Ingest PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run. PastCasesPanel_sameIdsTable_tabName=Past Cases with the Same Devices RecentFilesPanel_attachmentsTable_tabName=Recent Attachments diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java index 08679169d3..f4b4d6e956 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/PastCasesPanel.java @@ -41,7 +41,7 @@ import org.sleuthkit.datamodel.DataSource; "PastCasesPanel_caseColumn_title=Case", "PastCasesPanel_countColumn_title=Count", "PastCasesPanel_onNoCrIngest_message=No results will be shown because the Central Repository module was not run.", - "PastCasesPanel_notableFileTable_tabName=Cases with Common Notable", + "PastCasesPanel_notableFileTable_tabName=Cases with Common Notable Items at Time Of Ingest", "PastCasesPanel_sameIdsTable_tabName=Past Cases with the Same Devices",}) public class PastCasesPanel extends BaseDataSourceSummaryPanel { diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/Bundle.properties-MERGED index 1767afc028..956adf7e01 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/Bundle.properties-MERGED @@ -54,7 +54,7 @@ ExportIngestHistory_startTimeColumn=Start Time ExportIngestHistory_versionColumn=Module Version ExportPastCases_caseColumn_title=Case ExportPastCases_countColumn_title=Count -ExportPastCases_notableFileTable_tabName=Cases with Common Notable +ExportPastCases_notableFileTable_tabName=Cases with Common Notable Items at Time Of Ingest ExportPastCases_sameIdsTable_tabName=Past Cases with the Same Devices ExportRecentFiles_attachmentsTable_tabName=Recent Attachments ExportRecentFiles_col_head_date=Date diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/ExportPastCases.java b/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/ExportPastCases.java index be824477a1..96aefebaff 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/ExportPastCases.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/datasourcesummaryexport/ExportPastCases.java @@ -37,7 +37,7 @@ import org.sleuthkit.datamodel.DataSource; @Messages({ "ExportPastCases_caseColumn_title=Case", "ExportPastCases_countColumn_title=Count", - "ExportPastCases_notableFileTable_tabName=Cases with Common Notable", + "ExportPastCases_notableFileTable_tabName=Cases with Common Notable Items at Time Of Ingest", "ExportPastCases_sameIdsTable_tabName=Past Cases with the Same Devices",}) class ExportPastCases { From e2b92669f5aa03a901f2e245a6be3f6a498a3ad5 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 22 Sep 2021 17:13:24 -0400 Subject: [PATCH 43/51] 7989 temp fix for 'sliced' artifact objects --- .../autopsy/datamodel/BlackboardArtifactItem.java | 10 +++++----- .../autopsy/datamodel/BlackboardArtifactNode.java | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java index 9a4d9e8c22..9748c3b747 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java @@ -23,13 +23,13 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; /** - * An abstract super class for an Autopsy Data Model item class with an - * underlying BlackboardArtifact Sleuth Kit Data Model object, i.e., a - * DataArtifact or an AnalysisResult. + * An super class for an Autopsy Data Model item class with an underlying + * BlackboardArtifact Sleuth Kit Data Model object, i.e., a DataArtifact or an + * AnalysisResult. * - * @param The concrete BlackboardArtifact sub class type. + * @param The concrete BlackboardArtifact class type. */ -public abstract class BlackboardArtifactItem extends TskContentItem { +public class BlackboardArtifactItem extends TskContentItem { private final Content sourceContent; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index cc32ae029f..2fcba80feb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -384,8 +384,10 @@ public class BlackboardArtifactNode extends AbstractContentNode artifactItem; if (artifact instanceof AnalysisResult) { artifactItem = new AnalysisResultItem((AnalysisResult) artifact, content); - } else { + } else if (artifact instanceof DataArtifact) { artifactItem = new DataArtifactItem((DataArtifact) artifact, content); + } else { + artifactItem = new BlackboardArtifactItem<>(artifact, content); } /* From f8ae46823c7b3fffdbb3bd7019d7a2790b93a892 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 22 Sep 2021 19:35:26 -0400 Subject: [PATCH 44/51] apply formatting --- .../datamodel/BlackboardArtifactNode.java | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 2a9c63a4f4..bac58199ed 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -99,7 +99,6 @@ import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPas import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.BlackboardArtifact.Category; import org.sleuthkit.datamodel.HostAddress; -import org.sleuthkit.datamodel.OsAccount; import org.sleuthkit.datamodel.Pool; import org.sleuthkit.datamodel.DataArtifact; import org.sleuthkit.datamodel.DerivedFile; @@ -269,7 +268,7 @@ public class BlackboardArtifactNode extends AbstractContentNode( @@ -1467,7 +1468,7 @@ public class BlackboardArtifactNode extends AbstractContentNode Date: Wed, 22 Sep 2021 22:50:23 -0400 Subject: [PATCH 47/51] Update ChromeCacheExtractor.java Remove commented code. --- .../sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java index 752f8ce4f7..616b13d41f 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ChromeCacheExtractor.java @@ -1379,8 +1379,6 @@ final class ChromeCacheExtractor { } } catch (TskCoreException | IngestModuleException ex) { throw new TskCoreException(String.format("Failed to get external key from address %s", longKeyAddresses)); //NON-NLS -// } catch (NullPointerException ex) { -// String name = "a"; } } else { // key stored within entry From 461f5b2e4d831d3686d034f360fdd11680341d3c Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 23 Sep 2021 13:37:09 -0400 Subject: [PATCH 48/51] Update ExtractZoneIdentifier.java Try catch for IllegalArgument when it is thrown. log message and continue on processing --- .../autopsy/recentactivity/ExtractZoneIdentifier.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java index fc59685f9e..5cc6633205 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractZoneIdentifier.java @@ -318,7 +318,13 @@ final class ExtractZoneIdentifier extends Extract { */ ZoneIdentifierInfo(AbstractFile zoneFile) throws IOException { fileName = zoneFile.getName(); - properties.load(new ReadContentInputStream(zoneFile)); + // properties.load will throw IllegalArgument if unicode characters are found in the zone file. + try { + properties.load(new ReadContentInputStream(zoneFile)); + } catch (IllegalArgumentException ex) { + String message = String.format("Unable to parse Zone Id for File %s", fileName); //NON-NLS + LOG.log(Level.WARNING, message); + } } /** From b4b869dde686060763e271c5566e78f7ec09a06e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 23 Sep 2021 13:41:52 -0400 Subject: [PATCH 49/51] Revert "7989 temp fix for 'sliced' artifact objects" This reverts commit e2b92669f5aa03a901f2e245a6be3f6a498a3ad5. --- .../autopsy/datamodel/BlackboardArtifactItem.java | 10 +++++----- .../autopsy/datamodel/BlackboardArtifactNode.java | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java index 9748c3b747..9a4d9e8c22 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactItem.java @@ -23,13 +23,13 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; /** - * An super class for an Autopsy Data Model item class with an underlying - * BlackboardArtifact Sleuth Kit Data Model object, i.e., a DataArtifact or an - * AnalysisResult. + * An abstract super class for an Autopsy Data Model item class with an + * underlying BlackboardArtifact Sleuth Kit Data Model object, i.e., a + * DataArtifact or an AnalysisResult. * - * @param The concrete BlackboardArtifact class type. + * @param The concrete BlackboardArtifact sub class type. */ -public class BlackboardArtifactItem extends TskContentItem { +public abstract class BlackboardArtifactItem extends TskContentItem { private final Content sourceContent; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 2fcba80feb..cc32ae029f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -384,10 +384,8 @@ public class BlackboardArtifactNode extends AbstractContentNode artifactItem; if (artifact instanceof AnalysisResult) { artifactItem = new AnalysisResultItem((AnalysisResult) artifact, content); - } else if (artifact instanceof DataArtifact) { - artifactItem = new DataArtifactItem((DataArtifact) artifact, content); } else { - artifactItem = new BlackboardArtifactItem<>(artifact, content); + artifactItem = new DataArtifactItem((DataArtifact) artifact, content); } /* From ce2f31b1ae83ed2ae09ed152224d2d7d2db85683 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Fri, 24 Sep 2021 12:26:57 -0400 Subject: [PATCH 50/51] Fixed datasource summary tab name --- .../autopsy/datasourcesummary/ui/Bundle.properties-MERGED | 2 +- .../autopsy/datasourcesummary/ui/RecentFilesPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 59bcaa6ff4..06ca641cf7 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -75,7 +75,7 @@ RecentFilesPanel_col_header_domain=Domain RecentFilesPanel_col_header_path=Path RecentFilesPanel_col_header_sender=Sender RecentFilesPanel_docsTable_tabName=Recently Opened Documents -RecentFilesPanel_downloadsTable_tabName=Recently Downloads +RecentFilesPanel_downloadsTable_tabName=Recent Downloads RecentFilesPanel_no_open_documents=No recently open documents found. SizeRepresentationUtil_units_bytes=bytes SizeRepresentationUtil_units_gigabytes=GB diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java index 0bc09d59ec..4773dae2fc 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/RecentFilesPanel.java @@ -47,7 +47,7 @@ import org.sleuthkit.datamodel.DataSource; */ @Messages({ "RecentFilesPanel_docsTable_tabName=Recently Opened Documents", - "RecentFilesPanel_downloadsTable_tabName=Recently Downloads", + "RecentFilesPanel_downloadsTable_tabName=Recent Downloads", "RecentFilesPanel_attachmentsTable_tabName=Recent Attachments",}) public final class RecentFilesPanel extends BaseDataSourceSummaryPanel { From 47d842a3eb04bb338507fb9b2555eb0a598a2ef3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 24 Sep 2021 15:07:25 -0400 Subject: [PATCH 51/51] 7978 change returning new array list to Collections.emptyList() --- .../centralrepository/application/OtherOccurrences.java | 4 ++-- .../datamodel/CorrelationAttributeUtil.java | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java index 85f0fcd6de..f1f0b5ea91 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/application/OtherOccurrences.java @@ -25,8 +25,8 @@ import java.nio.file.Files; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -89,7 +89,7 @@ public final class OtherOccurrences { logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for osAccount %s.", osAccountAddr.get()), ex); } } - return new ArrayList<>(); + return Collections.emptyList(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index ffd9c96399..bc934b0926 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -86,7 +87,7 @@ public class CorrelationAttributeUtil { if (artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) { - return new ArrayList<>(); + return Collections.emptyList(); } return CorrelationAttributeUtil.makeCorrAttrsForSearch(artifact); } @@ -109,7 +110,7 @@ public class CorrelationAttributeUtil { } public static List makeCorrAttrsToSave(AnalysisResult file) { - return new ArrayList<>(); + return Collections.emptyList(); } public static List makeCorrAttrsToSave(OsAccountInstance osAccountInstance) { @@ -354,7 +355,7 @@ public class CorrelationAttributeUtil { /* * Normalize the phone number. */ - List corrAttrInstances = new ArrayList<>(); + List corrAttrInstances = Collections.emptyList(); if (value != null && CorrelationAttributeNormalizer.isValidPhoneNumber(value)) { value = CorrelationAttributeNormalizer.normalizePhone(value);