Merge remote-tracking branch 'upstream/develop' into streamImagesForAutoIngest

This commit is contained in:
apriestman
2020-07-29 14:48:01 -04:00
117 changed files with 3071 additions and 1457 deletions
+5
View File
@@ -59,6 +59,11 @@
<fileset dir="${thirdparty.dir}/InterestingFileSetRules"/>
</copy>
<!--Copy OfficialHashSets to release-->
<copy todir="${basedir}/release/OfficialHashSets" >
<fileset dir="${thirdparty.dir}/OfficialHashSets"/>
</copy>
<!-- The 'libgstlibav.dll' file is too big to store on GitHub, so we
have it stored in a ZIP file. We'll extract it in place and remove
the ZIP file afterward. -->
+2 -2
View File
@@ -83,7 +83,7 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sis-metadata-0.8.jar=release\\modules\\ext\\sis-metadata-0.8.jar
file.reference.sis-netcdf-0.8.jar=release\\modules\\ext\\sis-netcdf-0.8.jar
file.reference.sis-utility-0.8.jar=release\\modules\\ext\\sis-utility-0.8.jar
file.reference.sleuthkit-caseuco-4.9.0.jar=release\\modules\\ext\\sleuthkit-caseuco-4.9.0.jar
file.reference.sleuthkit-caseuco-4.10.0.jar=release/modules/ext/sleuthkit-caseuco-4.10.0.jar
file.reference.slf4j-api-1.7.25.jar=release\\modules\\ext\\slf4j-api-1.7.25.jar
file.reference.sqlite-jdbc-3.25.2.jar=release/modules/ext/sqlite-jdbc-3.25.2.jar
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
@@ -91,7 +91,7 @@ file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0
file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar
file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar
file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar
file.reference.sleuthkit-4.9.0.jar=release/modules/ext/sleuthkit-4.9.0.jar
file.reference.sleuthkit-4.10.0.jar=release/modules/ext/sleuthkit-4.10.0.jar
file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar
file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar
file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar
+4 -4
View File
@@ -472,8 +472,8 @@
<binary-origin>release/modules/ext/commons-pool2-2.4.2.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-4.9.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-4.9.0.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-4.10.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-4.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jxmapviewer2-2.4.jar</runtime-relative-path>
@@ -780,8 +780,8 @@
<binary-origin>release/modules/ext/curator-client-2.8.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-caseuco-4.9.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\sleuthkit-caseuco-4.9.0.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-caseuco-4.10.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-caseuco-4.10.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/fontbox-2.0.13.jar</runtime-relative-path>
@@ -29,6 +29,9 @@ import org.openide.util.Utilities;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
@@ -72,6 +75,12 @@ public class DeleteContentTagAction extends AbstractAction {
new Thread(() -> {
for (ContentTag tag : selectedTags) {
try {
// Check if there is an image tag before deleting the content tag.
ContentViewerTag<ImageTagRegion> imageTag = ContentViewerTagManager.getTag(tag, ImageTagRegion.class);
if(imageTag != null) {
ContentViewerTagManager.deleteTag(imageTag);
}
Case.getCurrentCaseThrows().getServices().getTagsManager().deleteContentTag(tag);
} catch (TskCoreException | NoCurrentCaseException ex) {
Logger.getLogger(DeleteContentTagAction.class.getName()).log(Level.SEVERE, "Error deleting tag", ex); //NON-NLS
@@ -39,6 +39,9 @@ import org.openide.util.actions.Presenter;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.tags.TagUtils;
import org.sleuthkit.datamodel.AbstractFile;
@@ -123,6 +126,13 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
try {
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS
// Check if there is an image tag before deleting the content tag.
ContentViewerTag<ImageTagRegion> imageTag = ContentViewerTagManager.getTag(contentTag, ImageTagRegion.class);
if(imageTag != null) {
ContentViewerTagManager.deleteTag(imageTag);
}
tagsManager.deleteContentTag(contentTag);
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
@@ -29,6 +29,9 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
@@ -83,9 +86,19 @@ public final class ReplaceContentTagAction extends ReplaceTagAction<ContentTag>
try {
logger.log(Level.INFO, "Replacing tag {0} with tag {1} for artifact {2}", new Object[]{oldTag.getName().getDisplayName(), newTagName.getDisplayName(), oldTag.getContent().getName()}); //NON-NLS
// Check if there is an image tag before deleting the content tag.
ContentViewerTag<ImageTagRegion> imageTag = ContentViewerTagManager.getTag(oldTag, ImageTagRegion.class);
if(imageTag != null) {
ContentViewerTagManager.deleteTag(imageTag);
}
tagsManager.deleteContentTag(oldTag);
tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment);
ContentTag newTag = tagsManager.addContentTag(oldTag.getContent(), newTagName, newComment);
// Resave the image tag if present.
if(imageTag != null) {
ContentViewerTagManager.saveTag(newTag, imageTag.getDetails());
}
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error replacing artifact tag", tskCoreException); //NON-NLS
Platform.runLater(()
@@ -305,8 +305,40 @@ public final class CentralRepoAccount {
normalizedAccountIdentifier = accountIdentifier.toLowerCase().trim();
}
} catch (CorrelationAttributeNormalizationException ex) {
throw new InvalidAccountIDException("Failed to normalize the account idenitier.", ex);
throw new InvalidAccountIDException("Failed to normalize the account idenitier " + accountIdentifier, ex);
}
return normalizedAccountIdentifier;
}
/**
* Normalizes an account identifier, based on the given account type.
*
* @param crAccountType Account type.
* @param accountIdentifier Account identifier to be normalized.
* @return Normalized identifier.
*
* @throws InvalidAccountIDException If the account identifier is invalid.
*/
public static String normalizeAccountIdentifier(CentralRepoAccountType crAccountType, String accountIdentifier) throws InvalidAccountIDException {
if (StringUtils.isBlank(accountIdentifier)) {
throw new InvalidAccountIDException("Account identifier is null or empty.");
}
String normalizedAccountIdentifier;
try {
if (crAccountType.getAcctType().equals(Account.Type.PHONE)) {
normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizePhone(accountIdentifier);
} else if (crAccountType.getAcctType().equals(Account.Type.EMAIL)) {
normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizeEmail(accountIdentifier);
} else {
// convert to lowercase
normalizedAccountIdentifier = accountIdentifier.toLowerCase();
}
} catch (CorrelationAttributeNormalizationException ex) {
throw new InvalidAccountIDException("Invalid account identifier", ex);
}
return normalizedAccountIdentifier;
}
}
@@ -262,9 +262,7 @@ public class CentralRepoDbUtil {
* used
*/
public static void setUseCentralRepo(boolean centralRepoCheckBoxIsSelected) {
if (!centralRepoCheckBoxIsSelected) {
closePersonasTopComponent();
}
closePersonasTopComponent();
ModuleSettings.setConfigSetting(CENTRAL_REPO_NAME, CENTRAL_REPO_USE_KEY, Boolean.toString(centralRepoCheckBoxIsSelected));
}
@@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.datamodel.HashHitInfo;
import org.sleuthkit.datamodel.InvalidAccountIDException;
/**
* Main interface for interacting with the database
@@ -880,9 +881,24 @@ public interface CentralRepository {
* @param crAccountType CR account type to look for or create
* @param accountUniqueID type specific unique account id
* @return CR account
*
* @throws CentralRepoException
*
* @throws CentralRepoException If there is an error accessing Central Repository.
* @throws InvalidAccountIDException If the account identifier is not valid.
*/
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException;
/**
* Gets an account from the accounts table matching the given type/ID, if
* one exists.
*
* @param crAccountType CR account type to look for or create
* @param accountUniqueID type specific unique account id
*
* @return CR account, if found, null otherwise.
*
* @throws CentralRepoException If there is an error accessing Central Repository.
* @throws InvalidAccountIDException If the account identifier is not valid.
*/
CentralRepoAccount getAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException;
}
@@ -35,6 +35,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.HashUtility;
import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@@ -184,7 +185,11 @@ public class CorrelationAttributeUtil {
}
}
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS
logger.log(Level.WARNING, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS
return correlationAttrs;
}
catch (InvalidAccountIDException ex) {
logger.log(Level.WARNING, String.format("Invalid account identifier (artifactID: %d)", artifact.getId())); // NON-NLS
return correlationAttrs;
}
catch (CentralRepoException ex) {
@@ -281,7 +286,7 @@ public class CorrelationAttributeUtil {
*
* @return The correlation attribute instance.
*/
private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException {
private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) throws InvalidAccountIDException, TskCoreException, CentralRepoException {
// Get the account type from the artifact
BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE));
@@ -52,6 +52,7 @@ import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
import org.sleuthkit.datamodel.HashHitInfo;
import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskData;
@@ -1080,21 +1081,34 @@ abstract class RdbmsCentralRepo implements CentralRepository {
* within TSK core
*/
@Override
public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException {
// Get the account fom the accounts table
String normalizedAccountID = CentralRepoAccount.normalizeAccountIdentifier(crAccountType, accountUniqueID);
String insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) "
+ "VALUES (?, ?) " + getConflictClause();
// insert the account. If there is a conflict, ignore it.
String insertSQL;
switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) {
case POSTGRESQL:
insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) " + getConflictClause(); //NON-NLS
break;
case SQLITE:
insertSQL = "INSERT OR IGNORE INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) "; //NON-NLS
break;
default:
throw new CentralRepoException(String.format("Cannot add account to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS
}
try (Connection connection = connect();
PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);) {
preparedStatement.setInt(1, crAccountType.getAccountTypeId());
preparedStatement.setString(2, accountUniqueID); // TBD: fill in the normalized ID
preparedStatement.setString(2, normalizedAccountID);
preparedStatement.executeUpdate();
// get the account from the db - should exist now.
return getAccount(crAccountType, accountUniqueID);
return getAccount(crAccountType, normalizedAccountID);
} catch (SQLException ex) {
throw new CentralRepoException("Error adding an account to CR database.", ex);
}
@@ -1177,15 +1191,17 @@ abstract class RdbmsCentralRepo implements CentralRepository {
* @return CentralRepoAccount for the give type/id. May return null if not
* found.
*
* @throws CentralRepoException
* @throws CentralRepoException If there is an error accessing Central Repository.
* @throws InvalidAccountIDException If the account identifier is not valid.
*/
private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID));
@Override
public CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException {
String normalizedAccountID = CentralRepoAccount.normalizeAccountIdentifier(crAccountType, accountUniqueID);
CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, normalizedAccountID));
if (crAccount == null) {
crAccount = getCRAccountFromDb(crAccountType, accountUniqueID);
crAccount = getCRAccountFromDb(crAccountType, normalizedAccountID);
if (crAccount != null) {
accountsCache.put(Pair.of(crAccountType, accountUniqueID), crAccount);
accountsCache.put(Pair.of(crAccountType, normalizedAccountID), crAccount);
}
}
@@ -43,6 +43,7 @@ import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileFilter;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
@@ -660,6 +661,8 @@ public class EamDbSettingsDialog extends JDialog {
* found.
*/
private static boolean testStatusAndCreate(Component parent, CentralRepoDbManager manager, EamDbSettingsDialog dialog) {
closePersonasTopComponent();
parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
manager.testStatus();
@@ -690,6 +693,21 @@ public class EamDbSettingsDialog extends JDialog {
parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
return true;
}
/**
* Closes Personas top component if it exists.
*/
private static void closePersonasTopComponent() {
SwingUtilities.invokeLater(() -> {
TopComponent personasWindow = WindowManager.getDefault().findTopComponent("PersonasTopComponent");
if (personasWindow != null && personasWindow.isOpened()) {
personasWindow.close();
}
});
}
/**
* This method returns if changes to the central repository configuration
@@ -6,6 +6,8 @@ AddMetadataDialog_empty_name_Title=Missing field(s)
CreatePersonaAccountDialog.title.text=Create Account
CreatePersonaAccountDialog_error_msg=Failed to create account.
CreatePersonaAccountDialog_error_title=Account failure
CreatePersonaAccountDialog_invalid_account_msg=Account identifier is not valid.
CreatePersonaAccountDialog_invalid_account_Title=Invalid account identifier
CreatePersonaAccountDialog_success_msg=Account added.
CreatePersonaAccountDialog_success_title=Account added
CTL_OpenPersonas=Personas
@@ -132,4 +134,4 @@ PersonasTopComponent_delete_exception_Title=Delete failure
PersonasTopComponent_Name=Personas
PersonasTopComponent_noCR_msg=Central Repository is not enabled.
PersonasTopComponent_search_exception_msg=Failed to search personas.
PersonasTopComponent_search_exception_Title=Search failure
PersonasTopComponent_search_exception_Title=There was a failure during the search. Try opening a case to fully initialize the central repository database.
@@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.Cent
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.InvalidAccountIDException;
/**
* Configuration dialog for creating an account.
@@ -216,7 +217,8 @@ public class CreatePersonaAccountDialog extends JDialog {
@Messages({
"CreatePersonaAccountDialog_error_title=Account failure",
"CreatePersonaAccountDialog_error_msg=Failed to create account.",
})
"CreatePersonaAccountDialog_invalid_account_Title=Invalid account identifier",
"CreatePersonaAccountDialog_invalid_account_msg=Account identifier is not valid.",})
private CentralRepoAccount createAccount(CentralRepoAccount.CentralRepoAccountType type, String identifier) {
CentralRepoAccount ret = null;
try {
@@ -227,8 +229,14 @@ public class CreatePersonaAccountDialog extends JDialog {
} catch (CentralRepoException e) {
logger.log(Level.SEVERE, "Failed to create account", e);
JOptionPane.showMessageDialog(this,
Bundle.CreatePersonaAccountDialog_error_title(),
Bundle.CreatePersonaAccountDialog_error_msg(),
Bundle.CreatePersonaAccountDialog_error_title(),
JOptionPane.ERROR_MESSAGE);
} catch (InvalidAccountIDException e) {
logger.log(Level.WARNING, "Invalid account identifier", e);
JOptionPane.showMessageDialog(this,
Bundle.CreatePersonaAccountDialog_invalid_account_msg(),
Bundle.CreatePersonaAccountDialog_invalid_account_Title(),
JOptionPane.ERROR_MESSAGE);
}
return ret;
@@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.centralrepository.persona;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -60,28 +62,6 @@ public final class PersonasTopComponent extends TopComponent {
private List<Persona> currentResults = null;
private Persona selectedPersona = null;
/**
* Listens for when this component will be rendered and executes a search to
* update gui when it is displayed.
*/
private final AncestorListener onAddListener = new AncestorListener() {
@Override
public void ancestorAdded(AncestorEvent event) {
resetSearchControls();
setKeywordSearchEnabled(false, true);
}
@Override
public void ancestorRemoved(AncestorEvent event) {
//Empty
}
@Override
public void ancestorMoved(AncestorEvent event) {
//Empty
}
};
@Messages({
"PersonasTopComponent_Name=Personas",
"PersonasTopComponent_delete_exception_Title=Delete failure",
@@ -165,7 +145,17 @@ public final class PersonasTopComponent extends TopComponent {
}
});
addAncestorListener(onAddListener);
/**
* Listens for when this component will be rendered and executes a
* search to update gui when it is displayed.
*/
addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
resetSearchControls();
setKeywordSearchEnabled(false, true);
}
});
}
/**
@@ -276,7 +266,7 @@ public final class PersonasTopComponent extends TopComponent {
}
@Messages({
"PersonasTopComponent_search_exception_Title=Search failure",
"PersonasTopComponent_search_exception_Title=There was a failure during the search. Try opening a case to fully initialize the central repository database.",
"PersonasTopComponent_search_exception_msg=Failed to search personas.",
"PersonasTopComponent_noCR_msg=Central Repository is not enabled.",})
private void executeSearch() {
@@ -65,8 +65,10 @@ abstract class CVTFilterRefresher implements RefreshThrottler.Refresher {
try (SleuthkitCase.CaseDbQuery dbQuery = skCase.executeQuery("SELECT MAX(date_time) as end, MIN(date_time) as start from account_relationships")) {
// ResultSet is closed by CasDBQuery
ResultSet rs = dbQuery.getResultSet();
rs.next();
startTime = rs.getInt("start"); // NON-NLS
endTime = rs.getInt("end"); // NON-NLS
}
// Get the devices with CVT artifacts
List<Integer> deviceObjIds = new ArrayList<>();
@@ -269,6 +269,7 @@ final public class FiltersPanel extends JPanel {
* Populate the Account Types filter widgets.
*
* @param accountTypesInUse List of accountTypes currently in use
* @param checkNewOnes
*
* @return True, if a new accountType was found
*/
@@ -314,9 +315,8 @@ final public class FiltersPanel extends JPanel {
/**
* Populate the devices filter widgets.
*
* @param selected Sets the initial state of device check box.
* @param sleuthkitCase The sleuthkit case for containing the data source
* information.
* @param dataSourceMap
* @param checkNewOnes
*
* @return true if a new device was found
*/
@@ -9,7 +9,6 @@ SummaryViewer.callLogsLabel.text=Call Logs:
ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages
ThreadPane.backButton.text=<---
SummaryViewer.caseReferencesPanel.border.title=Other Occurrences
SummaryViewer.fileReferencesPanel.border.title=File References in Current Case
MessageViewer.threadsLabel.text=Select a Thread to View
MessageViewer.threadNameLabel.text=<threadName>
MessageViewer.showingMessagesLabel.text=Showing Messages for Thread:
@@ -27,3 +26,5 @@ SummaryViewer.referencesLabel.text=Communication References:
SummaryViewer.referencesDataLabel.text=<reference count>
SummaryViewer.contactsLabel.text=Book Entries:
SummaryViewer.accountCountry.text=<account country>
SummaryViewer.fileRefPane.border.title=File References in Current Case
SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References>
@@ -49,13 +49,13 @@ SummaryViewer_CentralRepository_Message=<Enable Central Respository to see Other
SummaryViewer_Country_Code=Country:
SummaryViewer_Creation_Date_Title=Creation Date
SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.
SummaryViewer_Fetching_References=<Fetching File References>
SummaryViewer_FileRef_Message=<Select a single account to see File References>
SummaryViewer_FileRefNameColumn_Title=Path
SummaryViewer_TabTitle=Summary
ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages
ThreadPane.backButton.text=<---
SummaryViewer.caseReferencesPanel.border.title=Other Occurrences
SummaryViewer.fileReferencesPanel.border.title=File References in Current Case
MessageViewer.threadsLabel.text=Select a Thread to View
MessageViewer.threadNameLabel.text=<threadName>
MessageViewer.showingMessagesLabel.text=Showing Messages for Thread:
@@ -73,3 +73,5 @@ SummaryViewer.referencesLabel.text=Communication References:
SummaryViewer.referencesDataLabel.text=<reference count>
SummaryViewer.contactsLabel.text=Book Entries:
SummaryViewer.accountCountry.text=<account country>
SummaryViewer.fileRefPane.border.title=File Referernce(s) in Current Case
SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References>
@@ -49,7 +49,6 @@ SummeryViewer_FileRef_Message=<\u30a2\u30ab\u30a6\u30f3\u30c8\u30921\u3064\u9078
ThreadRootMessagePanel.showAllCheckBox.text=\u3059\u3079\u3066\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u8868\u793a
ThreadPane.backButton.text=<---
SummaryViewer.caseReferencesPanel.border.title=\u305d\u306e\u4ed6\u306e\u767a\u751f
SummaryViewer.fileReferencesPanel.border.title=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u306e\u30d5\u30a1\u30a4\u30eb\u30ec\u30d5\u30a1\u30ec\u30f3\u30b9
MessageViewer.threadsLabel.text=\u30b9\u30ec\u30c3\u30c9\u3092\u9078\u629e\u3057\u3066\u8868\u793a
MessageViewer.threadNameLabel.text=<threadName>
MessageViewer.showingMessagesLabel.text=\u6b21\u306e\u30b9\u30ec\u30c3\u30c9\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u8868\u793a\u4e2d\u3067\u3059:
@@ -18,7 +18,9 @@
*/
package org.sleuthkit.autopsy.communications.relationships;
import java.awt.event.ActionEvent;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
@@ -55,6 +57,8 @@ class MessageNode extends BlackboardArtifactNode {
private final Action preferredAction;
private final Action defaultNoopAction = new DefaultMessageAction();
MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) {
super(artifact);
@@ -148,7 +152,7 @@ class MessageNode extends BlackboardArtifactNode {
@Override
public Action getPreferredAction() {
return preferredAction;
return preferredAction != null ? preferredAction : defaultNoopAction;
}
private int getAttachmentsCount() throws TskCoreException {
@@ -171,4 +175,17 @@ class MessageNode extends BlackboardArtifactNode {
return attachmentsCount;
}
/**
* A no op action to override the default action of BlackboardArtifactNode
*/
private class DefaultMessageAction extends AbstractAction {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
// Do Nothing.
}
}
}
@@ -258,8 +258,6 @@ final class MessageViewer extends JPanel implements RelationshipsViewer {
*/
private void showMessagesPane() {
switchCard("messages");
Outline outline = rootTablePane.getOutlineView().getOutline();
outline.clearSelection();
}
/**
@@ -23,6 +23,8 @@ import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import static javax.swing.SwingUtilities.isDescendingFrom;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.openide.explorer.ExplorerManager;
@@ -82,6 +84,18 @@ class MessagesPanel extends javax.swing.JPanel implements Lookup.Provider {
} else {
messageContentViewer.setNode(null);
}
}
});
// This is a trick to get the first message to be selected after the ChildFactory has added
// new data to the table.
outlineViewPanel.getOutlineView().getOutline().getOutlineModel().addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
outline.setRowSelectionInterval(0, 0);
}
}
});
@@ -253,22 +253,6 @@
</Component>
</SubComponents>
</Container>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="File References in Current Case">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
@@ -285,5 +269,99 @@
</Constraint>
</Constraints>
</Component>
<Container class="javax.swing.JPanel" name="fileRefPane">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="File References in Current Case">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileRefPane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="fileRefScrolPanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="listPanelCard"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="scrollPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="fileRefList">
<Properties>
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
<StringArray count="5">
<StringItem index="0" value="Item 1"/>
<StringItem index="1" value="Item 2"/>
<StringItem index="2" value="Item 3"/>
<StringItem index="3" value="Item 4"/>
<StringItem index="4" value="Item 5"/>
</StringArray>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="selectAccountPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="selectAccountCard"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="selectAccountFileRefLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.selectAccountFileRefLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>
@@ -18,8 +18,14 @@
*/
package org.sleuthkit.autopsy.communications.relationships;
import java.util.Set;
import java.awt.CardLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.DefaultListModel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.openide.explorer.view.OutlineView;
@@ -27,8 +33,11 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AccountFileInstance;
/**
* Account Summary View Panel. This panel shows a list of various counts related
@@ -39,6 +48,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
public class SummaryViewer extends javax.swing.JPanel implements RelationshipsViewer {
private final Lookup lookup;
private final DefaultListModel<String> fileRefListModel;
private static final Logger logger = Logger.getLogger(SummaryViewer.class.getName());
@Messages({
"SummaryViewer_TabTitle=Summary",
@@ -60,14 +72,11 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
lookup = Lookup.getDefault();
initComponents();
OutlineView outlineView = fileReferencesPanel.getOutlineView();
fileRefListModel = new DefaultListModel<>();
fileRefList.setModel(fileRefListModel);
OutlineView outlineView = caseReferencesPanel.getOutlineView();
Outline outline = outlineView.getOutline();
outline.setRootVisible(false);
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.SummaryViewer_FileRefNameColumn_Title());
outlineView = caseReferencesPanel.getOutlineView();
outline = outlineView.getOutline();
outlineView.setPropertyColumns("creationDate", Bundle.SummaryViewer_Creation_Date_Title()); //NON-NLS
outline.setRootVisible(false);
@@ -76,7 +85,6 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
clearControls();
caseReferencesPanel.hideOutlineView(Bundle.SummaryViewer_CentralRepository_Message());
fileReferencesPanel.hideOutlineView(Bundle.SummaryViewer_FileRef_Message());
}
@Override
@@ -98,19 +106,24 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
caseReferencesPanel.showOutlineView();
}
CardLayout cardLayout = (CardLayout) fileRefPane.getLayout();
cardLayout.show(fileRefPane, "selectAccountCard");
fileRefListModel.removeAllElements();
// Request is that the SummaryViewer only show information if one
// account is selected
if (info.getAccounts().size() != 1) {
if (info == null || info.getAccounts().size() != 1) {
setEnabled(false);
clearControls();
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description_MuliSelect());
fileReferencesPanel.hideOutlineView(Bundle.SummaryViewer_FileRef_Message());
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description_MuliSelect());
selectAccountFileRefLabel.setText(Bundle.SummaryViewer_FileRef_Message());
} else {
Account[] accountArray = info.getAccounts().toArray(new Account[1]);
Account account = accountArray[0];
if (account.getAccountType().getTypeName().contains("PHONE")) {
String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID());
accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID()));
@@ -121,13 +134,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
accountCountry.setText("");
accountCountry.setEnabled(false);
}
if (account.getAccountType().equals(Account.Type.DEVICE)) {
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description());
} else {
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Device_Account_Description());
}
AccountSummary summaryDetails = new AccountSummary(account, info.getArtifacts());
thumbnailsDataLabel.setText(Integer.toString(summaryDetails.getThumbnailCnt()));
@@ -138,11 +151,10 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
referencesDataLabel.setText(Integer.toString(summaryDetails.getReferenceCnt()));
contactsDataLabel.setText(Integer.toString(summaryDetails.getContactsCnt()));
fileReferencesPanel.showOutlineView();
fileReferencesPanel.setNode(new AbstractNode(Children.create(new AccountSourceContentChildNodeFactory(info.getAccounts()), true)));
caseReferencesPanel.setNode(new AbstractNode(Children.create(new CorrelationCaseChildNodeFactory(info.getAccounts()), true)));
updateFileReferences(account);
setEnabled(true);
}
}
@@ -165,7 +177,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
contactsLabel.setEnabled(enabled);
messagesLabel.setEnabled(enabled);
caseReferencesPanel.setEnabled(enabled);
fileReferencesPanel.setEnabled(enabled);
fileRefList.setEnabled(enabled);
countsPanel.setEnabled(enabled);
attachmentsLabel.setEnabled(enabled);
referencesLabel.setEnabled(enabled);
@@ -184,29 +196,46 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
accoutDescriptionLabel.setText("");
referencesDataLabel.setText("");
accountCountry.setText("");
fileReferencesPanel.setNode(new AbstractNode(Children.LEAF));
fileRefListModel.clear();
caseReferencesPanel.setNode(new AbstractNode(Children.LEAF));
}
/**
* For the given accounts create a comma separated string of all of the
* names (TypeSpecificID).
*
* @param accounts Set of selected accounts
*
* @return String listing the account names
*/
private String createAccountLabel(Set<Account> accounts) {
StringBuilder buffer = new StringBuilder();
accounts.stream().map((account) -> {
buffer.append(account.getTypeSpecificID());
return account;
}).forEachOrdered((_item) -> {
buffer.append(", ");
});
@Messages({
"SummaryViewer_Fetching_References=<Fetching File References>"
})
private void updateFileReferences(final Account account) {
SwingWorker<List<String>, Void> worker = new SwingWorker<List<String>, Void>() {
@Override
protected List<String> doInBackground() throws Exception {
List<String> stringList = new ArrayList<>();
List<AccountFileInstance> accountFileInstanceList = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().getAccountFileInstances(account);
for (AccountFileInstance instance : accountFileInstanceList) {
stringList.add(instance.getFile().getUniquePath());
}
return stringList;
}
return buffer.toString().substring(0, buffer.length() - 2);
@Override
protected void done() {
try {
List<String> fileRefList = get();
fileRefList.forEach(value -> {
fileRefListModel.addElement(value);
});
CardLayout cardLayout = (CardLayout) fileRefPane.getLayout();
cardLayout.show(fileRefPane, "listPanelCard");
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, String.format(("Failed to get file references for account: %d"), account.getAccountID()), ex);
}
}
};
selectAccountFileRefLabel.setText(Bundle.SummaryViewer_Fetching_References());
worker.execute();
}
/**
@@ -237,8 +266,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
contactsDataLabel = new javax.swing.JLabel();
referencesLabel = new javax.swing.JLabel();
referencesDataLabel = new javax.swing.JLabel();
fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
fileRefPane = new javax.swing.JPanel();
javax.swing.JPanel fileRefScrolPanel = new javax.swing.JPanel();
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
fileRefList = new javax.swing.JList<>();
javax.swing.JPanel selectAccountPane = new javax.swing.JPanel();
selectAccountFileRefLabel = new javax.swing.JLabel();
setLayout(new java.awt.GridBagLayout());
@@ -393,17 +427,6 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
add(contanctsPanel, gridBagConstraints);
fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
add(fileReferencesPanel, gridBagConstraints);
caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
@@ -414,6 +437,38 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
add(caseReferencesPanel, gridBagConstraints);
fileRefPane.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileRefPane.border.title"))); // NOI18N
fileRefPane.setLayout(new java.awt.CardLayout());
fileRefScrolPanel.setLayout(new java.awt.BorderLayout());
fileRefList.setModel(new javax.swing.AbstractListModel<String>() {
String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
public int getSize() { return strings.length; }
public String getElementAt(int i) { return strings[i]; }
});
scrollPane.setViewportView(fileRefList);
fileRefScrolPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
fileRefPane.add(fileRefScrolPanel, "listPanelCard");
selectAccountPane.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(selectAccountFileRefLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.selectAccountFileRefLabel.text")); // NOI18N
selectAccountFileRefLabel.setEnabled(false);
selectAccountPane.add(selectAccountFileRefLabel, new java.awt.GridBagConstraints());
fileRefPane.add(selectAccountPane, "selectAccountCard");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weighty = 1.0;
add(fileRefPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
@@ -430,11 +485,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
private javax.swing.JLabel contactsLabel;
private javax.swing.JPanel contanctsPanel;
private javax.swing.JPanel countsPanel;
private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel fileReferencesPanel;
private javax.swing.JList<String> fileRefList;
private javax.swing.JPanel fileRefPane;
private javax.swing.JLabel messagesDataLabel;
private javax.swing.JLabel messagesLabel;
private javax.swing.JLabel referencesDataLabel;
private javax.swing.JLabel referencesLabel;
private javax.swing.JLabel selectAccountFileRefLabel;
private javax.swing.JPanel summaryPanel;
private javax.swing.JLabel thumbnailCntLabel;
private javax.swing.JLabel thumbnailsDataLabel;
@@ -633,21 +633,18 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
return new HashMap<>();
}
// make a list of all unique accounts for this contact
if (!account.getAccountType().equals(Account.Type.DEVICE)) {
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName());
CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID());
if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) {
uniqueAccountsList.add(crAccount);
}
}
Collection<PersonaAccount> personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
if (personaAccounts != null && !personaAccounts.isEmpty()) {
// look for unique accounts
Collection<CentralRepoAccount> accountCandidates
= personaAccounts
.stream()
.map(PersonaAccount::getAccount)
.collect(Collectors.toList());
for (CentralRepoAccount crAccount : accountCandidates) {
if (uniqueAccountsList.contains(crAccount) == false) {
uniqueAccountsList.add(crAccount);
}
}
// get personas for the account
Collection<Persona> personas
= personaAccounts
@@ -290,6 +290,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
contextContainer.add(usagePanel);
}
}
contextContainer.setBackground(javax.swing.UIManager.getDefaults().getColor("window"));
contextContainer.setEnabled(foundASource);
contextContainer.setVisible(foundASource);
jScrollPane.getViewport().setView(contextContainer);
@@ -87,12 +87,28 @@ public class EncodingUtils {
try (InputStream stream = new BufferedInputStream(new ReadContentInputStream(file))) {
CharsetDetector detector = new CharsetDetector();
detector.setText(stream);
CharsetMatch tikaResult = detector.detect();
if (tikaResult != null && tikaResult.getConfidence() >= MIN_CHARSETDETECT_MATCH_CONFIDENCE) {
String tikaCharSet = tikaResult.getName();
//Check if the nio package has support for the charset determined by Tika.
if(Charset.isSupported(tikaCharSet)) {
return Charset.forName(tikaCharSet);
CharsetMatch[] tikaResults = detector.detectAll();
// Get all guesses by Tika. These matches are ordered
// by descending confidence (largest first).
if (tikaResults.length > 0) {
CharsetMatch topPick = tikaResults[0];
if (topPick.getName().equalsIgnoreCase("IBM500") && tikaResults.length > 1) {
// Legacy encoding, let's discard this one in favor
// of the second pick. Tika has some problems with
// mistakenly identifying text as IBM500. See JIRA-6600
// and https://issues.apache.org/jira/browse/TIKA-2771 for
// more details.
topPick = tikaResults[1];
}
if (!topPick.getName().equalsIgnoreCase("IBM500") &&
topPick.getConfidence() >= MIN_CHARSETDETECT_MATCH_CONFIDENCE &&
Charset.isSupported(topPick.getName())) {
// Choose this charset since it's supported and has high
// enough confidence
return Charset.forName(topPick.getName());
}
}
}
@@ -186,7 +186,7 @@ FileSorter.SortingMethod.filetype.displayName=File Type
FileSorter.SortingMethod.frequency.displayName=Central Repo Frequency
FileSorter.SortingMethod.fullPath.displayName=Full Path
FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
GroupsListPanel.noResults.message.text=No results were found for the selected filters.
GroupsListPanel.noResults.message.text=No results were found for the selected filters.\n\nReminder:\n -The File Type Identification module must be run on each data source you want to find results in.\n -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n -The Exif module must be run on each data source if you are filtering by User Created content.
GroupsListPanel.noResults.title.text=No results found
ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
# {0} - otherInstanceCount
@@ -23,6 +23,7 @@ import java.awt.Color;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
@@ -30,21 +31,31 @@ import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.discovery.FileGroup.GroupSortingAlgorithm;
import static org.sleuthkit.autopsy.discovery.FileGroup.GroupSortingAlgorithm.BY_GROUP_SIZE;
import org.sleuthkit.autopsy.discovery.FileSearch.GroupingAttributeType;
import static org.sleuthkit.autopsy.discovery.FileSearch.GroupingAttributeType.PARENT_PATH;
import org.sleuthkit.autopsy.discovery.FileSorter.SortingMethod;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
import static org.sleuthkit.autopsy.discovery.FileSorter.SortingMethod.BY_FILE_NAME;
/**
* Dialog for displaying the controls and filters for configuration of a
* Discovery search.
*/
final class DiscoveryDialog extends javax.swing.JDialog {
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
Case.Events.DATA_SOURCE_ADDED, Case.Events.DATA_SOURCE_DELETED);
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
private static final long serialVersionUID = 1L;
private final static Logger logger = Logger.getLogger(DiscoveryDialog.class.getName());
private ImageFilterPanel imageFilterPanel = null;
@@ -54,8 +65,12 @@ final class DiscoveryDialog extends javax.swing.JDialog {
private static final Color UNSELECTED_COLOR = new Color(240, 240, 240);
private SearchWorker searchWorker = null;
private static DiscoveryDialog discDialog;
private static volatile boolean shouldUpdate = false;
private FileSearchData.FileType fileType = FileSearchData.FileType.IMAGE;
private final PropertyChangeListener listener;
private final Set<BlackboardAttribute> objectsDetected = new HashSet<>();
private final Set<BlackboardAttribute> interestingItems = new HashSet<>();
private final Set<BlackboardAttribute> hashSets = new HashSet<>();
/**
* Get the Discovery dialog instance.
@@ -66,6 +81,10 @@ final class DiscoveryDialog extends javax.swing.JDialog {
if (discDialog == null) {
discDialog = new DiscoveryDialog();
}
if (shouldUpdate) {
discDialog.updateSearchSettings();
shouldUpdate = false;
}
return discDialog;
}
@@ -89,6 +108,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
}
updateSearchSettings();
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this.new CasePropertyChangeListener());
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, this.new ModuleChangeListener());
}
/**
@@ -116,6 +136,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
add(imageFilterPanel, CENTER);
imageFilterPanel.addPropertyChangeListener(listener);
updateComboBoxes();
groupSortingComboBox.setSelectedItem(BY_GROUP_SIZE);
pack();
repaint();
}
@@ -129,6 +150,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
for (FileSearch.GroupingAttributeType type : FileSearch.GroupingAttributeType.getOptionsForGrouping()) {
addTypeToGroupByComboBox(type);
}
groupByCombobox.setSelectedItem(PARENT_PATH);
orderByCombobox.removeAllItems();
// Set up the file order list
for (FileSorter.SortingMethod method : FileSorter.SortingMethod.getOptionsForOrdering()) {
@@ -136,7 +158,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
orderByCombobox.addItem(method);
}
}
groupSortingComboBox.setSelectedIndex(0);
orderByCombobox.setSelectedItem(BY_FILE_NAME);
}
/**
@@ -512,7 +534,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
tc.toFront();
tc.requestActive();
}//GEN-LAST:event_searchButtonActionPerformed
@Override
public void dispose() {
setVisible(false);
@@ -531,7 +553,8 @@ final class DiscoveryDialog extends javax.swing.JDialog {
* The adjust the controls to reflect whether the settings are valid based
* on the error.
*
* @param error The error message to display, empty string if there is no error.
* @param error The error message to display, empty string if there is no
* error.
*/
private void setValid(String error) {
if (StringUtils.isBlank(error)) {
@@ -560,7 +583,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
* filters available.
*/
private class CasePropertyChangeListener implements PropertyChangeListener {
@Override
@SuppressWarnings("fallthrough")
public void propertyChange(PropertyChangeEvent evt) {
@@ -575,7 +598,7 @@ final class DiscoveryDialog extends javax.swing.JDialog {
case DATA_SOURCE_ADDED:
//fallthrough
case DATA_SOURCE_DELETED:
updateSearchSettings();
shouldUpdate = true;
break;
default:
//do nothing if the event is not one of the above events.
@@ -583,4 +606,83 @@ final class DiscoveryDialog extends javax.swing.JDialog {
}
}
}
/**
* PropertyChangeListener to listen to ingest module events that may modify
* the filters available.
*/
private class ModuleChangeListener implements PropertyChangeListener {
@Override
@SuppressWarnings("fallthrough")
public void propertyChange(PropertyChangeEvent evt) {
if (!shouldUpdate) {
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.getCurrentCaseThrows();
/**
* 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) {
if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID() && eventData.getArtifacts() != null) {
shouldUpdate = shouldUpdateFilters(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID(), eventData, objectsDetected);
} else if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
shouldUpdate = shouldUpdateFilters(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), eventData, hashSets);
} else if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
shouldUpdate = shouldUpdateFilters(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), eventData, interestingItems);
}
}
} catch (NoCurrentCaseException notUsed) {
// Case is closed, do nothing.
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to determine if discovery UI should be updated", ex);
}
}
}
}
/**
* Helper method to determine if the artifact in the eventData
* represents a new value for the filter.
*
* @param attributeTypeId The attribute id of the attribute which
* contains the value for the filter.
* @param eventData The event which contains the artifacts.
* @param filterSetToCheck The set of current values for the relevant
* filter.
*
* @return True if the value is a new value for the filter, false
* otherwise.
*
* @throws TskCoreException Thrown because the attributes were unable to
* be retrieved for one of the artifacts in the
* eventData.
*/
private boolean shouldUpdateFilters(int attributeTypeId, ModuleDataEvent eventData, Set<BlackboardAttribute> filterSetToCheck) throws TskCoreException {
for (BlackboardArtifact artifact : eventData.getArtifacts()) {
if (artifact.getAttributes() != null) {
for (BlackboardAttribute attr : artifact.getAttributes()) {
if (attr.getAttributeType().getTypeID() == attributeTypeId && !filterSetToCheck.contains(attr)) {
filterSetToCheck.add(attr);
return true;
}
}
}
}
return false;
}
}
}
@@ -22,6 +22,8 @@ import com.google.common.eventbus.Subscribe;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.JSplitPane;
@@ -47,14 +49,12 @@ public final class DiscoveryTopComponent extends TopComponent {
private static final long serialVersionUID = 1L;
private static final String PREFERRED_ID = "Discovery"; // NON-NLS
private static final int ANIMATION_INCREMENT = 30;
private volatile static int resultsAreaSize = 250;
private final GroupListPanel groupListPanel;
private final DetailsPanel detailsPanel;
private final ResultsPanel resultsPanel;
private int dividerLocation = -1;
private static final int ANIMATION_INCREMENT = 10;
private static final int RESULTS_AREA_SMALL_SIZE = 250;
private SwingAnimator animator = null;
/**
@@ -78,6 +78,19 @@ public final class DiscoveryTopComponent extends TopComponent {
}
});
rightSplitPane.addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equalsIgnoreCase(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
//Only change the saved location when it was a manual change by the user and not the animation or the window opening initially
if ((animator == null || !animator.isRunning()) && evt.getNewValue() instanceof Integer
&& ((int) evt.getNewValue() + 5) < (rightSplitPane.getHeight() - rightSplitPane.getDividerSize())) {
resultsAreaSize = (int) evt.getNewValue();
}
}
}
});
}
/**
@@ -130,6 +143,7 @@ public final class DiscoveryTopComponent extends TopComponent {
@Override
protected void componentClosed() {
DiscoveryDialog.getDiscoveryDialogInstance().cancelSearch();
DiscoveryEventUtils.getDiscoveryEventBus().post(new DiscoveryEventUtils.ClearInstanceSelectionEvent());
DiscoveryEventUtils.getDiscoveryEventBus().unregister(this);
DiscoveryEventUtils.getDiscoveryEventBus().unregister(groupListPanel);
DiscoveryEventUtils.getDiscoveryEventBus().unregister(resultsPanel);
@@ -245,6 +259,7 @@ public final class DiscoveryTopComponent extends TopComponent {
void handleDetailsVisibleEvent(DiscoveryEventUtils.DetailsVisibleEvent detailsVisibleEvent) {
if (animator != null && animator.isRunning()) {
animator.stop();
animator = null;
}
dividerLocation = rightSplitPane.getDividerLocation();
if (detailsVisibleEvent.isShowDetailsArea()) {
@@ -316,8 +331,9 @@ public final class DiscoveryTopComponent extends TopComponent {
@Override
public boolean hasTerminated() {
if (dividerLocation != JSplitPane.UNDEFINED_CONDITION && dividerLocation < RESULTS_AREA_SMALL_SIZE) {
dividerLocation = RESULTS_AREA_SMALL_SIZE;
if (dividerLocation != JSplitPane.UNDEFINED_CONDITION && dividerLocation < resultsAreaSize) {
dividerLocation = resultsAreaSize;
animator = null;
return true;
}
return false;
@@ -340,6 +356,7 @@ public final class DiscoveryTopComponent extends TopComponent {
public boolean hasTerminated() {
if (dividerLocation > rightSplitPane.getHeight() || dividerLocation == JSplitPane.UNDEFINED_CONDITION) {
dividerLocation = rightSplitPane.getHeight();
animator = null;
return true;
}
return false;
@@ -362,8 +379,9 @@ public final class DiscoveryTopComponent extends TopComponent {
@Override
public void paintComponent(Graphics g) {
if ((dividerLocation == JSplitPane.UNDEFINED_CONDITION) || (dividerLocation <= rightSplitPane.getHeight() && dividerLocation >= RESULTS_AREA_SMALL_SIZE)) {
rightSplitPane.setDividerLocation(dividerLocation);
if (animator != null && animator.isRunning() && (dividerLocation == JSplitPane.UNDEFINED_CONDITION
|| (dividerLocation <= getHeight() && dividerLocation >= resultsAreaSize))) {
setDividerLocation(dividerLocation);
}
super.paintComponent(g);
}
@@ -34,13 +34,15 @@ final class DocumentFilterPanel extends AbstractFiltersPanel {
DocumentFilterPanel() {
super();
initComponents();
addFilter(new SizeFilterPanel(FileSearchData.FileType.DOCUMENTS), false, null, 0);
SizeFilterPanel sizeFilterPanel = new SizeFilterPanel(FILE_TYPE);
int[] sizeIndicesSelected = {3, 4, 5};
addFilter(sizeFilterPanel, true, sizeIndicesSelected, 0);
addFilter(new DataSourceFilterPanel(), false, null, 0);
int[] pastOccurrencesIndices;
if (!CentralRepository.isEnabled()) {
pastOccurrencesIndices = new int[]{0};
} else {
pastOccurrencesIndices = new int[]{1, 2, 3, 4, 5, 6, 7};
pastOccurrencesIndices = new int[]{2, 3, 4};
}
addFilter(new PastOccurrencesFilterPanel(), true, pastOccurrencesIndices, 0);
addFilter(new HashSetFilterPanel(), false, null, 1);
@@ -63,7 +63,11 @@ final class GroupListPanel extends javax.swing.JPanel {
groupKeyList.setListData(new GroupKey[0]);
}
@Messages({"GroupsListPanel.noResults.message.text=No results were found for the selected filters.",
@Messages({"GroupsListPanel.noResults.message.text=No results were found for the selected filters.\n\n"
+ "Reminder:\n"
+ " -The File Type Identification module must be run on each data source you want to find results in.\n"
+ " -The Hash Lookup module must be run on each data source if you want to filter by past occurrence.\n"
+ " -The Exif module must be run on each data source if you are filtering by User Created content.",
"GroupsListPanel.noResults.title.text=No results found"})
/**
* Subscribe to and update list of groups in response to
@@ -86,7 +90,7 @@ final class GroupListPanel extends javax.swing.JPanel {
JOptionPane.showMessageDialog(DiscoveryTopComponent.getTopComponent(),
Bundle.GroupsListPanel_noResults_message_text(),
Bundle.GroupsListPanel_noResults_title_text(),
JOptionPane.INFORMATION_MESSAGE);
JOptionPane.PLAIN_MESSAGE);
}
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
@@ -35,14 +35,14 @@ final class ImageFilterPanel extends AbstractFiltersPanel {
super();
initComponents();
SizeFilterPanel sizeFilterPanel = new SizeFilterPanel(FILE_TYPE);
int[] sizeIndicesSelected = {1, 2, 3, 4, 5};
int[] sizeIndicesSelected = {3, 4, 5};
addFilter(sizeFilterPanel, true, sizeIndicesSelected, 0);
addFilter(new DataSourceFilterPanel(), false, null, 0);
int[] pastOccurrencesIndices;
if (!CentralRepository.isEnabled()) {
pastOccurrencesIndices = new int[]{0};
} else {
pastOccurrencesIndices = new int[]{1, 2, 3, 4, 5, 6, 7};
pastOccurrencesIndices = new int[]{2, 3, 4};
}
addFilter(new PastOccurrencesFilterPanel(), true, pastOccurrencesIndices, 0);
addFilter(new UserCreatedFilterPanel(), false, null, 1);
@@ -3,7 +3,7 @@
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 200]"/>
<Dimension value="[300, 60]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 700]"/>
@@ -315,7 +315,7 @@
<Container class="javax.swing.JPanel" name="resultsViewerPanel">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 160]"/>
<Dimension value="[0, 60]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 700]"/>
@@ -376,7 +376,7 @@ final class ResultsPanel extends javax.swing.JPanel {
javax.swing.Box.Filler filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0));
resultsViewerPanel = new javax.swing.JPanel();
setMinimumSize(new java.awt.Dimension(700, 200));
setMinimumSize(new java.awt.Dimension(300, 60));
setPreferredSize(new java.awt.Dimension(700, 700));
setLayout(new java.awt.BorderLayout());
@@ -533,7 +533,7 @@ final class ResultsPanel extends javax.swing.JPanel {
add(pagingPanel, java.awt.BorderLayout.PAGE_START);
resultsViewerPanel.setMinimumSize(new java.awt.Dimension(0, 160));
resultsViewerPanel.setMinimumSize(new java.awt.Dimension(0, 60));
resultsViewerPanel.setPreferredSize(new java.awt.Dimension(700, 700));
resultsViewerPanel.setLayout(new java.awt.BorderLayout());
add(resultsViewerPanel, java.awt.BorderLayout.CENTER);
@@ -39,7 +39,7 @@ final class SwingAnimator {
private Timer timer = null;
//duration in milliseconds betweeen each firing of the Timer
private static final int INITIAL_TIMING = 10;
private static final int INITIAL_TIMING = 30;
private int timing = INITIAL_TIMING;
/**
@@ -34,13 +34,15 @@ final class VideoFilterPanel extends AbstractFiltersPanel {
VideoFilterPanel() {
super();
initComponents();
addFilter(new SizeFilterPanel(FileSearchData.FileType.VIDEO), false, null, 0);
SizeFilterPanel sizeFilterPanel = new SizeFilterPanel(FILE_TYPE);
int[] sizeIndicesSelected = {3, 4, 5};
addFilter(sizeFilterPanel, true, sizeIndicesSelected, 0);
addFilter(new DataSourceFilterPanel(), false, null, 0);
int[] pastOccurrencesIndices;
if (!CentralRepository.isEnabled()) {
pastOccurrencesIndices = new int[]{0};
} else {
pastOccurrencesIndices = new int[]{1, 2, 3, 4, 5, 6, 7};
pastOccurrencesIndices = new int[]{2, 3, 4};
}
addFilter(new PastOccurrencesFilterPanel(), true, pastOccurrencesIndices, 0);
addFilter(new UserCreatedFilterPanel(), false, null, 1);
@@ -501,9 +501,9 @@ class GeoFilterPanel extends javax.swing.JPanel {
DataSource dataSource, BlackboardArtifact.ARTIFACT_TYPE artifactType) throws TskCoreException {
long count = 0;
String queryStr
= "SELECT count(DISTINCT artifact_id) AS count FROM"
= "SELECT count(DISTINCT artIds) AS count FROM"
+ " ("
+ " SELECT * FROM blackboard_artifacts as arts"
+ " SELECT arts.artifact_id as artIds, * FROM blackboard_artifacts as arts"
+ " INNER JOIN blackboard_attributes as attrs"
+ " ON attrs.artifact_id = arts.artifact_id"
+ " WHERE arts.artifact_type_id = " + artifactType.getTypeID()
@@ -516,7 +516,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS.getTypeID()
+ " or attrs.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID()
+ " )"
+ " )";
+ " ) as innerTable";
try (SleuthkitCase.CaseDbQuery queryResult = sleuthkitCase.executeQuery(queryStr);
ResultSet resultSet = queryResult.getResultSet()) {
if (resultSet.next()) {
@@ -23,8 +23,6 @@ HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking
HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}.
# {0} - fileName
HashDbIngestModule.lookingUpNoChangeHashValueErr=Error encountered while looking up no change hash value for {0}.
HashDbIngestModule.noChangeFileSearchWillNotExecuteWarn='No Change' file search will not be executed.
HashDbIngestModule.noChangeHashDbSetMsg=No 'No Change' hash set.
HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.
HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.
HashDbManager.CentralRepoHashDb.orgError=Error loading organization
@@ -34,6 +32,8 @@ HashDbManager.knownBad.text=Notable
HashDbManager.noChange.text=No Change
# {0} - hash set name
HashDbManager.noDbPath.message=Couldn't get valid hash set path for: {0}
# {0} - hashSetName
HashDbManager_handleNameConflict_conflictSuffix={0} (Custom)
HashDbSearchAction.noOpenCase.errMsg=No open case available.
HashDbSearchPanel.noOpenCase.errMsg=No open case available.
HashLookupSettingsPanel.centralRepo=Central Repository
@@ -22,21 +22,28 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
@@ -71,10 +78,29 @@ public class HashDbManager implements PropertyChangeListener {
private List<HashDb> hashSets = new ArrayList<>();
private Set<String> hashSetNames = new HashSet<>();
private Set<String> hashSetPaths = new HashSet<>();
private List<HashDb> officialHashSets = new ArrayList<>();
private Set<String> officialHashSetNames = new HashSet<>();
private Set<String> officialHashSetPaths = new HashSet<>();
PropertyChangeSupport changeSupport = new PropertyChangeSupport(HashDbManager.class);
private static final Logger logger = Logger.getLogger(HashDbManager.class.getName());
private boolean allDatabasesLoadedCorrectly = false;
private static final String OFFICIAL_HASH_SETS_FOLDER = "OfficialHashSets";
private static final String KDB_EXT = "kdb";
private static final String DB_NAME_PARAM = "dbName";
private static final String KNOWN_STATUS_PARAM = "knownStatus";
private static final Pattern OFFICIAL_FILENAME = Pattern.compile("(?<" + DB_NAME_PARAM + ">.+?)\\.(?<" + KNOWN_STATUS_PARAM + ">.+?)\\." + KDB_EXT);
private static final FilenameFilter DEFAULT_KDB_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith("." + KDB_EXT);
}
};
/**
* Property change event support In events: For both of these enums, the old
* value should be null, and the new value should be the hashset name
@@ -168,13 +194,7 @@ public class HashDbManager implements PropertyChangeListener {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.hashDbDoesNotExistExceptionMsg", path));
}
if (hashSetPaths.contains(path)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.hashDbAlreadyAddedExceptionMsg", path));
}
if (hashSetNames.contains(hashSetName)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.duplicateHashSetNameExceptionMsg", hashSetName));
}
checkDbCollision(path, hashSetName);
hashDb = addHashDatabase(SleuthkitJNI.openHashDatabase(path), hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType);
} catch (TskCoreException ex) {
@@ -225,13 +245,7 @@ public class HashDbManager implements PropertyChangeListener {
getHashDatabaseFileExtension()));
}
if (hashSetPaths.contains(path)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.hashDbAlreadyAddedExceptionMsg", path));
}
if (hashSetNames.contains(hashSetName)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.duplicateHashSetNameExceptionMsg", hashSetName));
}
checkDbCollision(path, hashSetName);
hashDb = addHashDatabase(SleuthkitJNI.createHashDatabase(path), hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType);
} catch (TskCoreException ex) {
@@ -240,6 +254,27 @@ public class HashDbManager implements PropertyChangeListener {
return hashDb;
}
/**
* Throws an exception if the provided path or hashSetName already belong to
* an existing database.
*
* @param path The path.
* @param hashSetName The hash set name.
*
* @throws
* org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException
* @throws MissingResourceException
*/
private void checkDbCollision(String path, String hashSetName) throws HashDbManagerException, MissingResourceException {
if (hashSetPaths.contains(path) || officialHashSetPaths.contains(path)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.hashDbAlreadyAddedExceptionMsg", path));
}
if (hashSetNames.contains(hashSetName) || officialHashSetNames.contains(hashSetName)) {
throw new HashDbManagerException(NbBundle.getMessage(HashDbManager.class, "HashDbManager.duplicateHashSetNameExceptionMsg", hashSetName));
}
}
private SleuthkitHashSet addHashDatabase(int handle, String hashSetName, boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType) throws TskCoreException {
// Wrap an object around the handle.
SleuthkitHashSet hashDb = new SleuthkitHashSet(handle, hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType);
@@ -420,9 +455,8 @@ public class HashDbManager implements PropertyChangeListener {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS
}
List<HashDb> hashDbs = new ArrayList<>();
hashDbs.addAll(this.hashSets);
return hashDbs;
return Stream.concat(this.officialHashSets.stream(), this.hashSets.stream())
.collect(Collectors.toList());
}
/**
@@ -431,16 +465,10 @@ public class HashDbManager implements PropertyChangeListener {
* @return A list, possibly empty, of hash databases.
*/
public synchronized List<HashDb> getKnownFileHashSets() {
List<HashDb> hashDbs = new ArrayList<>();
try {
updateHashSetsFromCentralRepository();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS
}
this.hashSets.stream().filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN)).forEach((db) -> {
hashDbs.add(db);
});
return hashDbs;
return getAllHashSets()
.stream()
.filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN))
.collect(Collectors.toList());
}
/**
@@ -449,16 +477,10 @@ public class HashDbManager implements PropertyChangeListener {
* @return A list, possibly empty, of hash databases.
*/
public synchronized List<HashDb> getKnownBadFileHashSets() {
List<HashDb> hashDbs = new ArrayList<>();
try {
updateHashSetsFromCentralRepository();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS
}
this.hashSets.stream().filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN_BAD)).forEach((db) -> {
hashDbs.add(db);
});
return hashDbs;
return getAllHashSets()
.stream()
.filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN_BAD))
.collect(Collectors.toList());
}
/**
@@ -467,26 +489,21 @@ public class HashDbManager implements PropertyChangeListener {
* @return A list, possibly empty, of hash databases.
*/
public synchronized List<HashDb> getUpdateableHashSets() {
return getUpdateableHashSets(this.hashSets);
return getUpdateableHashSets(getAllHashSets());
}
private List<HashDb> getUpdateableHashSets(List<HashDb> hashDbs) {
ArrayList<HashDb> updateableDbs = new ArrayList<>();
try {
updateHashSetsFromCentralRepository();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS
}
for (HashDb db : hashDbs) {
try {
if (db.isUpdateable()) {
updateableDbs.add(db);
}
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error checking updateable status of " + db.getHashSetName() + " hash set", ex); //NON-NLS
}
}
return updateableDbs;
return hashDbs
.stream()
.filter((HashDb db) -> {
try {
return db.isUpdateable();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error checking updateable status of " + db.getHashSetName() + " hash set", ex); //NON-NLS
return false;
}
})
.collect(Collectors.toList());
}
private List<HashDbInfo> getCentralRepoHashSetsFromDatabase() {
@@ -536,66 +553,197 @@ public class HashDbManager implements PropertyChangeListener {
}
private void loadHashsetsConfiguration() {
loadOfficialHashSets();
try {
HashLookupSettings settings = HashLookupSettings.readSettings();
this.configureSettings(settings);
this.configureSettings(settings, officialHashSetNames);
} catch (HashLookupSettings.HashLookupSettingsException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Could not read Hash lookup settings from disk.", ex);
}
}
/**
* Loads official hash sets into officialHashSets and also populates
* officialHashSetPaths and officialHashSetNames variables.
*/
private void loadOfficialHashSets() {
officialHashSetPaths = new HashSet<>();
officialHashSetNames = new HashSet<>();
try {
officialHashSets = loadOfficialHashSetsFromFolder(OFFICIAL_HASH_SETS_FOLDER);
officialHashSets.forEach(db -> {
officialHashSetNames.add(db.getHashSetName());
try {
String databasePath = db.getDatabasePath();
String indexPath = db.getIndexPath();
if (StringUtils.isNotBlank(databasePath) && !databasePath.equals("None")) { //NON-NLS
officialHashSetPaths.add(databasePath);
}
if (StringUtils.isNotBlank(indexPath) && !indexPath.equals("None")) { //NON-NLS
officialHashSetPaths.add(indexPath);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "There was an error loading the official hash set name.", ex);
}
});
} catch (HashDbManagerException ex) {
logger.log(Level.WARNING, "There was an error loading the official hash sets.", ex);
officialHashSets = new ArrayList<HashDb>();
}
}
/**
* Handles a potential conflict between official and non-official hash sets.
* Non-official hashsets have '(Custom)' added. If a conflict is identified,
* the hashset settings are fixed, saved, reloaded, and returned. Otherwise,
* the original list is returned.
*
* @param curHashsets The list of non-official hash sets.
* @param officialNames The set of names for official hash sets.
*
* @return The new list of non-official hash sets with conflicts removed.
*/
@Messages({
"# {0} - hashSetName",
"HashDbManager_handleNameConflict_conflictSuffix={0} (Custom)"
})
private List<HashDbInfo> handleNameConflict(List<HashDbInfo> curHashsets, Set<String> officialNames) {
Set<String> curNames = new HashSet<String>(officialNames);
boolean change = false;
List<HashDbInfo> newItems = new ArrayList<>();
for (HashDbInfo hashset : curHashsets) {
String thisName = hashset.getHashSetName();
if (curNames.contains(thisName)) {
while (curNames.contains(thisName)) {
thisName = Bundle.HashDbManager_handleNameConflict_conflictSuffix(thisName);
}
newItems.add(new HashDbInfo(
thisName,
hashset.getKnownFilesType(),
hashset.getSearchDuringIngest(),
hashset.getSendIngestMessages(),
hashset.getPath(),
hashset.getReferenceSetID(),
hashset.getVersion(),
hashset.isReadOnly(),
hashset.isCentralRepoDatabaseType()
));
change = true;
} else {
newItems.add(hashset);
}
curNames.add(thisName);
}
if (!change) {
return curHashsets;
} else {
try {
HashLookupSettings.writeSettings(new HashLookupSettings(newItems));
HashLookupSettings toRet = HashLookupSettings.readSettings();
return toRet.getHashDbInfo();
} catch (HashLookupSettings.HashLookupSettingsException ex) {
logger.log(Level.SEVERE, "There was an error while trying to resave after name conflict.", ex);
return newItems;
}
}
}
/**
* Loads official hash sets from the given folder.
*
* @param folder The folder from which to load official hash sets.
*
* @return The List of found hash sets.
*
* @throws HashDbManagerException If folder does not exist.
*/
private List<HashDb> loadOfficialHashSetsFromFolder(String folder) throws HashDbManagerException {
File configFolder = InstalledFileLocator.getDefault().locate(
folder, HashDbManager.class.getPackage().getName(), false);
if (configFolder == null || !configFolder.exists() || !configFolder.isDirectory()) {
throw new HashDbManagerException("Folder provided: " + folder + " does not exist.");
}
return Stream.of(configFolder.listFiles(DEFAULT_KDB_FILTER))
.map((f) -> {
try {
return getOfficialHashDbFromFile(f);
} catch (HashDbManagerException | TskCoreException ex) {
logger.log(Level.WARNING, String.format("Hashset: %s could not be properly read.", f.getAbsolutePath()), ex);
return null;
}
})
.filter((hashdb) -> hashdb != null)
.collect(Collectors.toList());
}
/**
* Loads an official hash set from the given file.
*
* @param file The kdb file to load.
*
* @return The HashDbInfo of the official set.
*
* @throws HashDbManagerException If file does not exist or does not match
* naming convention (See
* HashDbManager.OFFICIAL_FILENAME for
* regex).
*/
private HashDb getOfficialHashDbFromFile(File file) throws HashDbManagerException, TskCoreException {
if (file == null || !file.exists()) {
throw new HashDbManagerException(String.format("No file found for: %s", file == null ? "<null>" : file.getAbsolutePath()));
}
String filename = file.getName();
Matcher match = OFFICIAL_FILENAME.matcher(filename);
if (!match.find()) {
throw new HashDbManagerException(String.format("File with name: %s does not match regex of: %s", filename, OFFICIAL_FILENAME.toString()));
}
String hashdbName = match.group(DB_NAME_PARAM);
final String knownStatus = match.group(KNOWN_STATUS_PARAM);
KnownFilesType knownFilesType = Stream.of(HashDb.KnownFilesType.values())
.filter(k -> k.getIdentifier().toUpperCase().equals(knownStatus.toUpperCase()))
.findFirst()
.orElseThrow(() -> new HashDbManagerException(String.format("No KnownFilesType matches %s for file: %s", knownStatus, filename)));
return new SleuthkitHashSet(
SleuthkitJNI.openHashDatabase(file.getAbsolutePath()),
hashdbName,
true, //searchDuringIngest
false, //sendIngestMessages
knownFilesType,
true); // official set
}
/**
* Configures the given settings object by adding all contained hash db to
* the system.
*
* @param settings The settings to configure.
* @param settings The settings to configure.
* @param officialSetNames The official set names. Any name collisions will
* trigger rename for primary file.
*/
@Messages({"# {0} - hash set name", "HashDbManager.noDbPath.message=Couldn't get valid hash set path for: {0}",
"HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets"})
private void configureSettings(HashLookupSettings settings) {
private void configureSettings(HashLookupSettings settings, Set<String> officialSetNames) {
allDatabasesLoadedCorrectly = true;
List<HashDbInfo> hashDbInfoList = settings.getHashDbInfo();
hashDbInfoList = handleNameConflict(hashDbInfoList, officialSetNames);
for (HashDbInfo hashDbInfo : hashDbInfoList) {
try {
if (hashDbInfo.isFileDatabaseType()) {
String dbPath = this.getValidFilePath(hashDbInfo.getHashSetName(), hashDbInfo.getPath());
if (dbPath != null) {
addHashDatabase(SleuthkitJNI.openHashDatabase(dbPath), hashDbInfo.getHashSetName(), hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType());
} else {
logger.log(Level.WARNING, Bundle.HashDbManager_noDbPath_message(hashDbInfo.getHashSetName()));
allDatabasesLoadedCorrectly = false;
}
} else {
if (CentralRepository.isEnabled()) {
addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(),
hashDbInfo.getReferenceSetID(),
hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(),
hashDbInfo.getKnownFilesType(), hashDbInfo.isReadOnly());
}
}
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
NbBundle.getMessage(this.getClass(),
"HashDbManager.unableToOpenHashDbMsg", hashDbInfo.getHashSetName()),
NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"),
JOptionPane.ERROR_MESSAGE);
allDatabasesLoadedCorrectly = false;
}
configureLocalDb(hashDbInfo);
}
if (CentralRepository.isEnabled()) {
try {
updateHashSetsFromCentralRepository();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.HashDbManager_centralRepoLoadError_message(),
NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"),
JOptionPane.ERROR_MESSAGE);
allDatabasesLoadedCorrectly = false;
}
configureCrDbs();
}
/*
@@ -619,6 +767,56 @@ public class HashDbManager implements PropertyChangeListener {
}
}
/**
* Configures central repository hash set databases.
*/
private void configureCrDbs() {
try {
updateHashSetsFromCentralRepository();
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.HashDbManager_centralRepoLoadError_message(),
NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"),
JOptionPane.ERROR_MESSAGE);
allDatabasesLoadedCorrectly = false;
}
}
/**
* Handles configuring a local hash set database.
* @param hashDbInfo The local hash set database.
*/
private void configureLocalDb(HashDbInfo hashDbInfo) {
try {
if (hashDbInfo.isFileDatabaseType()) {
String dbPath = this.getValidFilePath(hashDbInfo.getHashSetName(), hashDbInfo.getPath());
if (dbPath != null) {
addHashDatabase(SleuthkitJNI.openHashDatabase(dbPath), hashDbInfo.getHashSetName(), hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType());
} else {
logger.log(Level.WARNING, Bundle.HashDbManager_noDbPath_message(hashDbInfo.getHashSetName()));
allDatabasesLoadedCorrectly = false;
}
} else {
if (CentralRepository.isEnabled()) {
addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(),
hashDbInfo.getReferenceSetID(),
hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(),
hashDbInfo.getKnownFilesType(), hashDbInfo.isReadOnly());
}
}
} catch (TskCoreException ex) {
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
NbBundle.getMessage(this.getClass(),
"HashDbManager.unableToOpenHashDbMsg", hashDbInfo.getHashSetName()),
NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"),
JOptionPane.ERROR_MESSAGE);
allDatabasesLoadedCorrectly = false;
}
}
private void updateHashSetsFromCentralRepository() throws TskCoreException {
if (CentralRepository.isEnabled()) {
List<HashDbInfo> crHashDbInfoList = getCentralRepoHashSetsFromDatabase();
@@ -702,17 +900,21 @@ public class HashDbManager implements PropertyChangeListener {
})
public enum KnownFilesType {
KNOWN(Bundle.HashDbManager_known_text(), TskData.FileKnown.KNOWN, false, false),
KNOWN_BAD(Bundle.HashDbManager_knownBad_text(), TskData.FileKnown.BAD, true, true),
NO_CHANGE(Bundle.HashDbManager_noChange_text(), TskData.FileKnown.UNKNOWN, true, false);
KNOWN(Bundle.HashDbManager_known_text(), "Known", TskData.FileKnown.KNOWN, false, false),
KNOWN_BAD(Bundle.HashDbManager_knownBad_text(), "Notable", TskData.FileKnown.BAD, true, true),
NO_CHANGE(Bundle.HashDbManager_noChange_text(), "NoChange", TskData.FileKnown.UNKNOWN, true, false);
private final String displayName;
private final String identifier;
private final TskData.FileKnown fileKnown;
private final boolean allowSendInboxMessages;
private final boolean defaultSendInboxMessages;
KnownFilesType(String displayName, TskData.FileKnown fileKnown, boolean allowSendInboxMessages, boolean defaultSendInboxMessages) {
KnownFilesType(String displayName, String identifier, TskData.FileKnown fileKnown,
boolean allowSendInboxMessages, boolean defaultSendInboxMessages) {
this.displayName = displayName;
this.identifier = identifier;
this.fileKnown = fileKnown;
this.allowSendInboxMessages = allowSendInboxMessages;
this.defaultSendInboxMessages = defaultSendInboxMessages;
@@ -740,6 +942,16 @@ public class HashDbManager implements PropertyChangeListener {
return defaultSendInboxMessages;
}
/**
* Returns the identifier for this KnownFilesType. This is used for
* Official Hash Sets in their naming convention.
*
* @return The identifier for this type.
*/
String getIdentifier() {
return identifier;
}
public String getDisplayName() {
return this.displayName;
}
@@ -863,14 +1075,20 @@ public class HashDbManager implements PropertyChangeListener {
private final HashDb.KnownFilesType knownFilesType;
private boolean indexing;
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private final boolean officialSet;
private SleuthkitHashSet(int handle, String hashSetName, boolean useForIngest, boolean sendHitMessages, KnownFilesType knownFilesType) {
this(handle, hashSetName, useForIngest, sendHitMessages, knownFilesType, false);
}
private SleuthkitHashSet(int handle, String hashSetName, boolean useForIngest, boolean sendHitMessages, KnownFilesType knownFilesType, boolean officialSet) {
this.handle = handle;
this.hashSetName = hashSetName;
this.searchDuringIngest = useForIngest;
this.sendIngestMessages = sendHitMessages;
this.knownFilesType = knownFilesType;
this.indexing = false;
this.officialSet = officialSet;
}
/**
@@ -956,6 +1174,10 @@ public class HashDbManager implements PropertyChangeListener {
*/
@Override
public boolean isUpdateable() throws TskCoreException {
if (isOfficialSet()) {
return false;
}
return SleuthkitJNI.isUpdateableHashDatabase(this.handle);
}
@@ -986,6 +1208,7 @@ public class HashDbManager implements PropertyChangeListener {
public void addHashes(Content content, String comment) throws TskCoreException {
// This only works for AbstractFiles and MD5 hashes at present.
assert content instanceof AbstractFile;
officialSetCheck();
if (content instanceof AbstractFile) {
AbstractFile file = (AbstractFile) content;
if (null != file.getMd5Hash()) {
@@ -994,6 +1217,17 @@ public class HashDbManager implements PropertyChangeListener {
}
}
/**
* Throws an exception if the current set is an official set.
*
* @throws TskCoreException
*/
private void officialSetCheck() throws TskCoreException {
if (isOfficialSet()) {
throw new TskCoreException("Hashes cannot be added to an official set");
}
}
/**
* Adds a list of hashes to the hash database at once
*
@@ -1003,6 +1237,7 @@ public class HashDbManager implements PropertyChangeListener {
*/
@Override
public void addHashes(List<HashEntry> hashes) throws TskCoreException {
officialSetCheck();
SleuthkitJNI.addToHashDatabase(hashes, handle);
}
@@ -1122,6 +1357,16 @@ public class HashDbManager implements PropertyChangeListener {
}
return true;
}
/**
* Returns whether or not the set is an official set. If the set is an
* official set, it is treated as readonly and cannot be deleted.
*
* @return Whether or not the set is an official set.
*/
boolean isOfficialSet() {
return officialSet;
}
}
/**
@@ -373,6 +373,35 @@ final class HashLookupSettings implements Serializable {
private final int referenceSetID;
private DatabaseType dbType;
/**
* Constructs a HashDbInfo object.
*
* @param hashSetName The name of the hash set
* @param knownFilesType The known files type
* @param searchDuringIngest Whether or not the db is searched during
* ingest
* @param sendIngestMessages Whether or not ingest messages are sent
* @param path The path to the db
* @param referenceSetID The reference set ID.
* @param version The version for the hashset.
* @param readOnly Whether or not the set is readonly.
* @param isCRType A central repo db type (otherwise, file type).
*/
HashDbInfo(String hashSetName, HashDbManager.HashDb.KnownFilesType knownFilesType, boolean searchDuringIngest, boolean sendIngestMessages,
String path, int referenceSetID, String version, boolean readOnly, boolean isCRType) {
this.hashSetName = hashSetName;
this.knownFilesType = knownFilesType;
this.searchDuringIngest = searchDuringIngest;
this.sendIngestMessages = sendIngestMessages;
this.path = path;
this.referenceSetID = referenceSetID;
this.version = version;
this.readOnly = readOnly;
this.dbType = isCRType ? DatabaseType.CENTRAL_REPOSITORY : DatabaseType.FILE;
}
/**
* Constructs a HashDbInfo object for files type
*
@@ -25,8 +25,10 @@ import java.awt.Frame;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.MissingResourceException;
import java.util.logging.Level;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
@@ -37,6 +39,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
@@ -197,68 +200,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
if (db instanceof SleuthkitHashSet) {
SleuthkitHashSet hashDb = (SleuthkitHashSet) db;
// Disable the central repo fields
hashDbVersionLabel.setText(Bundle.HashLookupSettingsPanel_notApplicable());
hashDbOrgLabel.setText(Bundle.HashLookupSettingsPanel_notApplicable());
// Enable the delete button if ingest is not running
deleteDatabaseButton.setEnabled(!ingestIsRunning);
try {
hashDbLocationLabel.setText(db.getDatabasePath());
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + db.getHashSetName() + " hash set", ex); //NON-NLS
hashDbLocationLabel.setText(ERROR_GETTING_PATH_TEXT);
}
try {
indexPathLabel.setText(hashDb.getIndexPath());
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index path of " + db.getHashSetName() + " hash set", ex); //NON-NLS
indexPathLabel.setText(ERROR_GETTING_PATH_TEXT);
}
// Update indexing components.
try {
if (hashDb.isIndexing()) {
indexButton.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.indexing"));
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexGen"));
hashDbIndexStatusLabel.setForeground(Color.black);
indexButton.setEnabled(false);
} else if (hashDb.hasIndex()) {
if (hashDb.hasIndexOnly()) {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexOnly"));
} else {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexed"));
}
hashDbIndexStatusLabel.setForeground(Color.black);
if (hashDb.canBeReIndexed()) {
indexButton.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.reIndex"));
indexButton.setEnabled(true);
} else {
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(false);
}
} else {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.noIndex"));
hashDbIndexStatusLabel.setForeground(Color.red);
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(true);
}
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index state of hash set", ex); //NON-NLS
hashDbIndexStatusLabel.setText(ERROR_GETTING_INDEX_STATUS_TEXT);
hashDbIndexStatusLabel.setForeground(Color.red);
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(false);
}
updateForSleuthkitHashSet(ingestIsRunning, hashDb);
} else {
// Disable the file type fields/buttons
@@ -291,6 +233,93 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
ingestWarningLabel.setVisible(ingestIsRunning);
}
private static String getPathString(String path, boolean justFilename) {
if (StringUtils.isBlank(path)) {
return "";
}
if (!justFilename) {
return path;
}
return new File(path).getName();
}
/**
* Updates UI for a SleuthkitHashSet.
*
* @param ingestIsRunning Whether or not ingest is running.
* @param hashDb The hash set to be included in the list of hash
* sets.
*
* @throws MissingResourceException
*/
private void updateForSleuthkitHashSet(boolean ingestIsRunning, SleuthkitHashSet hashDb) throws MissingResourceException {
// Disable the central repo fields
hashDbVersionLabel.setText(Bundle.HashLookupSettingsPanel_notApplicable());
hashDbOrgLabel.setText(Bundle.HashLookupSettingsPanel_notApplicable());
// Enable the delete button if ingest is not running and is not an official hashset
deleteDatabaseButton.setEnabled(!ingestIsRunning && !hashDb.isOfficialSet());
try {
String dbPath = getPathString(hashDb.getDatabasePath(), hashDb.isOfficialSet());
hashDbLocationLabel.setText(dbPath);
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + hashDb.getHashSetName() + " hash set", ex); //NON-NLS
hashDbLocationLabel.setText(ERROR_GETTING_PATH_TEXT);
}
try {
String indexPath = getPathString(hashDb.getIndexPath(), hashDb.isOfficialSet());
indexPathLabel.setText(indexPath);
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDb.getHashSetName() + " hash set", ex); //NON-NLS
indexPathLabel.setText(ERROR_GETTING_PATH_TEXT);
}
// Update indexing components.
try {
if (hashDb.isIndexing()) {
indexButton.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.indexing"));
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexGen"));
hashDbIndexStatusLabel.setForeground(Color.black);
indexButton.setEnabled(false);
} else if (hashDb.hasIndex()) {
if (hashDb.hasIndexOnly()) {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexOnly"));
} else {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.indexed"));
}
hashDbIndexStatusLabel.setForeground(Color.black);
if (hashDb.canBeReIndexed()) {
indexButton.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.reIndex"));
indexButton.setEnabled(true);
} else {
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(false);
}
} else {
hashDbIndexStatusLabel.setText(
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexStatusText.noIndex"));
hashDbIndexStatusLabel.setForeground(Color.red);
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(true);
}
} catch (TskCoreException ex) {
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index state of hash set", ex); //NON-NLS
hashDbIndexStatusLabel.setText(ERROR_GETTING_INDEX_STATUS_TEXT);
hashDbIndexStatusLabel.setForeground(Color.red);
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
indexButton.setEnabled(false);
}
}
private boolean isLocalIngestJobEvent(PropertyChangeEvent evt) {
if (evt instanceof AutopsyEvent) {
AutopsyEvent event = (AutopsyEvent) evt;
@@ -12,7 +12,7 @@ FilesSetDefsPanel.exportButtonActionPerformed.fileExistPrompt=File {0} exists, o
FilesSetDefsPanel.gigaBytes=Gigabytes
# {0} - fileName
# {1} - errorMessage
FilesSetDefsPanel.importSetButtonActionPerformed.importError=The rules file "{0}" could not be read:\n"{1}."
FilesSetDefsPanel.importSetButtonActionPerformed.importError=The rules file "{0}" could not be read:\n{1}.
FilesSetDefsPanel.importSetButtonActionPerformed.noFiles=No files sets were found in the selected files.
FilesSetDefsPanel.importSetButtonActionPerformed.noFilesSelected=No files sets were selected.
# {0} - filter name
@@ -24,6 +24,7 @@ FilesSetDefsPanel.interesting.exportButtonAction.featureName=Interesting Files S
FilesSetDefsPanel.interesting.ExportedMsg=Interesting files set exported
FilesSetDefsPanel.interesting.exportSetButton.text=Export Set
FilesSetDefsPanel.interesting.failExportMsg=Export of interesting files set failed
FilesSetDefsPanel.interesting.failImportMsg=Interesting files set not imported
FilesSetDefsPanel.interesting.fileExtensionFilterLbl=Autopsy Interesting File Set File (xml)
FilesSetDefsPanel.interesting.importButtonAction.featureName=Interesting Files Set Import
FilesSetDefsPanel.interesting.importOwConflict=Import Interesting files set conflict
@@ -33,7 +34,7 @@ FilesSetDefsPanel.interesting.newOwConflict=Interesting files set conflict
FilesSetDefsPanel.interesting.overwriteSetPrompt=Interesting files set "{0}" already exists locally, overwrite?
# {0} - FilesSet name
# {1} - New FilesSet name
FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name "{0}." Would you like to rename the set to "{1}?"
FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name "{0}." Would you like to rename your set to "{1}?"
FilesSetDefsPanel.Interesting.Title=Global Interesting Items Settings
FilesSetDefsPanel.kiloBytes=Kilobytes
FilesSetDefsPanel.loadError=Error loading interesting files sets from file.
@@ -64,7 +65,7 @@ FilesSetRulePanel.ZeroFileSizeError=File size condition value must not be 0 (Unl
FilesSetsManager.allFilesAndDirectories=All Files and Directories (Not Unallocated Space)
FilesSetsManager.allFilesDirectoriesAndUnallocated=All Files, Directories, and Unallocated Space
# {0} - regex
InterestingItemsFilesSetSettings.readDateCondition.failedCompiledRegex=Error detmining ''{0}'' number
InterestingItemsFilesSetSettings.readDateCondition.failedCompiledRegex=Error determining ''{0}'' number
# {0} - condition
# {1} - rule
InterestingItemsFilesSetSettings.readMetaTypeCondition.malformedXml=Files set is malformed for metatype condition, ''{0}'', in rule ''{1}''
@@ -180,3 +181,6 @@ FilesSetDefsPanel.mimeTypeLabel.text=MIME Type:
FilesSetDefsPanel.fileSizeLabel.text=File Size:
# {0} - filesSetName
StandardInterestingFileSetsLoader.customSuffixed={0} (Custom)
StandardInterestingFilesSetsLoader_cannotLoadStandard=Unable to properly read standard interesting files sets.
StandardInterestingFilesSetsLoader_cannotLoadUserConfigured=Unable to properly read user-configured interesting files sets.
StandardInterestingFilesSetsLoader_cannotUpdateInterestingFilesSets=Unable to write updated configuration for interesting files sets to config directory.
@@ -19,7 +19,7 @@ ReportProgressIndicator.switchToIndeterminateMessage=Report generation progress
ReportWizardDataSourceSelectionPanel.confirmEmptySelection=Are you sure you want to proceed with no selections?
ReportWizardDataSourceSelectionPanel.finishButton.text=Finish
ReportWizardDataSourceSelectionPanel.nextButton.text=Next
ReportWizardDataSourceSelectionPanel.title=Select which datasource(s) to include
ReportWizardDataSourceSelectionPanel.title=Select which data source(s) to include
ReportWizardFileOptionsVisualPanel.jLabel1.text=Select items to include in File Report:
ReportWizardFileOptionsVisualPanel.deselectAllButton.text=Deselect All
ReportWizardFileOptionsVisualPanel.selectAllButton.text=Select All
@@ -87,7 +87,7 @@ public class ReportWizardDataSourceSelectionPanel implements WizardDescriptor.Fi
}
@NbBundle.Messages({
"ReportWizardDataSourceSelectionPanel.title=Select which datasource(s) to include"
"ReportWizardDataSourceSelectionPanel.title=Select which data source(s) to include"
})
@Override
public CheckBoxListPanel<Long> getComponent() {
@@ -1,466 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.report.modules.caseuco;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.IOException;
import java.nio.file.Path;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Writes Autopsy DataModel objects to Case UCO format.
*
* Clients are expected to add the Case first. Then they should add each data
* source before adding any files for that data source.
*
* Here is an example, where we add everything:
*
* Path directory = Paths.get("C:", "Reports");
* CaseUcoReportGenerator caseUco = new CaseUcoReportGenerator(directory, "my-report");
*
* Case caseObj = Case.getCurrentCase();
* caseUco.addCase(caseObj);
* List<Content> dataSources = caseObj.getDataSources();
* for(Content dataSource : dataSources) {
* caseUco.addDataSource(dataSource, caseObj);
* List<AbstractFile> files = getAllFilesInDataSource(dataSource);
* for(AbstractFile file : files) {
* caseUco.addFile(file, dataSource);
* }
* }
*
* Path reportOutput = caseUco.generateReport();
* //Done. Report at - "C:\Reports\my-report.json-ld"
*
* Please note that the life cycle for this class ends with generateReport().
* The underlying file handle to 'my-report.json-ld' will be closed. Any further
* calls to addX() will result in an IOException.
*/
public final class CaseUcoReportGenerator {
private static final String EXTENSION = "json-ld";
private final TimeZone timeZone;
private final Path reportPath;
private final JsonGenerator reportGenerator;
/**
* Creates a CaseUCO Report Generator that writes a report in the specified
* directory.
*
* TimeZone is assumed to be GMT+0 for formatting file creation time,
* accessed time and modified time.
*
* @param directory Directory to write the CaseUCO report file. Assumes the
* calling thread has write access to the directory and that the directory
* exists.
* @param reportName Name of the CaseUCO report file.
* @throws IOException If an I/O error occurs
*/
public CaseUcoReportGenerator(Path directory, String reportName) throws IOException {
this.reportPath = directory.resolve(reportName + "." + EXTENSION);
JsonFactory jsonGeneratorFactory = new JsonFactory();
reportGenerator = jsonGeneratorFactory.createGenerator(reportPath.toFile(), JsonEncoding.UTF8);
// Puts a newline between each Key, Value pair for readability.
reportGenerator.setPrettyPrinter(new DefaultPrettyPrinter()
.withObjectIndenter(new DefaultIndenter(" ", "\n")));
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
reportGenerator.setCodec(mapper);
reportGenerator.writeStartObject();
reportGenerator.writeFieldName("@graph");
reportGenerator.writeStartArray();
//Assume GMT+0
this.timeZone = new SimpleTimeZone(0, "GMT");
}
/**
* Adds an AbstractFile instance to the Case UCO report.
*
* @param file AbstractFile instance to write
* @param parentDataSource The parent data source for this abstract file. It
* is assumed that this parent has been written to the report (via
* addDataSource) prior to this call. Otherwise, the report may be invalid.
* @throws IOException If an I/O error occurs.
* @throws TskCoreException
*/
public void addFile(AbstractFile file, Content parentDataSource) throws IOException, TskCoreException {
addFile(file, parentDataSource, null);
}
/**
* Adds an AbstractFile instance to the Case UCO report.
*
* @param file AbstractFile instance to write
* @param parentDataSource The parent data source for this abstract file. It
* is assumed that this parent has been written to the report (via
* addDataSource) prior to this call. Otherwise, the report may be invalid.
* @param localPath The location of the file on secondary storage, somewhere
* other than the case. Example: local disk. This value will be ignored if
* it is null.
* @throws IOException
* @throws TskCoreException
*/
public void addFile(AbstractFile file, Content parentDataSource, Path localPath) throws IOException, TskCoreException {
String fileTraceId = getFileTraceId(file);
//Create the Trace CASE node, which will contain attributes about some evidence.
//Trace is the standard term for evidence. For us, this means file system files.
CASENode fileTrace = new CASENode(fileTraceId, "Trace");
//The bits of evidence for each Trace node are contained within Property
//Bundles. There are a number of Property Bundles available in the CASE ontology.
//Build up the File Property Bundle, as the name implies - properties of
//the file itself.
CASEPropertyBundle filePropertyBundle = createFileBundle(file);
fileTrace.addBundle(filePropertyBundle);
//Build up the ContentData Property Bundle, as the name implies - properties of
//the File data itself.
CASEPropertyBundle contentDataPropertyBundle = createContentDataBundle(file);
fileTrace.addBundle(contentDataPropertyBundle);
if(localPath != null) {
String urlTraceId = getURLTraceId(file);
CASENode urlTrace = new CASENode(urlTraceId, "Trace");
CASEPropertyBundle urlPropertyBundle = new CASEPropertyBundle("URL");
urlPropertyBundle.addProperty("fullValue", localPath.toString());
urlTrace.addBundle(urlPropertyBundle);
contentDataPropertyBundle.addProperty("dataPayloadReferenceUrl", urlTraceId);
reportGenerator.writeObject(urlTrace);
}
//Create the Relationship CASE node. This defines how the Trace CASE node described above
//is related to another CASE node (in this case, the parent data source).
String relationshipID = getRelationshipId(file);
CASENode relationship = createRelationshipNode(relationshipID,
fileTraceId, getDataSourceTraceId(parentDataSource));
//Build up the PathRelation bundle for the relationship node,
//as the name implies - the Path of the Trace in the data source.
CASEPropertyBundle pathRelationPropertyBundle = new CASEPropertyBundle("PathRelation");
pathRelationPropertyBundle.addProperty("path", file.getUniquePath());
relationship.addBundle(pathRelationPropertyBundle);
//This completes the triage, write them to JSON.
reportGenerator.writeObject(fileTrace);
reportGenerator.writeObject(relationship);
}
private String getURLTraceId(Content content) {
return "url-" + content.getId();
}
/**
* All relationship nodes will be the same within our context. Namely, contained-within
* and isDirectional as true.
*/
private CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID) {
CASENode relationship = new CASENode(relationshipID, "Relationship");
relationship.addProperty("source", sourceID);
relationship.addProperty("target", targetID);
relationship.addProperty("kindOfRelationship", "contained-within");
relationship.addProperty("isDirectional", true);
return relationship;
}
/**
* Creates a File Property Bundle with a selection of file attributes.
*/
private CASEPropertyBundle createFileBundle(AbstractFile file) throws TskCoreException {
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
String createdTime = ContentUtils.getStringTimeISO8601(file.getCrtime(), timeZone);
String accessedTime = ContentUtils.getStringTimeISO8601(file.getAtime(), timeZone);
String modifiedTime = ContentUtils.getStringTimeISO8601(file.getMtime(), timeZone);
filePropertyBundle.addProperty("createdTime", createdTime);
filePropertyBundle.addProperty("accessedTime", accessedTime);
filePropertyBundle.addProperty("modifiedTime", modifiedTime);
if (!Strings.isNullOrEmpty(file.getNameExtension())) {
filePropertyBundle.addProperty("extension", file.getNameExtension());
}
filePropertyBundle.addProperty("fileName", file.getName());
filePropertyBundle.addProperty("filePath", file.getUniquePath());
filePropertyBundle.addProperty("isDirectory", file.isDir());
filePropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
return filePropertyBundle;
}
/**
* Creates a Content Data Property Bundle with a selection of file attributes.
*/
private CASEPropertyBundle createContentDataBundle(AbstractFile file) {
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
if (!Strings.isNullOrEmpty(file.getMIMEType())) {
contentDataPropertyBundle.addProperty("mimeType", file.getMIMEType());
}
if (!Strings.isNullOrEmpty(file.getMd5Hash())) {
List<CASEPropertyBundle> hashPropertyBundles = new ArrayList<>();
CASEPropertyBundle md5HashPropertyBundle = new CASEPropertyBundle("Hash");
md5HashPropertyBundle.addProperty("hashMethod", "MD5");
md5HashPropertyBundle.addProperty("hashValue", file.getMd5Hash());
hashPropertyBundles.add(md5HashPropertyBundle);
contentDataPropertyBundle.addProperty("hash", hashPropertyBundles);
}
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
return contentDataPropertyBundle;
}
/**
* Creates a unique CASE Node file trace id.
*/
private String getFileTraceId(AbstractFile file) {
return "file-" + file.getId();
}
/**
* Creates a unique CASE Node relationship id value.
*/
private String getRelationshipId(Content content) {
return "relationship-" + content.getId();
}
/**
* Adds a Content instance (which is known to be a DataSource) to the CASE
* report. This means writing a selection of attributes to a CASE or UCO
* object.
*
* @param dataSource Datasource content to write
* @param parentCase The parent case that this data source belongs in. It is
* assumed that this parent has been written to the report (via addCase)
* prior to this call. Otherwise, the report may be invalid.
*/
public void addDataSource(Content dataSource, Case parentCase) throws IOException, TskCoreException {
String dataSourceTraceId = this.getDataSourceTraceId(dataSource);
CASENode dataSourceTrace = new CASENode(dataSourceTraceId, "Trace");
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
String dataSourcePath = getDataSourcePath(dataSource);
filePropertyBundle.addProperty("filePath", dataSourcePath);
dataSourceTrace.addBundle(filePropertyBundle);
if (dataSource.getSize() > 0) {
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(dataSource.getSize()));
dataSourceTrace.addBundle(contentDataPropertyBundle);
}
// create a "relationship" entry between the case and the data source
String caseTraceId = getCaseTraceId(parentCase);
String relationshipTraceId = getRelationshipId(dataSource);
CASENode relationship = createRelationshipNode(relationshipTraceId,
dataSourceTraceId, caseTraceId);
CASEPropertyBundle pathRelationBundle = new CASEPropertyBundle("PathRelation");
pathRelationBundle.addProperty("path", dataSourcePath);
relationship.addBundle(pathRelationBundle);
//This completes the triage, write them to JSON.
reportGenerator.writeObject(dataSourceTrace);
reportGenerator.writeObject(relationship);
}
private String getDataSourcePath(Content dataSource) {
String dataSourcePath = "";
if (dataSource instanceof Image) {
String[] paths = ((Image) dataSource).getPaths();
if (paths.length > 0) {
//Get the first data source in the path, as this will
//be reflected in each file's uniquePath.
dataSourcePath = paths[0];
}
} else {
dataSourcePath = dataSource.getName();
}
dataSourcePath = dataSourcePath.replaceAll("\\\\", "/");
return dataSourcePath;
}
/**
* Creates a unique Case UCO trace id for a data source.
*
* @param dataSource
* @return
*/
private String getDataSourceTraceId(Content dataSource) {
return "data-source-" + dataSource.getId();
}
/**
* Adds a Case instance to the Case UCO report. This means writing a
* selection of Case attributes to a CASE/UCO object.
*
* @param caseObj Case instance to include in the report.
* @throws IOException If an I/O error is encountered.
*/
public void addCase(Case caseObj) throws IOException {
SleuthkitCase skCase = caseObj.getSleuthkitCase();
String caseDirPath = skCase.getDbDirPath();
String caseTraceId = getCaseTraceId(caseObj);
CASENode caseTrace = new CASENode(caseTraceId, "Trace");
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
// replace double slashes with single ones
caseDirPath = caseDirPath.replaceAll("\\\\", "/");
Case.CaseType caseType = caseObj.getCaseType();
if (caseType.equals(CaseType.SINGLE_USER_CASE)) {
filePropertyBundle.addProperty("filePath", caseDirPath + "/" + skCase.getDatabaseName());
filePropertyBundle.addProperty("isDirectory", false);
} else {
filePropertyBundle.addProperty("filePath", caseDirPath);
filePropertyBundle.addProperty("isDirectory", true);
}
caseTrace.addBundle(filePropertyBundle);
reportGenerator.writeObject(caseTrace);
}
/**
* Creates a unique Case UCO trace id for a Case.
*
* @param caseObj
* @return
*/
private String getCaseTraceId(Case caseObj) {
return "case-" + caseObj.getName();
}
/**
* Returns a Path to the completed Case UCO report file.
*
* This marks the end of the CaseUcoReportGenerator's life cycle. This
* function will close an underlying file handles, meaning any subsequent
* calls to addX() will result in an IOException.
*
* @return The Path to the finalized report.
* @throws IOException If an I/O error occurs.
*/
public Path generateReport() throws IOException {
//Finalize the report.
reportGenerator.writeEndArray();
reportGenerator.writeEndObject();
reportGenerator.close();
return reportPath;
}
/**
* A CASE or UCO object. CASE objects can have properties and
* property bundles.
*/
private final class CASENode {
private final String id;
private final String type;
//Dynamic properties added to this CASENode.
private final Map<String, Object> properties;
private final List<CASEPropertyBundle> propertyBundle;
public CASENode(String id, String type) {
this.id = id;
this.type = type;
properties = new LinkedHashMap<>();
propertyBundle = new ArrayList<>();
}
@JsonProperty("@id")
public String getId() {
return id;
}
@JsonProperty("@type")
public String getType() {
return type;
}
@JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
@JsonProperty("propertyBundle")
public List<CASEPropertyBundle> getPropertyBundle() {
return propertyBundle;
}
public void addProperty(String key, Object val) {
properties.put(key, val);
}
public void addBundle(CASEPropertyBundle bundle) {
propertyBundle.add(bundle);
}
}
/**
* Contains CASE or UCO properties.
*/
private final class CASEPropertyBundle {
private final Map<String, Object> properties;
public CASEPropertyBundle(String type) {
properties = new LinkedHashMap<>();
addProperty("@type", type);
}
@JsonAnyGetter
public Map<String, Object> getProperties() {
return properties;
}
public void addProperty(String key, Object val) {
properties.put(key, val);
}
}
}
@@ -19,7 +19,15 @@
*/
package org.sleuthkit.autopsy.report.modules.caseuco;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.stream.JsonWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -39,29 +47,37 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.caseuco.CaseUcoExporter;
import org.sleuthkit.caseuco.ContentNotExportableException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
/**
* CaseUcoReportModule generates a report in CASE-UCO format. This module will
* write all files and data sources to the report.
* Exports an Autopsy case to a CASE-UCO report file. This module will write all
* files and artifacts from the selected data sources.
*/
public final class CaseUcoReportModule implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(CaseUcoReportModule.class.getName());
private static final CaseUcoReportModule SINGLE_INSTANCE = new CaseUcoReportModule();
//Supported types of TSK_FS_FILES
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {{
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
}};
private static final String REPORT_FILE_NAME = "CASE_UCO_output";
private static final String EXTENSION = "json-ld";
//Supported types of TSK_FS_FILES
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {
{
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
}
};
private static final String REPORT_FILE_NAME = "CASE_UCO_output";
private static final String EXTENSION = "jsonld";
// Hidden constructor for the report
private CaseUcoReportModule() {
@@ -76,7 +92,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
public String getName() {
return NbBundle.getMessage(this.getClass(), "CaseUcoReportModule.getName.text");
}
@Override
public JPanel getConfigurationPanel() {
return null; // No configuration panel
@@ -84,7 +100,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
@Override
public String getRelativeFilePath() {
return REPORT_FILE_NAME + "." + EXTENSION;
return REPORT_FILE_NAME + "." + EXTENSION;
}
@Override
@@ -100,7 +116,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
public static String getReportFileName() {
return REPORT_FILE_NAME;
}
@Override
public boolean supportsDataSourceSelection() {
return true;
@@ -109,7 +125,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
/**
* Generates a CASE-UCO format report for all files in the Case.
*
* @param settings Report settings.
* @param settings Report settings.
* @param progressPanel panel to update the report's progress
*/
@NbBundle.Messages({
@@ -128,74 +144,134 @@ public final class CaseUcoReportModule implements GeneralReportModule {
try {
// Check if ingest has finished
warnIngest(progressPanel);
//Create report paths if they don't already exist.
Path reportDirectory = Paths.get(settings.getReportDirectoryPath());
try {
Files.createDirectories(reportDirectory);
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to create directory for CASE-UCO report.", ex);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_unableToCreateDirectories());
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_unableToCreateDirectories());
return;
}
CaseUcoReportGenerator generator =
new CaseUcoReportGenerator(reportDirectory, REPORT_FILE_NAME);
//First write the Case to the report file.
Case caseObj = Case.getCurrentCaseThrows();
generator.addCase(caseObj);
List<Content> dataSources = caseObj.getDataSources().stream()
.filter((dataSource) -> {
if(settings.getSelectedDataSources() == null) {
// Assume all data sources if list is null.
return true;
Case currentCase = Case.getCurrentCaseThrows();
Path caseJsonReportFile = reportDirectory.resolve(REPORT_FILE_NAME + "." + EXTENSION);
try (OutputStream stream = new FileOutputStream(caseJsonReportFile.toFile());
JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
reportWriter.setIndent(" ");
reportWriter.beginObject();
reportWriter.name("@graph");
reportWriter.beginArray();
CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
for (JsonElement element : exporter.exportSleuthkitCase()) {
gson.toJson(element, reportWriter);
}
// Get a list of selected data sources to process.
List<DataSource> dataSources = getSelectedDataSources(currentCase, settings);
progressPanel.setIndeterminate(false);
progressPanel.setMaximumProgress(dataSources.size());
progressPanel.start();
// First stage of reporting is for files and data sources.
// Iterate through each data source and dump all files contained
// in that data source.
for (int i = 0; i < dataSources.size(); i++) {
DataSource dataSource = dataSources.get(i);
progressPanel.updateStatusLabel(String.format(
Bundle.CaseUcoReportModule_processingDataSource(),
dataSource.getName()));
// Add the data source export.
for (JsonElement element : exporter.exportDataSource(dataSource)) {
gson.toJson(element, reportWriter);
}
// Search all children of the data source.
performDepthFirstSearch(dataSource, gson, exporter, reportWriter);
progressPanel.setProgress(i + 1);
}
// Second stage of reporting handles artifacts.
Set<Long> dataSourceIds = dataSources.stream()
.map((datasource) -> datasource.getId())
.collect(Collectors.toSet());
logger.log(Level.INFO, "Writing all artifacts to the CASE-UCO report. "
+ "Keyword hits will be skipped as they can't be represented"
+ " in CASE format.");
// Write all standard artifacts that are contained within the
// selected data sources.
for (ARTIFACT_TYPE artType : currentCase.getSleuthkitCase().getBlackboardArtifactTypesInUse()) {
if(artType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT)) {
// Keyword hits cannot be represented in CASE.
continue;
}
for (BlackboardArtifact artifact : currentCase.getSleuthkitCase().getBlackboardArtifacts(artType)) {
if (dataSourceIds.contains(artifact.getDataSource().getId())) {
try {
for (JsonElement element : exporter.exportBlackboardArtifact(artifact)) {
gson.toJson(element, reportWriter);
}
} catch (ContentNotExportableException ex) {
logger.log(Level.INFO, String.format("Unable to export blackboard artifact (id: %d, type: %d) to CASE/UCO. "
+ "The artifact type is either not supported or the artifact instance does not have any "
+ "exportable attributes.", artifact.getId(), artType.getTypeID()));
} catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
logger.log(Level.WARNING, String.format("Artifact instance (id: %d, type: %d) contained a "
+ "malformed json attribute.", artifact.getId(), artType.getTypeID()), ex);
}
}
return settings.getSelectedDataSources().contains(dataSource.getId());
})
.collect(Collectors.toList());
progressPanel.setIndeterminate(false);
progressPanel.setMaximumProgress(dataSources.size());
progressPanel.start();
//Then search each data source for file content.
for(int i = 0; i < dataSources.size(); i++) {
Content dataSource = dataSources.get(i);
progressPanel.updateStatusLabel(String.format(
Bundle.CaseUcoReportModule_processingDataSource(),
dataSource.getName()));
//Add the data source and then all children.
generator.addDataSource(dataSource, caseObj);
performDepthFirstSearch(dataSource, generator);
progressPanel.setProgress(i+1);
}
}
reportWriter.endArray();
reportWriter.endObject();
}
//Complete the report.
Path reportPath = generator.generateReport();
caseObj.addReport(reportPath.toString(),
Bundle.CaseUcoReportModule_srcModuleName(),
currentCase.addReport(caseJsonReportFile.toString(),
Bundle.CaseUcoReportModule_srcModuleName(),
REPORT_FILE_NAME);
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
} catch (IOException ex) {
logger.log(Level.WARNING, "I/O error encountered while generating the report.", ex);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_ioError());
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No case open.", ex);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_noCaseOpen());
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TskCoreException encounted while generating the report.", ex);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
String.format(Bundle.CaseUcoReportModule_tskCoreException(), ex.toString()));
}
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
}
/**
* Get the selected data sources from the settings instance.
*/
private List<DataSource> getSelectedDataSources(Case currentCase, GeneralReportSettings settings) throws TskCoreException {
return currentCase.getSleuthkitCase().getDataSources().stream()
.filter((dataSource) -> {
if (settings.getSelectedDataSources() == null) {
// Assume all data sources if list is null.
return true;
}
return settings.getSelectedDataSources().contains(dataSource.getId());
})
.collect(Collectors.toList());
}
/**
* Warn the user if ingest is still ongoing.
*/
@@ -204,30 +280,32 @@ public final class CaseUcoReportModule implements GeneralReportModule {
progressPanel.updateStatusLabel(Bundle.CaseUcoReportModule_ingestWarning());
}
}
/**
* Perform DFS on the data sources tree, which will search it in entirety.
* This traversal is more memory efficient than BFS (Breadth first search).
* Perform DFS on the data sources tree, which will search it in entirety.
*/
private void performDepthFirstSearch(Content dataSource,
CaseUcoReportGenerator generator) throws IOException, TskCoreException {
private void performDepthFirstSearch(DataSource dataSource,
Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter) throws IOException, TskCoreException {
Deque<Content> stack = new ArrayDeque<>();
stack.addAll(dataSource.getChildren());
//Depth First Search the data source tree.
while(!stack.isEmpty()) {
while (!stack.isEmpty()) {
Content current = stack.pop();
if(current instanceof AbstractFile) {
AbstractFile f = (AbstractFile) (current);
if(SUPPORTED_TYPES.contains(f.getMetaType().getValue())) {
generator.addFile(f, dataSource);
if (current instanceof AbstractFile) {
AbstractFile file = (AbstractFile) (current);
if (SUPPORTED_TYPES.contains(file.getMetaType().getValue())) {
for (JsonElement element : exporter.exportAbstractFile(file)) {
gson.toJson(element, reportWriter);
}
}
}
for(Content child : current.getChildren()) {
for (Content child : current.getChildren()) {
stack.push(child);
}
}
}
}
}
@@ -40,7 +40,7 @@ public class TimeLineModule {
private static final Logger logger = Logger.getLogger(TimeLineModule.class.getName());
private static final Object controllerLock = new Object();
private static TimeLineController controller;
private static volatile TimeLineController controller;
/**
* provides static utilities, can not be instantiated
@@ -28,8 +28,10 @@ import junit.framework.Test;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbModuleSuite;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.InvalidAccountIDException;
/**
* Tests the Account APIs on the Central Repository.
@@ -145,7 +147,7 @@ public class CentralRepoAccountsTest extends TestCase {
// Create the account
CentralRepository.getInstance()
.getOrCreateAccount(expectedAccountType, "+1 401-231-2552");
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -167,7 +169,7 @@ public class CentralRepoAccountsTest extends TestCase {
Assert.assertEquals(expectedAccountType, actualAccount.getAccountType());
Assert.assertEquals("+1 441-231-2552", actualAccount.getIdentifier());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -33,6 +33,7 @@ import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbModuleSuite;
import org.openide.util.Exceptions;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.TskData;
@@ -74,7 +75,7 @@ public class CentralRepoPersonasTest extends TestCase {
private static final String FACEBOOK_ID_CATDOG = "BalooSherkhan";
private static final String DOG_EMAIL_ID = "superpupper@junglebook.com";
private static final String CAT_WHATSAPP_ID = "111 222 3333";
private static final String CAT_WHATSAPP_ID = "1112223333@s.whatsapp.net";
private static final String EMAIL_ID_1 = "rkipling@jungle.book";
private static final String HOLMES_SKYPE_ID = "live:holmes@221baker.com";
@@ -383,7 +384,7 @@ public class CentralRepoPersonasTest extends TestCase {
// Confirm the account was removed
Assert.assertTrue(catPersona.getPersonaAccounts().isEmpty());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -518,7 +519,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(0, holmesMetadataList.size());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -795,7 +796,7 @@ public class CentralRepoPersonasTest extends TestCase {
}
catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
catch (CentralRepoException | CorrelationAttributeNormalizationException | InvalidAccountIDException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
@@ -820,7 +821,7 @@ public class CentralRepoPersonasTest extends TestCase {
// Verify Persona has a default name
Assert.assertEquals(Persona.getDefaultName(), persona.getName());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -893,7 +894,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(4, personaSearchResult.size());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -1004,7 +1005,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(6, personaSearchResult.size());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -1077,7 +1078,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(0, accountsWithUnknownIdentifier.size());
} catch (CentralRepoException ex) {
} catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}