diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 216a8e0f82..62fef4adaf 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -222,6 +222,10 @@ + net.sf.sevenzipjbinding + net.sf.sevenzipjbinding.impl + net.sf.sevenzipjbinding.simple + net.sf.sevenzipjbinding.simple.impl org.sleuthkit.autopsy.actions org.sleuthkit.autopsy.casemodule org.sleuthkit.autopsy.casemodule.events diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 7f4f813495..26288a54a2 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,8 +19,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; -import java.util.Collections; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JMenu; @@ -39,6 +39,7 @@ import org.sleuthkit.datamodel.TskCoreException; */ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { + private static final long serialVersionUID = 1L; private static final String NO_COMMENT = ""; AddTagAction(String menuText) { @@ -82,15 +83,16 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // to be reworked. private class TagMenu extends JMenu { + private static final long serialVersionUID = 1L; + TagMenu() { super(getActionDisplayName()); // Get the current set of tag names. TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List tagNames = null; + Map tagNamesMap = null; try { - tagNames = tagsManager.getAllTagNames(); - Collections.sort(tagNames); + tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException ex) { Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } @@ -102,11 +104,11 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { // Each tag name in the current set of tags gets its own menu item in // the "Quick Tags" sub-menu. Selecting one of these menu items adds // a tag with the associated tag name. - if (null != tagNames && !tagNames.isEmpty()) { - for (final TagName tagName : tagNames) { - JMenuItem tagNameItem = new JMenuItem(tagName.getDisplayName()); + if (null != tagNamesMap && !tagNamesMap.isEmpty()) { + for (Map.Entry entry : tagNamesMap.entrySet()) { + JMenuItem tagNameItem = new JMenuItem(entry.getKey()); tagNameItem.addActionListener((ActionEvent e) -> { - addTag(tagName, NO_COMMENT); + getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); quickTagMenu.add(tagNameItem); } @@ -114,7 +116,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags")); empty.setEnabled(false); quickTagMenu.add(empty); - } + } quickTagMenu.addSeparator(); @@ -143,5 +145,30 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { }); add(tagAndCommentItem); } + + /** + * Method to add to the action listener for each menu item. Allows a tag + * display name to be added to the menu with an action listener without + * having to instantiate a TagName object for it. + * When the method is called, the TagName object is created here if it + * doesn't already exist. + * + * @param tagDisplayName display name for the tag name + * @param tagName TagName object associated with the tag name, + * may be null + * @param comment comment for the content or artifact tag + */ + private void getAndAddTag(String tagDisplayName, TagName tagName, String comment) { + if (tagName == null) { + try { + tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS + } catch (TskCoreException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + } + } + addTag(tagName, comment); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties index d9cc839467..d964d91fc7 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties @@ -1,10 +1,10 @@ GetTagNameDialog.tagNameField.text= GetTagNameDialog.cancelButton.text=Cancel GetTagNameDialog.okButton.text=OK -GetTagNameDialog.preexistingLabel.text=Pre-existing Tags: +GetTagNameDialog.preexistingLabel.text=Pre-existing Tag Names: GetTagNameDialog.newTagPanel.border.title=New Tag GetTagNameDialog.tagNameLabel.text=Tag Name: -GetTagNameAndCommentDialog.newTagButton.text=New Tag +GetTagNameAndCommentDialog.newTagButton.text=New Tag Name GetTagNameAndCommentDialog.okButton.text=OK GetTagNameAndCommentDialog.commentText.toolTipText=Enter an optional tag comment or leave blank GetTagNameAndCommentDialog.commentText.text= @@ -42,7 +42,7 @@ GetTagNameDialog.createTag=Create Tag GetTagNameDialog.cancelName=Cancel GetTagNameDialog.mustSupplyTtagName.msg=Must supply a tag name to continue. GetTagNameDialog.tagNameErr=Tag Name -GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols\: \\ \: * ? " < > | +GetTagNameDialog.illegalChars.msg=The tag name contains illegal characters.\nCannot contain any of the following symbols\: \\ \: * ? " < > | , ; GetTagNameDialog.illegalCharsErr=Illegal Characters GetTagNameDialog.unableToAddTagNameToCase.msg=Unable to add the {0} tag name to the case. GetTagNameDialog.taggingErr=Tagging Error @@ -58,4 +58,4 @@ OpenOutputFolder.CouldNotOpenOutputFolder=Could not open output folder ShowIngestProgressSnapshotAction.actionName.text=Get Ingest Progress Snapshot OpenPythonModulesFolderAction.actionName.text=Python Plugins OpenPythonModulesFolderAction.errorMsg.folderNotFound=Python plugins folder not found: {0} -CTL_OpenPythonModulesFolderAction=Python Plugins \ No newline at end of file +CTL_OpenPythonModulesFolderAction=Python Plugins diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form index 47ff059281..17a9738dbd 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.form @@ -28,7 +28,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index eb4dbbab6b..73290602e9 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -21,8 +21,8 @@ package org.sleuthkit.autopsy.actions; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -import java.util.HashMap; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -43,7 +43,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private static final long serialVersionUID = 1L; private static final String NO_TAG_NAMES_MESSAGE = NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.noTags"); - private final HashMap tagNames = new HashMap<>(); + private final Map tagNamesMap = new TreeMap<>(); private TagNameAndComment tagNameAndComment = null; public static class TagNameAndComment { @@ -91,13 +91,18 @@ public class GetTagNameAndCommentDialog extends JDialog { * dialog. */ public static TagNameAndComment doDialog(Window owner) { - return new GetTagNameAndCommentDialog(owner).tagNameAndComment; + GetTagNameAndCommentDialog dialog = new GetTagNameAndCommentDialog(owner); + dialog.display(); + return dialog.tagNameAndComment; } private GetTagNameAndCommentDialog(Window owner) { super(owner, NbBundle.getMessage(GetTagNameAndCommentDialog.class, "GetTagNameAndCommentDialog.createTag"), ModalityType.APPLICATION_MODAL); + } + + private void display() { initComponents(); // Set up the dialog to close when Esc is pressed. @@ -106,6 +111,7 @@ public class GetTagNameAndCommentDialog extends JDialog { inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); ActionMap actionMap = getRootPane().getActionMap(); actionMap.put(cancelName, new AbstractAction() { + private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { dispose(); @@ -114,27 +120,27 @@ public class GetTagNameAndCommentDialog extends JDialog { // Populate the combo box with the available tag names and save the // tag name DTOs to be enable to return the one the user selects. + // Tag name DTOs may be null (user tag names that have not been used do + // not exist in the database). TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List currentTagNames = null; try { - currentTagNames = tagsManager.getAllTagNames(); + tagNamesMap.putAll(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - if (null != currentTagNames && currentTagNames.isEmpty()) { + if (null != tagNamesMap && tagNamesMap.isEmpty()) { tagCombo.addItem(NO_TAG_NAMES_MESSAGE); } else { - for (TagName tagName : currentTagNames) { - tagNames.put(tagName.getDisplayName(), tagName); - tagCombo.addItem(tagName.getDisplayName()); + for (String tagDisplayName : tagNamesMap.keySet()) { + tagCombo.addItem(tagDisplayName); } } // Center and show the dialog box. - this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - setVisible(true); + this.setLocationRelativeTo(this.getOwner()); + setVisible(true); } - + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -197,7 +203,7 @@ public class GetTagNameAndCommentDialog extends JDialog { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(newTagButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 78, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(cancelButton)) @@ -240,7 +246,18 @@ public class GetTagNameAndCommentDialog extends JDialog { }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - tagNameAndComment = new TagNameAndComment(tagNames.get((String) tagCombo.getSelectedItem()), commentText.getText()); + String tagDisplayName = (String) tagCombo.getSelectedItem(); + TagName tagNameFromCombo = tagNamesMap.get(tagDisplayName); + if (tagNameFromCombo == null) { + try { + tagNameFromCombo = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS + } catch (TskCoreException ex) { + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + } + } + tagNameAndComment = new TagNameAndComment(tagNameFromCombo, commentText.getText()); dispose(); }//GEN-LAST:event_okButtonActionPerformed @@ -257,7 +274,7 @@ public class GetTagNameAndCommentDialog extends JDialog { private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed TagName newTagName = GetTagNameDialog.doDialog(this); if (newTagName != null) { - tagNames.put(newTagName.getDisplayName(), newTagName); + tagNamesMap.put(newTagName.getDisplayName(), newTagName); tagCombo.addItem(newTagName.getDisplayName()); tagCombo.setSelectedItem(newTagName.getDisplayName()); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java index b24f69d757..3d1057a041 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,8 +22,9 @@ import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.ActionMap; @@ -44,8 +45,9 @@ import org.sleuthkit.datamodel.TskCoreException; public class GetTagNameDialog extends JDialog { + private static final long serialVersionUID = 1L; private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS - private final HashMap tagNames = new HashMap<>(); + private final Map tagNamesMap = new TreeMap<>(); private TagName tagName = null; /** @@ -70,14 +72,19 @@ public class GetTagNameDialog extends JDialog { * @return a TagName instance selected by the user, or null if the user * canceled the dialog. */ - public static TagName doDialog(final Window owner) { - return new GetTagNameDialog(owner).tagName; + public static TagName doDialog(Window owner) { + GetTagNameDialog dialog = new GetTagNameDialog(owner); + dialog.display(); + return dialog.tagName; } - private GetTagNameDialog(final Window owner) { - super(owner, + private GetTagNameDialog(Window owner) { + super(owner, NbBundle.getMessage(GetTagNameDialog.class, "GetTagNameDialog.createTag"), ModalityType.APPLICATION_MODAL); + } + + private void display() { setIconImage(ImageUtilities.loadImage(TAG_ICON_PATH)); initComponents(); @@ -87,6 +94,8 @@ public class GetTagNameDialog extends JDialog { inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); ActionMap actionMap = getRootPane().getActionMap(); actionMap.put(cancelName, new AbstractAction() { + private static final long serialVersionUID = 1L; + @Override public void actionPerformed(ActionEvent e) { cancelButtonActionPerformed(e); @@ -96,56 +105,38 @@ public class GetTagNameDialog extends JDialog { // Get the current set of tag names and hash them for a speedy lookup in // case the user chooses an existing tag name from the tag names table. TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); - List currentTagNames = null; try { - currentTagNames = tagsManager.getAllTagNames(); + tagNamesMap.putAll(tagsManager.getDisplayNamesToTagNamesMap()); } catch (TskCoreException ex) { Logger.getLogger(GetTagNameDialog.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS } - if (null != currentTagNames) { - for (TagName name : currentTagNames) { - this.tagNames.put(name.getDisplayName(), name); - } - } else { - currentTagNames = new ArrayList<>(); - } // Populate the tag names table. - tagsTable.setModel(new TagsTableModel(currentTagNames)); + tagsTable.setModel(new TagsTableModel(new ArrayList<>(tagNamesMap.keySet()))); tagsTable.setTableHeader(null); tagsTable.setCellSelectionEnabled(false); tagsTable.setFocusable(false); tagsTable.setRowHeight(tagsTable.getRowHeight() + 5); // Center and show the dialog box. - this.setLocationRelativeTo(owner); - setVisible(true); + this.setLocationRelativeTo(this.getOwner()); + setVisible(true); } - - private boolean containsIllegalCharacters(String content) { - return (content.contains("\\") - || content.contains(":") - || content.contains("*") - || content.contains("?") - || content.contains("\"") - || content.contains("<") - || content.contains(">") - || content.contains("|")); - } - + private class TagsTableModel extends AbstractTableModel { - private final ArrayList tagNames = new ArrayList<>(); + private static final long serialVersionUID = 1L; + private final ArrayList tagDisplayNames = new ArrayList<>(); - TagsTableModel(List tagNames) { - for (TagName tagName : tagNames) { - this.tagNames.add(tagName); + TagsTableModel(List tagDisplayNames) { + for (String tagDisplayName : tagDisplayNames) { + this.tagDisplayNames.add(tagDisplayName); } } @Override public int getRowCount() { - return tagNames.size(); + return tagDisplayNames.size(); } @Override @@ -160,7 +151,7 @@ public class GetTagNameDialog extends JDialog { @Override public String getValueAt(int rowIndex, int columnIndex) { - return tagNames.get(rowIndex).getDisplayName(); + return tagDisplayNames.get(rowIndex); } } @@ -305,13 +296,13 @@ public class GetTagNameDialog extends JDialog { "GetTagNameDialog.mustSupplyTtagName.msg"), NbBundle.getMessage(this.getClass(), "GetTagNameDialog.tagNameErr"), JOptionPane.ERROR_MESSAGE); - } else if (containsIllegalCharacters(tagDisplayName)) { + } else if (TagsManager.containsIllegalCharacters(tagDisplayName)) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "GetTagNameDialog.illegalChars.msg"), NbBundle.getMessage(this.getClass(), "GetTagNameDialog.illegalCharsErr"), JOptionPane.ERROR_MESSAGE); } else { - tagName = tagNames.get(tagDisplayName); + tagName = tagNamesMap.get(tagDisplayName); if (tagName == null) { try { tagName = Case.getCurrentCase().getServices().getTagsManager().addTagName(tagDisplayName); @@ -326,7 +317,7 @@ public class GetTagNameDialog extends JDialog { JOptionPane.ERROR_MESSAGE); tagName = null; } catch (TagsManager.TagNameAlreadyExistsException ex) { - Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS + Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database.", ex); //NON-NLS JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(), "GetTagNameDialog.tagNameAlreadyDef.msg", diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index 1a3be9454f..229d26af58 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -1,9 +1,18 @@ -TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} out of content size range (0 - {1}) -TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1}) -TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset -TagsManager.predefTagNames.bookmark.text=Bookmark -TagsManager.addContentTag.noCaseWarning=Failed to publish new content tag event. There is no case open. -TagsManager.deleteContentTag.noCaseWarning=Failed to publish content tag deleted event. There is no case open. -TagsManager.addBlackboardArtifactTag.noCaseWarning=Failed to publish new blackboard artifact tag event. There is no case open. -TagsManager.deleteBlackboardArtifactTag.noCaseWarning=Failed to publish blackboard artifact tag deleted event. There is no case open. +OptionsCategory_Name_TagNamesOptions=Tags +OptionsCategory_TagNames=TagNames Blackboard.unableToIndexArtifact.error.msg=Unable to index blackboard artifact {0} +NewUserTagNameDialog.title.text=New Tag Name +NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message=Tag name may not contain any of the following symbols\: \\ \: * ? " < > | , ; +NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title=Invalid character in tag name +TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.message=The tag name already exists in your settings +TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.title=Tag name already exists +NewUserTagNameDialog.JOptionPane.tagNameEmpty.message=The tag name cannot be empty +NewUserTagNameDialog.JOptionPane.tagNameEmpty.title=Empty tag name +TagOptionsPanel.tagTypesListLabel.text=Tag Names: +TagOptionsPanel.panelDescriptionLabel.text=Autopsy keeps a list of the tag names you have created in the past. Add more or delete them here. +NewTagNameDialog.okButton.text=OK +NewTagNameDialog.cancelButton.text=Cancel +NewTagNameDialog.tagNameTextField.text= +NewTagNameDialog.newTagNameLabel.text=New Tag Name: +TagOptionsPanel.deleteTagNameButton.text=Delete Tag Name +TagOptionsPanel.newTagNameButton.text=New Tag Name diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties index ca8ed3431a..a27e3ca586 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties @@ -1,9 +1,2 @@ -TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059 -TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059 -TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset TagsManager.predefTagNames.bookmark.text=\u30d6\u30c3\u30af\u30de\u30fc\u30af -TagsManager.addContentTag.noCaseWarning=\u65b0\u3057\u3044\u30bf\u30b0\u30a4\u30d9\u30f3\u30c8\u3092\u30d1\u30d6\u30ea\u30c3\u30b7\u30e5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 -TagsManager.deleteContentTag.noCaseWarning=\u524a\u9664\u3055\u308c\u305f\u30a4\u30d9\u30f3\u30c8\u306e\u30bf\u30b0\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u30d1\u30d6\u30ea\u30c3\u30b7\u30e5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 -TagsManager.addBlackboardArtifactTag.noCaseWarning=\u30a4\u30d9\u30f3\u30c8\u306e\u30bf\u30b0\u306e\u65b0\u3057\u3044blackboard\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30d1\u30d6\u30ea\u30c3\u30b7\u30e5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 -TagsManager.deleteBlackboardArtifactTag.noCaseWarning=\u524a\u9664\u3055\u308c\u305f\u30a4\u30d9\u30f3\u30c8\u306e\u30bf\u30b0\u306e\u65b0\u3057\u3044blackboard\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30d1\u30d6\u30ea\u30c3\u30b7\u30e5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u958b\u3044\u3066\u3044\u308b\u30b1\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 Blackboard.unableToIndexArtifact.error.msg=blackboard\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8{0}\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index e10e3d7ad5..57a6712282 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.ingest.IngestServices; @@ -43,6 +44,7 @@ import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskDataException; import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.CarvingResult; import org.sleuthkit.datamodel.TskData; @@ -53,6 +55,7 @@ import org.sleuthkit.datamodel.TskData; */ public class FileManager implements Closeable { + private static final Logger LOGGER = Logger.getLogger(FileManager.class.getName()); private SleuthkitCase caseDb; /** @@ -343,7 +346,7 @@ public class FileManager implements Closeable { } return caseDb.addCarvedFiles(carvingResult); } - + /** * Interface for receiving a notification for each file or directory added * to the case database by a FileManager add files operation. @@ -427,7 +430,11 @@ public class FileManager implements Closeable { } catch (TskCoreException ex) { if (null != trans) { - trans.rollback(); + try { + trans.rollback(); + } catch (TskCoreException ex2) { + LOGGER.log(Level.SEVERE, String.format("Failed to rollback transaction after exception: %s", ex.getMessage()), ex2); + } } throw ex; } @@ -506,7 +513,7 @@ public class FileManager implements Closeable { * @throws TskCoreException If there is a problem completing a database * operation. */ - private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, + private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, TskData.EncodingType encodingType, FileAddProgressUpdater progressUpdater) throws TskCoreException { if (localFile.isDirectory()) { /* @@ -542,7 +549,7 @@ public class FileManager implements Closeable { public synchronized void close() throws IOException { caseDb = null; } - + /** * Adds a set of local/logical files and/or directories to the case database * as data source. @@ -624,7 +631,7 @@ public class FileManager implements Closeable { } return caseDb.addCarvedFiles(filesToAdd); } - + /** * Adds a derived file to the case. * @@ -652,7 +659,7 @@ public class FileManager implements Closeable { * * @throws TskCoreException if there is a problem adding the file to the * case database. - * + * * @Deprecated Use the version with explicit EncodingType instead */ @Deprecated @@ -663,10 +670,10 @@ public class FileManager implements Closeable { boolean isFile, AbstractFile parentFile, String rederiveDetails, String toolName, String toolVersion, String otherDetails) throws TskCoreException { - return addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile, parentFile, + return addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime, isFile, parentFile, rederiveDetails, toolName, toolVersion, otherDetails, TskData.EncodingType.NONE); } - + /** * Adds a file or directory of logical/local files data source to the case * database, recursively adding the contents of directories. @@ -686,7 +693,7 @@ public class FileManager implements Closeable { * * @throws TskCoreException If there is a problem completing a database * operation. - * + * * @Deprecated Use the version with explicit EncodingType instead */ @Deprecated diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.form new file mode 100755 index 0000000000..b6400b7c31 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.form @@ -0,0 +1,98 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java new file mode 100755 index 0000000000..fbb7594c44 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/NewTagNameDialog.java @@ -0,0 +1,247 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 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.casemodule.services; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.util.NbBundle; + +final class NewTagNameDialog extends javax.swing.JDialog { + + private static final long serialVersionUID = 1L; + private String userTagDisplayName; + private BUTTON_PRESSED result; + + enum BUTTON_PRESSED { + OK, CANCEL; + } + + /** + * Creates a new NewUserTagNameDialog dialog. + */ + NewTagNameDialog() { + super(new JFrame(NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.title.text")), + NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.title.text"), true); + initComponents(); + this.display(); + } + + /** + * Sets display settings for the dialog and adds appropriate listeners. + */ + private void display() { + setLayout(new BorderLayout()); + + /* + * Center the dialog + */ + Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize(); + int width = this.getSize().width; + int height = this.getSize().height; + setLocation((screenDimension.width - width) / 2, (screenDimension.height - height) / 2); + + /* + * Add a handler for when the dialog window is closed directly. + */ + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + doButtonAction(false); + } + }); + + /* + * Add a listener to enable the OK button when the text field changes. + */ + tagNameTextField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + fire(); + } + @Override + public void removeUpdate(DocumentEvent e) { + fire(); + } + @Override + public void insertUpdate(DocumentEvent e) { + fire(); + } + private void fire() { + enableOkButton(); + } + }); + + enableOkButton(); + + /* + * Used to show the dialog. + */ + setResizable(false); + setVisible(true); + } + + /** + * Called when a button is pressed or when the dialog is closed. + * @param okPressed whether the OK button was pressed. + */ + private void doButtonAction(boolean okPressed) { + if (okPressed) { + String newTagDisplayName = tagNameTextField.getText().trim(); + if (newTagDisplayName.isEmpty()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.message"), + NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameEmpty.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + if (TagsManager.containsIllegalCharacters(newTagDisplayName)) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.message"), + NbBundle.getMessage(NewTagNameDialog.class, "NewUserTagNameDialog.JOptionPane.tagNameIllegalCharacters.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + userTagDisplayName = newTagDisplayName; + result = BUTTON_PRESSED.OK; + } else { + result = BUTTON_PRESSED.CANCEL; + } + setVisible(false); + } + + /** + * Returns the tag name entered by the user. + * + * @return a new user tag name + */ + String getTagName() { + return userTagDisplayName; + } + + /** + * Returns information about which button was pressed. + * + * @return BUTTON_PRESSED (OK, CANCEL) + */ + BUTTON_PRESSED getResult() { + return result; + } + + /** + * Enable the OK button if the tag name text field is not empty. + * Sets the enter button as default, so user can press enter to activate + * an okButton press and add the tag name. + */ + private void enableOkButton() { + okButton.setEnabled(!tagNameTextField.getText().isEmpty()); + getRootPane().setDefaultButton(okButton); + } + + /** + * 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() { + + newTagNameLabel = new javax.swing.JLabel(); + tagNameTextField = new javax.swing.JTextField(); + cancelButton = new javax.swing.JButton(); + okButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + org.openide.awt.Mnemonics.setLocalizedText(newTagNameLabel, org.openide.util.NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.newTagNameLabel.text")); // NOI18N + + tagNameTextField.setText(org.openide.util.NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.tagNameTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.cancelButton.text")); // NOI18N + cancelButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cancelButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(NewTagNameDialog.class, "NewTagNameDialog.okButton.text")); // NOI18N + okButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + okButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tagNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 220, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(okButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cancelButton)) + .addComponent(newTagNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(newTagNameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tagNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(50, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cancelButton) + .addComponent(okButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed + doButtonAction(true); + }//GEN-LAST:event_okButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + doButtonAction(false); + }//GEN-LAST:event_cancelButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton cancelButton; + private javax.swing.JLabel newTagNameLabel; + private javax.swing.JButton okButton; + private javax.swing.JTextField tagNameTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefiniton.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefiniton.java new file mode 100644 index 0000000000..101d68fa4c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefiniton.java @@ -0,0 +1,180 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.casemodule.services; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.datamodel.TagName; + +/** + * A tag name definition consisting of a display name, description and color. + */ +@Immutable +final class TagNameDefiniton implements Comparable { + + private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS + private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS + private final String displayName; + private final String description; + private final TagName.HTML_COLOR color; + + /** + * Constructs a tag name definition consisting of a display name, + * description and color. + * + * @param displayName The display name for the tag name. + * @param description The description for the tag name. + * @param color The color for the tag name. + */ + TagNameDefiniton(String displayName, String description, TagName.HTML_COLOR color) { + this.displayName = displayName; + this.description = description; + this.color = color; + } + + /** + * Gets the display name for the tag name. + * + * @return The display name. + */ + String getDisplayName() { + return displayName; + } + + /** + * Gets the description for the tag name. + * + * @return The description. + */ + String getDescription() { + return description; + } + + /** + * Gets the color for the tag name. + * + * @return The color. + */ + TagName.HTML_COLOR getColor() { + return color; + } + + /** + * Compares this tag name definition with the specified tag name definition + * for order. + * + * @param other The tag name definition to which to compare this tag name + * definition. + * + * @return Negative integer, zero, or a positive integer to indicate that + * this tag name definition is less than, equal to, or greater than + * the specified tag name definition. + */ + @Override + public int compareTo(TagNameDefiniton other) { + return this.getDisplayName().toLowerCase().compareTo(other.getDisplayName().toLowerCase()); + } + + /** + * Returns a hash code value for this tag name definition. + * + * @return The has code. + */ + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + Objects.hashCode(this.displayName); + return hash; + } + + /** + * Indicates whether some other object is "equal to" this tag name + * definition. + * + * @param obj The object to test for equality. + * + * @return True or false. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TagNameDefiniton)) { + return false; + } + TagNameDefiniton thatTagName = (TagNameDefiniton) obj; + return this.getDisplayName().equals(thatTagName.getDisplayName()); + } + + /** + * A string representation of this tag name definition. + * + * @return The display name of the tag type. + */ + @Override + public String toString() { + return displayName; + } + + /** + * @return A string representation of the tag name definition in the format + * that is used by the tags settings file. + */ + private String toSettingsFormat() { + return displayName + "," + description + "," + color.name(); + } + + /** + * Gets tag name definitions from the tag settings file. + * + * @return A set of tag name definition objects. + */ + static synchronized Set getTagNameDefinitions() { + Set tagNames = new HashSet<>(); + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); + if (null != setting && !setting.isEmpty()) { + List tagNameTuples = Arrays.asList(setting.split(";")); + for (String tagNameTuple : tagNameTuples) { + String[] tagNameAttributes = tagNameTuple.split(","); + tagNames.add(new TagNameDefiniton(tagNameAttributes[0], tagNameAttributes[1], TagName.HTML_COLOR.valueOf(tagNameAttributes[2]))); + } + } + return tagNames; + } + + /** + * Sets the tag name definitions in the tag settings file. + * + * @param tagNames A set of tag name definition objects. + */ + static synchronized void setTagNameDefinitions(Set tagNames) { + StringBuilder setting = new StringBuilder(); + for (TagNameDefiniton tagName : tagNames) { + if (setting.length() != 0) { + setting.append(";"); + } + setting.append(tagName.toSettingsFormat()); + } + ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString()); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form new file mode 100755 index 0000000000..1f81b4d3aa --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.form @@ -0,0 +1,204 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java new file mode 100755 index 0000000000..4f761062e8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagOptionsPanel.java @@ -0,0 +1,272 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 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.casemodule.services; + +import java.util.Set; +import java.util.TreeSet; +import javax.swing.DefaultListModel; +import javax.swing.JOptionPane; +import javax.swing.event.ListSelectionEvent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.datamodel.TagName; + +/** + * A panel to allow the user to create and delete custom tag types. + */ +final class TagOptionsPanel extends javax.swing.JPanel implements OptionsPanel { + + private static final long serialVersionUID = 1L; + private static final String DEFAULT_DESCRIPTION = ""; + private static final TagName.HTML_COLOR DEFAULT_COLOR = TagName.HTML_COLOR.NONE; + private final DefaultListModel tagTypesListModel; + private Set tagTypes; + + /** + * Creates new form TagsManagerOptionsPanel + */ + TagOptionsPanel() { + tagTypesListModel = new DefaultListModel<>(); + tagTypes = new TreeSet<>(TagNameDefiniton.getTagNameDefinitions()); + initComponents(); + customizeComponents(); + } + + private void customizeComponents() { + tagNamesList.setModel(tagTypesListModel); + tagNamesList.addListSelectionListener((ListSelectionEvent event) -> { + enableButtons(); + }); + } + + /** + * 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() { + + jPanel1 = new javax.swing.JPanel(); + panelDescriptionLabel = new javax.swing.JLabel(); + jSplitPane1 = new javax.swing.JSplitPane(); + modifyTagTypesListPanel = new javax.swing.JPanel(); + tagTypesListLabel = new javax.swing.JLabel(); + jScrollPane1 = new javax.swing.JScrollPane(); + tagNamesList = new javax.swing.JList<>(); + newTagNameButton = new javax.swing.JButton(); + deleteTagNameButton = new javax.swing.JButton(); + tagTypesAdditionalPanel = new javax.swing.JPanel(); + + jPanel1.setPreferredSize(new java.awt.Dimension(750, 500)); + + org.openide.awt.Mnemonics.setLocalizedText(panelDescriptionLabel, org.openide.util.NbBundle.getMessage(TagOptionsPanel.class, "TagOptionsPanel.panelDescriptionLabel.text")); // NOI18N + + jSplitPane1.setDividerLocation(400); + jSplitPane1.setDividerSize(1); + + org.openide.awt.Mnemonics.setLocalizedText(tagTypesListLabel, org.openide.util.NbBundle.getMessage(TagOptionsPanel.class, "TagOptionsPanel.tagTypesListLabel.text")); // NOI18N + + tagNamesList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(tagNamesList); + + newTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add-tag.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newTagNameButton, org.openide.util.NbBundle.getMessage(TagOptionsPanel.class, "TagOptionsPanel.newTagNameButton.text")); // NOI18N + newTagNameButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newTagNameButtonActionPerformed(evt); + } + }); + + deleteTagNameButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete-tag.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteTagNameButton, org.openide.util.NbBundle.getMessage(TagOptionsPanel.class, "TagOptionsPanel.deleteTagNameButton.text")); // NOI18N + deleteTagNameButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteTagNameButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout modifyTagTypesListPanelLayout = new javax.swing.GroupLayout(modifyTagTypesListPanel); + modifyTagTypesListPanel.setLayout(modifyTagTypesListPanelLayout); + modifyTagTypesListPanelLayout.setHorizontalGroup( + modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(modifyTagTypesListPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tagTypesListLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(modifyTagTypesListPanelLayout.createSequentialGroup() + .addComponent(newTagNameButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deleteTagNameButton) + .addGap(0, 113, Short.MAX_VALUE)) + .addComponent(jScrollPane1)) + .addContainerGap()) + ); + modifyTagTypesListPanelLayout.setVerticalGroup( + modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(modifyTagTypesListPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(tagTypesListLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(modifyTagTypesListPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(newTagNameButton) + .addComponent(deleteTagNameButton)) + .addContainerGap()) + ); + + jSplitPane1.setLeftComponent(modifyTagTypesListPanel); + + javax.swing.GroupLayout tagTypesAdditionalPanelLayout = new javax.swing.GroupLayout(tagTypesAdditionalPanel); + tagTypesAdditionalPanel.setLayout(tagTypesAdditionalPanelLayout); + tagTypesAdditionalPanelLayout.setHorizontalGroup( + tagTypesAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 356, Short.MAX_VALUE) + ); + tagTypesAdditionalPanelLayout.setVerticalGroup( + tagTypesAdditionalPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 456, Short.MAX_VALUE) + ); + + jSplitPane1.setRightComponent(tagTypesAdditionalPanel); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jSplitPane1) + .addComponent(panelDescriptionLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(panelDescriptionLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSplitPane1) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 778, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void newTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagNameButtonActionPerformed + NewTagNameDialog dialog = new NewTagNameDialog(); + NewTagNameDialog.BUTTON_PRESSED result = dialog.getResult(); + if (result == NewTagNameDialog.BUTTON_PRESSED.OK) { + String newTagDisplayName = dialog.getTagName(); + TagNameDefiniton newTagType = new TagNameDefiniton(newTagDisplayName, DEFAULT_DESCRIPTION, DEFAULT_COLOR); + /* + * If tag name already exists, don't add the tag name. + */ + if (!tagTypes.contains(newTagType)) { + tagTypes.add(newTagType); + updateTagNamesListModel(); + tagNamesList.setSelectedValue(newTagType, true); + enableButtons(); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } else { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(TagOptionsPanel.class, "TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.message"), + NbBundle.getMessage(TagOptionsPanel.class, "TagNamesSettingsPanel.JOptionPane.tagNameAlreadyExists.title"), + JOptionPane.INFORMATION_MESSAGE); + } + } + }//GEN-LAST:event_newTagNameButtonActionPerformed + + private void deleteTagNameButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTagNameButtonActionPerformed + TagNameDefiniton tagName = tagNamesList.getSelectedValue(); + tagTypes.remove(tagName); + updateTagNamesListModel(); + enableButtons(); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + }//GEN-LAST:event_deleteTagNameButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton deleteTagNameButton; + private javax.swing.JPanel jPanel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JSplitPane jSplitPane1; + private javax.swing.JPanel modifyTagTypesListPanel; + private javax.swing.JButton newTagNameButton; + private javax.swing.JLabel panelDescriptionLabel; + private javax.swing.JList tagNamesList; + private javax.swing.JPanel tagTypesAdditionalPanel; + private javax.swing.JLabel tagTypesListLabel; + // End of variables declaration//GEN-END:variables + + /** + * Updates the tag names model for the tag names list component. + */ + private void updateTagNamesListModel() { + tagTypesListModel.clear(); + for (TagNameDefiniton tagName : tagTypes) { + tagTypesListModel.addElement(tagName); + } + } + + /** + * Loads the stored custom tag types. + */ + @Override + public void load() { + tagTypes = new TreeSet<>(TagNameDefiniton.getTagNameDefinitions()); + updateTagNamesListModel(); + enableButtons(); + } + + /** + * Stores the custom tag types. + */ + @Override + public void store() { + TagNameDefiniton.setTagNameDefinitions(tagTypes); + } + + /** + * Enables the button components based on the state of the tag types list + * component. + */ + private void enableButtons() { + /* + * Only enable the delete button when there is a tag type selected in + * the tag types JList. + */ + deleteTagNameButton.setEnabled(tagNamesList.getSelectedIndex() != -1); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index e8d61bdeba..46982397f8 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -22,12 +22,14 @@ import java.io.Closeable; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; @@ -37,23 +39,20 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * A per case Autopsy service that manages the creation, updating, and deletion - * of tags applied to content and blackboard artifacts by users. + * A per case Autopsy service that manages the addition of content and artifact + * tags to the case database. */ public class TagsManager implements Closeable { - private static final Logger logger = Logger.getLogger(TagsManager.class.getName()); - private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS - private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS - private SleuthkitCase caseDb; - private final HashMap uniqueTagNames = new HashMap<>(); - private boolean tagNamesLoaded = false; + private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); + @NbBundle.Messages("TagsManager.predefTagNames.bookmark.text=Bookmark") + private static final Set STANDARD_TAG_DISPLAY_NAMES = new HashSet<>(Arrays.asList(Bundle.TagsManager_predefTagNames_bookmark_text())); + private final SleuthkitCase caseDb; /** - * Constructs a per case Autopsy service that manages the creation, - * updating, and deletion of tags applied to content and blackboard - * artifacts by users. - * + * Constructs a per case Autopsy service that manages the addition of + * content and artifact tags to the case database. + * * @param caseDb The case database. */ TagsManager(SleuthkitCase caseDb) { @@ -61,145 +60,152 @@ public class TagsManager implements Closeable { } /** - * Gets a list of all tag names currently available for tagging content or - * artifacts. + * Gets a list of all tag names currently in the case database. * - * @return A list, possibly empty, of TagName data transfer objects (DTOs). + * @return A list, possibly empty, of TagName objects. * - * @throws TskCoreException If there is an error reading from the case - * database. + * @throws TskCoreException If there is an error querying the case database. */ - public synchronized List getAllTagNames() throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); + public List getAllTagNames() throws TskCoreException { return caseDb.getAllTagNames(); } /** - * Gets a list of all tag names currently in use for tagging content or - * artifacts. + * Gets a list of all tag names currently in use in the case database for + * tagging content or artifacts. * - * @return A list, possibly empty, of TagName data transfer objects (DTOs). + * @return A list, possibly empty, of TagName objects. * - * @throws TskCoreException If there is an error reading from the case - * database. + * @throws TskCoreException If there is an error querying the case database. */ - public synchronized List getTagNamesInUse() throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); + public List getTagNamesInUse() throws TskCoreException { return caseDb.getTagNamesInUse(); } /** - * Checks whether a tag name with a given display name exists. + * Gets a map of tag display names to tag name entries in the case database. + * It has keys for the display names of the standard tag types, the current + * user's custom tag types, and the tags in the case database. The value for + * a given key will be null if the corresponding tag type is defined, but a + * tag name entry has not yet added to the case database. In that case, + * addTagName may be called to add the tag name entry. * - * @param tagDisplayName The display name to check. + * @return A map of tag display names to possibly null TagName object + * references. * - * @return True or false. + * @throws TskCoreException if there is an error querying the case database. */ - public synchronized boolean tagNameExists(String tagDisplayName) { - lazyLoadExistingTagNames(); - return uniqueTagNames.containsKey(tagDisplayName); + public synchronized Map getDisplayNamesToTagNamesMap() throws TskCoreException { + /** + * Order is important here. The keys (display names) for the standard + * tag types and current user's custom tag types are added to the map + * first, with null TagName values. If tag name entries exist for those + * keys, loading of the tag names from the database supplies the missing + * values. + * + * Note that creating the map on demand increases the probability that + * the display names of newly added custom tag types and the display + * names of tags added to a multi-user case by other users appear in the + * map. + */ + Map tagNames = new HashMap<>(); + tagNames.put(NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"), null); + Set customTypes = TagNameDefiniton.getTagNameDefinitions(); + for (TagNameDefiniton tagType : customTypes) { + tagNames.put(tagType.getDisplayName(), null); + } + for (TagName tagName : caseDb.getAllTagNames()) { + tagNames.put(tagName.getDisplayName(), tagName); + } + return new HashMap<>(tagNames); } /** - * Adds a new tag name to the current case and to the tags settings. + * Adds a tag name entry to the case database and adds a corresponding tag + * type to the current user's custom tag types. * - * @param displayName The display name for the new tag name. + * @param displayName The display name for the new tag type. * - * @return A TagName data transfer object (DTO) representing the new tag - * name. + * @return A TagName representing the tag name database entry that can be + * used to add instances of the tag type to the case database. * - * @throws TagNameAlreadyExistsException If the tag name would be a - * duplicate. + * @throws TagNameAlreadyExistsException If the tag name already exists in + * the case database. * @throws TskCoreException If there is an error adding the tag - * to the case database. + * name to the case database. */ - public TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } + public synchronized TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException { return addTagName(displayName, "", TagName.HTML_COLOR.NONE); } /** - * Adds a new tag name to the current case and to the tags settings. + * Adds a tag name entry to the case database and adds a corresponding tag + * type to the current user's custom tag types. * - * @param displayName The display name for the new tag name. - * @param description The description for the new tag name. + * @param displayName The display name for the new tag type. + * @param description The description for the new tag type. * - * @return A TagName data transfer object (DTO) representing the new tag - * name. + * @return A TagName object that can be used to add instances of the tag + * type to the case database. * - * @throws TagNameAlreadyExistsException If the tag name would be a - * duplicate. + * @throws TagNameAlreadyExistsException If the tag name already exists in + * the case database. * @throws TskCoreException If there is an error adding the tag - * to the case database. + * name to the case database. */ - public TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } + public synchronized TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException { return addTagName(displayName, description, TagName.HTML_COLOR.NONE); } /** - * Adds a new tag name to the current case and to the tags settings. + * Adds a tag name entry to the case database and adds a corresponding tag + * type to the current user's custom tag types. * - * @param displayName The display name for the new tag name. - * @param description The description for the new tag name. - * @param color The HTML color to associate with the new tag name. + * @param displayName The display name for the new tag type. + * @param description The description for the new tag type. + * @param color The color to associate with the new tag type. * - * @return A TagName data transfer object (DTO) representing the new tag - * name. + * @return A TagName object that can be used to add instances of the tag + * type to the case database. * - * @throws TagNameAlreadyExistsException If the tag name would be a - * duplicate. + * @throws TagNameAlreadyExistsException If the tag name already exists. * @throws TskCoreException If there is an error adding the tag - * to the case database. + * name to the case database. */ public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); + try { + TagName tagName = caseDb.addTagName(displayName, description, color); + if (!STANDARD_TAG_DISPLAY_NAMES.contains(displayName)) { + Set customTypes = TagNameDefiniton.getTagNameDefinitions(); + customTypes.add(new TagNameDefiniton(displayName, description, color)); + TagNameDefiniton.setTagNameDefinitions(customTypes); + } + return tagName; + } catch (TskCoreException ex) { + List existingTagNames = caseDb.getAllTagNames(); + for (TagName tagName : existingTagNames) { + if (tagName.getDisplayName().equals(displayName)) { + throw new TagNameAlreadyExistsException(); + } + } + throw ex; } - lazyLoadExistingTagNames(); - if (uniqueTagNames.containsKey(displayName)) { - throw new TagNameAlreadyExistsException(); - } - - /* - * Add the tag name to the case. - */ - TagName newTagName = caseDb.addTagName(displayName, description, color); - - /* - * Add the tag name to the tags settings. - */ - uniqueTagNames.put(newTagName.getDisplayName(), newTagName); - saveTagNamesToTagsSettings(); - - return newTagName; } /** * Tags a content object. * * @param content The content to tag. - * @param tagName The name to use for the tag. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * - * @return A ContentTag data transfer object (DTO) representing the new tag. + * @return A ContentTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } return addContentTag(content, tagName, "", -1, -1); } @@ -207,18 +213,17 @@ public class TagsManager implements Closeable { * Tags a content object. * * @param content The content to tag. - * @param tagName The name to use for the tag. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * @param comment A comment to store with the tag. * - * @return A ContentTag data transfer object (DTO) representing the new tag. + * @return A ContentTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } return addContentTag(content, tagName, comment, -1, -1); } @@ -226,56 +231,25 @@ public class TagsManager implements Closeable { * Tags a content object or a section of a content object. * * @param content The content to tag. - * @param tagName The name to use for the tag. + * @param tagName The representation of the desired tag type in the + * case database, which can be obtained by calling + * getTagNames and/or addTagName. * @param comment A comment to store with the tag. * @param beginByteOffset Designates the beginning of a tagged section. * @param endByteOffset Designates the end of a tagged section. * - * @return A ContentTag data transfer object (DTO) representing the new tag. + * @return A ContentTag object representing the new tag. * - * @throws IllegalArgumentException If a requested byte offset is out of - * range. - * @throws TskCoreException If there is an error adding the tag to - * the case database. + * @throws TskCoreException If there is an error adding the tag to the case + * database. */ - public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } + public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws TskCoreException { ContentTag tag; - synchronized (this) { - lazyLoadExistingTagNames(); - - if (null == comment) { - throw new IllegalArgumentException("Passed null comment argument"); - } - - if (beginByteOffset >= 0 && endByteOffset >= 1) { - if (beginByteOffset > content.getSize() - 1) { - throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(), - "TagsManager.addContentTag.exception.beginByteOffsetOOR.msg", - beginByteOffset, content.getSize() - 1)); - } - - if (endByteOffset > content.getSize() - 1) { - throw new IllegalArgumentException( - NbBundle.getMessage(this.getClass(), "TagsManager.addContentTag.exception.endByteOffsetOOR.msg", - endByteOffset, content.getSize() - 1)); - } - - if (endByteOffset < beginByteOffset) { - throw new IllegalArgumentException( - NbBundle.getMessage(this.getClass(), "TagsManager.addContentTag.exception.endLTbegin.msg")); - } - } - - tag = caseDb.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); - } - + tag = caseDb.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); try { Case.getCurrentCase().notifyContentTagAdded(tag); } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(TagsManager.class, "TagsManager.addContentTag.noCaseWarning"), ex); + LOGGER.log(Level.SEVERE, "Added a tag to a closed case", ex); } return tag; } @@ -289,18 +263,11 @@ public class TagsManager implements Closeable { * case database. */ public void deleteContentTag(ContentTag tag) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - synchronized (this) { - lazyLoadExistingTagNames(); - caseDb.deleteContentTag(tag); - } - + caseDb.deleteContentTag(tag); try { Case.getCurrentCase().notifyContentTagDeleted(tag); } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteContentTag.noCaseWarning"), ex); + LOGGER.log(Level.SEVERE, "Deleted a tag from a closed case", ex); } } @@ -313,17 +280,15 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getAllContentTags() throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getAllContentTags(); } /** * Gets content tags count by tag name. * - * @param tagName The tag name of interest. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * * @return A count of the content tags with the specified tag name. * @@ -331,29 +296,21 @@ public class TagsManager implements Closeable { * the case database. */ public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getContentTagsCountByTagName(tagName); } /** * Gets a content tag by tag id. * - * @param tagID The tag id of interest. + * @param tagId The tag id of interest. * * @return The content tag with the specified tag id. * * @throws TskCoreException If there is an error getting the tag from the * case database. */ - public synchronized ContentTag getContentTagByTagID(long tagID) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); - return caseDb.getContentTagByID(tagID); + public synchronized ContentTag getContentTagByTagID(long tagId) throws TskCoreException { + return caseDb.getContentTagByID(tagId); } /** @@ -368,10 +325,6 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getContentTagsByTagName(TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getContentTagsByTagName(tagName); } @@ -381,172 +334,136 @@ public class TagsManager implements Closeable { * @param content The content of interest. * * @return A list, possibly empty, of the tags that have been applied to the - * artifact. + * content. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List getContentTagsByContent(Content content) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getContentTagsByContent(content); } /** - * Tags a blackboard artifact object. + * Tags an artifact. * - * @param artifact The blackboard artifact to tag. - * @param tagName The name to use for the tag. + * @param artifact The artifact to tag. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * - * @return A BlackboardArtifactTag data transfer object (DTO) representing - * the new tag. + * @return A BlackboardArtifactTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ - public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } + public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException { return addBlackboardArtifactTag(artifact, tagName, ""); } /** - * Tags a blackboard artifact object. + * Tags an artifact. * - * @param artifact The blackboard artifact to tag. - * @param tagName The name to use for the tag. + * @param artifact The artifact to tag. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * @param comment A comment to store with the tag. * - * @return A BlackboardArtifactTag data transfer object (DTO) representing - * the new tag. + * @return A BlackboardArtifactTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ - public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - BlackboardArtifactTag tag; - synchronized (this) { - lazyLoadExistingTagNames(); - if (null == comment) { - throw new IllegalArgumentException("Passed null comment argument"); - } - tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment); - } - + public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { + BlackboardArtifactTag tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment); try { Case.getCurrentCase().notifyBlackBoardArtifactTagAdded(tag); } catch (IllegalStateException ex) { - logger.log(Level.SEVERE, NbBundle.getMessage(TagsManager.class, "TagsManager.addBlackboardArtifactTag.noCaseWarning"), ex); + LOGGER.log(Level.SEVERE, "Added a tag to a closed case", ex); } return tag; } /** - * Deletes a blackboard artifact tag. + * Deletes an artifact tag. * * @param tag The tag to delete. * * @throws TskCoreException If there is an error deleting the tag from the * case database. */ - public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - synchronized (this) { - lazyLoadExistingTagNames(); - caseDb.deleteBlackboardArtifactTag(tag); - } - + public synchronized void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { + caseDb.deleteBlackboardArtifactTag(tag); try { Case.getCurrentCase().notifyBlackBoardArtifactTagDeleted(tag); } catch (IllegalStateException ex) { - logger.log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteBlackboardArtifactTag.noCaseWarning"), ex); + LOGGER.log(Level.SEVERE, "Deleted a tag from a closed case", ex); } } /** - * Gets all blackboard artifact tags for the current case. + * Gets all artifact tags for the current case. * - * @return A list, possibly empty, of blackboard artifact tags. + * @return A list, possibly empty, of artifact tags. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List getAllBlackboardArtifactTags() throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getAllBlackboardArtifactTags(); } /** - * Gets blackboard artifact tags count by tag name. + * Gets an artifact tags count by tag name. * - * @param tagName The tag name of interest. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * - * @return A count of the blackboard artifact tags with the specified tag - * name. + * @return A count of the artifact tags with the specified tag name. * * @throws TskCoreException If there is an error getting the tags count from * the case database. */ public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } /** - * Gets a blackboard artifact tag by tag id. + * Gets an artifact tag by tag id. * - * @param tagID The tag id of interest. + * @param tagId The tag id of interest. * - * @return the blackboard artifact tag with the specified tag id. + * @return The artifact tag with the specified tag id. * * @throws TskCoreException If there is an error getting the tag from the * case database. */ - public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagID) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); - return caseDb.getBlackboardArtifactTagByID(tagID); + public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagId) throws TskCoreException { + return caseDb.getBlackboardArtifactTagByID(tagId); } /** - * Gets blackboard artifact tags by tag name. + * Gets artifact tags by tag name. * - * @param tagName The tag name of interest. + * @param tagName The representation of the desired tag type in the case + * database, which can be obtained by calling getTagNames + * and/or addTagName. * - * @return A list, possibly empty, of the blackboard artifact tags with the - * specified tag name. + * @return A list, possibly empty, of the artifact tags with the specified + * tag name. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsByTagName(tagName); } /** - * Gets blackboard artifact tags for a particular blackboard artifact. + * Gets artifact tags for a particular artifact. * - * @param artifact The blackboard artifact of interest. + * @param artifact The artifact of interest. * * @return A list, possibly empty, of the tags that have been applied to the * artifact. @@ -555,115 +472,29 @@ public class TagsManager implements Closeable { * case database. */ public synchronized List getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException { - if (null == caseDb) { - throw new TskCoreException("Tags manager has been closed"); - } - lazyLoadExistingTagNames(); return caseDb.getBlackboardArtifactTagsByArtifact(artifact); } /** - * Closes the tags manager, saving the avaialble tag names to secondary - * storage. + * Returns true if the tag display name contains an illegal character. Used + * after a tag display name is retrieved from user input. * - * @throws IOException If there is a problem closing the tags manager. - * @deprecated Tags manager clients should not close the tags manager. + * @param content Display name of the tag being added. + * + * @return boolean indicating whether the name has an invalid character. */ - @Override - @Deprecated - public synchronized void close() throws IOException { - saveTagNamesToTagsSettings(); - caseDb = null; - } + public static boolean containsIllegalCharacters(String content) { + return (content.contains("\\") + || content.contains(":") + || content.contains("*") + || content.contains("?") + || content.contains("\"") + || content.contains("<") + || content.contains(">") + || content.contains("|") + || content.contains(",") + || content.contains(";")); - /** - * Populates the tag names collection and the tag names table in the case - * database with the existing tag names from all sources. - */ - private void lazyLoadExistingTagNames() { - if (!tagNamesLoaded) { - addTagNamesFromCurrentCase(); - addTagNamesFromTagsSettings(); - addPredefinedTagNames(); - saveTagNamesToTagsSettings(); - tagNamesLoaded = true; - } - } - - /** - * Adds any tag names that are in the case database to the tag names - * collection. - */ - private void addTagNamesFromCurrentCase() { - try { - List currentTagNames = caseDb.getAllTagNames(); - for (TagName tagName : currentTagNames) { - uniqueTagNames.put(tagName.getDisplayName(), tagName); - } - } catch (TskCoreException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag types from the current case", ex); //NON-NLS - } - } - - /** - * Adds any tag names that are in the properties file to the tag names - * collection and to the case database. The properties file is used to make - * it possible to use tag names across cases. - */ - private void addTagNamesFromTagsSettings() { - String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY); - if (null != setting && !setting.isEmpty()) { - // Read the tag name setting and break it into tag name tuples. - List tagNameTuples = Arrays.asList(setting.split(";")); - - // Parse each tuple and add the tag names to the current case, one - // at a time to gracefully discard any duplicates or corrupt tuples. - for (String tagNameTuple : tagNameTuples) { - String[] tagNameAttributes = tagNameTuple.split(","); - if (!uniqueTagNames.containsKey(tagNameAttributes[0])) { - try { - TagName tagName = caseDb.addTagName(tagNameAttributes[0], tagNameAttributes[1], TagName.HTML_COLOR.getColorByName(tagNameAttributes[2])); - uniqueTagNames.put(tagName.getDisplayName(), tagName); - } catch (TskCoreException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add saved tag name " + tagNameAttributes[0], ex); //NON-NLS - } - } - } - } - } - - /** - * Adds the standard tag names to the tag names collection. - */ - private void addPredefinedTagNames() { - if (!uniqueTagNames.containsKey(NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"))) { - try { - TagName tagName = caseDb.addTagName( - NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"), "", TagName.HTML_COLOR.NONE); - uniqueTagNames.put(tagName.getDisplayName(), tagName); - } catch (TskCoreException ex) { - Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add standard 'Bookmark' tag name to case database", ex); //NON-NLS - } - } - } - - /** - * Saves the tag names to a properties file. The properties file is used to - * make it possible to use tag names across cases. - */ - private void saveTagNamesToTagsSettings() { - if (!uniqueTagNames.isEmpty()) { - StringBuilder setting = new StringBuilder(); - for (TagName tagName : uniqueTagNames.values()) { - if (setting.length() != 0) { - setting.append(";"); - } - setting.append(tagName.getDisplayName()).append(","); - setting.append(tagName.getDescription()).append(","); - setting.append(tagName.getColor().name()); - } - ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString()); - } } /** @@ -674,4 +505,36 @@ public class TagsManager implements Closeable { private static final long serialVersionUID = 1L; } + /** + * Checks whether a tag name with a given display name exists in the case + * database. + * + * @param tagDisplayName The display name. + * + * @return True or false. + * + * @deprecated Not reliable for multi-user cases. + */ + @Deprecated + public synchronized boolean tagNameExists(String tagDisplayName) { + try { + Map tagNames = getDisplayNamesToTagNamesMap(); + return tagNames.containsKey(tagDisplayName) && (tagNames.get(tagDisplayName) != null); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error querying case database for tag names", ex); + return false; + } + } + + /** + * Closes the tags manager. + * + * @throws IOException If there is a problem closing the tags manager. + * @deprecated Tags manager clients should not close the tags manager. + */ + @Override + @Deprecated + public synchronized void close() throws IOException { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java new file mode 100755 index 0000000000..3d36e8ae00 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsOptionsPanelController.java @@ -0,0 +1,128 @@ +/* +* Autopsy Forensic Browser +* +* Copyright 2011-2016 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.casemodule.services; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +@OptionsPanelController.TopLevelRegistration( + categoryName = "#OptionsCategory_Name_TagNamesOptions", + iconBase = "org/sleuthkit/autopsy/casemodule/services/tag-options-panel-icon.png", + keywords = "#OptionsCategory_TagNames", + keywordsCategory = "CustomTagNames", + position = 8 +) +public final class TagsOptionsPanelController extends OptionsPanelController { + + private TagOptionsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + + /** + * Component should load its data here. + */ + @Override + public void update() { + getPanel().load(); + changed = false; + } + + /** + * This method is called when both the Ok and Apply buttons are pressed. It + * applies to any of the panels that have been opened in the process of + * using the options pane. + */ + @Override + public void applyChanges() { + if (changed) { + getPanel().store(); + changed = false; + } + } + + /** + * This method is called when the Cancel button is pressed. It applies to + * any of the panels that have been opened in the process of using the + * options pane. + */ + @Override + public void cancel() { + } + + @Override + public boolean isValid() { + return true; + } + + /** + * Used to determine whether any changes have been made to this controller's + * panel. + * + * @return Whether or not a change has been made. + */ + @Override + public boolean isChanged() { + return changed; + } + + @Override + public JComponent getComponent(Lookup lkp) { + return getPanel(); + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + private TagOptionsPanel getPanel() { + if (panel == null) { + panel = new TagOptionsPanel(); + panel.addPropertyChangeListener((PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) { + changed(); + } + }); + } + return panel; + } + + void changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/tag-options-panel-icon.png b/Core/src/org/sleuthkit/autopsy/casemodule/services/tag-options-panel-icon.png new file mode 100755 index 0000000000..b782cfe010 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/casemodule/services/tag-options-panel-icon.png differ diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 990d83eee5..08a1f26b14 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +61,7 @@ import org.sleuthkit.datamodel.TskCoreException; //@ServiceProvider(service = DataResultViewer.class) final class DataResultViewerThumbnail extends AbstractDataResultViewer { + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); //flag to keep track if images are being loaded private int curPage; @@ -95,7 +96,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel( + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( new String[] { Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large() })); @@ -395,11 +396,8 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void switchPage() { - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - } + EventQueue.invokeLater(() -> { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); }); //Note the nodes factories are likely creating nodes in EDT anyway, but worker still helps @@ -437,7 +435,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); DialogDisplayer.getDefault().notify(d); - logger.log(Level.SEVERE, "Error making thumbnails: " + ex.getMessage()); //NON-NLS + logger.log(Level.SEVERE, "Error making thumbnails: {0}", ex.getMessage()); //NON-NLS } // catch and ignore if we were cancelled catch (java.util.concurrent.CancellationException ex) { } @@ -453,6 +451,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { goToPageField.setEnabled(false); pageNumLabel.setText(""); imagesRangeLabel.setText(""); + thumbnailSizeComboBox.setEnabled(false); } else { pageNumLabel.setText( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", @@ -464,7 +463,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { pageNextButton.setEnabled(!(curPage == totalPages)); pagePrevButton.setEnabled(!(curPage == 1)); goToPageField.setEnabled(totalPages > 1); - + thumbnailSizeComboBox.setEnabled(true); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 701367e9ba..891361ccec 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -22,6 +22,12 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles; +import org.sleuthkit.autopsy.datamodel._private.Accounts; +import org.sleuthkit.autopsy.datamodel._private.Accounts.AccountsRootNode; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java deleted file mode 100644 index 903d0bf3ed..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Accounts.java +++ /dev/null @@ -1,1304 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2016 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import com.google.common.collect.Range; -import com.google.common.collect.RangeMap; -import com.google.common.collect.TreeRangeMap; -import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.IOException; -import java.io.InputStreamReader; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Observable; -import java.util.Observer; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nonnull; -import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.Immutable; -import javax.swing.AbstractAction; -import javax.swing.Action; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; -import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; -import org.openide.util.Utilities; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * AutopsyVisitableItem for the Accounts section of the tree. All nodes, - * factories, and data objects related to accounts are inner classes. - */ -public class Accounts extends Observable implements AutopsyVisitableItem { - - private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName()); - private static final BlackboardArtifact.Type CREDIT_CARD_ACCOUNT_TYPE = new BlackboardArtifact.Type(TSK_CREDIT_CARD_ACCOUNT); - @NbBundle.Messages("AccountsRootNode.name=Accounts") - final public static String NAME = Bundle.AccountsRootNode_name(); - - /** - * Range Map from a (ranges of) B/IINs to data model object with details of - * the B/IIN, ie, bank name, phone, url, visa/amex/mastercard/..., - */ - @GuardedBy("Accounts.class") - private final static RangeMap iinRanges = TreeRangeMap.create(); - - /** - * Flag for if we have loaded the IINs from the file already. - */ - @GuardedBy("Accounts.class") - private static boolean iinsLoaded = false; - - private SleuthkitCase skCase; - - /** - * Should rejected accounts be shown in the accounts section of the tree. - */ - private boolean showRejected = false; - - /** - * Load the IIN range information from disk. If the map has already been - * initialized, don't load again. - */ - synchronized private static void loadIINRanges() { - if (iinsLoaded == false) { - try { - InputStreamReader in = new InputStreamReader(Accounts.class.getResourceAsStream("ranges.csv")); //NON-NLS - CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in); - - //parse each row and add to range map - for (CSVRecord record : rangesParser) { - - /** - * Because ranges.csv allows both 6 and (the newer) 8 digit - * IINs, but we need a consistent length for the range map, - * we pad all the numbers out to 8 digits - */ - String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS - - //if there is no end listed, use start, since ranges will be closed. - String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS - end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS - - final String numberLength = record.get("number_length"); //NON-NLS - - try { - IINRange iinRange = new IINRange(Integer.parseInt(start), - Integer.parseInt(end), - StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength), - record.get("scheme"), //NON-NLS - record.get("brand"), //NON-NLS - record.get("type"), //NON-NLS - record.get("country"), //NON-NLS - record.get("bank_name"), //NON-NLS - record.get("bank_url"), //NON-NLS - record.get("bank_phone"), //NON-NLS - record.get("bank_city")); //NON-NLS - - iinRanges.put(Range.closed(iinRange.getIINstart(), iinRange.getIINend()), iinRange); - - } catch (NumberFormatException numberFormatException) { - LOGGER.log(Level.WARNING, "Failed to parse IIN range: " + record.toString(), numberFormatException); //NON-NLS - } - iinsLoaded = true; - } - } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to load IIN ranges form ranges.csv", ex); //NON-NLS - MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information. Accounts will not have their BINs identified."); - } - } - } - - private final RejectAccounts rejectActionInstance; - private final ApproveAccounts approveActionInstance; - - /** - * Constructor - * - * @param skCase The SleuthkitCase object to use for db queries. - */ - Accounts(SleuthkitCase skCase) { - this.skCase = skCase; - - this.rejectActionInstance = new RejectAccounts(); - this.approveActionInstance = new ApproveAccounts(); - } - - /** - * Get the clause that should be used in order to (not) filter out rejected - * results from db queries. - * - * @return A clause that will or will not filter out rejected artifacts - * based on the state of showRejected. - */ - private String getRejectedArtifactFilterClause() { - return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID(); //NON-NLS - } - - /** - * Notify all observers that something has changed, causing a refresh of the - * accounts section of the tree. - */ - private void update() { - setChanged(); - notifyObservers(); - } - - /** - * Get an IINInfo object with details about the given IIN - * - * @param iin the IIN to get details of. - * - * @return - */ - synchronized static public IINInfo getIINInfo(int iin) { - loadIINRanges(); - return iinRanges.get(iin); - } - - @Override - public T accept(AutopsyItemVisitor v) { - return v.visit(this); - } - - /** - * Gets a new Action that when invoked toggles showing rejected artifacts on - * or off. - * - * @return An Action that will toggle whether rejected artifacts are shown - * in the tree rooted by this Accounts instance. - */ - public Action newToggleShowRejectedAction() { - return new ToggleShowRejected(); - } - - //Interface for objects that provide details about one or more IINs. - static public interface IINInfo { - - /** - * Get the city of the issuer. - * - * @return the city of the issuer. - */ - Optional getBankCity(); - - /** - * Get the name of the issuer. - * - * @return the name of the issuer. - */ - Optional getBankName(); - - /** - * Get the phone number of the issuer. - * - * @return the phone number of the issuer. - */ - Optional getBankPhoneNumber(); - - /** - * Get the URL of the issuer. - * - * @return the URL of the issuer. - */ - Optional getBankURL(); - - /** - * Get the brand of this IIN range. - * - * @return the brand of this IIN range. - */ - Optional getBrand(); - - /** - * Get the type of card (credit vs debit) for this IIN range. - * - * @return the type of cards in this IIN range. - */ - Optional getCardType(); - - /** - * Get the country of the issuer. - * - * @return the country of the issuer. - */ - Optional getCountry(); - - /** - * Get the length of account numbers in this IIN range. - * - * NOTE: the length is currently unused, and not in the data file for - * any ranges. It could be quite helpfull for validation... - * - * @return the length of account numbers in this IIN range. Or an empty - * Optional if the length is unknown. - * - */ - Optional getNumberLength(); - - /** - * Get the scheme this IIN range uses to, eg amex,visa,mastercard, etc - * - * @return the scheme this IIN range uses. - */ - Optional getScheme(); - } - - /** - * Details of a range of Issuer/Bank Identifiaction Number(s) (IIN/BIN) used - * by a bank. - */ - @Immutable - static private class IINRange implements IINInfo { - - private final int IINStart; //start of IIN range, 8 digits - private final int IINEnd; // end (incluse ) of IIN rnage, 8 digits - - private final Integer numberLength; // the length of accounts numbers with this IIN, currently unused - - /** - * AMEX, VISA, MASTERCARD, DINERS, DISCOVER, UNIONPAY - */ - private final String scheme; - private final String brand; - - /** - * DEBIT, CREDIT - */ - private final String cardType; - private final String country; - private final String bankName; - private final String bankCity; - private final String bankURL; - private final String bankPhoneNumber; - - /** - * Constructor - * - * @param IIN_start the first IIN in the range, must be 8 digits - * @param IIN_end the last(inclusive) IIN in the range, must be 8 - * digits - * @param number_length the length of account numbers in this IIN range - * @param scheme amex/visa/mastercard/etc - * @param brand the brand of this IIN range - * @param type credit vs debit - * @param country the country of the issuer - * @param bank_name the name of the issuer - * @param bank_url the url of the issuer - * @param bank_phone the phone number of the issuer - * @param bank_city the city of the issuer - */ - private IINRange(int IIN_start, int IIN_end, Integer number_length, String scheme, String brand, String type, String country, String bank_name, String bank_url, String bank_phone, String bank_city) { - this.IINStart = IIN_start; - this.IINEnd = IIN_end; - - this.numberLength = number_length; - this.scheme = StringUtils.defaultIfBlank(scheme, null); - this.brand = StringUtils.defaultIfBlank(brand, null); - this.cardType = StringUtils.defaultIfBlank(type, null); - this.country = StringUtils.defaultIfBlank(country, null); - this.bankName = StringUtils.defaultIfBlank(bank_name, null); - this.bankURL = StringUtils.defaultIfBlank(bank_url, null); - this.bankPhoneNumber = StringUtils.defaultIfBlank(bank_phone, null); - this.bankCity = StringUtils.defaultIfBlank(bank_city, null); - } - - /** - * Get the first IIN in this range - * - * @return the first IIN in this range. - */ - int getIINstart() { - return IINStart; - } - - /** - * Get the last (inclusive) IIN in this range. - * - * @return the last (inclusive) IIN in this range. - */ - int getIINend() { - return IINEnd; - } - - @Override - public Optional getNumberLength() { - return Optional.ofNullable(numberLength); - } - - @Override - public Optional getScheme() { - return Optional.ofNullable(scheme); - } - - @Override - public Optional getBrand() { - return Optional.ofNullable(brand); - } - - @Override - public Optional getCardType() { - return Optional.ofNullable(cardType); - } - - @Override - public Optional getCountry() { - return Optional.ofNullable(country); - } - - @Override - public Optional getBankName() { - return Optional.ofNullable(bankName); - } - - @Override - public Optional getBankURL() { - return Optional.ofNullable(bankURL); - } - - @Override - public Optional getBankPhoneNumber() { - return Optional.ofNullable(bankPhoneNumber); - } - - @Override - public Optional getBankCity() { - return Optional.ofNullable(bankCity); - } - } - - /** - * Base class for factories that are also observers. - * - * @param The type of keys used by this factory. - */ - private abstract class ObservingChildFactory extends ChildFactory.Detachable implements Observer { - - @Override - public void update(Observable o, Object arg) { - refresh(true); - } - - @Override - protected void removeNotify() { - super.removeNotify(); - Accounts.this.deleteObserver(this); - } - - @Override - protected void addNotify() { - super.addNotify(); - Accounts.this.addObserver(this); - } - } - - /** - * Top-level node for the accounts tree - */ - @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"}) - public class AccountsRootNode extends DisplayableItemNode { - - AccountsRootNode() { - super(Children.create(new AccountTypeFactory(), true), Lookups.singleton(Accounts.this)); - super.setName(Accounts.NAME); - super.setDisplayName(Bundle.Accounts_RootNode_displayName()); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account_menu.png"); //NON-NLS - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - } - - /** - * Creates child nodes for each account type (currently hard coded to make - * one for Credit Cards) - */ - private class AccountTypeFactory extends ObservingChildFactory { - - /* - * The pcl is in this class because it has the easiest mechanisms to add - * and remove itself during its life cycles. - */ - private final PropertyChangeListener pcl = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { - /** - * Checking for a current case is a stop gap measure until a - * different way of handling the closing of cases is worked - * out. Currently, remote events may be received for a case - * that is already closed. - */ - try { - Case.getCurrentCase(); - /** - * Even with the check above, it is still possible that - * the case will be closed in a different thread before - * this code executes. If that happens, it is possible - * for the event to have a null oldValue. - */ - ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); - if (null != eventData && CREDIT_CARD_ACCOUNT_TYPE.equals(eventData.getBlackboardArtifactType())) { - Accounts.this.update(); - } - } catch (IllegalStateException notUsed) { - // Case is closed, do nothing. - } - } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { - /** - * Checking for a current case is a stop gap measure until a - * different way of handling the closing of cases is worked - * out. Currently, remote events may be received for a case - * that is already closed. - */ - try { - Case.getCurrentCase(); - Accounts.this.update(); - } catch (IllegalStateException notUsed) { - // Case is closed, do nothing. - } - } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { - // case was closed. Remove listeners so that we don't get called with a stale case handle - if (evt.getNewValue() == null) { - removeNotify(); - skCase = null; - } - } - } - }; - - @Override - @NbBundle.Messages({"Accounts.AccountTypeFactory.accountType.creditCards=Credit Card Numbers"}) - protected boolean createKeys(List list) { - list.add(Bundle.Accounts_AccountTypeFactory_accountType_creditCards()); - return true; - } - - @Override - protected Node createNodeForKey(String key) { - return new AccountTypeNode(key); - } - - @Override - protected void removeNotify() { - IngestManager.getInstance().removeIngestJobEventListener(pcl); - IngestManager.getInstance().removeIngestModuleEventListener(pcl); - Case.removePropertyChangeListener(pcl); - super.removeNotify(); - } - - @Override - protected void addNotify() { - IngestManager.getInstance().addIngestJobEventListener(pcl); - IngestManager.getInstance().addIngestModuleEventListener(pcl); - Case.addPropertyChangeListener(pcl); - super.addNotify(); - Accounts.this.update(); - } - } - - /** - * Node for an account type. - * - * NOTE: currently hard coded to work for Credit Card only - */ - public class AccountTypeNode extends DisplayableItemNode { - - private AccountTypeNode(String accountTypeName) { - super(Children.create(new ViewModeFactory(), true)); - super.setName(accountTypeName); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - } - - /** - * Enum for the children under the credit card AccountTypeNode. - */ - private enum CreditCardViewMode { - BY_FILE, - BY_BIN; - } - - /** - * ChildFactory that makes nodes for the different account organizations (by - * file, by BIN) - */ - private class ViewModeFactory extends ObservingChildFactory { - - @Override - protected boolean createKeys(List list) { - list.addAll(Arrays.asList(CreditCardViewMode.values())); - return true; - } - - @Override - protected Node createNodeForKey(CreditCardViewMode key) { - switch (key) { - case BY_BIN: - return new ByBINNode(); - case BY_FILE: - return new ByFileNode(); - default: - return null; - } - } - } - - /** - * Node that is the root of the "by file" accounts tree. Its children are - * FileWithCCNNodes. - */ - public class ByFileNode extends DisplayableItemNode implements Observer { - - private final FileWithCCNFactory fileFactory; - - private ByFileNode() { - super(Children.LEAF); - fileFactory = new FileWithCCNFactory(); - setChildren(Children.create(fileFactory, true)); - setName("By File"); //NON-NLS - updateDisplayName(); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS - Accounts.this.addObserver(this); - } - - @NbBundle.Messages({ - "# {0} - number of children", - "Accounts.ByFileNode.displayName=By File ({0})"}) - private void updateDisplayName() { - ArrayList keys = new ArrayList<>(); - fileFactory.createKeys(keys); - setDisplayName(Bundle.Accounts_ByFileNode_displayName(keys.size())); - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public void update(Observable o, Object arg) { - updateDisplayName(); - } - } - - /** - * Node that is the root of the "By BIN" accounts tree. Its children are - * BINNodes. - */ - public class ByBINNode extends DisplayableItemNode implements Observer { - - private final BINFactory binFactory; - - private ByBINNode() { - super(Children.LEAF); - binFactory = new BINFactory(); - setChildren(Children.create(binFactory, true)); - setName("By BIN"); //NON-NLS - updateDisplayName(); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS - Accounts.this.addObserver(this); - } - - @NbBundle.Messages({ - "# {0} - number of children", - "Accounts.ByBINNode.displayName=By BIN ({0})"}) - private void updateDisplayName() { - ArrayList keys = new ArrayList<>(); - binFactory.createKeys(keys); - setDisplayName(Bundle.Accounts_ByBINNode_displayName(keys.size())); - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - public void update(Observable o, Object arg) { - updateDisplayName(); - } - } - - /** - * DataModel for a child of the ByFileNode. Represents a file(chunk) and its - * associated accounts. - */ - @Immutable - private static class FileWithCCN { - - private final long objID; - private final String solrDocumentId; - private final List artifactIDS; - private final long hits; - private final Set statuses; - - private FileWithCCN(long objID, String solrDocID, List artifactIDS, long hits, Set statuses) { - this.objID = objID; - this.solrDocumentId = solrDocID; - this.artifactIDS = artifactIDS; - this.hits = hits; - this.statuses = statuses; - } - - public long getObjID() { - return objID; - } - - public String getSolrDocmentID() { - return solrDocumentId; - } - - public List getArtifactIDS() { - return artifactIDS; - } - - public long getHits() { - return hits; - } - - public Set getStatuses() { - return statuses; - } - } - - /** - * TODO: this was copy-pasted from timeline. Is there a single accessible - * place it should go? - * - * - * take the result of a group_concat SQLite operation and split it into a - * set of X using the mapper to to convert from string to X - * - * @param the type of elements to return - * @param groupConcat a string containing the group_concat result ( a comma - * separated list) - * @param mapper a function from String to X - * - * @return a Set of X, each element mapped from one element of the original - * comma delimited string - */ - static List unGroupConcat(String groupConcat, Function mapper) { - return StringUtils.isBlank(groupConcat) ? Collections.emptyList() - : Stream.of(groupConcat.split(",")) //NON-NLS - .map(mapper::apply) - .collect(Collectors.toList()); - } - - /** - * Factory for the children of the ByFiles Node. - */ - private class FileWithCCNFactory extends ObservingChildFactory { - - @Override - protected boolean createKeys(List list) { - String query - = "SELECT blackboard_artifacts.obj_id," //NON-NLS - + " blackboard_attributes.value_text AS solr_document_id, " //NON-NLS - + " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS - + " COUNT( blackboard_artifacts.artifact_id) AS hits, " //NON-NLS - + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids " - + " FROM blackboard_artifacts " //NON-NLS - + " LEFT JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID.getTypeID() //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + getRejectedArtifactFilterClause() - + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS - + " ORDER BY hits DESC "; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); - ResultSet rs = results.getResultSet();) { - while (rs.next()) { - list.add(new FileWithCCN( - rs.getLong("obj_id"), //NON-NLS - rs.getString("solr_document_id"), //NON-NLS - unGroupConcat(rs.getString("artifact_IDs"), Long::valueOf), //NON-NLS - rs.getLong("hits"), //NON-NLS - new HashSet<>(unGroupConcat(rs.getString("review_status_ids"), id -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(id)))))); //NON-NLS - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(FileWithCCN key) { - //add all account artifacts for the file and the file itself to the lookup - try { - List lookupContents = new ArrayList<>(); - for (long artId : key.artifactIDS) { - lookupContents.add(skCase.getBlackboardArtifact(artId)); - } - AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID()); - lookupContents.add(abstractFileById); - return new FileWithCCNNode(key, abstractFileById, lookupContents.toArray()); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS - return null; - } - } - - @Override - public void update(Observable o, Object arg) { - refresh(true); - } - } - - /** - * Node that represents a file or chunk of an unallocated space file. - */ - public class FileWithCCNNode extends DisplayableItemNode { - - private final FileWithCCN fileKey; - private final String fileName; - - /** - * Constructor - * - * @param key The FileWithCCN that backs this node. - * @param content The Content object the key represents. - * @param lookupContents The contents of this Node's lookup. It should - * contain the content object and the account - * artifacts. - */ - @NbBundle.Messages({ - "# {0} - raw file name", - "# {1} - solr chunk id", - "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"}) - private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) { - super(Children.LEAF, Lookups.fixed(lookupContents)); - this.fileKey = key; - this.fileName = (key.getSolrDocmentID() == null) - ? content.getName() - : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getSolrDocmentID(), "_")); //NON-NLS - setName(fileName); - setDisplayName(fileName); - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - @Override - @NbBundle.Messages({ - "Accounts.FileWithCCNNode.nameProperty.displayName=File", - "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts", - "Accounts.FileWithCCNNode.statusProperty.displayName=Status", - "Accounts.FileWithCCNNode.noDescription=no description"}) - protected Sheet createSheet() { - Sheet s = super.createSheet(); - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - - ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_noDescription(), - fileName)); - ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_noDescription(), - fileKey.getHits())); - ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_noDescription(), - fileKey.getStatuses().stream() - .map(BlackboardArtifact.ReviewStatus::getDisplayName) - .collect(Collectors.joining(", ")))); //NON-NLS - - return s; - } - - @Override - public Action[] getActions(boolean context) { - Action[] actions = super.getActions(context); - ArrayList arrayList = new ArrayList<>(); - arrayList.addAll(Arrays.asList(actions)); - try { - arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false)); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error gettung content by id", ex); - } - arrayList.add(approveActionInstance); - arrayList.add(rejectActionInstance); - return arrayList.toArray(new Action[arrayList.size()]); - } - } - - public class BINNode extends DisplayableItemNode implements Observer { - - private final BinResult bin; - private final AccountFactory accountFactory; - - private BINNode(BinResult bin) { - super(Children.LEAF); - this.bin = bin; - - accountFactory = new AccountFactory(bin); - setChildren(Children.create(accountFactory, true)); - setName(bin.toString()); - updateDisplayName(); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS - Accounts.this.addObserver(this); - } - - @Override - public void update(Observable o, Object arg) { - updateDisplayName(); - } - - private void updateDisplayName() { - setDisplayName(getBinRangeString() + " (" + bin.getCount() + ")"); //NON-NLS - } - - private String getBinRangeString() { - if (bin.getIINStart() == bin.getIINEnd()) { - return Integer.toString(bin.getIINStart()); - } else { - return bin.getIINStart() + "-" + StringUtils.difference(bin.getIINStart() + "", bin.getIINEnd() + ""); - } - } - - @Override - public boolean isLeafTypeNode() { - return true; - } - - @Override - public T accept(DisplayableItemNodeVisitor v) { - return v.visit(this); - } - - private Sheet.Set getPropertySet(Sheet s) { - Sheet.Set ss = s.get(Sheet.PROPERTIES); - if (ss == null) { - ss = Sheet.createPropertiesSet(); - s.put(ss); - } - return ss; - } - - @Override - @NbBundle.Messages({ - "Accounts.BINNode.binProperty.displayName=Bank Identifier Number", - "Accounts.BINNode.accountsProperty.displayName=Accounts", - "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type", - "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme", - "Accounts.BINNode.brandProperty.displayName=Brand", - "Accounts.BINNode.bankProperty.displayName=Bank", - "Accounts.BINNode.bankCityProperty.displayName=Bank City", - "Accounts.BINNode.bankCountryProperty.displayName=Bank Country", - "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #", - "Accounts.BINNode.bankURLProperty.displayName=Bank URL", - "Accounts.BINNode.noDescription=no description"}) - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set properties = getPropertySet(sheet); - - properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), - Bundle.Accounts_BINNode_binProperty_displayName(), - Bundle.Accounts_BINNode_noDescription(), - getBinRangeString())); - properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), - Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - bin.getCount())); - - //add optional properties if they are available - if (bin.hasDetails()) { - bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(), - Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - cardType))); - bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(), - Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - scheme))); - bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(), - Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - brand))); - bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(), - Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - bankName))); - bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(), - Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - bankCity))); - bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(), - Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - country))); - bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), - Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - phoneNumber))); - bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(), - Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), - url))); - } - return sheet; - } - } - - /** - * Factory that generates the children of the ByBin node. - */ - private class BINFactory extends ObservingChildFactory { - - @Override - protected boolean createKeys(List list) { - RangeMap ranges = TreeRangeMap.create(); - - String query - = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS - + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS - + " FROM blackboard_artifacts " //NON-NLS - + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER.getTypeID() //NON-NLS - + getRejectedArtifactFilterClause() - + " GROUP BY BIN " //NON-NLS - + " ORDER BY BIN "; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { - ResultSet resultSet = results.getResultSet(); - while (resultSet.next()) { - final Integer bin = Integer.valueOf(resultSet.getString("BIN")); - long count = resultSet.getLong("count"); - - IINRange iinRange = (IINRange) getIINInfo(bin); - BinResult previousResult = ranges.get(bin); - - if (previousResult != null) { - ranges.remove(Range.closed(previousResult.getIINStart(), previousResult.getIINEnd())); - count += previousResult.getCount(); - } - - if (iinRange != null) { - ranges.put(Range.closed(iinRange.getIINstart(), iinRange.getIINend()), new BinResult(count, iinRange)); - } else { - ranges.put(Range.closed(bin, bin), new BinResult(count, bin, bin)); - } - } - ranges.asMapOfRanges().values().forEach(list::add); - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(BinResult key) { - return new BINNode(key); - } - } - - /** - * Data model item to back the BINNodes in the tree. Has the number of - * accounts found with the BIN. - */ - @Immutable - static private class BinResult implements IINInfo { - - /** - * The number of accounts with this BIN - */ - private final long count; - - private final IINRange iinRange; - private final int iinEnd; - private final int iinStart; - - private BinResult(long count, @Nonnull IINRange iinRange) { - this.count = count; - this.iinRange = iinRange; - iinStart = iinRange.getIINstart(); - iinEnd = iinRange.getIINend(); - } - - private BinResult(long count, int start, int end) { - this.count = count; - this.iinRange = null; - iinStart = start; - iinEnd = end; - } - - int getIINStart() { - return iinStart; - } - - int getIINEnd() { - return iinEnd; - } - - public long getCount() { - return count; - } - - boolean hasDetails() { - return iinRange != null; - } - - @Override - public Optional getNumberLength() { - return iinRange.getNumberLength(); - } - - @Override - public Optional getBankCity() { - return iinRange.getBankCity(); - } - - @Override - public Optional getBankName() { - return iinRange.getBankName(); - } - - @Override - public Optional getBankPhoneNumber() { - return iinRange.getBankPhoneNumber(); - } - - @Override - public Optional getBankURL() { - return iinRange.getBankURL(); - } - - @Override - public Optional getBrand() { - return iinRange.getBrand(); - } - - @Override - public Optional getCardType() { - return iinRange.getCardType(); - } - - @Override - public Optional getCountry() { - return iinRange.getCountry(); - } - - @Override - public Optional getScheme() { - return iinRange.getScheme(); - } - } - - /** - * Creates the nodes for the accounts of a given type - */ - private class AccountFactory extends ObservingChildFactory { - - private final BinResult bin; - - private AccountFactory(BinResult bin) { - this.bin = bin; - } - - @Override - protected boolean createKeys(List list) { - - String query - = "SELECT blackboard_artifacts.artifact_id " //NON-NLS - + " FROM blackboard_artifacts " //NON-NLS - + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID() //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER.getTypeID() //NON-NLS - + " AND blackboard_attributes.value_text >= \"" + bin.getIINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getIINEnd() + 1) + "\"" //NON-NLS - + getRejectedArtifactFilterClause() - + " ORDER BY blackboard_attributes.value_text"; //NON-NLS - try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); - ResultSet rs = results.getResultSet();) { - while (rs.next()) { - list.add(rs.getLong("artifact_id")); //NON-NLS - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS - return false; - } - return true; - } - - @Override - protected Node createNodeForKey(Long artifactID) { - if (skCase == null) { - return null; - } - - try { - BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID); - return new AccountArtifactNode(art); - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS - return null; - } - } - } - - private class AccountArtifactNode extends BlackboardArtifactNode { - - private final BlackboardArtifact artifact; - - private AccountArtifactNode(BlackboardArtifact artifact) { - super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS - this.artifact = artifact; - } - - @Override - public Action[] getActions(boolean context) { - List actionsList = new ArrayList<>(); - actionsList.addAll(Arrays.asList(super.getActions(context))); - actionsList.add(approveActionInstance); - actionsList.add(rejectActionInstance); - return actionsList.toArray(new Action[actionsList.size()]); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set properties = sheet.get(Sheet.PROPERTIES); - if (properties == null) { - properties = Sheet.createPropertiesSet(); - sheet.put(properties); - } - properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), - Bundle.Accounts_FileWithCCNNode_noDescription(), - artifact.getReviewStatus().getDisplayName())); - - return sheet; - } - } - - final class ToggleShowRejected extends AbstractAction { - - @NbBundle.Messages("ToggleShowRejected.name=Show Rejcted Results") - ToggleShowRejected() { - super(Bundle.ToggleShowRejected_name()); - } - - @Override - public void actionPerformed(ActionEvent e) { - showRejected = !showRejected; - Accounts.this.update(); - } - } - - private abstract class ReviewStatusAction extends AbstractAction { - - private final BlackboardArtifact.ReviewStatus newStatus; - - private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) { - super(displayName); - this.newStatus = newStatus; - - } - - @Override - public void actionPerformed(ActionEvent e) { - Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class).forEach(artifact -> { - try { - skCase.setReviewStatus(artifact, newStatus); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS - } - }); - Accounts.this.update(); - } - } - - private class ApproveAccounts extends ReviewStatusAction { - - @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"}) - private ApproveAccounts() { - super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED); - } - } - - private class RejectAccounts extends ReviewStatusAction { - - @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"}) - private RejectAccounts() { - super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java index fa59de87c1..53523be839 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java @@ -27,7 +27,6 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; @@ -68,7 +67,7 @@ public class ArtifactStringContent implements StringContent { buffer.append(""); //NON-NLS buffer.append("\n"); //NON-NLS - // cycle through each attribute and display in a row in the table. + // cycle through each attribute and display in a row in the table. for (BlackboardAttribute attr : artifact.getAttributes()) { // name column @@ -78,48 +77,36 @@ public class ArtifactStringContent implements StringContent { // value column buffer.append(""); //NON-NLS - if (attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_RCVD.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_START.getTypeID() - || attr.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_DATETIME_END.getTypeID()) { - long epoch = attr.getValueLong(); - String time = "0000-00-00 00:00:00"; - if (epoch != 0) { - dateFormatter.setTimeZone(getTimeZone(artifact)); - time = dateFormatter.format(new java.util.Date(epoch * 1000)); - } - buffer.append(time); - } else { - switch (attr.getAttributeType().getValueType()) { - case STRING: - String str = attr.getValueString(); - str = str.replaceAll(" ", " "); //NON-NLS - str = str.replaceAll("<", "<"); //NON-NLS - str = str.replaceAll(">", ">"); //NON-NLS - str = str.replaceAll("(\r\n|\n)", "
"); //NON-NLS - buffer.append(str); - break; - case INTEGER: - buffer.append(attr.getValueInt()); - break; - case LONG: - buffer.append(attr.getValueLong()); - break; - case DOUBLE: - buffer.append(attr.getValueDouble()); - break; - case BYTE: - buffer.append(Arrays.toString(attr.getValueBytes())); - break; - case DATETIME: - buffer.append(attr.getValueLong()); - break; - - } + switch (attr.getAttributeType().getValueType()) { + case STRING: + String str = attr.getValueString(); + str = str.replaceAll(" ", " "); //NON-NLS + str = str.replaceAll("<", "<"); //NON-NLS + str = str.replaceAll(">", ">"); //NON-NLS + str = str.replaceAll("(\r\n|\n)", "
"); //NON-NLS + buffer.append(str); + break; + case INTEGER: + buffer.append(attr.getValueInt()); + break; + case LONG: + buffer.append(attr.getValueLong()); + break; + case DOUBLE: + buffer.append(attr.getValueDouble()); + break; + case BYTE: + buffer.append(Arrays.toString(attr.getValueBytes())); + break; + case DATETIME: + long epoch = attr.getValueLong(); + String time = "0000-00-00 00:00:00"; + if (epoch != 0) { + dateFormatter.setTimeZone(getTimeZone(artifact)); + time = dateFormatter.format(new java.util.Date(epoch * 1000)); + } + buffer.append(time); + break; } if (!"".equals(attr.getContext())) { buffer.append(" ("); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index fbc17420b2..788d6969ec 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -185,13 +185,13 @@ public class BlackboardArtifactNode extends DisplayableItemNode { ss = Sheet.createPropertiesSet(); s.put(ss); } - final String NO_DESCR = NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.noDesc.text"); + final String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text"); Map map = new LinkedHashMap<>(); fillPropertyMap(map, artifact); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.srcFile.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), NO_DESCR, this.getDisplayName())); @@ -222,13 +222,13 @@ public class BlackboardArtifactNode extends DisplayableItemNode { actualMimeType = ""; //NON-NLS } } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.mimeType.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.mimeType.displayName"), NO_DESCR, actualMimeType)); } @@ -243,32 +243,32 @@ public class BlackboardArtifactNode extends DisplayableItemNode { if (sourcePath.isEmpty() == false) { ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.filePath.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.filePath.displayName"), NO_DESCR, sourcePath)); } if (Arrays.asList(SHOW_FILE_METADATA).contains(artifactTypeId)) { AbstractFile file = associated instanceof AbstractFile ? (AbstractFile) associated : null; - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), + ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", associated.getSize())); } @@ -287,8 +287,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode { if (dataSourceStr.isEmpty() == false) { ss.put(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.name"), - NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.dataSrc.displayName"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.dataSrc.displayName"), NO_DESCR, dataSourceStr)); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 7eb9bc14f4..3c24e02283 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -118,17 +118,6 @@ FileSize.createSheet.filterType.displayName=Filter Type FileSize.createSheet.filterType.desc=no description FileSize.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} FileTypeChildren.exception.notSupported.msg=Not supported for this type of Displayable Item\: {0} -FileTypeExtensionFilters.tskImgFilter.text=Images -FileTypeExtensionFilters.tskVideoFilter.text=Videos -FileTypeExtensionFilters.tskAudioFilter.text=Audio -FileTypeExtensionFilters.tskArchiveFilter.text=Archives -FileTypeExtensionFilters.tskDocumentFilter.text=Documents -FileTypeExtensionFilters.tskExecFilter.text=Executable -FileTypeExtensionFilters.autDocHtmlFilter.text=HTML -FileTypeExtensionFilters.autDocOfficeFilter.text=Office -FileTypeExtensionFilters.autoDocPdfFilter.text=PDF -FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text -FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text FileTypeNode.createSheet.filterType.name=Filter Type FileTypeNode.createSheet.filterType.displayName=Filter Type FileTypeNode.createSheet.filterType.desc=no description diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java new file mode 100644 index 0000000000..aa7b13e66b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/CreditCards.java @@ -0,0 +1,171 @@ +package org.sleuthkit.autopsy.datamodel; + +import com.google.common.collect.Range; +import com.google.common.collect.RangeMap; +import com.google.common.collect.TreeRangeMap; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.concurrent.GuardedBy; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel._private.BINRange; + +public class CreditCards { + + //Interface for objects that provide details about one or more BINs. + static public interface BankIdentificationNumber { + + /** + * Get the city of the issuer. + * + * @return the city of the issuer. + */ + Optional getBankCity(); + + /** + * Get the name of the issuer. + * + * @return the name of the issuer. + */ + Optional getBankName(); + + /** + * Get the phone number of the issuer. + * + * @return the phone number of the issuer. + */ + Optional getBankPhoneNumber(); + + /** + * Get the URL of the issuer. + * + * @return the URL of the issuer. + */ + Optional getBankURL(); + + /** + * Get the brand of this BIN range. + * + * @return the brand of this BIN range. + */ + Optional getBrand(); + + /** + * Get the type of card (credit vs debit) for this BIN range. + * + * @return the type of cards in this BIN range. + */ + Optional getCardType(); + + /** + * Get the country of the issuer. + * + * @return the country of the issuer. + */ + Optional getCountry(); + + /** + * Get the length of account numbers in this BIN range. + * + * NOTE: the length is currently unused, and not in the data file for + * any ranges. It could be quite helpfull for validation... + * + * @return the length of account numbers in this BIN range. Or an empty + * Optional if the length is unknown. + * + */ + Optional getNumberLength(); + + /** + * Get the scheme this BIN range uses to amex,visa,mastercard, etc + * + * @return the scheme this BIN range uses. + */ + Optional getScheme(); + } + + private static final Logger LOGGER = Logger.getLogger(CreditCards.class.getName()); + /** + * Range Map from a (ranges of) BINs to data model object with details of + * the BIN, ie, bank name, phone, url, visa/amex/mastercard/..., + */ + @GuardedBy("CreditCards.class") + private final static RangeMap binRanges = TreeRangeMap.create(); + + /** + * Flag for if we have loaded the BINs from the file already. + */ + @GuardedBy("CreditCards.class") + private static boolean binsLoaded = false; + + /** + * Load the BIN range information from disk. If the map has already been + * initialized, don't load again. + */ + synchronized private static void loadBINRanges() { + if (binsLoaded == false) { + try { + InputStreamReader in = new InputStreamReader(CreditCards.class.getResourceAsStream("ranges.csv")); //NON-NLS + CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in); + + //parse each row and add to range map + for (CSVRecord record : rangesParser) { + + /** + * Because ranges.csv allows both 6 and (the newer) 8 digit + * BINs, but we need a consistent length for the range map, + * we pad all the numbers out to 8 digits + */ + String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS + + //if there is no end listed, use start, since ranges will be closed. + String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS + end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS + + final String numberLength = record.get("number_length"); //NON-NLS + + try { + BINRange binRange = new BINRange(Integer.parseInt(start), + Integer.parseInt(end), + StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength), + record.get("scheme"), //NON-NLS + record.get("brand"), //NON-NLS + record.get("type"), //NON-NLS + record.get("country"), //NON-NLS + record.get("bank_name"), //NON-NLS + record.get("bank_url"), //NON-NLS + record.get("bank_phone"), //NON-NLS + record.get("bank_city")); //NON-NLS + + binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), binRange); + + } catch (NumberFormatException numberFormatException) { + LOGGER.log(Level.WARNING, "Failed to parse BIN range: " + record.toString(), numberFormatException); //NON-NLS + } + binsLoaded = true; + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Failed to load BIN ranges form ranges.csv", ex); //NON-NLS + MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information. Accounts will not have their BINs identified."); + } + } + } + + /** + * Get an BINInfo object with details about the given BIN + * + * @param bin the BIN to get details of. + * + * @return + */ + synchronized static public BankIdentificationNumber getBINInfo(int bin) { + loadBINRanges(); + return binRanges.get(bin); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java index e7aa3d8fe4..3ddb6a158d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSources.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; + /** * Root node to store the data sources in a case */ diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java index 3a1e70b164..92b75059b9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DeletedContent.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index fe3445728b..45a5abc66e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.De import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; +import org.sleuthkit.autopsy.datamodel._private.Accounts; /** * Visitor pattern that goes over all nodes in the directory tree. This includes @@ -127,7 +128,7 @@ public interface DisplayableItemNodeVisitor { */ T visit(Accounts.AccountsRootNode accountRootNode); - T visit(Accounts.AccountTypeNode accountTypeNode); + T visit(Accounts.CreditCardNumberAccountTypeNode accountTypeNode); T visit(Accounts.ByBINNode byArtifactNode); @@ -137,6 +138,8 @@ public interface DisplayableItemNodeVisitor { T visit(Accounts.BINNode binNode); + T visit(Accounts.DefaultAccountTypeNode node); + /** * Visitor with an implementable default behavior for all types. Override * specific visit types to not use the default behavior. @@ -350,7 +353,7 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(Accounts.AccountTypeNode node) { + public T visit(Accounts.CreditCardNumberAccountTypeNode node) { return defaultVisit(node); } @@ -378,5 +381,9 @@ public interface DisplayableItemNodeVisitor { public T visit(Accounts.BINNode node) { return defaultVisit(node); } + @Override + public T visit(Accounts.DefaultAccountTypeNode node) { + return defaultVisit(node); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index 89e8c5b217..b9626d5646 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index e638794da1..561e96d510 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -37,7 +39,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT; @@ -201,7 +203,7 @@ public class ExtractedContent implements AutopsyVisitableItem { doNotShow.add(new BlackboardArtifact.Type(TSK_KEYWORD_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT)); - doNotShow.add(new BlackboardArtifact.Type(TSK_CREDIT_CARD_ACCOUNT)); + doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT)); } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java index c5fee7d696..88ac89bdc9 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileSize.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java index e1b7553aba..e03b2ac924 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.util.List; import java.util.Observable; import java.util.Observer; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java index 3bbbf0b63c..86913224e1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java index 2264b95ac6..ab2d6c2492 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HashsetHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index b07f5568c0..57239561ba 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 319d9ecf9f..8b155ccf32 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.sql.ResultSet; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java index 391a49f757..d7665b2157 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesChildren.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java index c6c9c72471..903028abed 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterChildren.java @@ -28,7 +28,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles.RecentFilesFilter; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java index e9d8fe6b63..e4ab6811cf 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/RecentFilesFilterNode.java @@ -25,7 +25,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.datamodel.RecentFiles.RecentFilesFilter; +import org.sleuthkit.autopsy.datamodel._private.RecentFiles.RecentFilesFilter; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java index 7a4f581966..5bb16c5602 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Reports.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.awt.Desktop; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java index 61e9ad626e..60f0247f97 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Results.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Results.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index aa4688dea0..31bd236ade 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.Accounts; import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index ac85cd8787..1ab1e72ad0 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java index 4a9dee9886..4e5ef0f027 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Views.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Views.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.AutopsyItemVisitor; +import org.sleuthkit.autopsy.datamodel._private.AutopsyVisitableItem; import org.sleuthkit.datamodel.SleuthkitCase; /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index 4919594378..6f7ec0f2c5 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.datamodel; +import org.sleuthkit.autopsy.datamodel._private.FileTypeExtensionFilters; import java.util.Arrays; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java new file mode 100644 index 0000000000..4e29135b2d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Accounts.java @@ -0,0 +1,1368 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel._private; + +import com.google.common.collect.Range; +import com.google.common.collect.RangeMap; +import com.google.common.collect.TreeRangeMap; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import javax.swing.AbstractAction; +import javax.swing.Action; +import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.NodeNotFoundException; +import org.openide.nodes.NodeOp; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; +import org.sleuthkit.autopsy.datamodel.CreditCards; +import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * AutopsyVisitableItem for the Accounts section of the tree. All nodes, + * factories, and custom key class related to accounts are inner classes. + */ +final public class Accounts implements AutopsyVisitableItem { + + private static final Logger LOGGER = Logger.getLogger(Accounts.class.getName()); + + @NbBundle.Messages("AccountsRootNode.name=Accounts") + final public static String NAME = Bundle.AccountsRootNode_name(); + + private SleuthkitCase skCase; + private final EventBus reviewStatusBus = new EventBus("ReviewStatusBus"); + + /** + * Should rejected accounts be shown in the accounts section of the tree. + */ + private boolean showRejected = false; + + private final RejectAccounts rejectActionInstance; + private final ApproveAccounts approveActionInstance; + + /** + * Constructor + * + * @param skCase The SleuthkitCase object to use for db queries. + */ + public Accounts(SleuthkitCase skCase) { + this.skCase = skCase; + + this.rejectActionInstance = new RejectAccounts(); + this.approveActionInstance = new ApproveAccounts(); + } + + @Override + public T accept(AutopsyItemVisitor v) { + return v.visit(this); + } + + /** + * Get the clause that should be used in order to (not) filter out rejected + * results from db queries. + * + * @return A clause that will or will not filter out rejected artifacts + * based on the state of showRejected. + */ + private String getRejectedArtifactFilterClause() { + return showRejected ? " " : " AND blackboard_artifacts.review_status_id != " + BlackboardArtifact.ReviewStatus.REJECTED.getID() + " "; //NON-NLS + } + + /** + * Gets a new Action that when invoked toggles showing rejected artifacts on + * or off. + * + * @return An Action that will toggle whether rejected artifacts are shown + * in the tree rooted by this Accounts instance. + */ + public Action newToggleShowRejectedAction() { + return new ToggleShowRejected(); + } + + /** + * Base class for children that are also observers of the reviewStatusBus. + * It + * + * @param The type of keys used by this factory. + */ + private abstract class ObservingChildren extends Children.Keys { + + /** + * Create of keys used by this Children object to represent the child + * nodes. + */ + abstract protected Collection createKeys(); + + /** + * Refresh the keys for this Children + */ + void refreshKeys() { + setKeys(createKeys()); + } + + /** + * Handle a ReviewStatusChangeEvent + * + * @param event the ReviewStatusChangeEvent to handle. + */ + @Subscribe + abstract void handleReviewStatusChange(ReviewStatusChangeEvent event); + + @Override + protected void removeNotify() { + super.removeNotify(); + reviewStatusBus.unregister(ObservingChildren.this); + } + + @Override + protected void addNotify() { + super.addNotify(); + refreshKeys(); + reviewStatusBus.register(ObservingChildren.this); + } + } + + /** + * Top-level node for the accounts tree + */ + @NbBundle.Messages({"Accounts.RootNode.displayName=Accounts"}) + final public class AccountsRootNode extends DisplayableItemNode { + + /** + * Creates child nodes for each account type in the db. + */ + final private class AccountTypeFactory extends ObservingChildren { + + /* + * The pcl is in this class because it has the easiest mechanisms to + * add and remove itself during its life cycles. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { + /** + * Checking for a current case is a stop gap measure + * until a different way of handling the closing of + * cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCase(); + /** + * Even with the check above, it is still possible + * that the case will be closed in a different + * thread before this code executes. If that + * happens, it is possible for the event to have a + * null oldValue. + */ + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + if (null != eventData + && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { + refreshKeys(); + } + } catch (IllegalStateException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + /** + * Checking for a current case is a stop gap measure + * until a different way of handling the closing of + * cases is worked out. Currently, remote events may be + * received for a case that is already closed. + */ + try { + Case.getCurrentCase(); + refreshKeys(); + } catch (IllegalStateException notUsed) { + // Case is closed, do nothing. + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + if (evt.getNewValue() == null) { + removeNotify(); + skCase = null; + } + } + } + }; + + @Subscribe + @Override + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + } + + @Override + protected List createKeys() { + List list = new ArrayList<>(); + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery( + "SELECT DISTINCT blackboard_attributes.value_text as account_type " + + " FROM blackboard_attributes " + + " WHERE blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID()); + ResultSet resultSet = executeQuery.getResultSet()) { + while (resultSet.next()) { + String accountType = resultSet.getString("account_type"); + list.add(accountType); + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account_types", ex); + } + return list; + } + + @Override + protected Node[] createNodes(String key) { + try { + Account.Type accountType = Account.Type.valueOf(key); + switch (accountType) { + case CREDIT_CARD: + return new Node[]{new CreditCardNumberAccountTypeNode()}; + default: + return new Node[]{new DefaultAccountTypeNode(key)}; + } + } catch (IllegalArgumentException ex) { + LOGGER.log(Level.WARNING, "Unknown account type: {0}", key); + //Flesh out what happens with other account types here. + return new Node[]{new DefaultAccountTypeNode(key)}; + } + } + + @Override + protected void removeNotify() { + IngestManager.getInstance().removeIngestJobEventListener(pcl); + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removePropertyChangeListener(pcl); + super.removeNotify(); + } + + @Override + protected void addNotify() { + IngestManager.getInstance().addIngestJobEventListener(pcl); + IngestManager.getInstance().addIngestModuleEventListener(pcl); + Case.addPropertyChangeListener(pcl); + super.addNotify(); + refreshKeys(); + } + + } + + public AccountsRootNode() { + super(Children.LEAF, Lookups.singleton(Accounts.this)); + setChildren(Children.createLazy(AccountTypeFactory::new)); + setName(Accounts.NAME); + setDisplayName(Bundle.Accounts_RootNode_displayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/accounts.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + } + + /** + * Default Node class for unknown account types and account types that have + * no special behavior. + */ + final public class DefaultAccountTypeNode extends DisplayableItemNode { + + private final String accountTypeName; + + final private class DefaultAccountFactory extends ObservingChildren { + + private DefaultAccountFactory() { + } + + @Override + protected Collection createKeys() { + List list = new ArrayList<>(); + String query + = "SELECT blackboard_artifacts.artifact_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text = '" + accountTypeName + "'" //NON-NLS + + getRejectedArtifactFilterClause(); //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(rs.getLong("artifact_id")); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + } + return list; + } + + @Override + protected Node[] createNodes(Long t) { + try { + return new Node[]{new BlackboardArtifactNode(skCase.getBlackboardArtifact(t))}; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error get black board artifact with id " + t, ex); + return new Node[0]; + } + } + + @Override + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + } + } + + private DefaultAccountTypeNode(String accountTypeName) { + super(Children.LEAF); + this.accountTypeName = accountTypeName; + setChildren(Children.createLazy(DefaultAccountFactory::new)); + setName(accountTypeName); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + } + + /** + * Enum for the children under the credit card AccountTypeNode. + */ + private enum CreditCardViewMode { + BY_FILE, + BY_BIN; + } + + /** + * Node for the Credit Card account type. * + */ + final public class CreditCardNumberAccountTypeNode extends DisplayableItemNode { + + /** + * ChildFactory that makes nodes for the different account organizations + * (by file, by BIN) + */ + final private class ViewModeFactory extends ObservingChildren { + + @Subscribe + @Override + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + } + + /** + * + */ + @Override + protected List createKeys() { + return Arrays.asList(CreditCardViewMode.values()); + + } + + @Override + protected Node[] createNodes(CreditCardViewMode key) { + switch (key) { + case BY_BIN: + return new Node[]{new ByBINNode()}; + case BY_FILE: + return new Node[]{new ByFileNode()}; + default: + return new Node[0]; + } + } + } + + private CreditCardNumberAccountTypeNode() { + super(Children.LEAF); + setChildren(new ViewModeFactory()); + setName(Account.Type.CREDIT_CARD.getDisplayName()); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + } + + /** + * Node that is the root of the "by file" accounts tree. Its children are + * FileWithCCNNodes. + */ + final public class ByFileNode extends DisplayableItemNode { + + /** + * Factory for the children of the ByFiles Node. + */ + final private class FileWithCCNFactory extends ObservingChildren { + + @Subscribe + @Override + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + } + + @Override + protected List createKeys() { + List list = new ArrayList<>(); + String query + = "SELECT blackboard_artifacts.obj_id," //NON-NLS + + " solr_attribute.value_text AS solr_document_id, " //NON-NLS + + " GROUP_CONCAT(blackboard_artifacts.artifact_id) AS artifact_IDs, " //NON-NLS + + " COUNT( blackboard_artifacts.artifact_id) AS hits, " //NON-NLS + + " GROUP_CONCAT(blackboard_artifacts.review_status_id) AS review_status_ids " + + " FROM blackboard_artifacts " //NON-NLS + + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS + + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause() + + " GROUP BY blackboard_artifacts.obj_id, solr_document_id " //NON-NLS + + " ORDER BY hits DESC "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(new FileWithCCN( + rs.getLong("obj_id"), //NON-NLS + rs.getString("solr_document_id"), //NON-NLS + unGroupConcat(rs.getString("artifact_IDs"), Long::valueOf), //NON-NLS + rs.getLong("hits"), //NON-NLS + new HashSet<>(unGroupConcat(rs.getString("review_status_ids"), id -> BlackboardArtifact.ReviewStatus.withID(Integer.valueOf(id)))))); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS + + } + return list; + } + + @Override + protected Node[] createNodes(FileWithCCN key) { + //add all account artifacts for the file and the file itself to the lookup + try { + List lookupContents = new ArrayList<>(); + for (long artId : key.artifactIDs) { + lookupContents.add(skCase.getBlackboardArtifact(artId)); + } + AbstractFile abstractFileById = skCase.getAbstractFileById(key.getObjID()); + lookupContents.add(abstractFileById); + return new Node[]{new FileWithCCNNode(key, abstractFileById, lookupContents.toArray())}; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error getting content for file with ccn hits.", ex); //NON-NLS + return new Node[0]; + } + } + } + + private ByFileNode() { + super(Children.LEAF); + setChildren(Children.createLazy(FileWithCCNFactory::new)); + setName("By File"); //NON-NLS + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @NbBundle.Messages({ + "# {0} - number of children", + "Accounts.ByFileNode.displayName=By File ({0})"}) + private void updateDisplayName() { + String query + = "SELECT count(*) FROM ( SELECT count(*) AS documents " + + " FROM blackboard_artifacts " //NON-NLS + + " LEFT JOIN blackboard_attributes as solr_attribute ON blackboard_artifacts.artifact_id = solr_attribute.artifact_id " //NON-NLS + + " AND solr_attribute.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID.getTypeID() //NON-NLS + + " LEFT JOIN blackboard_attributes as account_type ON blackboard_artifacts.artifact_id = account_type.artifact_id " //NON-NLS + + " AND account_type.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + " AND account_type.value_text = '" + Account.Type.CREDIT_CARD.name() + "'" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause() + + " GROUP BY blackboard_artifacts.obj_id, solr_attribute.value_text )"; + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + setDisplayName(Bundle.Accounts_ByFileNode_displayName(rs.getLong("count(*)"))); + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for files with ccn hits.", ex); //NON-NLS + + } + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Subscribe + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + } + } + + /** + * Node that is the root of the "By BIN" accounts tree. Its children are + * BINNodes. + */ + final public class ByBINNode extends DisplayableItemNode { + + /** + * Factory that generates the children of the ByBin node. + */ + final private class BINFactory extends ObservingChildren { + + @Subscribe + @Override + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + } + + @Override + protected List createKeys() { + List list = new ArrayList<>(); + + RangeMap binRanges = TreeRangeMap.create(); + + String query + = "SELECT SUBSTR(blackboard_attributes.value_text,1,8) AS BIN, " //NON-NLS + + " COUNT(blackboard_artifacts.artifact_id) AS count " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause() + + " GROUP BY BIN " //NON-NLS + + " ORDER BY BIN "; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { + ResultSet resultSet = results.getResultSet(); + //sort all te individual bins in to the ranges + while (resultSet.next()) { + final Integer bin = Integer.valueOf(resultSet.getString("BIN")); + long count = resultSet.getLong("count"); + + BINRange binRange = (BINRange) CreditCards.getBINInfo(bin); + BinResult previousResult = binRanges.get(bin); + + if (previousResult != null) { + binRanges.remove(Range.closed(previousResult.getBINStart(), previousResult.getBINEnd())); + count += previousResult.getCount(); + } + + if (binRange != null) { + binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), new BinResult(count, binRange)); + } else { + binRanges.put(Range.closed(bin, bin), new BinResult(count, bin, bin)); + } + } + binRanges.asMapOfRanges().values().forEach(list::add); + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS + + } + return list; + } + + @Override + protected Node[] createNodes(BinResult key) { + return new Node[]{new BINNode(key)}; + } + } + + private ByBINNode() { + super(Children.LEAF); + setChildren(Children.createLazy(BINFactory::new)); + setName("By BIN"); //NON-NLS + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @NbBundle.Messages({ + "# {0} - number of children", + "Accounts.ByBINNode.displayName=By BIN ({0})"}) + private void updateDisplayName() { + String query + = "SELECT count(distinct SUBSTR(blackboard_attributes.value_text,1,8)) AS BINs " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id" //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + getRejectedArtifactFilterClause(); //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query)) { + ResultSet resultSet = results.getResultSet(); + while (resultSet.next()) { + setDisplayName(Bundle.Accounts_ByBINNode_displayName(resultSet.getLong("BINs"))); + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for BINs.", ex); //NON-NLS + } + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Subscribe + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + } + } + + /** + * DataModel for a child of the ByFileNode. Represents a file(chunk) and its + * associated accounts. + */ + @Immutable + final private static class FileWithCCN { + + @Override + public int hashCode() { + int hash = 5; + hash = 79 * hash + (int) (this.objID ^ (this.objID >>> 32)); + hash = 79 * hash + Objects.hashCode(this.keywordSearchDocID); + hash = 79 * hash + Objects.hashCode(this.artifactIDs); + hash = 79 * hash + (int) (this.hits ^ (this.hits >>> 32)); + hash = 79 * hash + Objects.hashCode(this.statuses); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FileWithCCN other = (FileWithCCN) obj; + if (this.objID != other.objID) { + return false; + } + if (this.hits != other.hits) { + return false; + } + if (!Objects.equals(this.keywordSearchDocID, other.keywordSearchDocID)) { + return false; + } + if (!Objects.equals(this.artifactIDs, other.artifactIDs)) { + return false; + } + if (!Objects.equals(this.statuses, other.statuses)) { + return false; + } + return true; + } + + private final long objID; + private final String keywordSearchDocID; + private final List artifactIDs; + private final long hits; + private final Set statuses; + + private FileWithCCN(long objID, String solrDocID, List artifactIDs, long hits, Set statuses) { + this.objID = objID; + this.keywordSearchDocID = solrDocID; + this.artifactIDs = artifactIDs; + this.hits = hits; + this.statuses = statuses; + } + + /** + * Get the object ID of the file. + * + * @return the object ID of the file. + */ + public long getObjID() { + return objID; + } + + /** + * Get the keyword search docuement id. This is used for unnalocated + * files to limit results to one chunk/page + * + * @return the keyword search document id. + */ + public String getkeywordSearchDocID() { + return keywordSearchDocID; + } + + /** + * Get the artifact ids of the account artifacts from this file. + * + * @return the artifact ids of the account artifacts from this file. + */ + public List getArtifactIDs() { + return artifactIDs; + } + + /** + * Get the number of account artifacts from this file. + * + * @return the number of account artifacts from this file. + */ + public long getHits() { + return hits; + } + + /** + * Get the status(s) of the account artifacts from this file. + * + * @return the status(s) of the account artifacts from this file. + */ + public Set getStatuses() { + return statuses; + } + } + + /** + * TODO: this was copy-pasted from timeline. Is there a single accessible + * place it should go? + * + * + * take the result of a group_concat SQLite operation and split it into a + * set of X using the mapper to to convert from string to X + * + * @param the type of elements to return + * @param groupConcat a string containing the group_concat result ( a comma + * separated list) + * @param mapper a function from String to X + * + * @return a Set of X, each element mapped from one element of the original + * comma delimited string + */ + static List unGroupConcat(String groupConcat, Function mapper) { + return StringUtils.isBlank(groupConcat) ? Collections.emptyList() + : Stream.of(groupConcat.split(",")) //NON-NLS + .map(mapper::apply) + .collect(Collectors.toList()); + } + + /** + * Node that represents a file or chunk of an unallocated space file. + */ + final public class FileWithCCNNode extends DisplayableItemNode { + + private final FileWithCCN fileKey; + private final String fileName; + + /** + * Constructor + * + * @param key The FileWithCCN that backs this node. + * @param content The Content object the key represents. + * @param lookupContents The contents of this Node's lookup. It should + * contain the content object and the account + * artifacts. + */ + @NbBundle.Messages({ + "# {0} - raw file name", + "# {1} - solr chunk id", + "Accounts.FileWithCCNNode.unallocatedSpaceFile.displayName={0}_chunk_{1}"}) + private FileWithCCNNode(FileWithCCN key, Content content, Object[] lookupContents) { + super(Children.LEAF, Lookups.fixed(lookupContents)); + this.fileKey = key; + this.fileName = (key.getkeywordSearchDocID() == null) + ? content.getName() + : Bundle.Accounts_FileWithCCNNode_unallocatedSpaceFile_displayName(content.getName(), StringUtils.substringAfter(key.getkeywordSearchDocID(), "_")); //NON-NLS + setName(fileName + key.getObjID()); + setDisplayName(fileName); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + @NbBundle.Messages({ + "Accounts.FileWithCCNNode.nameProperty.displayName=File", + "Accounts.FileWithCCNNode.accountsProperty.displayName=Accounts", + "Accounts.FileWithCCNNode.statusProperty.displayName=Status", + "Accounts.FileWithCCNNode.noDescription=no description"}) + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + + ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_nameProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileName)); + ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_accountsProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileKey.getHits())); + ss.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + fileKey.getStatuses().stream() + .map(BlackboardArtifact.ReviewStatus::getDisplayName) + .collect(Collectors.joining(", ")))); //NON-NLS + + return s; + } + + @Override + public Action[] getActions(boolean context) { + Action[] actions = super.getActions(context); + ArrayList arrayList = new ArrayList<>(); + arrayList.addAll(Arrays.asList(actions)); + try { + arrayList.addAll(DataModelActionsFactory.getActions(Accounts.this.skCase.getContentById(fileKey.getObjID()), false)); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error gettung content by id", ex); + } + + arrayList.add(approveActionInstance); + arrayList.add(rejectActionInstance); + + return arrayList.toArray(new Action[arrayList.size()]); + } + } + + final public class BINNode extends DisplayableItemNode { + + /** + * Creates the nodes for the credit card numbers + */ + final private class CreditCardNumberFactory extends ObservingChildren { + + @Subscribe + @Override + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + refreshKeys(); + //make sure to refresh the nodes for artifacts that changed statuses. + event.artifacts.stream().map(BlackboardArtifact::getArtifactID).forEach(this::refreshKey); + } + + /** + * + */ + @Override + protected List createKeys() { + List list = new ArrayList<>(); + + String query + = "SELECT blackboard_artifacts.artifact_id " //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text >= \"" + bin.getBINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getBINEnd() + 1) + "\"" //NON-NLS + + getRejectedArtifactFilterClause() + + " ORDER BY blackboard_attributes.value_text"; //NON-NLS + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + list.add(rs.getLong("artifact_id")); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + + } + return list; + } + + @Override + protected Node[] createNodes(Long artifactID) { + if (skCase == null) { + return new Node[0]; + } + + try { + BlackboardArtifact art = skCase.getBlackboardArtifact(artifactID); + return new Node[]{new AccountArtifactNode(art)}; + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error creating BlackboardArtifactNode for artifact with ID " + artifactID, ex); //NON-NLS + return new Node[0]; + } + } + } + private final BinResult bin; +// private final CreditCardNumberFactory accountFactory; + + private BINNode(BinResult bin) { + super(Children.LEAF); + setChildren(Children.createLazy(CreditCardNumberFactory::new)); + this.bin = bin; + setName(getBinRangeString()); + updateDisplayName(); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/bank.png"); //NON-NLS + reviewStatusBus.register(this); + } + + @Subscribe + public void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateDisplayName(); + } + + private void updateDisplayName() { + String query + = "SELECT count(blackboard_artifacts.artifact_id ) AS count" //NON-NLS + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER.getTypeID() //NON-NLS + + " AND blackboard_attributes.value_text >= \"" + bin.getBINStart() + "\" AND blackboard_attributes.value_text < \"" + (bin.getBINEnd() + 1) + "\"" //NON-NLS + + getRejectedArtifactFilterClause(); + try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query); + ResultSet rs = results.getResultSet();) { + while (rs.next()) { + setDisplayName(getBinRangeString() + " (" + rs.getLong("count") + ")"); //NON-NLS + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS + + } + + } + + private String getBinRangeString() { + if (bin.getBINStart() == bin.getBINEnd()) { + return Integer.toString(bin.getBINStart()); + } else { + return bin.getBINStart() + "-" + StringUtils.difference(bin.getBINStart() + "", bin.getBINEnd() + ""); + } + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + private Sheet.Set getPropertySet(Sheet s) { + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + return ss; + } + + @Override + @NbBundle.Messages({ + "Accounts.BINNode.binProperty.displayName=Bank Identifier Number", + "Accounts.BINNode.accountsProperty.displayName=Accounts", + "Accounts.BINNode.cardTypeProperty.displayName=Payment Card Type", + "Accounts.BINNode.schemeProperty.displayName=Credit Card Scheme", + "Accounts.BINNode.brandProperty.displayName=Brand", + "Accounts.BINNode.bankProperty.displayName=Bank", + "Accounts.BINNode.bankCityProperty.displayName=Bank City", + "Accounts.BINNode.bankCountryProperty.displayName=Bank Country", + "Accounts.BINNode.bankPhoneProperty.displayName=Bank Phone #", + "Accounts.BINNode.bankURLProperty.displayName=Bank URL", + "Accounts.BINNode.noDescription=no description"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set properties = getPropertySet(sheet); + + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_binProperty_displayName(), + Bundle.Accounts_BINNode_binProperty_displayName(), + Bundle.Accounts_BINNode_noDescription(), + getBinRangeString())); + properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_accountsProperty_displayName(), + Bundle.Accounts_BINNode_accountsProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bin.getCount())); + + //add optional properties if they are available + if (bin.hasDetails()) { + bin.getCardType().ifPresent(cardType -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_cardTypeProperty_displayName(), + Bundle.Accounts_BINNode_cardTypeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + cardType))); + bin.getScheme().ifPresent(scheme -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_schemeProperty_displayName(), + Bundle.Accounts_BINNode_schemeProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + scheme))); + bin.getBrand().ifPresent(brand -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_brandProperty_displayName(), + Bundle.Accounts_BINNode_brandProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + brand))); + bin.getBankName().ifPresent(bankName -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankProperty_displayName(), + Bundle.Accounts_BINNode_bankProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankName))); + bin.getBankCity().ifPresent(bankCity -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCityProperty_displayName(), + Bundle.Accounts_BINNode_bankCityProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + bankCity))); + bin.getCountry().ifPresent(country -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankCountryProperty_displayName(), + Bundle.Accounts_BINNode_bankCountryProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + country))); + bin.getBankPhoneNumber().ifPresent(phoneNumber -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), + Bundle.Accounts_BINNode_bankPhoneProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + phoneNumber))); + bin.getBankURL().ifPresent(url -> properties.put(new NodeProperty<>(Bundle.Accounts_BINNode_bankURLProperty_displayName(), + Bundle.Accounts_BINNode_bankURLProperty_displayName(), Bundle.Accounts_BINNode_noDescription(), + url))); + } + return sheet; + } + } + + /** + * Data model item to back the BINNodes in the tree. Has the number of + * accounts found with the BIN. + */ + @Immutable + final static private class BinResult implements CreditCards.BankIdentificationNumber { + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + this.binEnd; + hash = 97 * hash + this.binStart; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final BinResult other = (BinResult) obj; + if (this.binEnd != other.binEnd) { + return false; + } + if (this.binStart != other.binStart) { + return false; + } + return true; + } + + /** + * The number of accounts with this BIN + */ + private final long count; + + private final BINRange binRange; + private final int binEnd; + private final int binStart; + + private BinResult(long count, @Nonnull BINRange binRange) { + this.count = count; + this.binRange = binRange; + binStart = binRange.getBINstart(); + binEnd = binRange.getBINend(); + } + + private BinResult(long count, int start, int end) { + this.count = count; + this.binRange = null; + binStart = start; + binEnd = end; + } + + int getBINStart() { + return binStart; + } + + int getBINEnd() { + return binEnd; + } + + long getCount() { + return count; + } + + boolean hasDetails() { + return binRange != null; + } + + @Override + public Optional getNumberLength() { + return binRange.getNumberLength(); + } + + @Override + public Optional getBankCity() { + return binRange.getBankCity(); + } + + @Override + public Optional getBankName() { + return binRange.getBankName(); + } + + @Override + public Optional getBankPhoneNumber() { + return binRange.getBankPhoneNumber(); + } + + @Override + public Optional getBankURL() { + return binRange.getBankURL(); + } + + @Override + public Optional getBrand() { + return binRange.getBrand(); + } + + @Override + public Optional getCardType() { + return binRange.getCardType(); + } + + @Override + public Optional getCountry() { + return binRange.getCountry(); + } + + @Override + public Optional getScheme() { + return binRange.getScheme(); + } + } + + final private class AccountArtifactNode extends BlackboardArtifactNode { + + private final BlackboardArtifact artifact; + + private AccountArtifactNode(BlackboardArtifact artifact) { + super(artifact, "org/sleuthkit/autopsy/images/credit-card.png"); //NON-NLS + this.artifact = artifact; + setName("" + this.artifact.getArtifactID()); + } + + @Override + public Action[] getActions(boolean context) { + List actionsList = new ArrayList<>(); + actionsList.addAll(Arrays.asList(super.getActions(context))); + + actionsList.add(approveActionInstance); + actionsList.add(rejectActionInstance); + + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set properties = sheet.get(Sheet.PROPERTIES); + if (properties == null) { + properties = Sheet.createPropertiesSet(); + sheet.put(properties); + } + properties.put(new NodeProperty<>(Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_statusProperty_displayName(), + Bundle.Accounts_FileWithCCNNode_noDescription(), + artifact.getReviewStatus().getDisplayName())); + + return sheet; + } + } + + private final class ToggleShowRejected extends AbstractAction { + + @NbBundle.Messages("ToggleShowRejected.name=Show Rejcted Results") + ToggleShowRejected() { + super(Bundle.ToggleShowRejected_name()); + } + + @Override + public void actionPerformed(ActionEvent e) { + showRejected = !showRejected; + reviewStatusBus.post(new ReviewStatusChangeEvent(Collections.emptySet(), null)); + } + } + + private abstract class ReviewStatusAction extends AbstractAction { + + private final BlackboardArtifact.ReviewStatus newStatus; + + private ReviewStatusAction(String displayName, BlackboardArtifact.ReviewStatus newStatus) { + super(displayName); + this.newStatus = newStatus; + + } + + @Override + public void actionPerformed(ActionEvent e) { + + /* get paths for selected nodes to reselect after applying review + * status change */ + List selectedPaths = Utilities.actionsGlobalContext().lookupAll(Node.class).stream() + .map(node -> { + String[] createPath; + /* + * If the we are rejecting and not showing rejected + * results, then the selected node, won't exist any + * more, so we select the previous one in stead. + */ + if (newStatus == BlackboardArtifact.ReviewStatus.REJECTED && showRejected == false) { + List siblings = Arrays.asList(node.getParentNode().getChildren().getNodes()); + int indexOf = siblings.indexOf(node); + //there is no previous for the first node, so instead we select the next one + Node sibling = indexOf > 0 + ? siblings.get(indexOf - 1) + : siblings.get(indexOf + 1); + createPath = NodeOp.createPath(sibling, null); + } else { + createPath = NodeOp.createPath(node, null); + } + //for the reselect to work we need to strip off the first part of the path. + return Arrays.copyOfRange(createPath, 1, createPath.length); + }).collect(Collectors.toList()); + + //change status of selected artifacts + final Collection artifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); + artifacts.forEach(artifact -> { + try { + skCase.setReviewStatus(artifact, newStatus); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error changing artifact review status.", ex); //NON-NLS + } + }); + //post event + reviewStatusBus.post(new ReviewStatusChangeEvent(artifacts, newStatus)); + + final DataResultTopComponent directoryListing = DirectoryTreeTopComponent.findInstance().getDirectoryListing(); + final Node rootNode = directoryListing.getRootNode(); + + //convert paths back to nodes + List toArray = new ArrayList<>(); + selectedPaths.forEach(path -> { + try { + toArray.add(NodeOp.findPath(rootNode, path)); + } catch (NodeNotFoundException ex) { + //just ingnore paths taht don't exist. this is expected since we are rejecting + } + }); + //select nodes + directoryListing.setSelectedNodes(toArray.toArray(new Node[toArray.size()])); + } + } + + final private class ApproveAccounts extends ReviewStatusAction { + + @NbBundle.Messages({"ApproveAccountsAction.name=Approve Accounts"}) + private ApproveAccounts() { + super(Bundle.ApproveAccountsAction_name(), BlackboardArtifact.ReviewStatus.APPROVED); + } + } + + final private class RejectAccounts extends ReviewStatusAction { + + @NbBundle.Messages({"RejectAccountsAction.name=Reject Accounts"}) + private RejectAccounts() { + super(Bundle.RejectAccountsAction_name(), BlackboardArtifact.ReviewStatus.REJECTED); + } + } + + class ReviewStatusChangeEvent { + + Collection artifacts; + BlackboardArtifact.ReviewStatus newReviewStatus; + + public ReviewStatusChangeEvent(Collection artifacts, BlackboardArtifact.ReviewStatus newReviewStatus) { + this.artifacts = artifacts; + this.newReviewStatus = newReviewStatus; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java similarity index 85% rename from Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java index ceb390e88e..bf6e2246ea 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyItemVisitor.java @@ -16,14 +16,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; + +import org.sleuthkit.autopsy.datamodel.DataSources; +import org.sleuthkit.autopsy.datamodel.DeletedContent; +import org.sleuthkit.autopsy.datamodel.EmailExtracted; +import org.sleuthkit.autopsy.datamodel.ExtractedContent; +import org.sleuthkit.autopsy.datamodel.FileSize; +import org.sleuthkit.autopsy.datamodel.HashsetHits; +import org.sleuthkit.autopsy.datamodel.InterestingHits; +import org.sleuthkit.autopsy.datamodel.KeywordHits; +import org.sleuthkit.autopsy.datamodel.Reports; +import org.sleuthkit.autopsy.datamodel.Results; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.autopsy.datamodel.Views; /** * This visitor goes over the AutopsyVisitableItems, which are currently the * nodes in the tree that are structural and not nodes that are from * Sleuthkit-based data model objects. */ -interface AutopsyItemVisitor { +public interface AutopsyItemVisitor { T visit(DataSources i); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java similarity index 91% rename from Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java index a8efccb494..3eb949ff01 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyVisitableItem.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/AutopsyVisitableItem.java @@ -16,13 +16,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; +; /** * AutopsyVisitableItems are the nodes in the directory tree that are for * structure only. They are not associated with content objects. */ -interface AutopsyVisitableItem { +public interface AutopsyVisitableItem { /** * visitor pattern support diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java new file mode 100644 index 0000000000..c29f9be85d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/BINRange.java @@ -0,0 +1,146 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel._private; + +import java.util.Optional; +import javax.annotation.concurrent.Immutable; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.datamodel.CreditCards; + +/** + * Details of a range of Bank Identification Number(s) (BIN) used by a bank. + */ +@Immutable +public class BINRange implements CreditCards.BankIdentificationNumber { + + private final int BINStart; //start of BIN range, 8 digits + private final int BINEnd; // end (incluse ) of BIN rnage, 8 digits + + private final Integer numberLength; // the length of accounts numbers with this BIN, currently unused + + /** + * AMEX, VISA, MASTERCARD, DINERS, DISCOVER, UNIONPAY + */ + private final String scheme; + private final String brand; + + /** + * DEBIT, CREDIT + */ + private final String cardType; + private final String country; + private final String bankName; + private final String bankCity; + private final String bankURL; + private final String bankPhoneNumber; + + /** + * Constructor + * + * @param BIN_start the first BIN in the range, must be 8 digits + * @param BIN_end the last(inclusive) BIN in the range, must be 8 + * digits + * @param number_length the length of account numbers in this BIN range + * @param scheme amex/visa/mastercard/etc + * @param brand the brand of this BIN range + * @param type credit vs debit + * @param country the country of the issuer + * @param bank_name the name of the issuer + * @param bank_url the url of the issuer + * @param bank_phone the phone number of the issuer + * @param bank_city the city of the issuer + */ + public BINRange(int BIN_start, int BIN_end, Integer number_length, String scheme, String brand, String type, String country, String bank_name, String bank_url, String bank_phone, String bank_city) { + this.BINStart = BIN_start; + this.BINEnd = BIN_end; + + this.numberLength = number_length; + this.scheme = StringUtils.defaultIfBlank(scheme, null); + this.brand = StringUtils.defaultIfBlank(brand, null); + this.cardType = StringUtils.defaultIfBlank(type, null); + this.country = StringUtils.defaultIfBlank(country, null); + this.bankName = StringUtils.defaultIfBlank(bank_name, null); + this.bankURL = StringUtils.defaultIfBlank(bank_url, null); + this.bankPhoneNumber = StringUtils.defaultIfBlank(bank_phone, null); + this.bankCity = StringUtils.defaultIfBlank(bank_city, null); + } + + /** + * Get the first BIN in this range + * + * @return the first BIN in this range. + */ + public int getBINstart() { + return BINStart; + } + + /** + * Get the last (inclusive) BIN in this range. + * + * @return the last (inclusive) BIN in this range. + */ + public int getBINend() { + return BINEnd; + } + + @Override + public Optional getNumberLength() { + return Optional.ofNullable(numberLength); + } + + @Override + public Optional getScheme() { + return Optional.ofNullable(scheme); + } + + @Override + public Optional getBrand() { + return Optional.ofNullable(brand); + } + + @Override + public Optional getCardType() { + return Optional.ofNullable(cardType); + } + + @Override + public Optional getCountry() { + return Optional.ofNullable(country); + } + + @Override + public Optional getBankName() { + return Optional.ofNullable(bankName); + } + + @Override + public Optional getBankURL() { + return Optional.ofNullable(bankURL); + } + + @Override + public Optional getBankPhoneNumber() { + return Optional.ofNullable(bankPhoneNumber); + } + + @Override + public Optional getBankCity() { + return Optional.ofNullable(bankCity); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties new file mode 100644 index 0000000000..dc778857c5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/Bundle.properties @@ -0,0 +1,12 @@ +FileTypeExtensionFilters.tskImgFilter.text=Images +FileTypeExtensionFilters.tskVideoFilter.text=Videos +FileTypeExtensionFilters.tskAudioFilter.text=Audio +FileTypeExtensionFilters.tskArchiveFilter.text=Archives +FileTypeExtensionFilters.tskDocumentFilter.text=Documents +FileTypeExtensionFilters.tskExecFilter.text=Executable +FileTypeExtensionFilters.autDocHtmlFilter.text=HTML +FileTypeExtensionFilters.autDocOfficeFilter.text=Office +FileTypeExtensionFilters.autoDocPdfFilter.text=PDF +FileTypeExtensionFilters.autDocTxtFilter.text=Plain Text +FileTypeExtensionFilters.autDocRtfFilter.text=Rich Text + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java index 1e2dd473dd..09a650161d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensionFilters.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/FileTypeExtensionFilters.java @@ -16,20 +16,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; import java.util.Arrays; import java.util.List; - import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.FileTypeExtensions; import org.sleuthkit.datamodel.SleuthkitCase; /** * Filters database results by file extension. */ -class FileTypeExtensionFilters implements AutopsyVisitableItem { +public class FileTypeExtensionFilters implements AutopsyVisitableItem { - private SleuthkitCase skCase; + private final SleuthkitCase skCase; // root node filters public enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface { @@ -53,10 +53,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.tskExecFilter.text"), Arrays.asList(".exe", ".dll", ".bat", ".cmd", ".com")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private RootFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -110,10 +110,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { NbBundle.getMessage(FileTypeExtensionFilters.class, "FileTypeExtensionFilters.autDocRtfFilter.text"), Arrays.asList(".rtf")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private DocumentFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -157,10 +157,10 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { ExecutableFilter_CMD(3, "ExecutableFilter_CMD", ".cmd", Arrays.asList(".cmd")), //NON-NLS ExecutableFilter_COM(4, "ExecutableFilter_COM", ".com", Arrays.asList(".com")); //NON-NLS - private int id; - private String name; - private String displayName; - private List filter; + private final int id; + private final String name; + private final String displayName; + private final List filter; private ExecutableFilter(int id, String name, String displayName, List filter) { this.id = id; @@ -208,7 +208,7 @@ class FileTypeExtensionFilters implements AutopsyVisitableItem { return this.skCase; } - interface SearchFilterInterface { + public interface SearchFilterInterface { public String getName(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java b/Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java rename to Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java index cc8b8424cb..d3163d579d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/RecentFiles.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/_private/RecentFiles.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.datamodel; +package org.sleuthkit.autopsy.datamodel._private; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.SleuthkitCase; @@ -25,7 +25,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; * Recent files node support NOTE: As of june '15 we do not display this in the * tree. It can be added back when we have filtering in the results area. */ -class RecentFiles implements AutopsyVisitableItem { +public class RecentFiles implements AutopsyVisitableItem { SleuthkitCase skCase; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index d8985ac8a7..ec66a5d4c7 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -37,38 +37,15 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; -import org.sleuthkit.autopsy.datamodel.Accounts; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; -import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.DeletedContentNode; -import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; -import org.sleuthkit.autopsy.datamodel.EmailExtracted; -import org.sleuthkit.autopsy.datamodel.EmailExtracted.AccountNode; -import org.sleuthkit.autopsy.datamodel.EmailExtracted.FolderNode; -import org.sleuthkit.autopsy.datamodel.ExtractedContent; -import org.sleuthkit.autopsy.datamodel.ExtractedContent.TypeNode; import org.sleuthkit.autopsy.datamodel.FileNode; -import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; -import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; -import org.sleuthkit.autopsy.datamodel.FileTypeNode; -import org.sleuthkit.autopsy.datamodel.FileTypesNode; -import org.sleuthkit.autopsy.datamodel.HashsetHits; -import org.sleuthkit.autopsy.datamodel.HashsetHits.HashsetNameNode; -import org.sleuthkit.autopsy.datamodel.ImageNode; -import org.sleuthkit.autopsy.datamodel.InterestingHits; -import org.sleuthkit.autopsy.datamodel.KeywordHits; -import org.sleuthkit.autopsy.datamodel.KeywordHits.ListNode; -import org.sleuthkit.autopsy.datamodel.KeywordHits.TermNode; import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; -import org.sleuthkit.autopsy.datamodel.RecentFilesFilterNode; -import org.sleuthkit.autopsy.datamodel.RecentFilesNode; import org.sleuthkit.autopsy.datamodel.Reports; -import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; -import org.sleuthkit.autopsy.datamodel.VolumeNode; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -301,112 +278,12 @@ public class DataResultFilterNode extends FilterNode { */ private class GetPreferredActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default { - @Override - public AbstractAction visit(ImageNode in) { - return openChild(in); - } - - @Override - public AbstractAction visit(VolumeNode vn) { - return openChild(vn); - } - - @Override - public AbstractAction visit(ExtractedContent.RootNode ecn) { - return openChild(ecn); - } - - @Override - public AbstractAction visit(KeywordHits.RootNode khrn) { - return openChild(khrn); - } - - @Override - public AbstractAction visit(HashsetHits.RootNode hhrn) { - return openChild(hhrn); - } - - @Override - public AbstractAction visit(HashsetNameNode hhsn) { - return openChild(hhsn); - } - - @Override - public AbstractAction visit(InterestingHits.RootNode iarn) { - return openChild(iarn); - } - - @Override - public AbstractAction visit(InterestingHits.SetNameNode iasn) { - return openChild(iasn); - } - - @Override - public AbstractAction visit(EmailExtracted.RootNode eern) { - return openChild(eern); - } - - @Override - public AbstractAction visit(AccountNode eean) { - return openChild(eean); - } - - @Override - public AbstractAction visit(FolderNode eefn) { - return openChild(eefn); - } - - @Override - public AbstractAction visit(RecentFilesNode rfn) { - return openChild(rfn); - } - - @Override - public AbstractAction visit(DeletedContentsNode dcn) { - return openChild(dcn); - } - - @Override - public AbstractAction visit(DeletedContentNode dcn) { - return openChild(dcn); - } - - @Override - public AbstractAction visit(FileSizeRootNode fsrn) { - return openChild(fsrn); - } - - @Override - public AbstractAction visit(FileSizeNode fsn) { - return openChild(fsn); - } - @Override public AbstractAction visit(BlackboardArtifactNode ban) { return new ViewContextAction( NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban); } - @Override - public AbstractAction visit(TypeNode atn) { - return openChild(atn); - } - - @Override - public AbstractAction visit(Tags.TagNameNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Tags.ContentTagTypeNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Tags.BlackboardArtifactTagTypeNode node) { - return openChild(node); - } - @Override public AbstractAction visit(DirectoryNode dn) { if (dn.getDisplayName().equals(DirectoryNode.DOTDOTDIR)) { @@ -418,11 +295,6 @@ public class DataResultFilterNode extends FilterNode { } } - @Override - public AbstractAction visit(VirtualDirectoryNode ldn) { - return openChild(ldn); - } - @Override public AbstractAction visit(FileNode fn) { if (fn.hasContentChildren()) { @@ -441,69 +313,14 @@ public class DataResultFilterNode extends FilterNode { } } - @Override - public AbstractAction visit(FileTypeNode fsfn) { - return openChild(fsfn); - } - - @Override - public AbstractAction visit(FileTypesNode sfn) { - return openChild(sfn); - } - - @Override - public AbstractAction visit(RecentFilesFilterNode rffn) { - return openChild(rffn); - } - - @Override - public AbstractAction visit(ListNode khsn) { - return openChild(khsn); - } - - @Override - public AbstractAction visit(TermNode khmln) { - return openChild(khmln); - } - @Override public AbstractAction visit(Reports.ReportNode reportNode) { return reportNode.getPreferredAction(); } - @Override - public AbstractAction visit(Accounts.BINNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Accounts.FileWithCCNNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Accounts.ByFileNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Accounts.ByBINNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Accounts.AccountsRootNode node) { - return openChild(node); - } - - @Override - public AbstractAction visit(Accounts.AccountTypeNode node) { - return openChild(node); - } - @Override protected AbstractAction defaultVisit(DisplayableItemNode c) { - return null; + return openChild(c); } /** diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 987c2bc827..67cb358f6a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -57,7 +57,6 @@ import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.Accounts; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; @@ -71,6 +70,7 @@ import org.sleuthkit.autopsy.datamodel.RootContentChildren; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.autopsy.datamodel.Views; import org.sleuthkit.autopsy.datamodel.ViewsNode; +import org.sleuthkit.autopsy.datamodel._private.Accounts; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java index 4a904042c7..a52dd2cbf5 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExternalViewerAction.java @@ -40,7 +40,7 @@ public class ExternalViewerAction extends AbstractAction { private final static Logger logger = Logger.getLogger(ExternalViewerAction.class.getName()); private org.sleuthkit.datamodel.AbstractFile fileObject; - final static String[] EXECUTABLE_EXT = {".exe", ".dll", ".com", ".bat", ".msi", ".reg", ".scr"}; //NON-NLS + final static String[] EXECUTABLE_EXT = {".exe", ".dll", ".com", ".bat", ".msi", ".reg", ".scr", ".cmd"}; //NON-NLS public ExternalViewerAction(String title, Node fileNode) { super(title); diff --git a/Core/src/org/sleuthkit/autopsy/images/account_menu.png b/Core/src/org/sleuthkit/autopsy/images/accounts.png similarity index 100% rename from Core/src/org/sleuthkit/autopsy/images/account_menu.png rename to Core/src/org/sleuthkit/autopsy/images/accounts.png diff --git a/Core/src/org/sleuthkit/autopsy/images/add-tag.png b/Core/src/org/sleuthkit/autopsy/images/add-tag.png new file mode 100755 index 0000000000..e60e692bac Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/add-tag.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/delete-tag.png b/Core/src/org/sleuthkit/autopsy/images/delete-tag.png new file mode 100755 index 0000000000..25edd1ab6b Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/delete-tag.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/edit-tag.png b/Core/src/org/sleuthkit/autopsy/images/edit-tag.png new file mode 100755 index 0000000000..4bc2a7ec13 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/edit-tag.png differ diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index ea32f57537..f3057a946d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -254,8 +254,8 @@ class ReportHTML implements TableReportModule { case TSK_REMOTE_DRIVE: in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS break; - case TSK_CREDIT_CARD_ACCOUNT: - in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/credit-card.png"); //NON-NLS + case TSK_ACCOUNT: + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS break; default: logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = " + dataType); //NON-NLS @@ -264,7 +264,17 @@ class ReportHTML implements TableReportModule { iconFilePath = path + File.separator + iconFileName; break; } - } else { // no defined artifact found for this dataType + } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) { + /* TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE + * attribute, with a synthetic compound dataType name, so they are + * not caught by the switch statement above. For now we just give + * them all the general account icon, but we could do something else + * in the future. + */ + in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS + iconFileName = "accounts.png"; //NON-NLS + iconFilePath = path + File.separator + iconFileName; + } else { // no defined artifact found for this dataType in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS iconFileName = "star.png"; //NON-NLS iconFilePath = path + File.separator + iconFileName; diff --git a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java index a7ad4b1415..8ba53127d5 100755 --- a/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/TableReportGenerator.java @@ -18,12 +18,16 @@ */ package org.sleuthkit.autopsy.report; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimaps; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -43,6 +47,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.Type; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; @@ -119,10 +124,10 @@ class TableReportGenerator { */ private void makeBlackboardArtifactTables() { // Make a comment string describing the tag names filter in effect. - StringBuilder comment = new StringBuilder(); + String comment = ""; if (!tagNamesFilter.isEmpty()) { - comment.append(NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text")); - comment.append(makeCommaSeparatedList(tagNamesFilter)); + comment += NbBundle.getMessage(this.getClass(), "ReportGenerator.artifactTable.taggedResults.text"); + comment += makeCommaSeparatedList(tagNamesFilter); } // Add a table to the report for every enabled blackboard artifact type. @@ -139,10 +144,10 @@ class TableReportGenerator { // Keyword hits and hashset hit artifacts get special handling. if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - writeKeywordHits(tableReport, comment.toString(), tagNamesFilter); + writeKeywordHits(tableReport, comment, tagNamesFilter); continue; } else if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) { - writeHashsetHits(tableReport, comment.toString(), tagNamesFilter); + writeHashsetHits(tableReport, comment, tagNamesFilter); continue; } @@ -152,54 +157,92 @@ class TableReportGenerator { continue; } - /* - Gets all of the attribute types of this artifact type by adding - all of the types to a set - */ - Set attrTypeSet = new TreeSet<>((BlackboardAttribute.Type o1, BlackboardAttribute.Type o2) -> o1.getDisplayName().compareTo(o2.getDisplayName())); - for (ArtifactData data : artifactList) { - List attributes = data.getAttributes(); - for (BlackboardAttribute attribute : attributes) { - attrTypeSet.add(attribute.getAttributeType()); + /* TSK_ACCOUNT artifacts get grouped by their TSK_ACCOUNT_TYPE + * attribute, and then handed off to the standard method for writing + * tables. */ + if (type.getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { + //Group account artifacts by their account type + ListMultimap groupedArtifacts = Multimaps.index(artifactList, + artifactData -> { + try { + return artifactData.getArtifact().getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)).getValueString(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Unable to get value of TSK_ACCOUNT_TYPE attribute. Defaulting to \"unknown\"", ex); + return "unknown"; + } + }); + for (String accountType : groupedArtifacts.keySet()) { + /* If the report is a ReportHTML, the data type name + * eventualy makes it to useDataTypeIcon which expects but + * does not require a artifact name, so we make a synthetic + * compund name by appending a ":" and the account type. + */ + final String compundDataTypeName = BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName() + ": " + accountType; + writeTableForDataType(groupedArtifacts.get(accountType), type, compundDataTypeName, comment); } + } else { + //all other artifact types are sent to writeTableForDataType directly + writeTableForDataType(artifactList, type, type.getDisplayName(), comment); } - // Get the columns appropriate for the artifact type. This is - // used to get the data that will be in the cells below based on - // type, and display the column headers. - List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); - if (columns.isEmpty()) { - continue; - } - columnHeaderMap.put(type.getTypeID(), columns); - - // The artifact list is sorted now, as getting the row data is - // dependent on having the columns, which is necessary for - // sorting. - Collections.sort(artifactList); - List columnHeaderNames = new ArrayList<>(); - for (Column currColumn : columns) { - columnHeaderNames.add(currColumn.getColumnHeader()); - } - - tableReport.startDataType(type.getDisplayName(), comment.toString()); - tableReport.startTable(columnHeaderNames); - for (ArtifactData artifactData : artifactList) { - // Get the row data for this artifact, and has the - // module add it. - List rowData = artifactData.getRow(); - if (rowData.isEmpty()) { - continue; - } - - tableReport.addRow(rowData); - } - // Finish up this data type - progressPanel.increment(); - tableReport.endTable(); - tableReport.endDataType(); } } + /** + * + * Write the given list of artifacts to the table for the given type. + * + * @param artifactList The List of artifacts to include in the table. + * @param type The Type of artifacts included in the table. All the + * artifacts in artifactList should be of this type. + * @param tableName The name of the table. + * @param comment A comment to put in the header. + */ + private void writeTableForDataType(List artifactList, BlackboardArtifact.Type type, String tableName, String comment) { + /* + * Make a sorted set of all of the attribute types that are on any of + * the given artifacts. + */ + Set attrTypeSet = new TreeSet<>(Comparator.comparing(BlackboardAttribute.Type::getDisplayName)); + for (ArtifactData data : artifactList) { + List attributes = data.getAttributes(); + for (BlackboardAttribute attribute : attributes) { + attrTypeSet.add(attribute.getAttributeType()); + } + } + /* Get the columns appropriate for the artifact type. This is used to + * get the data that will be in the cells below based on type, and + * display the column headers. + */ + List columns = getArtifactTableColumns(type.getTypeID(), attrTypeSet); + if (columns.isEmpty()) { + return; + } + columnHeaderMap.put(type.getTypeID(), columns); + + /* The artifact list is sorted now, as getting the row data is dependent + * on having the columns, which is necessary for sorting. + */ + Collections.sort(artifactList); + + tableReport.startDataType(tableName, comment); + tableReport.startTable(Lists.transform(columns, Column::getColumnHeader)); + + for (ArtifactData artifactData : artifactList) { + // Get the row data for this artifact, and has the + // module add it. + List rowData = artifactData.getRow(); + if (rowData.isEmpty()) { + return; + } + + tableReport.addRow(rowData); + } + // Finish up this data type + progressPanel.increment(); + tableReport.endTable(); + tableReport.endDataType(); + } + /** * Make table for tagged files */ @@ -1449,8 +1492,9 @@ class TableReportGenerator { columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.remotePath"), new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_REMOTE_PATH))); - } else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + } else if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { columns.add(new StatusColumn()); + attributeTypeSet.remove(new Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); } else { // This is the case that it is a custom type. The reason an else is // necessary is to make sure that the source file column is added @@ -1584,6 +1628,7 @@ class TableReportGenerator { } } + private class AttributeColumn implements Column { private final String columnHeader; @@ -1643,10 +1688,6 @@ class TableReportGenerator { @Override public String getCellData(ArtifactData artData) { return getFileUniquePath(artData.getContent()); - /*else if (this.columnHeader.equals(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tags"))) { - return makeCommaSeparatedList(artData.getTags()); - } - return "";*/ } @Override diff --git a/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png b/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png new file mode 100644 index 0000000000..3a53d894dc Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/report/images/account_menu.png differ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index d3581629f0..f147e16170 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; @@ -127,7 +127,7 @@ public class ExtractedContentViewer implements DataContentViewer { //if the node had artifacts in the lookup use them, other wise look up all credit card artifacts for the content. Collection artifacts = nodeLookup.lookupAll(BlackboardArtifact.class); artifacts = (artifacts == null || artifacts.isEmpty()) - ? content.getArtifacts(TSK_CREDIT_CARD_ACCOUNT) + ? content.getArtifacts(TSK_ACCOUNT) : artifacts; /* @@ -140,7 +140,7 @@ public class ExtractedContentViewer implements DataContentViewer { */ for (BlackboardArtifact artifact : artifacts) { try { - BlackboardAttribute solrIDAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID)); + BlackboardAttribute solrIDAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID)); if (solrIDAttr != null) { String valueString = solrIDAttr.getValueString(); if (StringUtils.isNotBlank(valueString)) { @@ -148,7 +148,7 @@ public class ExtractedContentViewer implements DataContentViewer { } } - BlackboardAttribute keyWordAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)); + BlackboardAttribute keyWordAttr = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); if (keyWordAttr != null) { String valueString = keyWordAttr.getValueString(); if (StringUtils.isNotBlank(valueString)) { @@ -188,7 +188,7 @@ public class ExtractedContentViewer implements DataContentViewer { * For keyword hit artifacts, add the text of the artifact that hit, * not the hit artifact; otherwise add the text for the artifact. */ - if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID() || artifact.getArtifactTypeID() == TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID() || artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE); if (attribute != null) { @@ -296,7 +296,7 @@ public class ExtractedContentViewer implements DataContentViewer { Collection artifacts = node.getLookup().lookupAll(BlackboardArtifact.class); if (artifacts != null) { for (BlackboardArtifact art : artifacts) { - if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { return true; } } @@ -321,7 +321,7 @@ public class ExtractedContentViewer implements DataContentViewer { if (art == null) { return 4; } else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID() - || art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT.getTypeID()) { + || art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { return 6; } else { return 4; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java index ab06fc0cbc..028908dc34 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchList.java @@ -122,7 +122,7 @@ abstract class KeywordSearchList { //CCN List ccns = new ArrayList<>(); - ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)); + ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); lockedLists.add("Credit Card Numbers"); addList("Credit Card Numbers", ccns, true, false, true); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java index eea866a2e3..7dcb605c18 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java @@ -34,8 +34,9 @@ import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.response.TermsResponse.Term; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Version; -import org.sleuthkit.autopsy.datamodel.Accounts; +import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -52,8 +53,7 @@ final class TermComponentQuery implements KeywordSearchQuery { private static final boolean DEBUG = Version.Type.DEVELOPMENT.equals(Version.getBuildType()); private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName(); - private static final BlackboardAttribute.Type SOLR_DOCUMENT_ID_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_SOLR_DOCUMENT_ID); - private static final BlackboardAttribute.Type ACCOUNT_NUMBER_TYPE = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER); + private static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID); //TODO: move these regex and the luhn check to a new class, something like: CreditCardNumberValidator /* @@ -186,8 +186,13 @@ final class TermComponentQuery implements KeywordSearchQuery { Collection attributes = new ArrayList<>(); try { //if the keyword hit matched the credit card number keyword/regex... - if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) { - newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_CREDIT_CARD_ACCOUNT); + if (keyword.getType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { + newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_ACCOUNT); + final BlackboardAttribute attr = new BlackboardAttribute( + new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE), + MODULE_NAME, Account.Type.CREDIT_CARD.name()); + newArtifact.addAttribute(attr); + // make account artifact //try to match it against the track 1 regex Matcher matcher = TRACK1_PATTERN.matcher(hit.getSnippet()); @@ -204,31 +209,31 @@ final class TermComponentQuery implements KeywordSearchQuery { AbstractFile file = (AbstractFile) hit.getContent(); if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) { - newArtifact.addAttribute(new BlackboardAttribute(SOLR_DOCUMENT_ID_TYPE, MODULE_NAME, hit.getSolrDocumentId())); + newArtifact.addAttribute(new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId())); } } - String ccn = newArtifact.getAttribute(ACCOUNT_NUMBER_TYPE).getValueString(); - final int iin = Integer.parseInt(ccn.substring(0, 8)); + String ccn = newArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)).getValueString(); + final int bin = Integer.parseInt(ccn.substring(0, 8)); - Accounts.IINInfo iinInfo = Accounts.getIINInfo(iin); + CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin); - if (iinInfo != null) { - iinInfo.getScheme().ifPresent(scheme - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_SCHEME, scheme)); - iinInfo.getCardType().ifPresent(cardType - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_PAYMENT_CARD_TYPE, cardType)); - iinInfo.getBrand().ifPresent(brand - -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BRAND, brand)); - iinInfo.getBankName().ifPresent(bankName + if (binInfo != null) { + binInfo.getScheme().ifPresent(scheme + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CARD_SCHEME, scheme)); + binInfo.getCardType().ifPresent(cardType + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CARD_TYPE, cardType)); + binInfo.getBrand().ifPresent(brand + -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BRAND_NAME, brand)); + binInfo.getBankName().ifPresent(bankName -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_BANK_NAME, bankName)); - iinInfo.getBankPhoneNumber().ifPresent(phoneNumber + binInfo.getBankPhoneNumber().ifPresent(phoneNumber -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, phoneNumber)); - iinInfo.getBankURL().ifPresent(url + binInfo.getBankURL().ifPresent(url -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_URL, url)); - iinInfo.getCountry().ifPresent(country + binInfo.getCountry().ifPresent(country -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_COUNTRY, country)); - iinInfo.getBankCity().ifPresent(city + binInfo.getBankCity().ifPresent(city -> addAttributeSafe(newArtifact, ATTRIBUTE_TYPE.TSK_CITY, city)); } } else { @@ -323,7 +328,7 @@ final class TermComponentQuery implements KeywordSearchQuery { for (Term term : terms) { final String termStr = KeywordSearchUtil.escapeLuceneQuery(term.getTerm()); - if (keyword.getType() == ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER) { + if (keyword.getType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { //If the keyword is a credit card number, pass it through luhn validator Matcher matcher = CCN_PATTERN.matcher(term.getTerm()); matcher.find(); @@ -383,7 +388,7 @@ final class TermComponentQuery implements KeywordSearchQuery { BlackboardAttribute.Type type = new BlackboardAttribute.Type(attrType); if (artifact.getAttribute(type) == null) { String value = matcher.group(groupName); - if (attrType.equals(ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER)) { + if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) { value = CharMatcher.anyOf(" -").removeFrom(value); } if (StringUtils.isNotBlank(value)) { @@ -404,11 +409,11 @@ final class TermComponentQuery implements KeywordSearchQuery { */ static private void parseTrack2Data(BlackboardArtifact artifact, Matcher matcher) throws IllegalArgumentException, TskCoreException { //try to add all the attrributes common to track 1 and 2 - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_ACCOUNT_NUMBER, "accountNumber", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_EXPIRATION, "expiration", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_SERVICE_CODE, "serviceCode", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_DISCRETIONARY, "discretionary", matcher); - addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CREDIT_CARD_LRC, "LRC", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_NUMBER, "accountNumber", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION, "expiration", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE, "serviceCode", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY, "discretionary", matcher); + addAttributeIfNotAlreadyCaptured(artifact, ATTRIBUTE_TYPE.TSK_CARD_LRC, "LRC", matcher); }