diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 4b6368d3a4..6f10bcf364 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -98,6 +98,11 @@ import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsUpdatedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesUpdatedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagSetsEvent.TagSetsAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.TagSetsEvent.TagSetsDeletedEvent; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException; import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; import org.sleuthkit.autopsy.casemodule.services.Services; @@ -484,7 +489,32 @@ public class Case { /** * One or more hosts have been removed from a person. */ - HOSTS_REMOVED_FROM_PERSON; + HOSTS_REMOVED_FROM_PERSON, + + /** + * One or more TagNames have been added. + */ + TAG_NAMES_ADDED, + + /** + * One or more TagNames have been updated. + */ + TAG_NAMES_UPDATED, + + /** + * One or more TagNames have been deleted. + */ + TAG_NAMES_DELETED, + + /** + * One or more TagSets have been added. + */ + TAG_SETS_ADDED, + + /** + * One or more TagSets have been removed. + */ + TAG_SETS_DELETED; }; @@ -608,7 +638,31 @@ public class Case { public void publisHostsRemovedFromPersonEvent(TskEvent.HostsRemovedFromPersonTskEvent event) { eventPublisher.publish(new HostsRemovedFromPersonEvent(event.getPerson(), event.getHostIds())); } + + @Subscribe + public void publicTagNamesAdded(TskEvent.TagNamesAddedTskEvent event) { + eventPublisher.publish(new TagNamesAddedEvent(event.getTagNames())); + } + @Subscribe + public void publicTagNamesUpdated(TskEvent.TagNamesUpdatedTskEvent event) { + eventPublisher.publish(new TagNamesUpdatedEvent(event.getTagNames())); + } + + @Subscribe + public void publicTagNamesDeleted(TskEvent.TagNamesDeletedTskEvent event) { + eventPublisher.publish(new TagNamesDeletedEvent(event.getTagNameIds())); + } + + @Subscribe + public void publicTagSetsAdded(TskEvent.TagSetsAddedTskEvent event) { + eventPublisher.publish(new TagSetsAddedEvent(event.getTagSets())); + } + + @Subscribe + public void publicTagSetsDeleted(TskEvent.TagSetsDeletedTskEvent event) { + eventPublisher.publish(new TagSetsDeletedEvent(event.getTagSetIds())); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java new file mode 100755 index 0000000000..60ac94ff23 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagNamesEvent.java @@ -0,0 +1,126 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TaggingManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for TagName added and update events. + */ +public class TagNamesEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct the base event for TagNames that have been added or updated. + * + * @param eventName The name of the event. + * @param tagNames The TagNames that have been modified. + */ + private TagNamesEvent(String eventName, List tagNames) { + super(eventName, null, null, tagNames, TagName::getId); + } + + /** + * Returns a list of the added or modified TagNames. + * + * @return The event list of TagNames. + */ + public List getTagNames() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List tagNames = new ArrayList<>(); + TaggingManager taggingMrg = caseDb.getTaggingManager(); + for (Long id : ids) { + tagNames.add(taggingMrg.getTagName(id)); + } + + return tagNames; + } + + /** + * Application events published when TagNames have been Added from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesAddedEvent extends TagNamesEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagNames have been + * added to the Sleuth Kit data model. + * + * @param tagNames The TagNames that have been added. + */ + public TagNamesAddedEvent(List tagNames) { + super(Case.Events.TAG_NAMES_ADDED.name(), tagNames); + } + } + + /** + * Application events published when TagNames have been updated from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesUpdatedEvent extends TagNamesEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagNames have been + * updated in the Sleuth Kit data model. + * + * @param tagNames The TagNames that have been updated. + */ + public TagNamesUpdatedEvent(List tagNames) { + super(Case.Events.TAG_NAMES_UPDATED.name(), tagNames); + } + } + + /** + * Application events published when TagNames have been deleted from the + * Sleuth Kit data model for a case. + */ + public static class TagNamesDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when the TagNames have been + * deleted from the Sleuth Kit data model for a case. + * + * @param tagNameIds The IDs of the TagNames that have been deleted. + */ + public TagNamesDeletedEvent(List tagNameIds) { + super(Case.Events.TAG_NAMES_DELETED.name(), tagNameIds); + } + + public List getTagNameIds() { + return getOldValue(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java new file mode 100755 index 0000000000..0896241e7d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagSetsEvent.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TagSet; +import org.sleuthkit.datamodel.TaggingManager; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A base class for TagSet added and update events. + */ +public class TagSetsEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct a new TagSetEvent. + * + * @param eventName + * @param tagSets + */ + private TagSetsEvent(String eventName, List tagSets) { + super(eventName, null, null, tagSets, TagSet::getId); + } + + /** + * Returns a list of the TagSet objects that were added or modified for this + * event. + * + * @return A list of TagSet objects. + */ + public List getTagSets() { + return this.getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List tagSets = new ArrayList<>(); + TaggingManager taggingMrg = caseDb.getTaggingManager(); + for (Long id : ids) { + tagSets.add(taggingMrg.getTagSet(id)); + } + return tagSets; + } + + /** + * Application events published when TagSets have been Added from the Sleuth + * Kit data model for a case. + */ + public static class TagSetsAddedEvent extends TagSetsEvent { + + private static final long serialVersionUID = 1L; + + /** + * Construct an application event published when TagSetss have been + * added to the Sleuth Kit data model. + * + * @param tagSets The TagSets that have been added. + */ + public TagSetsAddedEvent(List tagSets) { + super(Case.Events.TAG_SETS_ADDED.name(), tagSets); + } + } + + /** + * Application events published when TagSets have been deleted from the + * Sleuth Kit data model for a case. + */ + public static class TagSetsDeletedEvent extends TskDataModelObjectsDeletedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when the TagSets have been + * deleted from the Sleuth Kit data model for a case. + * + * @param tagNameIds The IDs of the TagNames that have been deleted. + */ + public TagSetsDeletedEvent(List tagNameIds) { + super(Case.Events.TAG_SETS_DELETED.name(), tagNameIds); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index ff07662e64..f3645fd7c6 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -236,7 +236,7 @@ final public class TagNameDefinition implements Comparable { TagName saveToCase(SleuthkitCase caseDb) { TagName tagName = null; try { - tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus); + tagName = caseDb.getTaggingManager().addOrUpdateTagName(displayName, description, color, knownStatus); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error saving tag name definition", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 4bb813a779..a4bd51bcce 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -18,9 +18,12 @@ */ package org.sleuthkit.autopsy.casemodule.services; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -29,8 +32,11 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.openide.util.WeakListeners; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent; +import org.sleuthkit.autopsy.casemodule.events.TagNamesEvent.TagNamesDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -60,6 +66,37 @@ public class TagsManager implements Closeable { private static String PROJECT_VIC_TAG_SET_NAME = "Project VIC"; private static final Object lock = new Object(); + + private final Map allTagNameMap = Collections.synchronizedMap(new HashMap<>()); + + private final PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(Case.Events.TAG_NAMES_ADDED.name()) + || evt.getPropertyName().equals(Case.Events.TAG_NAMES_UPDATED.name())) { + TagNamesEvent tagEvent = (TagNamesEvent) evt; + List addTagNames = tagEvent.getTagNames(); + for (TagName tag : addTagNames) { + allTagNameMap.put(tag.getDisplayName(), tag); + } + } else if (evt.getPropertyName().equals(Case.Events.TAG_NAMES_DELETED.name())) { + TagNamesDeletedEvent tagEvent = (TagNamesDeletedEvent) evt; + List deletedIds = tagEvent.getTagNameIds(); + List keysToRemove = new ArrayList<>(); + for (TagName tagName : getAllTagNames()) { + if (deletedIds.contains(tagName.getId())) { + keysToRemove.add(tagName.getDisplayName()); + } + } + + for (String key : keysToRemove) { + allTagNameMap.remove(key); + } + } + } + }; + + private final PropertyChangeListener weakListener = WeakListeners.propertyChange(listener, null); static { @@ -177,8 +214,6 @@ public class TagsManager implements Closeable { /* * No current case, nothing more to add to the set. */ - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get list of TagNames from TagsManager.", ex); } return tagDisplayNames; } @@ -268,21 +303,26 @@ public class TagsManager implements Closeable { // add the standard tag names for (TagNameDefinition def : TagNameDefinition.getStandardTagNameDefinitions()) { - caseDb.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); + taggingMgr.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); } //Assume new case and add all tag sets for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) { List tagNamesInSet = new ArrayList<>(); for (TagNameDefinition tagNameDef : setDef.getTagNameDefinitions()) { - tagNamesInSet.add(caseDb.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus())); + tagNamesInSet.add(taggingMgr.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus())); } if (!tagNamesInSet.isEmpty()) { taggingMgr.addTagSet(setDef.getName(), tagNamesInSet); } } + } + + for(TagName tagName: caseDb.getAllTagNames()) { + allTagNameMap.put(tagName.getDisplayName(), tagName); } + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex); } catch (IOException ex) { @@ -292,6 +332,10 @@ public class TagsManager implements Closeable { for (TagNameDefinition tagName : TagNameDefinition.getTagNameDefinitions()) { tagName.saveToCase(caseDb); } + + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_UPDATED), weakListener); + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_ADDED), weakListener); + Case.addEventTypeSubscriber(Collections.singleton(Case.Events.TAG_NAMES_DELETED), weakListener); } /** @@ -337,11 +381,12 @@ public class TagsManager implements Closeable { * Gets a list of all tag names currently in the case database. * * @return A list, possibly empty, of TagName objects. - * - * @throws TskCoreException If there is an error querying the case database. */ - public List getAllTagNames() throws TskCoreException { - return caseDb.getAllTagNames(); + public synchronized List getAllTagNames() { + + List tagNames = new ArrayList<>(); + tagNames.addAll(allTagNameMap.values()); + return tagNames; } /** @@ -439,7 +484,7 @@ public class TagsManager implements Closeable { */ public Map getDisplayNamesToTagNamesMap() throws TskCoreException { Map tagNames = new HashMap<>(); - for (TagName tagName : caseDb.getAllTagNames()) { + for (TagName tagName : getAllTagNames()) { tagNames.put(tagName.getDisplayName(), tagName); } return tagNames; @@ -521,13 +566,13 @@ public class TagsManager implements Closeable { public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TagNameAlreadyExistsException, TskCoreException { synchronized (lock) { try { - TagName tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus); + TagName tagName = caseDb.getTaggingManager().addOrUpdateTagName(displayName, description, color, knownStatus); Set customTypes = TagNameDefinition.getTagNameDefinitions(); customTypes.add(new TagNameDefinition(displayName, description, color, knownStatus)); TagNameDefinition.setTagNameDefinitions(customTypes); return tagName; } catch (TskCoreException ex) { - List existingTagNames = caseDb.getAllTagNames(); + List existingTagNames = getAllTagNames(); for (TagName tagName : existingTagNames) { if (tagName.getDisplayName().equals(displayName)) { throw new TagNameAlreadyExistsException(); @@ -1039,5 +1084,4 @@ public class TagsManager implements Closeable { @Override public void close() throws IOException { } - } diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED index 90e7cc4a11..447b4e3038 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/Bundle.properties-MERGED @@ -2,7 +2,7 @@ CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empt CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created. CommandListIngestSettingsPanel_Default_Report_DisplayName=Default CommandListIngestSettingsPanel_Make_Config=Make new profile... -CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name: +CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (commas not allowed): OpenIDE-Module-Name=CommandLineAutopsy OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings OptionsCategory_Keywords_General=Options diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java index db60e7a0af..ca11d03f13 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestSettingsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019-2020 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -280,7 +280,7 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { add(nodePanel, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents @Messages({ - "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name:", + "CommandListIngestSettingsPanel_Report_Name_Msg=Please supply a report profile name (commas not allowed):", "CommandLineIngestSettingPanel_empty_report_name_mgs=Report profile name was empty, no profile created.", "CommandLineIngestSettingPanel_existing_report_name_mgs=Report profile name was already exists, no profile created." }) @@ -288,6 +288,10 @@ public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { String reportName = getReportName(); if (reportName.equals(Bundle.CommandListIngestSettingsPanel_Make_Config())) { reportName = JOptionPane.showInputDialog(this, Bundle.CommandListIngestSettingsPanel_Report_Name_Msg()); + + // sanitize report name. Remove all commas because in CommandLineOptionProcessor we use commas + // to separate multiple report names + reportName = reportName.replaceAll(",", ""); // User hit cancel if (reportName == null) { diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java index ad84bdcd0f..2f5644928e 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2019-2020 Basis Technology Corp. + * Copyright 2019-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,15 @@ package org.sleuthkit.autopsy.commandlineingest; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import org.netbeans.api.sendopts.CommandException; import org.netbeans.spi.sendopts.Env; @@ -291,7 +294,6 @@ public class CommandLineOptionProcessor extends OptionProcessor { } // Add "GENERATE_REPORTS" command, if present - String reportProfile = null; if (values.containsKey(generateReportsOption)) { // 'caseDir' must only be specified if the case is not being created during the current run @@ -300,24 +302,34 @@ public class CommandLineOptionProcessor extends OptionProcessor { handleError("'caseDir' argument is empty"); } + List reportProfiles; argDirs = values.get(generateReportsOption); if (argDirs.length > 0) { - reportProfile = argDirs[0]; + // use custom report configuration(s) + reportProfiles = Stream.of(argDirs[0].split(",")) + .map(String::trim) + .collect(Collectors.toList()); + + if (reportProfiles == null || reportProfiles.isEmpty()) { + handleError("'generateReports' argument is empty"); + } + + for (String reportProfile : reportProfiles) { + if (reportProfile.isEmpty()) { + handleError("Empty report profile name"); + } + CommandLineCommand newCommand = new CommandLineCommand(CommandLineCommand.CommandType.GENERATE_REPORTS); + newCommand.addInputValue(CommandLineCommand.InputType.CASE_FOLDER_PATH.name(), caseDir); + newCommand.addInputValue(CommandLineCommand.InputType.REPORT_PROFILE_NAME.name(), reportProfile); + commands.add(newCommand); + } + } else { + // use default report configuration + CommandLineCommand newCommand = new CommandLineCommand(CommandLineCommand.CommandType.GENERATE_REPORTS); + newCommand.addInputValue(CommandLineCommand.InputType.CASE_FOLDER_PATH.name(), caseDir); + commands.add(newCommand); } - // If the user doesn't supply an options for generateReports the - // argsDirs length will be 0, so if reportProfile is empty - // something is not right. - if (reportProfile != null && reportProfile.isEmpty()) { - handleError("'generateReports' argument is empty"); - } - - CommandLineCommand newCommand = new CommandLineCommand(CommandLineCommand.CommandType.GENERATE_REPORTS); - newCommand.addInputValue(CommandLineCommand.InputType.CASE_FOLDER_PATH.name(), caseDir); - if (reportProfile != null) { - newCommand.addInputValue(CommandLineCommand.InputType.REPORT_PROFILE_NAME.name(), reportProfile); - } - commands.add(newCommand); runFromCommandLine = true; } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountViewer.java index 4b2b4ac972..a45d96046a 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountViewer.java @@ -141,7 +141,7 @@ public class OsAccountViewer extends javax.swing.JPanel implements DataContentVi @Override public int isPreferred(Node node) { - return 5; + return 1; } /** diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED index 3f2af1ea8a..075a0e7afb 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties-MERGED @@ -1,7 +1,10 @@ DataSourceFilter.errorMessage.emptyDataSource=At least one data source must be selected. DateSearchFilter.errorMessage.endDateBeforeStartDate=The end date should be after the start date. DateSearchFilter.errorMessage.noCheckboxSelected=At least one date type checkbox must be selected. +FileSearchPanel.cancelledSearch.text=Search Was Cancelled FileSearchPanel.emptyNode.display.text=No results found. +FileSearchPanel.searchingNode.display.text=Performing file search by attributes. Please wait. +FileSearchPanel.searchingPath.text=File Search In Progress HashSearchFilter.errorMessage.emptyHash=Hash data is empty. HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters. # {0} - hash data length diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java index 16fc228649..14b1578076 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/FileSearchPanel.java @@ -29,15 +29,17 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.nodes.Node; import org.openide.util.NbBundle; -import org.openide.windows.TopComponent; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; @@ -55,14 +57,15 @@ import org.sleuthkit.datamodel.TskCoreException; */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives class FileSearchPanel extends javax.swing.JPanel { - + + private static final Logger logger = Logger.getLogger(FileSearchPanel.class.getName()); private static final long serialVersionUID = 1L; - private final List filters = new ArrayList<>(); private static int resultWindowCount = 0; //keep track of result windows so they get unique names private static final MimeTypeFilter mimeTypeFilter = new MimeTypeFilter(); private static final DataSourceFilter dataSourceFilter = new DataSourceFilter(); private static final String EMPTY_WHERE_CLAUSE = NbBundle.getMessage(DateSearchFilter.class, "FileSearchPanel.emptyWhereClause.text"); + private static SwingWorker searchWorker = null; enum EVENT { CHECKED @@ -176,56 +179,102 @@ class FileSearchPanel extends javax.swing.JPanel { * Action when the "Search" button is pressed. * */ - @NbBundle.Messages("FileSearchPanel.emptyNode.display.text=No results found.") + @NbBundle.Messages({"FileSearchPanel.emptyNode.display.text=No results found.", + "FileSearchPanel.searchingNode.display.text=Performing file search by attributes. Please wait.", + "FileSearchPanel.searchingPath.text=File Search In Progress", + "FileSearchPanel.cancelledSearch.text=Search Was Cancelled"}) private void search() { - // change the cursor to "waiting cursor" for this operation - this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + if (searchWorker != null && searchWorker.isDone()) { + searchWorker.cancel(true); + } try { if (this.isValidSearch()) { - String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); - String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); - // try to get the number of matches first Case currentCase = Case.getCurrentCaseThrows(); // get the most updated case - long totalMatches = 0; - List contentList = null; - try { - SleuthkitCase tskDb = currentCase.getSleuthkitCase(); - contentList = tskDb.findAllFilesWhere(this.getQuery()); - - } catch (TskCoreException ex) { - Logger logger = Logger.getLogger(this.getClass().getName()); - logger.log(Level.WARNING, "Error while trying to get the number of matches.", ex); //NON-NLS - } - - if (contentList == null) { - contentList = Collections.emptyList(); - } - - SearchNode sn = new SearchNode(contentList); - TableFilterNode tableFilterNode = new TableFilterNode(sn, true, sn.getName()); - final TopComponent searchResultWin; - if (contentList.isEmpty()) { - Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - emptyNode, 0); - } else { - searchResultWin = DataResultTopComponent.createInstance(title, pathText, - tableFilterNode, contentList.size()); - } + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_searchingNode_display_text()), true); + String title = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.title", ++resultWindowCount); + String pathText = Bundle.FileSearchPanel_searchingPath_text(); + final DataResultTopComponent searchResultWin = DataResultTopComponent.createInstance(title, pathText, + emptyNode, 0); searchResultWin.requestActive(); // make it the active top component + searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + searchWorker = new SwingWorker() { + List contentList = null; - /** - * If total matches more than 1000, pop up a dialog box that say - * the performance maybe be slow and to increase the - * performance, tell the users to refine their search. - */ - if (totalMatches > 10000) { - // show info - String msg = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.msg", totalMatches); - String details = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.details"); - MessageNotifyUtil.Notify.info(msg, details); + @Override + protected TableFilterNode doInBackground() throws Exception { + try { + SleuthkitCase tskDb = currentCase.getSleuthkitCase(); + contentList = tskDb.findAllFilesWhere(getQuery()); + + } catch (TskCoreException ex) { + Logger logger = Logger.getLogger(this.getClass().getName()); + logger.log(Level.WARNING, "Error while trying to get the number of matches.", ex); //NON-NLS + } + if (contentList == null) { + contentList = Collections.emptyList(); + } + if (contentList.isEmpty()) { + return new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + } + SearchNode sn = new SearchNode(contentList); + return new TableFilterNode(sn, true, sn.getName()); + } + + @Override + protected void done() { + String pathText = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.pathText"); + try { + TableFilterNode tableFilterNode = get(); + if (tableFilterNode == null) { //just incase this get() gets modified to return null or somehow can return null + tableFilterNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_emptyNode_display_text()), true); + } + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.setNode(tableFilterNode); + searchResultWin.requestActive(); // make it the active top component + } + /** + * If total matches more than 1000, pop up a dialog + * box that say the performance maybe be slow and to + * increase the performance, tell the users to + * refine their search. + */ + if (contentList.size() > 10000) { + // show info + String msg = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.msg", contentList.size()); + String details = NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.results.details"); + MessageNotifyUtil.Notify.info(msg, details); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Error while performing file search by attributes", ex); + } catch (CancellationException ex) { + if (searchResultWin != null && searchResultWin.isOpened()) { + Node emptyNode = new TableFilterNode(new EmptyNode(Bundle.FileSearchPanel_cancelledSearch_text()), true); + searchResultWin.setNode(emptyNode); + pathText = Bundle.FileSearchPanel_cancelledSearch_text(); + } + logger.log(Level.INFO, "File search by attributes was cancelled", ex); + } finally { + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.setPath(pathText); + searchResultWin.requestActive(); // make it the active top component + searchResultWin.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + } + } + }; + if (searchResultWin != null && searchResultWin.isOpened()) { + searchResultWin.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("tcClosed") && !searchWorker.isDone() && evt.getOldValue() == null) { + searchWorker.cancel(true); + logger.log(Level.INFO, "User has closed the results window while search was in progress, search will be cancelled"); + } + } + }); } + searchWorker.execute(); } else { throw new FilterValidationException( NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.exception.noFilterSelected.msg")); @@ -234,8 +283,6 @@ class FileSearchPanel extends javax.swing.JPanel { NotifyDescriptor d = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "FileSearchPanel.search.validationErr.msg", ex.getMessage())); DialogDisplayer.getDefault().notify(d); - } finally { - this.setCursor(null); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 6fba326497..662cf7a067 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -219,7 +219,7 @@ public final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewe JMenuItem[] getMenuItems() throws TskCoreException { List menuItems = new ArrayList<>(); BlackboardArtifact artifact = dataModelWaypoint.getArtifact(); - Content content = artifact.getSleuthkitCase().getContentById(artifact.getObjectID()); + Content content = dataModelWaypoint.getContent(); menuItems.addAll(getTimelineMenuItems(dataModelWaypoint.getArtifact())); menuItems.addAll(getDataModelActionFactoryMenuItems(artifact, content)); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 223b1e9cb3..13fda6f7e1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -28,6 +28,7 @@ import java.util.Set; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; /** @@ -44,6 +45,7 @@ public class Waypoint { final private AbstractFile image; final private BlackboardArtifact artifact; final private GeoPath parentGeoPath; + final private Content content; final private List propertiesList; @@ -93,6 +95,11 @@ public class Waypoint { this.parentGeoPath = parentGeoPath; propertiesList = createGeolocationProperties(attributeMap); + try { + content = artifact.getSleuthkitCase().getContentById(artifact.getObjectID()); + } catch (TskCoreException ex) { + throw new GeoLocationDataException(String.format("Failed to get contend for artifact id (%d)", artifact.getId()), ex); + } } /** @@ -248,6 +255,10 @@ public class Waypoint { } return list; } + + public Content getContent() { + return content; + } /** * Simple property class for waypoint properties that a purely diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobPipeline.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobPipeline.java index 59fa6c4dcc..c68dde5911 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobPipeline.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobPipeline.java @@ -538,7 +538,10 @@ final class IngestJobPipeline { * @return True or false. */ boolean hasFileIngestModules() { - return (fileIngestPipelines.isEmpty() == false); + if (!fileIngestPipelines.isEmpty()) { + return !fileIngestPipelines.get(0).isEmpty(); + } + return false; } /** @@ -1438,7 +1441,7 @@ final class IngestJobPipeline { } pausedIngestThreads.clear(); } - + // If a data source had no tasks in progress it may now be complete. checkForStageCompleted(); } diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index 3efcd51250..f18f05f61f 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -340,7 +340,7 @@ public class PortableCaseReportModule implements ReportModule { progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags()); try { for (TagName tagName : tagNames) { - TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus()); + TagName newTagName = portableSkCase.getTaggingManager().addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus()); oldTagNameToNewTagName.put(tagName, newTagName); } } catch (TskCoreException ex) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryService.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryService.java index 4a8664eb6f..897b50ae2b 100755 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryService.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryService.java @@ -149,7 +149,7 @@ public class ImageGalleryService implements AutopsyService { private void addProjetVicTagSet(Case currentCase) throws TskCoreException { List tagNames = new ArrayList<>(); for (TagNameDefinition def : PROJECT_VIC_US_CATEGORIES) { - tagNames.add(currentCase.getSleuthkitCase().addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus())); + tagNames.add(currentCase.getSleuthkitCase().getTaggingManager().addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus())); } currentCase.getServices().getTagsManager().addTagSet(PROJECT_VIC_TAG_SET_NAME, tagNames); } diff --git a/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py b/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py index 7217584315..27f9be6161 100644 --- a/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py +++ b/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py @@ -40,7 +40,7 @@ from java.lang import Class from java.lang import System from java.sql import DriverManager, SQLException from java.util.logging import Level -from java.util import ArrayList +from java.util import Arrays from java.io import File from org.sleuthkit.datamodel import SleuthkitCase from org.sleuthkit.datamodel import AbstractFile @@ -113,7 +113,7 @@ class ContactsDbIngestModule(DataSourceIngestModule): progressBar.switchToIndeterminate() # Use blackboard class to index blackboard artifacts for keyword search - blackboard = Case.getCurrentCase().getServices().getBlackboard() + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() # Find files named contacts.db, regardless of parent path fileManager = Case.getCurrentCase().getServices().getFileManager() @@ -162,30 +162,21 @@ class ContactsDbIngestModule(DataSourceIngestModule): # Make an artifact on the blackboard, TSK_CONTACT and give it attributes for each of the fields - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT) - attributes = ArrayList() + art = file.newDataArtifact(BlackboardArtifact.Type.TSK_CONTACT, Arrays.asList( + BlackboardAttribute(BlackboardAttribute.Type.TSK_NAME_PERSON, + ContactsDbIngestModuleFactory.moduleName, name), + BlackboardAttribute(BlackboardAttribute.Type.TSK_EMAIL, + ContactsDbIngestModuleFactory.moduleName, email), + BlackboardAttribute(BlackboardAttribute.Type.TSK_PHONE_NUMBER, + ContactsDbIngestModuleFactory.moduleName, phone) + )) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID(), - ContactsDbIngestModuleFactory.moduleName, name)) - - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID(), - ContactsDbIngestModuleFactory.moduleName, email)) - - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID(), - ContactsDbIngestModuleFactory.moduleName, phone)) - - art.addAttributes(attributes) try: # index the artifact for keyword search - blackboard.indexArtifact(art) + blackboard.postArtifact(art, ContactsDbIngestModuleFactory.moduleName) except Blackboard.BlackboardException as e: self.log(Level.SEVERE, "Error indexing artifact " + art.getDisplayName()) - - # Fire an event to notify the UI and others that there are new artifacts - IngestServices.getInstance().fireModuleDataEvent( - ModuleDataEvent(ContactsDbIngestModuleFactory.moduleName, - BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT, None)) - + # Clean up stmt.close() dbConn.close() diff --git a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py index 4544c1884c..230aba11f8 100644 --- a/pythonExamples/Aug2015DataSourceTutorial/RunExe.py +++ b/pythonExamples/Aug2015DataSourceTutorial/RunExe.py @@ -145,7 +145,7 @@ class RunExeIngestModule(DataSourceIngestModule): # Add each argument in its own line. I.e. "-f foo" would be two calls to .add() cmd.add(imagePaths[0]) - processBuilder = ProcessBuilder(cmd); + processBuilder = ProcessBuilder(cmd) processBuilder.redirectOutput(reportFile) ExecUtil.execute(processBuilder, DataSourceIngestModuleProcessTerminator(self.context)) diff --git a/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py b/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py index e24a8395fc..5bf710e9d5 100644 --- a/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py +++ b/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py @@ -57,6 +57,8 @@ from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Services from org.sleuthkit.autopsy.casemodule.services import FileManager from org.sleuthkit.autopsy.casemodule.services import Blackboard +from org.sleuthkit.datamodel import Score +from java.util import Arrays # Factory that defines the name and details of the module and allows Autopsy # to create instances of the modules that will do the anlaysis. @@ -107,7 +109,7 @@ class FindBigRoundFilesIngestModule(FileIngestModule): def process(self, file): # Use blackboard class to index blackboard artifacts for keyword search - blackboard = Case.getCurrentCase().getServices().getBlackboard() + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() # Skip non-files if ((file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) or @@ -120,22 +122,19 @@ class FindBigRoundFilesIngestModule(FileIngestModule): # Make an artifact on the blackboard. TSK_INTERESTING_FILE_HIT is a generic type of # artifact. Refer to the developer docs for other examples. - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT) - att = BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), - FindBigRoundFilesIngestModuleFactory.moduleName, "Big and Round Files") - art.addAttribute(att) + art = file.newAnalysisResult(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE, + None, "Big and Round Files", None, + Arrays.asList( + BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, + FindBigRoundFilesIngestModuleFactory.moduleName, + "Big and Round Files"))).getAnalysisResult() try: - # index the artifact for keyword search - blackboard.indexArtifact(art) + # post the artifact for listeners of artifact events + blackboard.postArtifact(art, FindBigRoundFilesIngestModuleFactory.moduleName) except Blackboard.BlackboardException as e: self.log(Level.SEVERE, "Error indexing artifact " + art.getDisplayName()) - # Fire an event to notify the UI and others that there is a new artifact - IngestServices.getInstance().fireModuleDataEvent( - ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName, - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None)) - return IngestModule.ProcessResult.OK # Where any shutdown code is run and resources are freed. diff --git a/pythonExamples/Registry_Example.py b/pythonExamples/Registry_Example.py index 885d682ab5..f99ead8086 100644 --- a/pythonExamples/Registry_Example.py +++ b/pythonExamples/Registry_Example.py @@ -45,12 +45,13 @@ from java.lang import Class from java.lang import System from java.sql import DriverManager, SQLException from java.util.logging import Level -from java.util import ArrayList +from java.util import Arrays from org.sleuthkit.datamodel import SleuthkitCase from org.sleuthkit.datamodel import AbstractFile from org.sleuthkit.datamodel import ReadContentInputStream from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute +from org.sleuthkit.datamodel import Blackboard from org.sleuthkit.datamodel import TskData from org.sleuthkit.autopsy.ingest import IngestModule from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException @@ -130,12 +131,13 @@ class RegistryExampleIngestModule(DataSourceIngestModule): tempDir = os.path.join(Case.getCurrentCase().getTempDirectory(), "RegistryExample") self.log(Level.INFO, "create Directory " + tempDir) try: - os.mkdir(tempDir) + os.mkdir(tempDir) except: - self.log(Level.INFO, "ExampleRegistry Directory already exists " + tempDir) + self.log(Level.INFO, "ExampleRegistry Directory already exists " + tempDir) # Set the database to be read to the once created by the prefetch parser program - skCase = Case.getCurrentCase().getSleuthkitCase(); + skCase = Case.getCurrentCase().getSleuthkitCase() + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() fileManager = Case.getCurrentCase().getServices().getFileManager() # Look for files to process @@ -170,13 +172,13 @@ class RegistryExampleIngestModule(DataSourceIngestModule): # Setup Artifact and Attributes - try: - artID = skCase.addArtifactType( "TSK_REGISTRY_RUN_KEYS", "Registry Run Keys") - except: - self.log(Level.INFO, "Artifacts Creation Error, some artifacts may not exist now. ==> ") - - artId = skCase.getArtifactTypeID("TSK_REGISTRY_RUN_KEYS") - + artType = skCase.getArtifactType("TSK_REGISTRY_RUN_KEYS") + if not artType: + try: + artType = skCase.addBlackboardArtifactType( "TSK_REGISTRY_RUN_KEYS", "Registry Run Keys") + except: + self.log(Level.WARNING, "Artifacts Creation Error, some artifacts may not exist now. ==> ") + try: attributeIdRunKeyName = skCase.addArtifactAttributeType("TSK_REG_RUN_KEY_NAME", BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, "Run Key Name") except: @@ -198,25 +200,24 @@ class RegistryExampleIngestModule(DataSourceIngestModule): # RefistryKeysFound is a list that contains a list with the following records abstractFile, Registry Key Location, Key Name, Key value for registryKey in self.registryKeysFound: - attributes = ArrayList() - art = registryKey[0].newArtifact(artId) + self.log(Level.INFO, "Creating artifact for registry key with path: " + registryKey[1] + " and key: " + registryKey[2]) + art = registryKey[0].newDataArtifact(artType, Arrays.asList( + BlackboardAttribute(attributeIdRegKeyLoc, moduleName, registryKey[1]), + BlackboardAttribute(attributeIdRunKeyName, moduleName, registryKey[2]), + BlackboardAttribute(attributeIdRunKeyValue, moduleName, registryKey[3]) + )) - attributes.add(BlackboardAttribute(attributeIdRegKeyLoc, moduleName, registryKey[1])) - attributes.add(BlackboardAttribute(attributeIdRunKeyName, moduleName, registryKey[2])) - attributes.add(BlackboardAttribute(attributeIdRunKeyValue, moduleName, registryKey[3])) - art.addAttributes(attributes) - # index the artifact for keyword search try: - blackboard.indexArtifact(art) - except: - self._logger.log(Level.WARNING, "Error indexing artifact " + art.getDisplayName()) + blackboard.postArtifact(art, moduleName) + except Blackboard.BlackboardException as ex: + self.log(Level.SEVERE, "Unable to index blackboard artifact " + str(art.getArtifactTypeName()), ex) #Clean up registryExample directory and files try: - shutil.rmtree(tempDir) + shutil.rmtree(tempDir) except: - self.log(Level.INFO, "removal of directory tree failed " + tempDir) + self.log(Level.INFO, "removal of directory tree failed " + tempDir) # After all databases, post a message to the ingest messages in box. message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, @@ -236,7 +237,7 @@ class RegistryExampleIngestModule(DataSourceIngestModule): softwareRegFile = RegistryHiveFile(File(softwareHive)) for runKey in self.registrySoftwareRunKeys: currentKey = self.findRegistryKey(softwareRegFile, runKey) - if len(currentKey.getValueList()) > 0: + if currentKey and len(currentKey.getValueList()) > 0: skValues = currentKey.getValueList() for skValue in skValues: regKey = [] @@ -255,7 +256,7 @@ class RegistryExampleIngestModule(DataSourceIngestModule): ntuserRegFile = RegistryHiveFile(File(ntuserHive)) for runKey in self.registryNTUserRunKeys: currentKey = self.findRegistryKey(ntuserRegFile, runKey) - if len(currentKey.getValueList()) > 0: + if currentKey and len(currentKey.getValueList()) > 0: skValues = currentKey.getValueList() for skValue in skValues: regKey = [] @@ -276,9 +277,10 @@ class RegistryExampleIngestModule(DataSourceIngestModule): for key in regKeyList: currentKey = currentKey.getSubkey(key) return currentKey - except: - # Key not found - return null + except Exception as ex: + # Key not found + self.log(Level.SEVERE, "registry key parsing issue:", ex) + return None diff --git a/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py b/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py index 5cf87cdc93..11bf862b95 100644 --- a/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py +++ b/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py @@ -37,6 +37,7 @@ import os +import codecs from java.lang import System from java.util.logging import Level from org.sleuthkit.datamodel import TskData @@ -72,11 +73,11 @@ class CSVReportModule(GeneralReportModuleAdapter): # The 'baseReportDir' object being passed in is a string with the directory that reports are being stored in. Report should go into baseReportDir + getRelativeFilePath(). # The 'progressBar' object is of type ReportProgressPanel. # See: http://sleuthkit.org/autopsy/docs/api-docs/latest/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html - def generateReport(self, baseReportDir, progressBar): + def generateReport(self, reportSettings, progressBar): # Open the output file. - fileName = os.path.join(baseReportDir, self.getRelativeFilePath()) - report = open(fileName, 'w') + fileName = os.path.join(reportSettings.getReportDirectoryPath(), self.getRelativeFilePath()) + report = codecs.open(fileName, "w", "utf-8") # Query the database for the files (ignore the directories) sleuthkitCase = Case.getCurrentCase().getSleuthkitCase() diff --git a/pythonExamples/dataSourceIngestModule.py b/pythonExamples/dataSourceIngestModule.py index bfe745b3a4..ecb4f01477 100644 --- a/pythonExamples/dataSourceIngestModule.py +++ b/pythonExamples/dataSourceIngestModule.py @@ -53,9 +53,8 @@ from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Services from org.sleuthkit.autopsy.casemodule.services import FileManager from org.sleuthkit.autopsy.casemodule.services import Blackboard -from org.sleuthkit.autopsy.casemodule.services import Blackboard from org.sleuthkit.datamodel import Score -from java.util import ArrayList +from java.util import Arrays # Factory that defines the name and details of the module and allows Autopsy # to create instances of the modules that will do the analysis. @@ -86,8 +85,6 @@ class SampleJythonDataSourceIngestModuleFactory(IngestModuleFactoryAdapter): # Data Source-level ingest module. One gets created per data source. # TODO: Rename this to something more specific. Could just remove "Factory" from above name. class SampleJythonDataSourceIngestModule(DataSourceIngestModule): - LIKELY_NOTABLE_SCORE = Score(Score.Significance.LIKELY_NOTABLE, Score.MethodCategory.AUTO) - _logger = Logger.getLogger(SampleJythonDataSourceIngestModuleFactory.moduleName) def log(self, level, msg): @@ -118,7 +115,7 @@ class SampleJythonDataSourceIngestModule(DataSourceIngestModule): progressBar.switchToIndeterminate() # Use blackboard class to index blackboard artifacts for keyword search - blackboard = Case.getCurrentCase().getServices().getBlackboard() + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() # For our example, we will use FileManager to get all # files with the word "test" @@ -142,13 +139,15 @@ class SampleJythonDataSourceIngestModule(DataSourceIngestModule): # Make an artifact on the blackboard. TSK_INTERESTING_FILE_HIT is a generic type of # artfiact. Refer to the developer docs for other examples. - attrs = ArrayList() - attrs.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, SampleJythonDataSourceIngestModuleFactory.moduleName, "Test file")) - art = file.newAnalysisResult(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, self.LIKELY_NOTABLE_SCORE, None, "Test file", None, attrs) + attrs = Arrays.asList(BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, + SampleJythonDataSourceIngestModuleFactory.moduleName, + "Test file")) + art = file.newAnalysisResult(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE, + None, "Test file", None, attrs).getAnalysisResult() try: - # index the artifact for keyword search - blackboard.indexArtifact(art) + # post the artifact for listeners of artifact events. + blackboard.postArtifact(art, SampleJythonDataSourceIngestModuleFactory.moduleName) except Blackboard.BlackboardException as e: self.log(Level.SEVERE, "Error indexing artifact " + art.getDisplayName()) diff --git a/pythonExamples/fileIngestModule.py b/pythonExamples/fileIngestModule.py index e4aa12bab7..f72f1c4792 100644 --- a/pythonExamples/fileIngestModule.py +++ b/pythonExamples/fileIngestModule.py @@ -55,8 +55,7 @@ from org.sleuthkit.autopsy.casemodule import Case from org.sleuthkit.autopsy.casemodule.services import Services from org.sleuthkit.autopsy.casemodule.services import FileManager from org.sleuthkit.autopsy.casemodule.services import Blackboard -from org.sleuthkit.datamodel import Score -from java.util import ArrayList +from java.util import Arrays # Factory that defines the name and details of the module and allows Autopsy # to create instances of the modules that will do the anlaysis. @@ -89,7 +88,6 @@ class SampleJythonFileIngestModuleFactory(IngestModuleFactoryAdapter): # TODO: Rename this to something more specific. Could just remove "Factory" from above name. # Looks at the attributes of the passed in file. class SampleJythonFileIngestModule(FileIngestModule): - LIKELY_NOTABLE_SCORE = Score(Score.Significance.LIKELY_NOTABLE, Score.MethodCategory.AUTO) _logger = Logger.getLogger(SampleJythonFileIngestModuleFactory.moduleName) @@ -119,7 +117,7 @@ class SampleJythonFileIngestModule(FileIngestModule): return IngestModule.ProcessResult.OK # Use blackboard class to index blackboard artifacts for keyword search - blackboard = Case.getCurrentCase().getServices().getBlackboard() + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() # For an example, we will flag files with .txt in the name and make a blackboard artifact. if file.getName().lower().endswith(".txt"): @@ -129,23 +127,18 @@ class SampleJythonFileIngestModule(FileIngestModule): # Make an artifact on the blackboard. TSK_INTERESTING_FILE_HIT is a generic type of # artifact. Refer to the developer docs for other examples. - attrs = ArrayList() - attrs.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, + attrs = Arrays.asList(BlackboardAttribute(BlackboardAttribute.Type.TSK_SET_NAME, SampleJythonFileIngestModuleFactory.moduleName, "Text Files")) - art = file.newAnalysisResult(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, self.LIKELY_NOTABLE_SCORE, None, "Text Files", None, attrs) + art = file.newAnalysisResult(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT, Score.SCORE_LIKELY_NOTABLE, + None, "Text Files", None, attrs).getAnalysisResult() try: - # index the artifact for keyword search - blackboard.indexArtifact(art) + # post the artifact for listeners of artifact events + blackboard.postArtifact(art, SampleJythonFileIngestModuleFactory.moduleName) except Blackboard.BlackboardException as e: self.log(Level.SEVERE, "Error indexing artifact " + art.getDisplayName()) - # Fire an event to notify the UI and others that there is a new artifact - IngestServices.getInstance().fireModuleDataEvent( - ModuleDataEvent(SampleJythonFileIngestModuleFactory.moduleName, - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None)) - # For the example (this wouldn't be needed normally), we'll query the blackboard for data that was added # by other modules. We then iterate over its attributes. We'll just print them, but you would probably # want to do something with them. diff --git a/pythonExamples/reportmodule.py b/pythonExamples/reportmodule.py index 33c6941eda..df973e05fb 100644 --- a/pythonExamples/reportmodule.py +++ b/pythonExamples/reportmodule.py @@ -67,10 +67,12 @@ class SampleGeneralReportModule(GeneralReportModuleAdapter): return "sampleReport.txt" # TODO: Update this method to make a report - # The 'baseReportDir' object being passed in is a string with the directory that reports are being stored in. Report should go into baseReportDir + getRelativeFilePath(). + # The 'reportSettings' object being passed in is an instance of org.sleuthkit.autopsy.report.GeneralReportSettings. + # GeneralReportSettings.getReportDirectoryPath() is the directory that reports are being stored in. + # Report should go into GeneralReportSettings.getReportDirectoryPath() + getRelativeFilePath(). # The 'progressBar' object is of type ReportProgressPanel. # See: http://sleuthkit.org/autopsy/docs/api-docs/latest/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html - def generateReport(self, baseReportDir, progressBar): + def generateReport(self, reportSettings, progressBar): # For an example, we write a file with the number of files created in the past 2 weeks # Configure progress bar for 2 tasks @@ -95,7 +97,7 @@ class SampleGeneralReportModule(GeneralReportModuleAdapter): progressBar.increment() # Write the count to the report file. - fileName = os.path.join(baseReportDir, self.getRelativeFilePath()) + fileName = os.path.join(reportSettings.getReportDirectoryPath(), self.getRelativeFilePath()) report = open(fileName, 'w') report.write("file count = %d" % fileCount) report.close()