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. *