diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java index 3cb8764f6f..069b13ef2e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java @@ -48,6 +48,7 @@ public class Services implements Closeable { //create and initialize FileManager as early as possibly in the new/opened Case fileManager = new FileManager(tskCase); services.add(fileManager); + tagsManager = new TagsManager(tskCase); services.add(tagsManager); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 07151aa809..ef9d3f6934 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -20,21 +20,228 @@ package org.sleuthkit.autopsy.casemodule.services; import java.io.Closeable; import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardArtifactTag; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagType; +import org.sleuthkit.datamodel.TskCoreException; /** - * A instance of this class functions as an Autopsy service that manages the - * creation, updating, and deletion of tags applied to files and artifacts by - * users. + * A singleton instance of this class functions as an Autopsy service that + * manages the creation, updating, and deletion of tags applied to Content and + * BlackboardArtifacts objects by users. */ public class TagsManager implements Closeable { - private final SleuthkitCase database; + private static final String TAGS_SETTINGS_FILE_NAME = "tags"; + private static final String TAG_TYPES_SETTING_KEY = "tagTypes"; + private final SleuthkitCase tskCase; + private final HashMap tagTypes = new HashMap<>(); + + TagsManager(SleuthkitCase tskCase) { + this.tskCase = tskCase; + loadTagTypesFromTagSettings(); + } + + private void loadTagTypesFromTagSettings() { + // Get any tag types already added to the current case. + try { + List currentTagTypes = tskCase.getTagTypes(); + for (TagType tagType : currentTagTypes) { + tagTypes.put(tagType.getDisplayName(), tagType); + } + } + catch (TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag types from the current case", ex); + } + + // Read the saved tag types, if any, from the tags settings file and + // add them to the current case if they haven't already been added, e.g, + // when the case was last opened. + String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_FILE_NAME, TAG_TYPES_SETTING_KEY); + if (null != setting && !setting.isEmpty()) { + // Read the tag types setting and break in into tag type tuples. + List tagTypeTuples = Arrays.asList(setting.split(";")); - TagsManager(SleuthkitCase database) { - this.database = database; + // Parse each tuple and add the tag types to the current case, one + // at a time to gracefully discard any duplicates or corrupt tuples. + for (String tagTypeTuple : tagTypeTuples) { + String[] tagTypeAttributes = tagTypeTuple.split(","); + if (!tagTypes.containsKey(tagTypeAttributes[0])) { + TagType tagType = new TagType(tagTypeAttributes[0], tagTypeAttributes[1], TagType.HTML_COLOR.getColorByName(tagTypeAttributes[2])); + try { + tskCase.addTagType(tagType); + tagTypes.put(tagType.getDisplayName(),tagType); + } + catch(TskCoreException ex) { + Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, "Failed to add saved " + tagType.getDisplayName() + " tag type to the current case", ex); + } + } + } + + saveTagTypesToTagsSettings(); + } + } + + private void saveTagTypesToTagsSettings() { + if (!tagTypes.isEmpty()) { + StringBuilder setting = new StringBuilder(); + for (TagType tagType : tagTypes.values()) { + if (setting.length() != 0) { + setting.append(";"); + } + setting.append(tagType.getDisplayName()).append(","); + setting.append(tagType.getDescription()).append(","); + setting.append(tagType.getColor().name()); + } + + ModuleSettings.setConfigSetting(TAGS_SETTINGS_FILE_NAME, TAG_TYPES_SETTING_KEY, setting.toString()); + } + } + + /** + * Gets a list of all tag types currently available for tagging content or + * blackboard artifacts. + * @return A list, possibly empty, of TagType data transfer objects (DTOs). + * @throws TskCoreException + */ + public List getTagTypes() throws TskCoreException { + return tskCase.getTagTypes(); + } + + /** + * Adds a new tag type to the current case and to the tags settings file. + * @param displayName The display name for the new tag type. + * @return A TagType object representing the new type on success, null on failure. + * @throws TskCoreException + */ + public TagType addTagType(String displayName) throws TagTypeAlreadyExistsException, TskCoreException { + return addTagType(displayName, "", TagType.HTML_COLOR.NONE); + } + + /** + * Adds a new tag type to the current case and to the tags settings file. + * @param displayName The display name for the new tag type. + * @param description The description for the new tag type. + * @return A TagType object representing the new type on success, null on failure. + * @throws TskCoreException + */ + public TagType addTagType(String displayName, String description) throws TagTypeAlreadyExistsException, TskCoreException { + return addTagType(displayName, description, TagType.HTML_COLOR.NONE); + } + + /** + * Adds a new tag type to the current case and to the tags settings file. + * @param displayName The display name for the new tag type. + * @param description The description for the new tag type. + * @param color The HTML color to associate with the new tag type. + * @return A TagType object representing the new type. + * @throws TskCoreException + */ + public synchronized TagType addTagType(String displayName, String description, TagType.HTML_COLOR color) throws TagTypeAlreadyExistsException, TskCoreException { + if (tagTypes.containsKey(displayName)) { + throw new TagTypeAlreadyExistsException(); + } + + TagType newTagType = new TagType(displayName, description, color); + tskCase.addTagType(newTagType); + tagTypes.put(newTagType.getDisplayName(), newTagType); + saveTagTypesToTagsSettings(); + return newTagType; + } + + public class TagTypeAlreadyExistsException extends Exception { + } + + /** + * Tags a Content object. + * @param content The Content to tag. + * @param tagType The type of tag to add. + * @throws TskCoreException + */ + public void addContentTag(Content content, TagType tagType) throws TskCoreException { + addContentTag(content, tagType, "", 0, content.getSize()); + } + + /** + * Tags a Content object. + * @param content The Content to tag. + * @param tagType The type of tag to add. + * @param comment A comment to store with the tag. + * @throws TskCoreException + */ + public void addContentTag(Content content, TagType tagType, String comment) throws TskCoreException { + addContentTag(content, tagType, comment, 0, content.getSize() - 1); + } + + /** + * Tags a Content object or a portion of a content object. + * @param content The Content to tag. + * @param tagType The type of tag to add. + * @param comment A comment to store with the tag. + * @param beginByteOffset Designates the beginning of a tagged extent. + * @param endByteOffset Designates the end of a tagged extent. + * @throws TskCoreException + */ + public void addContentTag(Content content, TagType tagType, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException { + if (beginByteOffset < 0) { + throw new IllegalArgumentException("Content extent incorrect: beginByteOffset < 0"); + } + + if (endByteOffset <= beginByteOffset) { + throw new IllegalArgumentException("Content extent incorrect: endByteOffset <= beginByteOffset"); + } + + if (endByteOffset > content.getSize() - 1) { + throw new IllegalArgumentException("Content extent incorrect: endByteOffset exceeds content size"); + } + + tskCase.addContentTag(new ContentTag(content, tagType, comment, beginByteOffset, endByteOffset)); + } + + /** + * Deletes a content tag. + * @param tag The tag to delete. + * @throws TskCoreException + */ + public void deleteContentTag(ContentTag tag) throws TskCoreException { + tskCase.deleteContentTag(tag); + } + + /** + * Tags a BlackboardArtifact object. + * @param artifact The BlackboardArtifact to tag. + * @param tagType The type of tag to add. + * @throws TskCoreException + */ + public void addBlackboardArtifactTag(BlackboardArtifact artifact, TagType tagType) throws TskCoreException { + addBlackboardArtifactTag(artifact, tagType, ""); + } + + /** + * Tags a BlackboardArtifact object. + * @param artifact The BlackboardArtifact to tag. + * @param tagType The type of tag to add. + * @param comment A comment to store with the tag. + * @throws TskCoreException + */ + public void addBlackboardArtifactTag(BlackboardArtifact artifact, TagType tagType, String comment) throws TskCoreException { + tskCase.addBlackboardArtifactTag(new BlackboardArtifactTag(artifact, tagType, comment)); + } + + void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { + tskCase.deleteBlackboardArtifactTag(tag); } @Override - public void close() throws IOException { + public void close() throws IOException { + saveTagTypesToTagsSettings(); } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java index 657673ac74..b7d3c39e17 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java @@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.util.Collection; +import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import org.openide.util.Utilities; import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TagType; +import org.sleuthkit.datamodel.TskCoreException; public class TagAbstractFileAction extends AbstractAction implements Presenter.Popup { // This class is a singleton to support multi-selection of nodes, since @@ -60,11 +67,28 @@ public class TagAbstractFileAction extends AbstractAction implements Presenter.P } @Override - protected void applyTag(String tagName, String comment) { - Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); - for (AbstractFile file : selectedFiles) { - Tags.createTag(file, tagName, comment); - } + protected void applyTag(String tagDisplayName, String comment) { + try { + TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); + TagType tagType = tagsManager.addTagType(tagDisplayName); + + Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); + for (AbstractFile file : selectedFiles) { + Tags.createTag(file, tagDisplayName, comment); + try { + tagsManager.addContentTag(file, tagType); + } + catch (TskCoreException ex) { + Logger.getLogger(TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Error tagging content", ex); + } + } + } + catch (TagsManager.TagTypeAlreadyExistsException ex) { + JOptionPane.showMessageDialog(null, "A " + tagDisplayName + " tag type has already been defined.", "Duplicate Tag Type", JOptionPane.ERROR_MESSAGE); + } + catch (TskCoreException ex) { + Logger.getLogger(TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag type", ex); + } } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java index 3d1a9641b3..3f2c8fd407 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java @@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.util.Collection; +import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.JMenuItem; +import javax.swing.JOptionPane; import org.openide.util.Utilities; import org.openide.util.actions.Presenter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TagType; +import org.sleuthkit.datamodel.TskCoreException; public class TagBlackboardArtifactAction extends AbstractAction implements Presenter.Popup { // This class is a singleton to support multi-selection of nodes, since @@ -61,11 +68,28 @@ public class TagBlackboardArtifactAction extends AbstractAction implements Prese } @Override - protected void applyTag(String tagName, String comment) { - Collection selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); - for (BlackboardArtifact artifact : selectedArtifacts) { - Tags.createTag(artifact, tagName, comment); - } + protected void applyTag(String tagDisplayName, String comment) { + try { + TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager(); + TagType tagType = tagsManager.addTagType(tagDisplayName); + + Collection selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class); + for (BlackboardArtifact artifact : selectedArtifacts) { + Tags.createTag(artifact, tagDisplayName, comment); + try { + tagsManager.addBlackboardArtifactTag(artifact, tagType); + } + catch (TskCoreException ex) { + Logger.getLogger(TagBlackboardArtifactMenu.class.getName()).log(Level.SEVERE, "Error tagging result", ex); + } + } + } + catch (TagsManager.TagTypeAlreadyExistsException ex) { + JOptionPane.showMessageDialog(null, "A " + tagDisplayName + " tag type has already been defined.", "Duplicate Tag Type", JOptionPane.ERROR_MESSAGE); + } + catch (TskCoreException ex) { + Logger.getLogger(TagBlackboardArtifactMenu.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag type", ex); + } } } }