mirror of
https://github.com/elisspace/autopsy.git
synced 2026-09-30 22:39:52 +00:00
Merge remote-tracking branch 'upstream/develop' into streamImagesForAutoIngest
This commit is contained in:
@@ -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. -->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(()
|
||||
|
||||
Executable → Regular
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
+7
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -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.
|
||||
|
||||
+10
-2
@@ -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;
|
||||
|
||||
+14
-24
@@ -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>
|
||||
|
||||
+3
-1
@@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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="<String>"/>
|
||||
</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, "{key}")"/>
|
||||
</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;
|
||||
|
||||
+10
-13
@@ -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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+319
-298
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
+4
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user