diff --git a/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java b/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java index 3e67e6a442..8102b01e13 100644 --- a/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java +++ b/Core/src/org/sleuthkit/autopsy/appservices/AutopsyService.java @@ -90,6 +90,7 @@ public interface AutopsyService { private final Case theCase; private final ProgressIndicator progressIndicator; private volatile boolean cancelRequested; + private final boolean isNewCase; /** * Constructs the context for the creation/opening/upgrading of @@ -100,9 +101,23 @@ public interface AutopsyService { * case-level resources */ public CaseContext(Case theCase, ProgressIndicator progressIndicator) { + this(theCase, progressIndicator, false); + } + + /** + * Constructs the context for the creation/opening/upgrading of + * case-level resources by a service. + * + * @param theCase The case. + * @param progressIndicator A progress indicator for the opening of the + * case-level resources. + * @param isNewCase True if theCase is a new case. + */ + public CaseContext(Case theCase, ProgressIndicator progressIndicator, boolean isNewCase) { this.theCase = theCase; this.progressIndicator = progressIndicator; this.cancelRequested = false; + this.isNewCase = isNewCase; } /** @@ -145,6 +160,16 @@ public interface AutopsyService { public boolean cancelRequested() { return this.cancelRequested; } + + /** + * Indicates whether or the case is a new case in the process of being + * created. + * + * @return True if it is a new case. + */ + public boolean isNewCase() { + return this.isNewCase; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 17980cd0cd..a1443bde31 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -132,7 +132,6 @@ class AddImageTask implements Runnable { List errorMessages = new ArrayList<>(); List newDataSources = new ArrayList<>(); try { - currentCase.getSleuthkitCase().acquireSingleUserCaseWriteLock(); synchronized (tskAddImageProcessLock) { if (!tskAddImageProcessStopped) { tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles, imageWriterPath); @@ -147,7 +146,6 @@ class AddImageTask implements Runnable { commitOrRevertAddImageProcess(currentCase, errorMessages, newDataSources); progressMonitor.setProgress(100); } finally { - currentCase.getSleuthkitCase().releaseSingleUserCaseWriteLock(); DataSourceProcessorCallback.DataSourceProcessorResult result; if (criticalErrorOccurred) { result = DataSourceProcessorResult.CRITICAL_ERRORS; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 47fff7320f..7cd1738524 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1958,7 +1958,7 @@ public class Case { checkForCancellation(); openCaseLevelServices(progressIndicator); checkForCancellation(); - openAppServiceCaseResources(progressIndicator); + openAppServiceCaseResources(progressIndicator, true); checkForCancellation(); openCommunicationChannels(progressIndicator); return null; @@ -2007,7 +2007,7 @@ public class Case { checkForCancellation(); openCaseLevelServices(progressIndicator); checkForCancellation(); - openAppServiceCaseResources(progressIndicator); + openAppServiceCaseResources(progressIndicator, false); checkForCancellation(); openCommunicationChannels(progressIndicator); checkForCancellation(); @@ -2060,6 +2060,7 @@ public class Case { private final SleuthkitCase tskCase; private final String caseName; + private final long MAX_IMAGE_THRESHOLD = 100; private final ProgressIndicator progressIndicator; /** @@ -2117,28 +2118,18 @@ public class Case { * event that the operation is * cancelled prior to completion. */ - private void openFileSystems(List images) throws InterruptedException { + private void openFileSystems(List images) throws TskCoreException, InterruptedException { byte[] tempBuff = new byte[512]; - + for (Image image : images) { String imageStr = image.getName(); progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr)); - Collection fileSystems = this.tskCase.getFileSystems(image); + Collection fileSystems = this.tskCase.getImageFileSystems(image); checkIfCancelled(); for (FileSystem fileSystem : fileSystems) { - try { - fileSystem.read(tempBuff, 0, 512); - } catch (TskCoreException ex) { - String fileSysStr = fileSystem.getName(); - - logger.log( - Level.WARNING, - String.format("Could not open filesystem: %s in image: %s for case: %s.", fileSysStr, imageStr, caseName), - ex); - } - + fileSystem.read(tempBuff, 0, 512); checkIfCancelled(); } @@ -2153,6 +2144,14 @@ public class Case { if (images == null) { return; } + + if (images.size() > MAX_IMAGE_THRESHOLD) { + // If we have a large number of images, don't try to preload anything + logger.log( + Level.INFO, + String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size())); + return; + } checkIfCancelled(); openFileSystems(images); @@ -2160,6 +2159,9 @@ public class Case { logger.log( Level.INFO, String.format("Background operation opening all file systems in %s has been cancelled.", caseName)); + } catch (Exception ex) { + // Exception firewall + logger.log(Level.WARNING, "Error while opening file systems in background", ex); } } @@ -2516,7 +2518,7 @@ public class Case { "# {0} - service name", "Case.serviceOpenCaseResourcesProgressIndicator.cancellingMessage=Cancelling opening case resources by {0}...", "# {0} - service name", "Case.servicesException.notificationTitle={0} Error" }) - private void openAppServiceCaseResources(ProgressIndicator progressIndicator) throws CaseActionException { + private void openAppServiceCaseResources(ProgressIndicator progressIndicator, boolean isNewCase) throws CaseActionException { /* * Each service gets its own independently cancellable/interruptible * task, running in a named thread managed by an executor service, with @@ -2548,7 +2550,7 @@ public class Case { appServiceProgressIndicator = new LoggingProgressIndicator(); } appServiceProgressIndicator.start(Bundle.Case_progressMessage_preparing()); - AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator); + AutopsyService.CaseContext context = new AutopsyService.CaseContext(this, appServiceProgressIndicator, isNewCase); String threadNameSuffix = service.getServiceName().replaceAll("[ ]", "-"); //NON-NLS threadNameSuffix = threadNameSuffix.toLowerCase(); TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_RESOURCES_THREAD_NAME, threadNameSuffix)); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index 2a13b81c10..65d8d0372e 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -55,11 +55,11 @@ final class TagNameDefinition implements Comparable { private static final String TAG_SETTING_VERSION_KEY = "CustomTagNameVersion"; private static final int TAG_SETTINGS_VERSION = 1; - private static final String CATEGORY_ONE_NAME = "CAT-1: Child Exploitation (Illegal)"; - private static final String CATEGORY_TWO_NAME = "CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"; - private static final String CATEGORY_THREE_NAME = "CAT-3: CGI/Animation (Child Exploitive)"; - private static final String CATEGORY_FOUR_NAME = "CAT-4: Exemplar/Comparison (Internal Use Only)"; - private static final String CATEGORY_FIVE_NAME = "CAT-5: Non-pertinent"; + private static final String CATEGORY_ONE_NAME = "Child Exploitation (Illegal)"; + private static final String CATEGORY_TWO_NAME = "Child Exploitation (Non-Illegal/Age Difficult)"; + private static final String CATEGORY_THREE_NAME = "CGI/Animation (Child Exploitive)"; + private static final String CATEGORY_FOUR_NAME = "Exemplar/Comparison (Internal Use Only)"; + private static final String CATEGORY_FIVE_NAME = "Non-pertinent"; private final String displayName; private final String description; @@ -119,6 +119,33 @@ final class TagNameDefinition implements Comparable { return strList; } + /** + * Returns the bookmark tag display string. + * + * @return + */ + static String getBookmarkDisplayString() { + return Bundle.TagNameDefinition_predefTagNames_bookmark_text(); + } + + /** + * Returns the Follow Up tag display string. + * + * @return + */ + static String getFollowUpDisplayString() { + return Bundle.TagNameDefinition_predefTagNames_followUp_text(); + } + + /** + * Returns the Notable tag display string. + * + * @return + */ + static String getNotableDisplayString() { + return Bundle.TagNameDefinition_predefTagNames_notableItem_text(); + } + /** * Gets the display name for the tag name. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 49388d619e..565d761b47 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -55,7 +55,7 @@ public class TagsManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); private final SleuthkitCase caseDb; - static String DEFAULT_TAG_SET_NAME = "Project VIC (United States)"; + private static String DEFAULT_TAG_SET_NAME = "Project VIC"; static { @@ -146,6 +146,12 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * Gets the set of display names of notable (TskData.FileKnown.BAD) tag types. + * If a case is not open the list will only include only the user defined + * custom tags. Otherwise the list will include all notable tags. + * @return + */ public static List getNotableTagDisplayNames() { List tagDisplayNames = new ArrayList<>(); for (TagNameDefinition tagDef : TagNameDefinition.getTagNameDefinitions()) { @@ -153,6 +159,22 @@ public class TagsManager implements Closeable { tagDisplayNames.add(tagDef.getDisplayName()); } } + + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + for (TagName tagName : tagsManager.getAllTagNames()) { + if(tagName.getKnownStatus() == TskData.FileKnown.BAD && + !tagDisplayNames.contains(tagName.getDisplayName())) { + tagDisplayNames.add(tagName.getDisplayName()); + } + } + } catch (NoCurrentCaseException ignored) { + /* + * No current case, nothing more to add to the set. + */ + } catch(TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get list of TagNames from TagsManager.", ex); + } return tagDisplayNames; } @@ -162,7 +184,62 @@ public class TagsManager implements Closeable { * @return list of predefined tag names */ public static List getStandardTagNames() { - return TagNameDefinition.getStandardTagNames(); + List tagList = new ArrayList<>(); + + for (TagNameDefinition tagNameDef : TagNameDefinition.getStandardTagNameDefinitions()) { + tagList.add(tagNameDef.getDisplayName()); + } + + try { + List tagSetList = Case.getCurrentCaseThrows().getSleuthkitCase().getTaggingManager().getTagSets(); + for (TagSet tagSet : tagSetList) { + if (tagSet.getName().equals(DEFAULT_TAG_SET_NAME)) { + for (TagName tagName : tagSet.getTagNames()) { + tagList.add(tagName.getDisplayName()); + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get Project VIC tags from the database.", ex); + } + + return tagList; + } + + /** + * Returns the name of the Category TagSet. + * + * @return Name of category TagSet. + */ + public static String getCategoryTagSetName() { + return DEFAULT_TAG_SET_NAME; + } + + /** + * Returns the bookmark tag display string. + * + * @return + */ + public static String getBookmarkDisplayString() { + return TagNameDefinition.getBookmarkDisplayString(); + } + + /** + * Returns the Follow Up tag display string. + * + * @return + */ + public static String getFollowUpDisplayString() { + return TagNameDefinition.getFollowUpDisplayString(); + } + + /** + * Returns the Notable tag display string. + * + * @return + */ + public static String getNotableDisplayString() { + return TagNameDefinition.getNotableDisplayString(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index b71606594f..a4ce71cb36 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -26,6 +26,7 @@ import org.sleuthkit.datamodel.TskData; 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; /** * Main interface for interacting with the database @@ -553,6 +554,22 @@ public interface CentralRepository { */ public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; + + /** + * Retrieves the given file HashHitInfo if the given file hash is in this + * reference set. Only searches the reference_files table. + * + * @param hash The hash to find in a search. + * @param referenceSetID The referenceSetID within which the file should exist. + * + * @return The HashHitInfo if found or null if not found. + * + * @throws CentralRepoException + * @throws CorrelationAttributeNormalizationException + */ + HashHitInfo lookupHash(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; + + /** * Check if the given value is in a specific reference set * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index de88396de7..42174a4c65 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -41,6 +41,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -51,6 +52,7 @@ import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; 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.SleuthkitCase; import org.sleuthkit.datamodel.TskData; @@ -80,7 +82,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { private static final Cache, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder() .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). build(); - + private boolean isCRTypeCacheInitialized; private static final Cache typeCache = CacheBuilder.newBuilder().build(); private static final Cache caseCacheByUUID = CacheBuilder.newBuilder() @@ -103,8 +105,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { static final int DEFAULT_BULK_THRESHHOLD = 1000; private static final int QUERY_STR_MAX_LEN = 1000; - - + /** * Connect to the DB and initialize it. * @@ -135,6 +136,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * Get an ephemeral connection. */ protected abstract Connection getEphemeralConnection(); + /** * Add a new name/value pair in the db_info table. * @@ -221,7 +223,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * Reset the contents of the caches associated with EamDb results. */ public final void clearCaches() { - synchronized(typeCache) { + synchronized (typeCache) { typeCache.invalidateAll(); isCRTypeCacheInitialized = false; } @@ -1015,27 +1017,26 @@ abstract class RdbmsCentralRepo implements CentralRepository { // @@@ We should cache the case and data source IDs in memory String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); boolean artifactHasAnAccount = CentralRepoDbUtil.correlationAttribHasAnAccount(eamArtifact.getCorrelationType()); - + String sql; // _instance table for accounts have an additional account_id column if (artifactHasAnAccount) { - sql = "INSERT INTO " - + tableName - + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id, account_id) " - + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " - + getConflictClause(); - } - else { - sql = "INSERT INTO " - + tableName - + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) " - + "VALUES (?, ?, ?, ?, ?, ?, ?) " - + getConflictClause(); + sql = "INSERT INTO " + + tableName + + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id, account_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + + getConflictClause(); + } else { + sql = "INSERT INTO " + + tableName + + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?) " + + getConflictClause(); } try (Connection conn = connect(); - PreparedStatement preparedStatement = conn.prepareStatement(sql);) { - + PreparedStatement preparedStatement = conn.prepareStatement(sql);) { + if (!eamArtifact.getCorrelationValue().isEmpty()) { preparedStatement.setInt(1, eamArtifact.getCorrelationCase().getID()); preparedStatement.setInt(2, eamArtifact.getCorrelationDataSource().getID()); @@ -1049,7 +1050,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { preparedStatement.setString(6, eamArtifact.getComment()); } preparedStatement.setLong(7, eamArtifact.getFileObjectId()); - + // set in the accountId only for artifacts that represent accounts if (artifactHasAnAccount) { if (eamArtifact.getAccountId() >= 0) { @@ -1064,21 +1065,21 @@ abstract class RdbmsCentralRepo implements CentralRepository { } catch (SQLException ex) { throw new CentralRepoException("Error inserting new artifact into artifacts table.", ex); // NON-NLS - } + } } - /** - * Gets the Central Repo account for the given account type and account ID. - * Create a new account first, if one doesn't exist - * - * @param accountType account type - * @param accountUniqueID unique account identifier - * - * @return A matching account, either existing or newly created. - * - * @throws TskCoreException exception thrown if a critical error occurs - * within TSK core - */ + /** + * Gets the Central Repo account for the given account type and account ID. + * Create a new account first, if one doesn't exist + * + * @param accountType account type + * @param accountUniqueID unique account identifier + * + * @return A matching account, either existing or newly created. + * + * @throws TskCoreException exception thrown if a critical error occurs + * within TSK core + */ @Override public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { // Get the account fom the accounts table @@ -1104,56 +1105,53 @@ abstract class RdbmsCentralRepo implements CentralRepository { return account; } - @Override public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException { try { return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName)); } catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) { - throw new CentralRepoException("Error looking up CR account type in cache.", ex); - } + throw new CentralRepoException("Error looking up CR account type in cache.", ex); + } } - - @Override public Collection getAllAccountTypes() throws CentralRepoException { - - Collection accountTypes = new ArrayList<>(); - - String sql = "SELECT * FROM account_types"; - try ( Connection conn = connect(); - PreparedStatement preparedStatement = conn.prepareStatement(sql);) { - + Collection accountTypes = new ArrayList<>(); + + String sql = "SELECT * FROM account_types"; + try (Connection conn = connect(); + PreparedStatement preparedStatement = conn.prepareStatement(sql);) { + try (ResultSet resultSet = preparedStatement.executeQuery();) { while (resultSet.next()) { Account.Type acctType = new Account.Type(resultSet.getString("type_name"), resultSet.getString("display_name")); CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id")); - + accountTypes.add(crAccountType); - } + } } } catch (SQLException ex) { throw new CentralRepoException("Error getting account types from central repository.", ex); // NON-NLS - } + } return accountTypes; } - + /** * Gets the CR account type for the specified type name. - * + * * @param accountTypeName account type name to look for + * * @return CR account type - * - * @throws CentralRepoException + * + * @throws CentralRepoException */ private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { String sql = "SELECT * FROM account_types WHERE type_name = ?"; - try ( Connection conn = connect(); - PreparedStatement preparedStatement = conn.prepareStatement(sql);) { + try (Connection conn = connect(); + PreparedStatement preparedStatement = conn.prepareStatement(sql);) { preparedStatement.setString(1, accountTypeName); try (ResultSet resultSet = preparedStatement.executeQuery();) { @@ -1168,24 +1166,27 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } catch (SQLException ex) { throw new CentralRepoException("Error getting correlation type by id.", ex); // NON-NLS - } + } } - + /** - * Get the CR account with the given account type and the unique account identifier. - * Looks in the cache first. - * If not found in cache, reads from the database and saves in cache. - * - * Returns null if the account is not found in the cache and not in the database. - * - * @param crAccountType account type to look for + * Get the CR account with the given account type and the unique account + * identifier. Looks in the cache first. If not found in cache, reads from + * the database and saves in cache. + * + * Returns null if the account is not found in the cache and not in the + * database. + * + * @param crAccountType account type to look for * @param accountUniqueID unique account id - * @return CentralRepoAccount for the give type/id. May return null if not found. - * - * @throws CentralRepoException + * + * @return CentralRepoAccount for the give type/id. May return null if not + * found. + * + * @throws CentralRepoException */ private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { - + CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID)); if (crAccount == null) { crAccount = getCRAccountFromDb(crAccountType, accountUniqueID); @@ -1193,30 +1194,29 @@ abstract class RdbmsCentralRepo implements CentralRepository { accountsCache.put(Pair.of(crAccountType, accountUniqueID), crAccount); } } - + return crAccount; } - - + /** - * Get the Account with the given account type and account identifier, - * from the database. + * Get the Account with the given account type and account identifier, from + * the database. * - * @param accountType account type + * @param accountType account type * @param accountUniqueID unique account identifier * * @return Account, returns NULL is no matching account found * * @throws TskCoreException exception thrown if a critical error occurs - * within TSK core + * within TSK core */ private CentralRepoAccount getCRAccountFromDb(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { CentralRepoAccount account = null; String sql = "SELECT * FROM accounts WHERE account_type_id = ? AND account_unique_identifier = ?"; - try ( Connection connection = connect(); - PreparedStatement preparedStatement = connection.prepareStatement(sql);) { + try (Connection connection = connect(); + PreparedStatement preparedStatement = connection.prepareStatement(sql);) { preparedStatement.setInt(1, crAccountType.getAccountTypeId()); preparedStatement.setString(2, accountUniqueID); @@ -1232,8 +1232,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { return account; } - - + private void checkAddArtifactInstanceNulls(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { if (eamArtifact == null) { throw new CentralRepoException("CorrelationAttribute is null"); @@ -1574,7 +1573,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { synchronized (bulkArtifacts) { if (bulkArtifacts.get(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType())) == null) { - bulkArtifacts.put(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()), new ArrayList<>()); + bulkArtifacts.put(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()), new ArrayList<>()); } bulkArtifacts.get(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType())).add(eamArtifact); bulkArtifactsCount++; @@ -2001,8 +2000,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { String sqlUpdate = "UPDATE " + tableName - + " SET known_status=?, comment=? " - + "WHERE id=?"; + + " SET known_status=? WHERE id=?"; try { preparedQuery = conn.prepareStatement(sqlQuery); @@ -2016,15 +2014,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { preparedUpdate = conn.prepareStatement(sqlUpdate); preparedUpdate.setByte(1, knownStatus.getFileKnownValue()); - // NOTE: if the user tags the same instance as BAD multiple times, - // the comment from the most recent tagging is the one that will - // prevail in the DB. - if ("".equals(eamArtifact.getComment())) { - preparedUpdate.setNull(2, Types.INTEGER); - } else { - preparedUpdate.setString(2, eamArtifact.getComment()); - } - preparedUpdate.setInt(3, instance_id); + preparedUpdate.setInt(2, instance_id); preparedUpdate.executeUpdate(); } else { @@ -2307,6 +2297,42 @@ abstract class RdbmsCentralRepo implements CentralRepository { return isValueInReferenceSet(hash, referenceSetID, CorrelationAttributeInstance.FILES_TYPE_ID); } + @Override + public HashHitInfo lookupHash(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException { + int correlationTypeID = CorrelationAttributeInstance.FILES_TYPE_ID; + String normalizeValued = CorrelationAttributeNormalizer.normalize(this.getCorrelationTypeById(correlationTypeID), hash); + + Connection conn = connect(); + + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + String sql = "SELECT value,comment FROM %s WHERE value=? AND reference_set_id=?"; + + String fileTableName = CentralRepoDbUtil.correlationTypeToReferenceTableName(getCorrelationTypeById(correlationTypeID)); + + try { + preparedStatement = conn.prepareStatement(String.format(sql, fileTableName)); + preparedStatement.setString(1, normalizeValued); + preparedStatement.setInt(2, referenceSetID); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + String comment = resultSet.getString("comment"); + String hashFound = resultSet.getString("value"); + HashHitInfo found = new HashHitInfo(hashFound, "", ""); + found.addComment(comment); + return found; + } else { + return null; + } + } catch (SQLException ex) { + throw new CentralRepoException("Error determining if value (" + normalizeValued + ") is in reference set " + referenceSetID, ex); // NON-NLS + } finally { + CentralRepoDbUtil.closeStatement(preparedStatement); + CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeConnection(conn); + } + } + /** * Check if the given value is in a specific reference set * @@ -2476,7 +2502,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { CentralRepoDbUtil.closeConnection(conn); } } - + /** * Process a SELECT query * @@ -2514,7 +2540,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } - } + } @Override public void executeInsertSQL(String insertClause) throws CentralRepoException { @@ -2557,7 +2583,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", selectSQL, ex.getMessage()), ex); } } - + @Override public CentralRepoOrganization newOrganization(CentralRepoOrganization eamOrg) throws CentralRepoException { if (eamOrg == null) { @@ -2697,15 +2723,16 @@ abstract class RdbmsCentralRepo implements CentralRepository { } /** - * Queries the examiner table for the given user name. - * Adds a row if the user is not found in the examiner table. + * Queries the examiner table for the given user name. Adds a row if the + * user is not found in the examiner table. * * @param examinerLoginName user name to look for. + * * @return CentralRepoExaminer for the given user name. + * * @throws CentralRepoException If there is an error in looking up or - * inserting the user in the examiners table. + * inserting the user in the examiners table. */ - @Override public CentralRepoExaminer getOrInsertExaminer(String examinerLoginName) throws CentralRepoException { @@ -2730,7 +2757,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { default: throw new CentralRepoException(String.format("Cannot add examiner to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS } - statement.execute(insertSQL); + statement.execute(insertSQL); // Query the table again to get the row for the user try (ResultSet resultSet2 = statement.executeQuery(querySQL)) { @@ -2750,7 +2777,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName, ex); } } - + /** * Update an existing organization. * @@ -3145,7 +3172,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { typeId = newCorrelationTypeKnownId(newType); } - synchronized(typeCache) { + synchronized (typeCache) { typeCache.put(newType.getId(), newType); } return typeId; @@ -3362,7 +3389,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { preparedStatement.setInt(4, aType.isEnabled() ? 1 : 0); preparedStatement.setInt(5, aType.getId()); preparedStatement.executeUpdate(); - synchronized(typeCache) { + synchronized (typeCache) { typeCache.put(aType.getId(), aType); } } catch (SQLException ex) { @@ -3386,7 +3413,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { @Override public CorrelationAttributeInstance.Type getCorrelationTypeById(int typeId) throws CentralRepoException { try { - synchronized(typeCache) { + synchronized (typeCache) { return typeCache.get(typeId, () -> getCorrelationTypeByIdFromCr(typeId)); } } catch (CacheLoader.InvalidCacheLoadException ignored) { @@ -3397,7 +3424,6 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } - /** * Get the EamArtifact.Type that has the given Type.Id from the central repo * @@ -3436,24 +3462,25 @@ abstract class RdbmsCentralRepo implements CentralRepository { } /** - * Reads the correlation types from the database and loads them up in the cache. - * + * Reads the correlation types from the database and loads them up in the + * cache. + * * @throws CentralRepoException If there is an error. */ private void getCorrelationTypesFromCr() throws CentralRepoException { - + // clear out the cache - synchronized(typeCache) { + synchronized (typeCache) { typeCache.invalidateAll(); isCRTypeCacheInitialized = false; } - - String sql = "SELECT * FROM correlation_types"; - try ( Connection conn = connect(); - PreparedStatement preparedStatement = conn.prepareStatement(sql); - ResultSet resultSet = preparedStatement.executeQuery();) { - synchronized(typeCache) { + String sql = "SELECT * FROM correlation_types"; + try (Connection conn = connect(); + PreparedStatement preparedStatement = conn.prepareStatement(sql); + ResultSet resultSet = preparedStatement.executeQuery();) { + + synchronized (typeCache) { while (resultSet.next()) { CorrelationAttributeInstance.Type aType = getCorrelationTypeFromResultSet(resultSet); typeCache.put(aType.getId(), aType); @@ -3462,9 +3489,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } catch (SQLException ex) { throw new CentralRepoException("Error getting correlation types.", ex); // NON-NLS - } + } } - + /** * Convert a ResultSet to a EamCase object * @@ -3622,13 +3649,13 @@ abstract class RdbmsCentralRepo implements CentralRepository { case POSTGRESQL: return "INSERT " + sql + " ON CONFLICT DO NOTHING"; //NON-NLS case SQLITE: - return "INSERT OR IGNORE " + sql; - + return "INSERT OR IGNORE " + sql; + default: throw new CentralRepoException("Unknown Central Repo DB platform" + CentralRepoDbManager.getSavedDbChoice().getDbPlatform()); } } - + /** * Determine if a specific column already exists in a specific table * @@ -3741,7 +3768,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { */ if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) { final String addIntegerColumnTemplate = "ALTER TABLE %s ADD COLUMN %s INTEGER;"; //NON-NLS - + final String addSsidTableTemplate = RdbmsCentralRepoFactory.getCreateArtifactInstancesTableTemplate(selectedPlatform); final String addCaseIdIndexTemplate = RdbmsCentralRepoFactory.getAddCaseIdIndexTemplate(); final String addDataSourceIdIndexTemplate = RdbmsCentralRepoFactory.getAddDataSourceIdIndexTemplate(); @@ -3761,7 +3788,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { default: throw new CentralRepoException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); } - + final String dataSourcesTableName = "data_sources"; final String dataSourceObjectIdColumnName = "datasource_obj_id"; if (!doesColumnExist(conn, dataSourcesTableName, dataSourceObjectIdColumnName)) { @@ -3926,7 +3953,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { // Upgrade to 1.4 (new CentralRepoDbUpgrader13To14()).upgradeSchema(dbSchemaVersion, conn); - + // Upgrade to 1.5 (new CentralRepoDbUpgrader14To15()).upgradeSchema(dbSchemaVersion, conn); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index d8bd1c43c3..94a22492c1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -27,7 +27,6 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; -import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -55,6 +54,8 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.Tag; +import org.sleuthkit.autopsy.events.AutopsyEvent; /** * Listen for case events and update entries in the Central Repository database @@ -66,12 +67,12 @@ final class CaseEventListener implements PropertyChangeListener { private static final Logger LOGGER = Logger.getLogger(CaseEventListener.class.getName()); private final ExecutorService jobProcessingExecutor; private static final String CASE_EVENT_THREAD_NAME = "Case-Event-Listener-%d"; - + private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of( Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED, Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED, Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, - Case.Events.DATA_SOURCE_ADDED, + Case.Events.DATA_SOURCE_ADDED, Case.Events.TAG_DEFINITION_CHANGED, Case.Events.CURRENT_CASE, Case.Events.DATA_SOURCE_NAME_CHANGED); @@ -86,6 +87,10 @@ final class CaseEventListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { + if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { + return; + } + CentralRepository dbManager; try { dbManager = CentralRepository.getInstance(); @@ -93,7 +98,7 @@ final class CaseEventListener implements PropertyChangeListener { LOGGER.log(Level.SEVERE, "Failed to get instance of db manager.", ex); return; } - + // If any changes are made to which event types are handled the change // must also be made to CASE_EVENTS_OF_INTEREST. switch (Case.Events.valueOf(evt.getPropertyName())) { @@ -127,7 +132,7 @@ final class CaseEventListener implements PropertyChangeListener { break; } } - + /* * Add all of our Case Event Listeners to the case. */ @@ -142,6 +147,46 @@ final class CaseEventListener implements PropertyChangeListener { Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this); } + /** + * Returns true if the tag has a notable status. + * + * @param t The tag to use in determination. + * + * @return Whether or not it is a notable tag. + */ + private static boolean isNotableTag(Tag t) { + return (t != null && isNotableTagName(t.getName())); + } + + /** + * Returns true if the tag name has a notable status. + * + * @param t The tag name to use in determination. + * + * @return Whether or not it is a notable tag name. + */ + private static boolean isNotableTagName(TagName t) { + return (t != null && TagsManager.getNotableTagDisplayNames().contains(t.getDisplayName())); + } + + /** + * Searches a list of tags for a tag with a notable status. + * + * @param tags The tags to search. + * + * @return Whether or not the list contains a notable tag. + */ + private static boolean hasNotableTag(List tags) { + if (tags == null) { + return false; + } + + return tags.stream() + .filter(CaseEventListener::isNotableTag) + .findFirst() + .isPresent(); + } + private final class ContentTagTask implements Runnable { private final CentralRepository dbManager; @@ -158,72 +203,98 @@ final class CaseEventListener implements PropertyChangeListener { return; } - AbstractFile af; - TskData.FileKnown knownStatus; - String comment; - if (Case.Events.valueOf(event.getPropertyName()) == Case.Events.CONTENT_TAG_ADDED) { - // For added tags, we want to change the known status to BAD if the - // tag that was just added is in the list of central repo tags. - final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event; - final ContentTag tagAdded = tagAddedEvent.getAddedTag(); + Case.Events curEventType = Case.Events.valueOf(event.getPropertyName()); + if (curEventType == Case.Events.CONTENT_TAG_ADDED && event instanceof ContentTagAddedEvent) { + handleTagAdded((ContentTagAddedEvent) event); + } else if (curEventType == Case.Events.CONTENT_TAG_DELETED && event instanceof ContentTagDeletedEvent) { + handleTagDeleted((ContentTagDeletedEvent) event); + } else { + LOGGER.log(Level.SEVERE, + String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.", + event, curEventType)); + } + } - if (TagsManager.getNotableTagDisplayNames().contains(tagAdded.getName().getDisplayName())) { - if (tagAdded.getContent() instanceof AbstractFile) { - af = (AbstractFile) tagAdded.getContent(); - knownStatus = TskData.FileKnown.BAD; - comment = tagAdded.getComment(); - } else { - LOGGER.log(Level.WARNING, "Error updating non-file object"); - return; - } - } else { - // The added tag isn't flagged as bad in central repo, so do nothing - return; - } - } else { // CONTENT_TAG_DELETED - // For deleted tags, we want to set the file status to UNKNOWN if: - // - The tag that was just removed is notable in central repo - // - There are no remaining tags that are notable - final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) event; - long contentID = tagDeletedEvent.getDeletedTagInfo().getContentID(); - - String tagName = tagDeletedEvent.getDeletedTagInfo().getName().getDisplayName(); - if (!TagsManager.getNotableTagDisplayNames().contains(tagName)) { - // If the tag that got removed isn't on the list of central repo tags, do nothing - return; - } - - try { - // Get the remaining tags on the content object - Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(contentID); - TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); - List tags = tagsManager.getContentTagsByContent(content); - - if (tags.stream() - .map(tag -> tag.getName().getDisplayName()) - .filter(TagsManager.getNotableTagDisplayNames()::contains) - .collect(Collectors.toList()) - .isEmpty()) { - - // There are no more bad tags on the object - if (content instanceof AbstractFile) { - af = (AbstractFile) content; - knownStatus = TskData.FileKnown.UNKNOWN; - comment = ""; - } else { - LOGGER.log(Level.WARNING, "Error updating non-file object"); - return; - } - } else { - // There's still at least one bad tag, so leave the known status as is - return; - } - } catch (TskCoreException | NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Failed to find content", ex); - return; - } + private void handleTagDeleted(ContentTagDeletedEvent evt) { + // ensure tag deleted event has a valid content id + if (evt.getDeletedTagInfo() == null) { + LOGGER.log(Level.SEVERE, "ContentTagDeletedEvent did not have valid content to provide a content id."); + return; } + try { + // obtain content + Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(evt.getDeletedTagInfo().getContentID()); + if (content == null) { + LOGGER.log(Level.WARNING, + String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID())); + return; + } + + // then handle the event + handleTagChange(content); + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error updating non-file object: " + evt.getDeletedTagInfo().getContentID(), ex); + } + } + + private void handleTagAdded(ContentTagAddedEvent evt) { + // ensure tag added event has a valid content id + if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null) { + LOGGER.log(Level.SEVERE, "ContentTagAddedEvent did not have valid content to provide a content id."); + return; + } + + // then handle the event + handleTagChange(evt.getAddedTag().getContent()); + } + + /** + * When a tag is added or deleted, check if there are other notable tags + * for the item. If there are, set known status as notable. If not set + * status as unknown. + * + * @param content The content for the tag that was added or deleted. + */ + private void handleTagChange(Content content) { + AbstractFile af = null; + try { + af = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(content.getId()); + } catch (NoCurrentCaseException | TskCoreException ex) { + Long contentID = (content != null) ? content.getId() : null; + LOGGER.log(Level.WARNING, "Error updating non-file object: " + contentID, ex); + } + + if (af == null) { + return; + } + + try { + // Get the tags on the content object + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + + if (hasNotableTag(tagsManager.getContentTagsByContent(content))) { + // if there is a notable tag on the object, set content known status to bad + setContentKnownStatus(af, TskData.FileKnown.BAD); + } else { + // otherwise, set to unknown + setContentKnownStatus(af, TskData.FileKnown.UNKNOWN); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex); + } + } + + /** + * Sets the known status for the correlation attribute instance for the + * given abstract file. + * + * @param af The abstract file for which to set the correlation + * attribute instance. + * @param knownStatus The new known status for the correlation attribute + * instance. + */ + private void setContentKnownStatus(AbstractFile af, TskData.FileKnown knownStatus) { final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile(af); if (eamArtifact != null) { @@ -234,7 +305,7 @@ final class CaseEventListener implements PropertyChangeListener { LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS } } - } // CONTENT_TAG_ADDED, CONTENT_TAG_DELETED + } } private final class BlackboardTagTask implements Runnable { @@ -253,87 +324,125 @@ final class CaseEventListener implements PropertyChangeListener { return; } - Content content; - BlackboardArtifact bbArtifact; - TskData.FileKnown knownStatus; - String comment; - if (Case.Events.valueOf(event.getPropertyName()) == Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED) { - // For added tags, we want to change the known status to BAD if the - // tag that was just added is in the list of central repo tags. - final BlackBoardArtifactTagAddedEvent tagAddedEvent = (BlackBoardArtifactTagAddedEvent) event; - final BlackboardArtifactTag tagAdded = tagAddedEvent.getAddedTag(); - - if (TagsManager.getNotableTagDisplayNames().contains(tagAdded.getName().getDisplayName())) { - content = tagAdded.getContent(); - bbArtifact = tagAdded.getArtifact(); - knownStatus = TskData.FileKnown.BAD; - comment = tagAdded.getComment(); - } else { - // The added tag isn't flagged as bad in central repo, so do nothing - return; - } - } else { //BLACKBOARD_ARTIFACT_TAG_DELETED - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); - return; - } - // For deleted tags, we want to set the file status to UNKNOWN if: - // - The tag that was just removed is notable in central repo - // - There are no remaining tags that are notable - final BlackBoardArtifactTagDeletedEvent tagDeletedEvent = (BlackBoardArtifactTagDeletedEvent) event; - long contentID = tagDeletedEvent.getDeletedTagInfo().getContentID(); - long artifactID = tagDeletedEvent.getDeletedTagInfo().getArtifactID(); - - String tagName = tagDeletedEvent.getDeletedTagInfo().getName().getDisplayName(); - if (!TagsManager.getNotableTagDisplayNames().contains(tagName)) { - // If the tag that got removed isn't on the list of central repo tags, do nothing - return; - } - - try { - // Get the remaining tags on the artifact - content = openCase.getSleuthkitCase().getContentById(contentID); - bbArtifact = openCase.getSleuthkitCase().getBlackboardArtifact(artifactID); - TagsManager tagsManager = openCase.getServices().getTagsManager(); - List tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact); - - if (tags.stream() - .map(tag -> tag.getName().getDisplayName()) - .filter(TagsManager.getNotableTagDisplayNames()::contains) - .collect(Collectors.toList()) - .isEmpty()) { - - // There are no more bad tags on the object - knownStatus = TskData.FileKnown.UNKNOWN; - comment = ""; - - } else { - // There's still at least one bad tag, so leave the known status as is - return; - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to find content", ex); - return; - } + Case.Events curEventType = Case.Events.valueOf(event.getPropertyName()); + if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED && event instanceof BlackBoardArtifactTagAddedEvent) { + handleTagAdded((BlackBoardArtifactTagAddedEvent) event); + } else if (curEventType == Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED && event instanceof BlackBoardArtifactTagDeletedEvent) { + handleTagDeleted((BlackBoardArtifactTagDeletedEvent) event); + } else { + LOGGER.log(Level.WARNING, + String.format("Received an event %s of type %s and was expecting either CONTENT_TAG_ADDED or CONTENT_TAG_DELETED.", + event, curEventType)); } + } - if ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)) { + private void handleTagDeleted(BlackBoardArtifactTagDeletedEvent evt) { + // ensure tag deleted event has a valid content id + if (evt.getDeletedTagInfo() == null) { + LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagDeletedEvent did not have valid content to provide a content id."); return; } + try { + Case openCase = Case.getCurrentCaseThrows(); + + // obtain content + Content content = openCase.getSleuthkitCase().getContentById(evt.getDeletedTagInfo().getContentID()); + if (content == null) { + LOGGER.log(Level.WARNING, + String.format("Unable to get content for item with content id: %d.", evt.getDeletedTagInfo().getContentID())); + return; + } + + // obtain blackboard artifact + BlackboardArtifact bbArtifact = openCase.getSleuthkitCase().getBlackboardArtifact(evt.getDeletedTagInfo().getArtifactID()); + if (bbArtifact == null) { + LOGGER.log(Level.WARNING, + String.format("Unable to get blackboard artifact for item with artifact id: %d.", evt.getDeletedTagInfo().getArtifactID())); + return; + } + + // then handle the event + handleTagChange(content, bbArtifact); + } catch (NoCurrentCaseException | TskCoreException ex) { + LOGGER.log(Level.WARNING, "Error updating non-file object.", ex); + } + } + + private void handleTagAdded(BlackBoardArtifactTagAddedEvent evt) { + // ensure tag added event has a valid content id + if (evt.getAddedTag() == null || evt.getAddedTag().getContent() == null || evt.getAddedTag().getArtifact() == null) { + LOGGER.log(Level.SEVERE, "BlackBoardArtifactTagAddedEvent did not have valid content to provide a content id."); + return; + } + + // then handle the event + handleTagChange(evt.getAddedTag().getContent(), evt.getAddedTag().getArtifact()); + } + + /** + * When a tag is added or deleted, check if there are other notable tags + * for the item. If there are, set known status as notable. If not set + * status as unknown. + * + * @param content The content for the tag that was added or deleted. + * @param bbArtifact The artifact for the tag that was added or deleted. + */ + private void handleTagChange(Content content, BlackboardArtifact bbArtifact) { + Case openCase; + try { + openCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + return; + } + + try { + if (isKnownFile(content)) { + return; + } + + TagsManager tagsManager = openCase.getServices().getTagsManager(); + List tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbArtifact); + if (hasNotableTag(tags)) { + setArtifactKnownStatus(bbArtifact, TskData.FileKnown.BAD); + } else { + setArtifactKnownStatus(bbArtifact, TskData.FileKnown.UNKNOWN); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to obtain tags manager for case.", ex); + return; + } + } + + /** + * Determines if the content is an abstract file and is a known file. + * + * @param content The content to assess. + * + * @return True if an abstract file and a known file. + */ + private boolean isKnownFile(Content content) { + return ((content instanceof AbstractFile) && (((AbstractFile) content).getKnown() == TskData.FileKnown.KNOWN)); + } + + /** + * Sets the known status of a blackboard artifact in the central + * repository. + * + * @param bbArtifact The blackboard artifact to set known status. + * @param knownStatus The new known status. + */ + private void setArtifactKnownStatus(BlackboardArtifact bbArtifact, TskData.FileKnown knownStatus) { List convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact); for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) { - eamArtifact.setComment(comment); try { dbManager.setAttributeInstanceKnownStatus(eamArtifact, knownStatus); } catch (CentralRepoException ex) { LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database while setting artifact known status.", ex); //NON-NLS } } - } // BLACKBOARD_ARTIFACT_TAG_ADDED, BLACKBOARD_ARTIFACT_TAG_DELETED + } } @@ -434,8 +543,8 @@ final class CaseEventListener implements PropertyChangeListener { //if the file will have no tags with a status which would prevent the current status from being changed if (!hasTagWithConflictingKnownStatus) { Content taggedContent = contentTag.getContent(); - if (taggedContent instanceof AbstractFile) { - final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile((AbstractFile)taggedContent); + if (taggedContent instanceof AbstractFile) { + final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile((AbstractFile) taggedContent); if (eamArtifact != null) { CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus()); } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java index c5c92b11c0..a12675994e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java @@ -20,11 +20,19 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.Function; import java.util.logging.Level; -import org.apache.commons.lang3.StringEscapeUtils; +import java.util.stream.Collectors; +import javax.swing.JLabel; +import javax.swing.text.EditorKit; +import javax.swing.text.html.HTMLEditorKit; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; +import static org.openide.util.NbBundle.Messages; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.Node; import org.openide.util.lookup.ServiceProvider; @@ -32,8 +40,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; @@ -46,26 +52,190 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; /** * Annotations view of file contents. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @ServiceProvider(service = DataContentViewer.class, position = 8) -@NbBundle.Messages({ +@Messages({ "AnnotationsContentViewer.title=Annotations", - "AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content." + "AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.", + "AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:", + "AnnotationsContentViewer.tagEntry.title=Tags", + "AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:", + "AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner:", + "AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:", + "AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment", + "AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments", + "AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments", + "AnnotationsContentViewer.fileHitEntry.setName=Set Name:", + "AnnotationsContentViewer.fileHitEntry.comment=Comment:", + "AnnotationsContentViewer.sourceFile.title=Source File", + "AnnotationsContentViewer.onEmpty=No annotations were found for this particular item." }) public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer { + /** + * Describes a key value pair for an item of type T where the key is the + * field name to display and the value is retrieved from item of type T + * using a provided Function. + * + * @param The item type. + */ + private static class ItemEntry { + + private final String itemName; + private final Function valueRetriever; + + ItemEntry(String itemName, Function valueRetriever) { + this.itemName = itemName; + this.valueRetriever = valueRetriever; + } + + String getItemName() { + return itemName; + } + + Function getValueRetriever() { + return valueRetriever; + } + + String retrieveValue(T object) { + return valueRetriever.apply(object); + } + } + + /** + * Describes a section that will be appended to the annotations view panel. + * + * @param The item type for items to display. + */ + private static class SectionConfig { + + private final String title; + private final List> attributes; + + SectionConfig(String title, List> attributes) { + this.title = title; + this.attributes = attributes; + } + + /** + * @return The title for the section. + */ + String getTitle() { + return title; + } + + /** + * @return Describes key-value pairs on the object to display to the + * user. + */ + List> getAttributes() { + return attributes; + } + } + private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName()); + private static final String EMPTY_HTML = ""; + + private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize(); + + // how big the subheader should be + private static final int SUBHEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 12 / 11; + + // how big the header should be + private static final int HEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 14 / 11; + + // the subsection indent + private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE; + + // spacing occurring after an item + private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE; + private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE * 2; + private static final int DEFAULT_SUBSECTION_SPACING = DEFAULT_FONT_SIZE / 2; + private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2; + + // html stylesheet classnames for components + private static final String MESSAGE_CLASSNAME = "message"; + private static final String SUBSECTION_CLASSNAME = "subsection"; + private static final String SUBHEADER_CLASSNAME = "subheader"; + private static final String SECTION_CLASSNAME = "section"; + private static final String HEADER_CLASSNAME = "header"; + private static final String VERTICAL_TABLE_CLASSNAME = "vertical-table"; + + // additional styling for components + private static final String STYLE_SHEET_RULE + = String.format(" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE) + + String.format(" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ", + SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING) + + String.format(" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", HEADER_CLASSNAME, HEADER_FONT_SIZE) + + String.format(" td { vertical-align: top; font-size:%dpx; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px;} ", DEFAULT_FONT_SIZE, CELL_SPACING) + + String.format(" th { vertical-align: top; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px} ", DEFAULT_FONT_SIZE, CELL_SPACING) + + String.format(" .%s { margin: %dpx 0px; padding-left: %dpx; } ", SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD) + + String.format(" .%s { margin-bottom: %dpx; } ", SECTION_CLASSNAME, DEFAULT_SECTION_SPACING); + + // describing table values for a tag + private static final List> TAG_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(), + (tag) -> (tag.getName() != null) ? tag.getName().getDisplayName() : null), + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), (tag) -> tag.getUserName()), + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), (tag) -> tag.getComment()) + ); + + private static final SectionConfig TAG_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_tagEntry_title(), TAG_ENTRIES); + + // file set attributes and table configurations + private static final List> FILESET_HIT_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_setName(), + (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)), + new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_comment(), + (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)) + ); + + private static final SectionConfig INTERESTING_FILE_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_interestingFileHitTitle(), FILESET_HIT_ENTRIES); + + private static final SectionConfig HASHSET_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_hashSetHitTitle(), FILESET_HIT_ENTRIES); + + private static final SectionConfig ARTIFACT_COMMENT_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_artifactCommentTitle(), FILESET_HIT_ENTRIES); + + // central repository attributes and table configuration + private static final List> CR_COMMENTS_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(), + cai -> (cai.getCorrelationCase() != null) ? cai.getCorrelationCase().getDisplayName() : null), + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), cai -> cai.getComment()), + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), cai -> cai.getFilePath()) + ); + + private static final SectionConfig CR_COMMENTS_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_centralRepositoryEntry_title(), CR_COMMENTS_ENTRIES); + /** * Creates an instance of AnnotationsContentViewer. */ public AnnotationsContentViewer() { initComponents(); Utilities.configureTextPaneAsHtml(jTextPane1); + // get html editor kit and apply additional style rules + EditorKit editorKit = jTextPane1.getEditorKit(); + if (editorKit instanceof HTMLEditorKit) { + HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit; + htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE); + } } @Override @@ -75,7 +245,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data return; } - StringBuilder html = new StringBuilder(); + Document html = Jsoup.parse(EMPTY_HTML); + Element body = html.getElementsByTag("body").first(); BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); Content sourceFile = null; @@ -101,279 +272,439 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data artifact.getDisplayName(), artifact.getArtifactID()), ex); } + boolean somethingWasRendered = false; if (artifact != null) { - populateTagData(html, artifact, sourceFile); + somethingWasRendered = renderArtifact(body, artifact, sourceFile); } else { - populateTagData(html, sourceFile); + somethingWasRendered = renderContent(body, sourceFile, false); } - if (sourceFile instanceof AbstractFile) { - populateCentralRepositoryData(html, artifact, (AbstractFile) sourceFile); + if (!somethingWasRendered) { + appendMessage(body, Bundle.AnnotationsContentViewer_onEmpty()); } - setText(html.toString()); + jTextPane1.setText(html.html()); jTextPane1.setCaretPosition(0); } /** - * Populate the "Selected Item" sections with tag data for the supplied - * content. + * Renders annotations for an artifact. * - * @param html The HTML text to update. - * @param content Selected content. + * @param parent The html element to render content int. + * @param bba The blackboard artifact to render. + * @param sourceContent The content from which the blackboard artifact + * comes. + * + * @return If any content was actually rendered. */ - private void populateTagData(StringBuilder html, Content content) { + private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) { + boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false); + + if (CentralRepository.isEnabled()) { + List centralRepoComments = getCentralRepositoryData(bba); + boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false); + contentRendered = contentRendered || crRendered; + } + + // if artifact is a hashset hit or interesting file and has a non-blank comment + if ((ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == bba.getArtifactTypeID() + || ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID()) + && (hasTskComment(bba))) { + + boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false); + contentRendered = contentRendered || filesetRendered; + } + + Element sourceFileSection = appendSection(parent, Bundle.AnnotationsContentViewer_sourceFile_title()); + boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent, true); + + if (!sourceFileRendered) { + sourceFileSection.remove(); + } + + return contentRendered || sourceFileRendered; + } + + /** + * Renders annotations for a content item. + * + * @param parent The parent within which to render. + * @param sourceContent The content for which annotations will be gathered. + * @param isSubheader True if this section should be rendered as a + * subheader as opposed to a top-level header. + * + * @return If any content was actually rendered. + */ + private static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader) { + boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader); + + if (sourceContent instanceof AbstractFile) { + AbstractFile sourceFile = (AbstractFile) sourceContent; + + if (CentralRepository.isEnabled()) { + List centralRepoComments = getCentralRepositoryData(sourceFile); + boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader); + contentRendered = contentRendered || crRendered; + } + + boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG, + getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_HASHSET_HIT), + isSubheader); + + boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG, + getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT), + isSubheader); + + contentRendered = contentRendered || hashsetRendered || interestingFileRendered; + } + return contentRendered; + } + + /** + * Retrieves tags associated with a content item. + * + * @param sourceContent The content for which to gather content. + * + * @return The Tags associated with this item. + */ + private static List getTags(Content sourceContent) { try { SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - startSection(html, "Selected Item"); - List fileTagsList = tskCase.getContentTagsByContent(content); - if (fileTagsList.isEmpty()) { - addMessage(html, "There are no tags for the selected content."); - } else { - for (ContentTag tag : fileTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); + return tskCase.getContentTagsByContent(sourceContent); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS } + return new ArrayList<>(); } /** - * Populate the "Selected Item" and "Source File" sections with tag data for - * a supplied artifact. + * Retrieves tags for blackboard artifact tags. * - * @param html The HTML text to update. - * @param artifact A selected artifact. - * @param sourceFile The source content of the selected artifact. + * @param bba The blackboard artifact for which to retrieve tags. + * + * @return The found tags. */ - private void populateTagData(StringBuilder html, BlackboardArtifact artifact, Content sourceFile) { + private static List getTags(BlackboardArtifact bba) { try { SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - startSection(html, "Selected Item"); - List artifactTagsList = tskCase.getBlackboardArtifactTagsByArtifact(artifact); - if (artifactTagsList.isEmpty()) { - addMessage(html, "There are no tags for the selected artifact."); - } else { - for (BlackboardArtifactTag tag : artifactTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); - - if (sourceFile != null) { - startSection(html, "Source File"); - List fileTagsList = tskCase.getContentTagsByContent(sourceFile); - if (fileTagsList.isEmpty()) { - addMessage(html, "There are no tags for the source content."); - } else { - for (ContentTag tag : fileTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); - } + return tskCase.getBlackboardArtifactTagsByArtifact(bba); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS } + return new ArrayList<>(); } /** - * Populate the "Central Repository Comments" section with data. + * Retrieves the blackboard artifacts for a source file matching a certain + * type that have a non-blank TSK_COMMENT. + * + * @param sourceFile The source file for which to fetch artifacts. + * @param type The type of blackboard artifact to fetch. + * + * @return The artifacts found matching this type. + */ + private static List getFileSetHits(AbstractFile sourceFile, ARTIFACT_TYPE type) { + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + return tskCase.getBlackboardArtifacts(type, sourceFile.getId()).stream() + .filter((bba) -> hasTskComment(bba)) + .collect(Collectors.toList()); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception while getting file set hits from the case database.", ex); //NON-NLS + } + return new ArrayList<>(); + } + + /** + * Returns true if the artifact contains a non-blank TSK_COMMENT attribute. + * + * @param artifact The artifact to check. + * + * @return True if it has a non-blank TSK_COMMENT. + */ + private static boolean hasTskComment(BlackboardArtifact artifact) { + return StringUtils.isNotBlank(tryGetAttribute(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)); + } + + /** + * Attempts to retrieve the attribute of a particular type from a blackboard + * artifact. + * + * @param artifact The artifact from which to retrieve the information. + * @param attributeType The attribute type to retrieve from the artifact. + * + * @return The string value of the attribute or null if not found. + */ + private static String tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + if (artifact == null) { + return null; + } + + BlackboardAttribute attr = null; + try { + attr = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to fetch attribute of type %s for artifact %s", attributeType, artifact), ex); + } + + if (attr == null) { + return null; + } + + return attr.getValueString(); + } + + /** + * Gets the "Central Repository Comments" section with data for the + * blackboard artifact. + * + * @param artifact The selected artifact. + * + * @return The Correlation Attribute Instances associated with the artifact + * that have comments. + */ + private static List getCentralRepositoryData(BlackboardArtifact artifact) { + if (artifact == null) { + return new ArrayList<>(); + } + + List> lookupKeys = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact) + .stream() + .map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue())) + .collect(Collectors.toList()); + + return getCorrelationAttributeComments(lookupKeys); + } + + /** + * Gets the "Central Repository Comments" section with data. * - * @param html The HTML text to update. - * @param artifact A selected artifact (can be null). * @param sourceFile A selected file, or a source file of the selected * artifact. + * + * @return The Correlation Attribute Instances associated with the + * sourcefile that have comments. */ - private void populateCentralRepositoryData(StringBuilder html, BlackboardArtifact artifact, AbstractFile sourceFile) { - if (CentralRepository.isEnabled()) { - startSection(html, "Central Repository Comments"); - List instancesList = new ArrayList<>(); - if (artifact != null) { - instancesList.addAll(CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact)); - } - try { - List artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); - String md5 = sourceFile.getMd5Hash(); - if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) { - for (CorrelationAttributeInstance.Type attributeType : artifactTypes) { - if (attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) { - CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCase()); - instancesList.add(new CorrelationAttributeInstance( - attributeType, - md5, - correlationCase, - CorrelationDataSource.fromTSKDataSource(correlationCase, sourceFile.getDataSource()), - sourceFile.getParentPath() + sourceFile.getName(), - "", - sourceFile.getKnown(), - sourceFile.getId())); - break; - } - } - } - - boolean commentDataFound = false; - - for (CorrelationAttributeInstance instance : instancesList) { - List correlatedInstancesList - = CentralRepository.getInstance().getArtifactInstancesByTypeValue(instance.getCorrelationType(), instance.getCorrelationValue()); - for (CorrelationAttributeInstance correlatedInstance : correlatedInstancesList) { - if (correlatedInstance.getComment() != null && correlatedInstance.getComment().isEmpty() == false) { - commentDataFound = true; - addCentralRepositoryEntry(html, correlatedInstance); - } - } - } - - if (commentDataFound == false) { - addMessage(html, "There is no comment data for the selected content in the Central Repository."); - } - } catch (CentralRepoException | TskCoreException ex) { - logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.SEVERE, "Error normalizing instance from Central Repository database.", ex); // NON-NLS - } - endSection(html); + private static List getCentralRepositoryData(AbstractFile sourceFile) { + if (sourceFile == null || StringUtils.isEmpty(sourceFile.getMd5Hash())) { + return new ArrayList<>(); } + + List artifactTypes = null; + try { + artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS + } + + if (artifactTypes == null || artifactTypes.isEmpty()) { + return new ArrayList<>(); + } + + String md5 = sourceFile.getMd5Hash(); + + // get key lookups for a file attribute types and the md5 hash + List> lookupKeys = artifactTypes.stream() + .filter((attributeType) -> attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) + .map((attributeType) -> Pair.of(attributeType, md5)) + .collect(Collectors.toList()); + + return getCorrelationAttributeComments(lookupKeys); } /** - * Set the text of the text panel. + * Given a type and a value for that type, does a lookup in the Central + * Repository for matching values that have comments. * - * @param text The text to set to the text panel. + * @param lookupKeys The type and value to lookup. + * + * @return The found correlation attribute instances. */ - private void setText(String text) { - jTextPane1.setText("" + text + ""); //NON-NLS + private static List getCorrelationAttributeComments(List> lookupKeys) { + List instancesToRet = new ArrayList<>(); + + try { + // use lookup instances to find the actual correlation attributes for the items selected + for (Pair typeVal : lookupKeys) { + instancesToRet.addAll(CentralRepository.getInstance() + .getArtifactInstancesByTypeValue(typeVal.getKey(), typeVal.getValue()) + .stream() + // for each one found, if it has a comment, return + .filter((cai) -> StringUtils.isNotBlank(cai.getComment())) + .collect(Collectors.toList())); + } + + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.SEVERE, "Error normalizing instance from Central Repository database.", ex); // NON-NLS + } + + return instancesToRet; } /** - * Start a new data section. + * Append entries to the parent element in the annotations viewer. Entries + * will be formatted as a table in the format specified in the + * SectionConfig. * - * @param html The HTML text to add the section to. - * @param sectionName The name of the section. + * @param parent The parent element for which the entries will be + * attached. + * @param config The display configuration for this entry type (i.e. + * table type, name, if data is not present). + * @param items The items to display. + * @param isSubsection Whether or not this should be displayed as a + * subsection. If not displayed as a top-level section. + * + * @return If there was actual content rendered for this set of entries. */ - private void startSection(StringBuilder html, String sectionName) { - html.append("

") - .append(sectionName) - .append("


"); //NON-NLS + private static boolean appendEntries(Element parent, SectionConfig config, List items, + boolean isSubsection) { + if (items == null || items.isEmpty()) { + return false; + } + + Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle()); + appendVerticalEntryTables(sectionDiv, items, config.getAttributes()); + return true; } /** - * Add a message. + * Appends a table where items are displayed in rows of key-value pairs. * - * @param html The HTML text to add the message to. - * @param message The message text. + * @param parent The parent to append the table. + * @param items The items to process into a series of tables. + * @param rowHeaders The keys and the means to process items in order to get + * key-value pairs. + * + * @return The parent element provided as parameter. */ - private void addMessage(StringBuilder html, String message) { - html.append("

") - .append(message) - .append("


"); //NON-NLS + private static Element appendVerticalEntryTables(Element parent, List items, List> rowHeaders) { + boolean isFirst = true; + for (T item : items) { + if (item == null) { + continue; + } + + List> tableData = rowHeaders.stream() + .map(row -> Arrays.asList(row.getItemName(), row.retrieveValue(item))) + .collect(Collectors.toList()); + + Element childTable = appendTable(parent, 2, tableData, null); + childTable.attr("class", VERTICAL_TABLE_CLASSNAME); + + if (isFirst) { + isFirst = false; + } else { + childTable.attr("style", String.format("margin-top: %dpx;", DEFAULT_TABLE_SPACING)); + } + } + + return parent; } /** - * Add a data table containing information about a tag. + * Appends a generic table to the parent element. * - * @param html The HTML text to add the table to. - * @param tag The tag whose information will be used to populate the table. + * @param parent The parent element that will have a table appended + * to it. + * @param columnNumber The number of columns to append. + * @param content The content in content.get(row).get(column) format. + * @param columnHeaders The column headers or null if no column headers + * should be created. + * + * @return The created table. */ - @NbBundle.Messages({ - "AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:", - "AnnotationsContentViewer.tagEntryDataLabel.tagUser=Tag User:", - "AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:" - }) - private void addTagEntry(StringBuilder html, Tag tag) { - startTable(html); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(), tag.getName().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), tag.getUserName()); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), formatHtmlString(tag.getComment())); - endTable(html); + private static Element appendTable(Element parent, int columnNumber, List> content, List columnHeaders) { + Element table = parent.appendElement("table"); + if (columnHeaders != null && !columnHeaders.isEmpty()) { + Element header = table.appendElement("thead"); + appendRow(header, columnHeaders, columnNumber, true); + } + Element tableBody = table.appendElement("tbody"); + + content.forEach((rowData) -> appendRow(tableBody, rowData, columnNumber, false)); + return table; } /** - * Add a data table containing information about a correlation attribute - * instance in the Central Repository. + * Appends a row to the parent element (should be thead or tbody). * - * @param html The HTML text to add the table to. - * @param attributeInstance The attribute instance whose information will be - * used to populate the table. + * @param rowParent The parent table element. + * @param data The data to place in columns within the table. + * @param columnNumber The number of columns to append. + * @param isHeader Whether or not this should have header cells ('th') + * instead of regular cells ('td'). + * + * @return The row created. */ - @NbBundle.Messages({ - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:" - }) - private void addCentralRepositoryEntry(StringBuilder html, CorrelationAttributeInstance attributeInstance) { - startTable(html); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(), attributeInstance.getCorrelationCase().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_type(), attributeInstance.getCorrelationType().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), formatHtmlString(attributeInstance.getComment())); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), attributeInstance.getFilePath()); - endTable(html); + private static Element appendRow(Element rowParent, List data, int columnNumber, boolean isHeader) { + String cellType = isHeader ? "th" : "td"; + Element row = rowParent.appendElement("tr"); + for (int i = 0; i < columnNumber; i++) { + Element cell = row.appendElement(cellType); + if (data != null && i < data.size()) { + cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i)); + } + } + return row; } /** - * Start a data table. + * Appends a new section with a section header to the parent element. * - * @param html The HTML text to add the table to. + * @param parent The element to append this section to. + * @param headerText The text for the section. + * + * @return The div for the new section. */ - private void startTable(StringBuilder html) { - html.append(""); //NON-NLS + private static Element appendSection(Element parent, String headerText) { + Element sectionDiv = parent.appendElement("div"); + sectionDiv.attr("class", SECTION_CLASSNAME); + Element header = sectionDiv.appendElement("h1"); + header.text(headerText); + header.attr("class", HEADER_CLASSNAME); + return sectionDiv; } /** - * Add a data row to a table. + * Appends a new subsection with a subsection header to the parent element. * - * @param html The HTML text to add the row to. - * @param key The key for the left column of the data row. - * @param value The value for the right column of the data row. + * @param parent The element to append this subsection to. + * @param headerText The text for the subsection. + * + * @return The div for the new subsection. */ - private void addRow(StringBuilder html, String key, String value) { - html.append(""); //NON-NLS + private static Element appendSubsection(Element parent, String headerText) { + Element subsectionDiv = parent.appendElement("div"); + subsectionDiv.attr("class", SUBSECTION_CLASSNAME); + Element header = subsectionDiv.appendElement("h2"); + header.text(headerText); + header.attr("class", SUBHEADER_CLASSNAME); + return subsectionDiv; } /** - * End a data table. + * Appends a message to the parent element. This is typically used in the + * event that no data exists for a certain type. * - * @param html The HTML text on which to end a table. + * @param parent The parent element that will have this message appended to + * it. + * @param message The message to append. + * + * @return The paragraph element for the new message. */ - private void endTable(StringBuilder html) { - html.append("
"); //NON-NLS - html.append(key); - html.append(""); //NON-NLS - html.append(value); - html.append("


"); //NON-NLS - } - - /** - * End a data section. - * - * @param html The HTML text on which to end a section. - */ - private void endSection(StringBuilder html) { - html.append("
"); //NON-NLS - } - - /** - * Apply escape sequence to special characters. Line feed and carriage - * return character combinations will be converted to HTML line breaks. - * - * @param text The text to format. - * - * @return The formatted text. - */ - private String formatHtmlString(String text) { - String formattedString = StringEscapeUtils.escapeHtml4(text); - return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "
"); + private static Element appendMessage(Element parent, String message) { + Element messageEl = parent.appendElement("p"); + messageEl.text(message); + messageEl.attr("class", MESSAGE_CLASSNAME); + return messageEl; } /** @@ -462,6 +793,6 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data @Override public void resetComponent() { - setText(""); + jTextPane1.setText(EMPTY_HTML); } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java new file mode 100644 index 0000000000..985ca96879 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ArtifactContentViewer.java @@ -0,0 +1,56 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Common interface implemented by artifact viewers. + * + * An artifact viewer displays the artifact in a custom + * layout panel suitable for the artifact type. + * + */ +public interface ArtifactContentViewer { + + /** + * Called to display the contents of the given artifact. + * + * @param artifact the artifact to display. + */ + void setArtifact(BlackboardArtifact artifact); + + /** + * Returns the panel. + * + * @return display panel. + */ + Component getComponent(); + + /** + * Checks whether the given artifact is supported by the viewer. + * + * @param artifact Artifact to check. + * + * @return True if the artifact can be displayed by the viewer, false otherwise. + */ + boolean isSupported(BlackboardArtifact artifact); + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index ebc9a58e1a..4f1041c642 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -953,4 +953,10 @@ manager.properties.lafError =\ manager.properties.brokenProperty = Broken default property {0} value: {1} -manager.properties.missingProperty = Missing default property {0} value: {1} \ No newline at end of file +manager.properties.missingProperty = Missing default property {0} value: {1} +DefaultArtifactContentViewer.copyMenuItem.text=Copy +DefaultArtifactContentViewer.selectAllMenuItem.text=Select All +ContactArtifactViewer.contactNameLabel.text=Joanna Doe +ContactArtifactViewer.phonesLabel.text=Phone +ContactArtifactViewer.emailsLabel.text=Email +ContactArtifactViewer.othersLabel.text=Other diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index fd9d2d345e..a0526646bb 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -15,13 +15,22 @@ # governing permissions and limitations under the License. # +AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case: AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment: AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path: AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type: +AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment +AnnotationsContentViewer.fileHitEntry.comment=Comment: +AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments +AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments +AnnotationsContentViewer.fileHitEntry.setName=Set Name: +AnnotationsContentViewer.onEmpty=No annotations were found for this particular item. +AnnotationsContentViewer.sourceFile.title=Source File +AnnotationsContentViewer.tagEntry.title=Tags AnnotationsContentViewer.tagEntryDataLabel.comment=Comment: AnnotationsContentViewer.tagEntryDataLabel.tag=Tag: -AnnotationsContentViewer.tagEntryDataLabel.tagUser=Tag User: +AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner: AnnotationsContentViewer.title=Annotations AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content. ApplicationContentViewer.title=Application diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties index f7b7844e60..18fdb5ce50 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle_ja.properties @@ -164,3 +164,5 @@ MediaViewImagePanel.tagsMenu.text_1=\u30bf\u30b0\u30e1\u30cb\u30e5\u30fc SQLiteViewer.readTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} # {0} - tableName SQLiteViewer.selectTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u30ab\u30a6\u30f3\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} +DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e +DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form new file mode 100644 index 0000000000..30a06c53a5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.form @@ -0,0 +1,170 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java new file mode 100644 index 0000000000..922948fae4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/ContactArtifactViewer.java @@ -0,0 +1,320 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * This class displays TSK_CONTACT artifact. + */ +public class ContactArtifactViewer extends javax.swing.JPanel implements ArtifactContentViewer { + + private final static Logger logger = Logger.getLogger(ContactArtifactViewer.class.getName()); + private static final long serialVersionUID = 1L; + + /** + * Creates new form for ContactArtifactViewer + */ + public ContactArtifactViewer() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + namePanel = new javax.swing.JPanel(); + contactNameLabel = new javax.swing.JLabel(); + phonesLabel = new javax.swing.JLabel(); + phoneNumbersPanel = new javax.swing.JPanel(); + emailsLabel = new javax.swing.JLabel(); + emailsPanel = new javax.swing.JPanel(); + othersLabel = new javax.swing.JLabel(); + otherAttrsPanel = new javax.swing.JPanel(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767)); + filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); + + setLayout(new java.awt.GridBagLayout()); + + contactNameLabel.setFont(contactNameLabel.getFont().deriveFont((contactNameLabel.getFont().getStyle() | java.awt.Font.ITALIC) | java.awt.Font.BOLD, contactNameLabel.getFont().getSize()+6)); + org.openide.awt.Mnemonics.setLocalizedText(contactNameLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.contactNameLabel.text")); // NOI18N + + javax.swing.GroupLayout namePanelLayout = new javax.swing.GroupLayout(namePanel); + namePanel.setLayout(namePanelLayout); + namePanelLayout.setHorizontalGroup( + namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(namePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(contactNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 240, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + namePanelLayout.setVerticalGroup( + namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(namePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(contactNameLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + add(namePanel, gridBagConstraints); + + phonesLabel.setFont(phonesLabel.getFont().deriveFont(phonesLabel.getFont().getStyle() | java.awt.Font.BOLD, phonesLabel.getFont().getSize()+2)); + org.openide.awt.Mnemonics.setLocalizedText(phonesLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.phonesLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(phonesLabel, gridBagConstraints); + + phoneNumbersPanel.setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(phoneNumbersPanel, gridBagConstraints); + + emailsLabel.setFont(emailsLabel.getFont().deriveFont(emailsLabel.getFont().getStyle() | java.awt.Font.BOLD, emailsLabel.getFont().getSize()+2)); + org.openide.awt.Mnemonics.setLocalizedText(emailsLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.emailsLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(emailsLabel, gridBagConstraints); + + emailsPanel.setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(emailsPanel, gridBagConstraints); + + othersLabel.setFont(othersLabel.getFont().deriveFont(othersLabel.getFont().getStyle() | java.awt.Font.BOLD, othersLabel.getFont().getSize()+2)); + org.openide.awt.Mnemonics.setLocalizedText(othersLabel, org.openide.util.NbBundle.getMessage(ContactArtifactViewer.class, "ContactArtifactViewer.othersLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 5; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(othersLabel, gridBagConstraints); + + otherAttrsPanel.setLayout(new java.awt.GridBagLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 6; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 19, 0, 0); + add(otherAttrsPanel, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 7; + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weighty = 1.0; + add(filler1, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 4; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 8; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.ipadx = 2; + gridBagConstraints.weightx = 1.0; + add(filler2, gridBagConstraints); + }// //GEN-END:initComponents + + @Override + public void setArtifact(BlackboardArtifact artifact) { + + // wipe the panel clean + this.removeAll(); + initComponents(); + + List phoneNumList = new ArrayList<>(); + List emailList = new ArrayList<>(); + List nameList = new ArrayList<>(); + List otherList = new ArrayList<>(); + + try { + // Get all the attributes and group them by the section panels they go in + for (BlackboardAttribute bba : artifact.getAttributes()) { + if (bba.getAttributeType().getTypeName().startsWith("TSK_PHONE")) { + phoneNumList.add(bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_EMAIL")) { + emailList.add(bba); + } else if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { + nameList.add(bba); + } else { + otherList.add(bba); + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting attributes for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + } + + // update name section + updateNamePanel(nameList); + + // update contact attributes sections + updateSection(phoneNumList, this.phonesLabel, this.phoneNumbersPanel); + updateSection(emailList, this.emailsLabel, this.emailsPanel); + updateSection(otherList, this.othersLabel, this.otherAttrsPanel); + + // repaint + this.revalidate(); + } + + @Override + public Component getComponent() { + // Slap a vertical scrollbar on the panel. + return new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + } + + /** + * Checks if the given artifact is supported by this viewer. + * This viewer supports TSK_CONTACT artifacts. + * + * @param artifact artifact to check. + * @return True if the artifact is supported, false otherwise. + */ + @Override + public boolean isSupported(BlackboardArtifact artifact) { + return artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID(); + } + + /** + * Updates the contact name in the view. + * + * @param attributesList + */ + private void updateNamePanel(List attributesList) { + for (BlackboardAttribute bba : attributesList) { + if (bba.getAttributeType().getTypeName().startsWith("TSK_NAME")) { + contactNameLabel.setText(bba.getDisplayString()); + break; + } + } + + contactNameLabel.revalidate(); + } + + /** + * Updates the view by displaying the given list of attributes in the given section panel. + * + * @param sectionAttributesList list of attributes to display. + * @param sectionLabel section name label. + * @param sectionPanel section panel to display the attributes in. + */ + private void updateSection(List sectionAttributesList, JLabel sectionLabel, JPanel sectionPanel) { + + // If there are no attributes for tis section, hide the section panel and the section label + if (sectionAttributesList.isEmpty()) { + sectionLabel.setVisible(false); + sectionPanel.setVisible(false); + return; + } + + // create a gridbag layout to show each attribute on one line + GridBagLayout gridBagLayout = new GridBagLayout(); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.FIRST_LINE_START; + constraints.gridy = 0; + constraints.insets = new java.awt.Insets(4, 12, 0, 0); + for (BlackboardAttribute bba : sectionAttributesList) { + constraints.fill = GridBagConstraints.NONE; + constraints.weightx = 0; + + constraints.gridx = 0; + + // Add a label for attribute type + javax.swing.JLabel attrTypeLabel = new javax.swing.JLabel(); + String attrLabel = bba.getAttributeType().getDisplayName(); + attrTypeLabel.setText(attrLabel); + + // make type label bold - uncomment if needed. + //attrTypeLabel.setFont(attrTypeLabel.getFont().deriveFont(Font.BOLD, attrTypeLabel.getFont().getSize() )); + + gridBagLayout.setConstraints(attrTypeLabel, constraints); + sectionPanel.add(attrTypeLabel); + + // Add the attribute value + constraints.gridx++; + javax.swing.JLabel attrValueLabel = new javax.swing.JLabel(); + attrValueLabel.setText(bba.getValueString()); + gridBagLayout.setConstraints(attrValueLabel, constraints); + sectionPanel.add(attrValueLabel); + + // add a filler to take up rest of the space + constraints.gridx++; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + sectionPanel.add(new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0))); + + constraints.gridy++; + } + sectionPanel.setLayout(gridBagLayout); + sectionPanel.revalidate(); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel contactNameLabel; + private javax.swing.JLabel emailsLabel; + private javax.swing.JPanel emailsPanel; + private javax.swing.Box.Filler filler1; + private javax.swing.Box.Filler filler2; + private javax.swing.JPanel namePanel; + private javax.swing.JPanel otherAttrsPanel; + private javax.swing.JLabel othersLabel; + private javax.swing.JPanel phoneNumbersPanel; + private javax.swing.JLabel phonesLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form new file mode 100644 index 0000000000..eb1f729611 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form @@ -0,0 +1,73 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java new file mode 100644 index 0000000000..d77ca887d8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java @@ -0,0 +1,524 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.datatransfer.StringSelection; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.logging.Level; +import javax.swing.JMenuItem; +import javax.swing.JTextArea; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.TableColumnModelEvent; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; +import javax.swing.event.TableColumnModelListener; +import javax.swing.text.View; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.netbeans.swing.etable.ETable; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonArray; +import java.util.Locale; +import java.util.Map; +import javax.swing.SwingUtilities; + +/** + * This class displays a Blackboard artifact as a table listing all it's + * attributes names and values. + */ + +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DefaultArtifactContentViewer extends javax.swing.JPanel implements ArtifactContentViewer { + + @NbBundle.Messages({ + "DefaultArtifactContentViewer.attrsTableHeader.type=Type", + "DefaultArtifactContentViewer.attrsTableHeader.value=Value", + "DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)", + "DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database", + "DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database" + }) + + private final static Logger logger = Logger.getLogger(DefaultArtifactContentViewer.class.getName()); + + private static final long serialVersionUID = 1L; + + private static final String[] COLUMN_HEADERS = { + Bundle.DefaultArtifactContentViewer_attrsTableHeader_type(), + Bundle.DefaultArtifactContentViewer_attrsTableHeader_value(), + Bundle.DefaultArtifactContentViewer_attrsTableHeader_sources()}; + private static final int[] COLUMN_WIDTHS = {100, 800, 100}; + private static final int CELL_BOTTOM_MARGIN = 5; + private static final int CELL_RIGHT_MARGIN = 1; + + public DefaultArtifactContentViewer() { + initResultsTable(); + initComponents(); + resultsTableScrollPane.setViewportView(resultsTable); + customizeComponents(); + resetComponents(); + resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer()); + } + + private void initResultsTable() { + resultsTable = new ETable(); + resultsTable.setModel(new javax.swing.table.DefaultTableModel() { + private static final long serialVersionUID = 1L; + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + }); + resultsTable.setCellSelectionEnabled(true); + resultsTable.getTableHeader().setReorderingAllowed(false); + resultsTable.setColumnHidingAllowed(false); + resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); + resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() { + + @Override + public void columnAdded(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnRemoved(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnMoved(TableColumnModelEvent e) { + // do nothing + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + updateRowHeights(); //When the user changes column width we may need to resize row height + } + + @Override + public void columnSelectionChanged(ListSelectionEvent e) { + // do nothing + } + }); + resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN); + + } + + /** + * Sets the row heights to the heights of the content in their Value column. + */ + private void updateRowHeights() { + int valueColIndex = -1; + for (int col = 0; col < resultsTable.getColumnCount(); col++) { + if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) { + valueColIndex = col; + } + } + if (valueColIndex != -1) { + for (int row = 0; row < resultsTable.getRowCount(); row++) { + Component comp = resultsTable.prepareRenderer( + resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex); + final int rowHeight; + if (comp instanceof JTextArea) { + final JTextArea tc = (JTextArea) comp; + final View rootView = tc.getUI().getRootView(tc); + java.awt.Insets i = tc.getInsets(); + rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex) + .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders + Integer.MAX_VALUE); + rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); + } else { + rowHeight = comp.getPreferredSize().height; + } + if (rowHeight > 0) { + resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN); + } + } + } + } + + /** + * Update the column widths so that the Value column has most of the space. + */ + private void updateColumnSizes() { + Enumeration columns = resultsTable.getColumnModel().getColumns(); + while (columns.hasMoreElements()) { + TableColumn col = columns.nextElement(); + if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) { + col.setPreferredWidth(COLUMN_WIDTHS[0]); + } else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) { + col.setPreferredWidth(COLUMN_WIDTHS[1]); + } else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) { + col.setPreferredWidth(COLUMN_WIDTHS[2]); + } + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + rightClickMenu = new javax.swing.JPopupMenu(); + copyMenuItem = new javax.swing.JMenuItem(); + selectAllMenuItem = new javax.swing.JMenuItem(); + resultsTableScrollPane = new javax.swing.JScrollPane(); + + copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.copyMenuItem.text")); // NOI18N + rightClickMenu.add(copyMenuItem); + + selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.selectAllMenuItem.text")); // NOI18N + rightClickMenu.add(selectAllMenuItem); + + setPreferredSize(new java.awt.Dimension(100, 58)); + + resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); + resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 58, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JMenuItem copyMenuItem; + private javax.swing.JScrollPane resultsTableScrollPane; + private javax.swing.JPopupMenu rightClickMenu; + private javax.swing.JMenuItem selectAllMenuItem; + // End of variables declaration//GEN-END:variables + private ETable resultsTable; + + private void customizeComponents() { + resultsTable.setComponentPopupMenu(rightClickMenu); + ActionListener actList = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JMenuItem jmi = (JMenuItem) e.getSource(); + if (jmi.equals(copyMenuItem)) { + StringBuilder selectedText = new StringBuilder(512); + for (int row : resultsTable.getSelectedRows()) { + for (int col : resultsTable.getSelectedColumns()) { + selectedText.append((String) resultsTable.getValueAt(row, col)); + selectedText.append('\t'); + } + //if its the last row selected don't add a new line + if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) { + selectedText.append(System.lineSeparator()); + } + } + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null); + } else if (jmi.equals(selectAllMenuItem)) { + resultsTable.selectAll(); + } + } + }; + copyMenuItem.addActionListener(actList); + + selectAllMenuItem.addActionListener(actList); + } + + /** + * Resets the components to an empty view state. + */ + private void resetComponents() { + + ((DefaultTableModel) resultsTable.getModel()).setRowCount(0); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void setArtifact(BlackboardArtifact artifact) { + try { + ResultsTableArtifact resultsTableArtifact = new ResultsTableArtifact(artifact, artifact.getParent()); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + updateView(resultsTableArtifact); + } + }); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting parent content for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + } + + } + + @Override + public boolean isSupported(BlackboardArtifact artifact) { + // This viewer supports all artifacts. + return true; + } + + /** + * This class is a container to hold the data necessary for the artifact + * being viewed. + */ + private class ResultsTableArtifact { + + private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); + private String[][] rowData = null; + private final String artifactDisplayName; + private final Content content; + + ResultsTableArtifact(BlackboardArtifact artifact, Content content) { + artifactDisplayName = artifact.getDisplayName(); + this.content = content; + addRows(artifact); + } + + ResultsTableArtifact(String errorMsg) { + artifactDisplayName = errorMsg; + rowData = new String[1][3]; + rowData[0] = new String[]{"", errorMsg, ""}; + content = null; + } + + private String[][] getRows() { + return rowData; + } + + private void addRows(BlackboardArtifact artifact) { + List rowsToAdd = new ArrayList<>(); + try { + /* + * Add rows for each attribute. + */ + for (BlackboardAttribute attr : artifact.getAttributes()) { + /* + * Attribute value column. + */ + String value; + switch (attr.getAttributeType().getValueType()) { + + // Use Autopsy date formatting settings, not TSK defaults + case DATETIME: + value = epochTimeToString(attr.getValueLong()); + break; + case JSON: + // Get the attribute's JSON value and convert to indented multiline display string + String jsonVal = attr.getValueString(); + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(jsonVal).getAsJsonObject(); + + value = toJsonDisplayString(json, ""); + break; + + case STRING: + case INTEGER: + case LONG: + case DOUBLE: + case BYTE: + default: + value = attr.getDisplayString(); + break; + } + /* + * Attribute sources column. + */ + String sources = StringUtils.join(attr.getSources(), ", "); + rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources}); + } + /* + * Add a row for the source content path. + */ + String path = ""; + try { + if (null != content) { + path = content.getUniquePath(); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); + path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message(); + } + rowsToAdd.add(new String[]{"Source File Path", path, ""}); + /* + * Add a row for the artifact id. + */ + rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""}); + } catch (TskCoreException ex) { + rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""}); + } + rowData = rowsToAdd.toArray(new String[0][0]); + } + + /** + * @return the artifactDisplayName + */ + String getArtifactDisplayName() { + return artifactDisplayName; + } + + private static final String INDENT_RIGHT = " "; + private static final String NEW_LINE = "\n"; + + /** + * Recursively converts a JSON element into an indented multi-line + * display string. + * + * @param element JSON element to convert + * @param startIndent Starting indentation for the element. + * + * @return A multi-line display string. + */ + private String toJsonDisplayString(JsonElement element, String startIndent) { + + StringBuilder sb = new StringBuilder(""); + JsonObject obj = element.getAsJsonObject(); + + for (Map.Entry entry : obj.entrySet()) { + appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb ); + } + + String returnString = sb.toString(); + if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { + returnString = returnString.substring(NEW_LINE.length()); + } + return returnString; + } + + + /** + * Converts the given JSON element into string and appends to the given string builder. + * + * @param jsonKey + * @param jsonElement + * @param startIndent Starting indentation for the element. + * @param sb String builder to append to. + */ + private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) { + if (jsonElement.isJsonArray()) { + JsonArray jsonArray = jsonElement.getAsJsonArray(); + if (jsonArray.size() > 0) { + int count = 1; + sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey)); + for (JsonElement arrayMember : jsonArray) { + sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count)); + sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT))); + count++; + } + } + } else if (jsonElement.isJsonObject()) { + sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT))); + } else if (jsonElement.isJsonPrimitive()) { + String attributeName = jsonKey; + String attributeValue; + if (attributeName.toUpperCase().contains("DATETIME")) { + attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString())); + } else { + attributeValue = jsonElement.getAsString(); + } + sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue)); + } else if (jsonElement.isJsonNull()) { + sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey)); + } + } + + /** + * Converts epoch time to readable string. + * + * @param epochTime epoch time value to be converted to string. + * @return String with human readable time. + */ + private String epochTimeToString(long epochTime) { + String dateTimeString = "0000-00-00 00:00:00"; + if (null != content && 0 != epochTime) { + dateFormatter.setTimeZone(ContentUtils.getTimeZone(content)); + dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000)); + } + return dateTimeString; + } + + } + + /** + * Updates the table view with the given artifact data. + * + * It should be called on EDT. + * + * @param resultsTableArtifact Artifact data to display in the view. + */ + private void updateView(ResultsTableArtifact resultsTableArtifact) { + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); + tModel.setDataVector(resultsTableArtifact.getRows(), COLUMN_HEADERS); + updateColumnSizes(); + updateRowHeights(); + resultsTable.clearSelection(); + + this.setCursor(null); + } + + /** + * TableCellRenderer for displaying multiline text. + */ + private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer { + + @Override + public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + javax.swing.JTextArea jtex = new javax.swing.JTextArea(); + if (value instanceof String) { + jtex.setText((String) value); + jtex.setLineWrap(true); + jtex.setWrapStyleWord(false); + } + //cell backgroud color when selected + if (isSelected) { + jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground")); + } else { + jtex.setBackground(javax.swing.UIManager.getColor("Table.background")); + } + return jtex; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 4571bcc3df..8834b0f5d7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -30,8 +30,6 @@ DataContentViewerArtifact.pageLabel2.text=Result DataContentViewerArtifact.nextPageButton.text= DataContentViewerArtifact.currentPageLabel.text=1 DataContentViewerArtifact.ofLabel.text=of -DataContentViewerArtifact.copyMenuItem.text=Copy -DataContentViewerArtifact.selectAllMenuItem.text=Select All DataContentViewerArtifact.pageLabel.text=Result: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties index 6f941632c8..ec91946e79 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle_ja.properties @@ -82,8 +82,6 @@ DataContentViewerArtifact.pageLabel2.text=\u7d50\u679c DataContentViewerArtifact.nextPageButton.text= DataContentViewerArtifact.currentPageLabel.text=1 DataContentViewerArtifact.ofLabel.text=/ -DataContentViewerArtifact.copyMenuItem.text=\u30b3\u30d4\u30fc -DataContentViewerArtifact.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e DataContentViewerArtifact.pageLabel.text=\u7d50\u679c: AdvancedConfigurationDialog.applyButton.text=OK DataContentViewerHex.goToPageTextField.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form index 5827ce617f..c4928f5111 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.form @@ -1,30 +1,6 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - + @@ -45,16 +21,16 @@ - - + + - - + + @@ -248,19 +224,34 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 78ccd6e4ec..d6afb1f420 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -20,178 +20,68 @@ package org.sleuthkit.autopsy.corecomponents; import java.awt.Component; import java.awt.Cursor; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.datatransfer.StringSelection; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; -import javax.swing.JMenuItem; -import javax.swing.JTextArea; import javax.swing.SwingWorker; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.TableColumnModelEvent; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumn; -import javax.swing.event.TableColumnModelListener; -import javax.swing.text.View; -import org.apache.commons.lang.StringUtils; import org.openide.nodes.Node; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; -import org.netbeans.swing.etable.ETable; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonArray; -import java.util.Map; +import java.util.Arrays; +import java.util.Collections; +import org.sleuthkit.autopsy.contentviewers.ArtifactContentViewer; +import org.sleuthkit.autopsy.contentviewers.ContactArtifactViewer; +import org.sleuthkit.autopsy.contentviewers.DefaultArtifactContentViewer; /** * Instances of this class display the BlackboardArtifacts associated with the - * Content represented by a Node. Each BlackboardArtifact is rendered displayed - * in a JTable representation of its BlackboardAttributes. + * Content represented by a Node. + * + * It goes through a list of known ArtifactContentViewer to find a viewer that + * supports a given artifact and then hands it the artifact to display. */ @ServiceProvider(service = DataContentViewer.class, position = 7) @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class DataContentViewerArtifact extends javax.swing.JPanel implements DataContentViewer { + private static final long serialVersionUID = 1L; + @NbBundle.Messages({ - "DataContentViewerArtifact.attrsTableHeader.type=Type", - "DataContentViewerArtifact.attrsTableHeader.value=Value", - "DataContentViewerArtifact.attrsTableHeader.sources=Source(s)", "DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database", "DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database" }) private final static Logger logger = Logger.getLogger(DataContentViewerArtifact.class.getName()); private final static String WAIT_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.waitText"); private final static String ERROR_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.errorText"); + private Node currentNode; // @@@ Remove this when the redundant setNode() calls problem is fixed. private int currentPage = 1; private final Object lock = new Object(); - private List artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents() - SwingWorker currentTask; // Accessed by multiple threads, use startNewTask() - private static final String[] COLUMN_HEADERS = { - Bundle.DataContentViewerArtifact_attrsTableHeader_type(), - Bundle.DataContentViewerArtifact_attrsTableHeader_value(), - Bundle.DataContentViewerArtifact_attrsTableHeader_sources()}; - private static final int[] COLUMN_WIDTHS = {100, 800, 100}; - private static final int CELL_BOTTOM_MARGIN = 5; - private static final int CELL_RIGHT_MARGIN = 1; + private List artifactTableContents; // Accessed by multiple threads, use getArtifactContents() and setArtifactContents() + private SwingWorker currentTask; // Accessed by multiple threads, use startNewTask() + + private final Collection KNOWN_ARTIFACT_VIEWERS = + Arrays.asList( + new ContactArtifactViewer() + ); + public DataContentViewerArtifact() { - initResultsTable(); + initComponents(); - resultsTableScrollPane.setViewportView(resultsTable); - customizeComponents(); + resetComponents(); - resultsTable.setDefaultRenderer(Object.class, new MultiLineTableCellRenderer()); - } - - private void initResultsTable() { - resultsTable = new ETable(); - resultsTable.setModel(new javax.swing.table.DefaultTableModel() { - private static final long serialVersionUID = 1L; - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return false; - } - }); - resultsTable.setCellSelectionEnabled(true); - resultsTable.getTableHeader().setReorderingAllowed(false); - resultsTable.setColumnHidingAllowed(false); - resultsTable.getColumnModel().getSelectionModel().setSelectionMode(javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION); - resultsTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() { - - @Override - public void columnAdded(TableColumnModelEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - } - - @Override - public void columnMoved(TableColumnModelEvent e) { - - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - updateRowHeights(); //When the user changes column width we may need to resize row height - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } - }); - resultsTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_NEXT_COLUMN); - - } - - /** - * Sets the row heights to the heights of the content in their Value column. - */ - private void updateRowHeights() { - int valueColIndex = -1; - for (int col = 0; col < resultsTable.getColumnCount(); col++) { - if (resultsTable.getColumnName(col).equals(COLUMN_HEADERS[1])) { - valueColIndex = col; - } - } - if (valueColIndex != -1) { - for (int row = 0; row < resultsTable.getRowCount(); row++) { - Component comp = resultsTable.prepareRenderer( - resultsTable.getCellRenderer(row, valueColIndex), row, valueColIndex); - final int rowHeight; - if (comp instanceof JTextArea) { - final JTextArea tc = (JTextArea) comp; - final View rootView = tc.getUI().getRootView(tc); - java.awt.Insets i = tc.getInsets(); - rootView.setSize(resultsTable.getColumnModel().getColumn(valueColIndex) - .getWidth() - (i.left + i.right +CELL_RIGHT_MARGIN), //current width minus borders - Integer.MAX_VALUE); - rowHeight = (int) rootView.getPreferredSpan(View.Y_AXIS); - } else { - rowHeight = comp.getPreferredSize().height; - } - if (rowHeight > 0) { - resultsTable.setRowHeight(row, rowHeight + CELL_BOTTOM_MARGIN); - } - } - } - } - - /** - * Update the column widths so that the Value column has most of the space. - */ - private void updateColumnSizes() { - Enumeration columns = resultsTable.getColumnModel().getColumns(); - while (columns.hasMoreElements()) { - TableColumn col = columns.nextElement(); - if (col.getHeaderValue().equals(COLUMN_HEADERS[0])) { - col.setPreferredWidth(COLUMN_WIDTHS[0]); - } else if (col.getHeaderValue().equals(COLUMN_HEADERS[1])) { - col.setPreferredWidth(COLUMN_WIDTHS[1]); - } else if (col.getHeaderValue().equals(COLUMN_HEADERS[2])) { - col.setPreferredWidth(COLUMN_WIDTHS[2]); - } - } } /** @@ -204,9 +94,6 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; - rightClickMenu = new javax.swing.JPopupMenu(); - copyMenuItem = new javax.swing.JMenuItem(); - selectAllMenuItem = new javax.swing.JMenuItem(); jScrollPane1 = new javax.swing.JScrollPane(); jPanel1 = new javax.swing.JPanel(); totalPageLabel = new javax.swing.JLabel(); @@ -218,13 +105,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat prevPageButton = new javax.swing.JButton(); artifactLabel = new javax.swing.JLabel(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); - resultsTableScrollPane = new javax.swing.JScrollPane(); - - copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.copyMenuItem.text")); // NOI18N - rightClickMenu.add(copyMenuItem); - - selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.selectAllMenuItem.text")); // NOI18N - rightClickMenu.add(selectAllMenuItem); + artifactContentPanel = new javax.swing.JPanel(); setPreferredSize(new java.awt.Dimension(100, 58)); @@ -341,43 +222,41 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat jScrollPane1.setViewportView(jPanel1); - resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); + artifactContentPanel.setLayout(new javax.swing.OverlayLayout(artifactContentPanel)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 561, Short.MAX_VALUE) + .addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(artifactContentPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE)) ); }// //GEN-END:initComponents private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed - currentPage = currentPage + 1; + currentPage += 1; currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName()); + artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName()); startNewTask(new SelectedArtifactChangedTask(currentPage)); }//GEN-LAST:event_nextPageButtonActionPerformed private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed - currentPage = currentPage - 1; + currentPage -= 1; currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(artifactTableContents.get(currentPage - 1).getArtifactDisplayName()); + artifactLabel.setText(artifactTableContents.get(currentPage - 1).getDisplayName()); startNewTask(new SelectedArtifactChangedTask(currentPage)); }//GEN-LAST:event_prevPageButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel artifactContentPanel; private javax.swing.JLabel artifactLabel; - private javax.swing.JMenuItem copyMenuItem; private javax.swing.JLabel currentPageLabel; private javax.swing.Box.Filler filler1; private javax.swing.JPanel jPanel1; @@ -387,41 +266,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat private javax.swing.JLabel pageLabel; private javax.swing.JLabel pageLabel2; private javax.swing.JButton prevPageButton; - private javax.swing.JScrollPane resultsTableScrollPane; - private javax.swing.JPopupMenu rightClickMenu; - private javax.swing.JMenuItem selectAllMenuItem; private javax.swing.JLabel totalPageLabel; // End of variables declaration//GEN-END:variables - private ETable resultsTable; - private void customizeComponents() { - resultsTable.setComponentPopupMenu(rightClickMenu); - ActionListener actList = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - JMenuItem jmi = (JMenuItem) e.getSource(); - if (jmi.equals(copyMenuItem)) { - StringBuilder selectedText = new StringBuilder(512); - for (int row : resultsTable.getSelectedRows()) { - for (int col : resultsTable.getSelectedColumns()) { - selectedText.append((String) resultsTable.getValueAt(row, col)); - selectedText.append("\t"); - } - //if its the last row selected don't add a new line - if (row != resultsTable.getSelectedRows()[resultsTable.getSelectedRows().length - 1]) { - selectedText.append(System.lineSeparator()); - } - } - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(selectedText.toString()), null); - } else if (jmi.equals(selectAllMenuItem)) { - resultsTable.selectAll(); - } - } - }; - copyMenuItem.addActionListener(actList); - - selectAllMenuItem.addActionListener(actList); - } /** * Resets the components to an empty view state. @@ -431,10 +278,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat currentPageLabel.setText(""); artifactLabel.setText(""); totalPageLabel.setText(""); - ((DefaultTableModel) resultsTable.getModel()).setRowCount(0); + prevPageButton.setEnabled(false); nextPageButton.setEnabled(false); currentNode = null; + + artifactContentPanel.removeAll(); } @Override @@ -521,183 +370,13 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } } - /** - * This class is a container to hold the data necessary for each of the - * result pages associated with file or artifact beivng viewed. - */ - private class ResultsTableArtifact { - - private final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - private String[][] rowData = null; - private final String artifactDisplayName; - private final Content content; - - ResultsTableArtifact(BlackboardArtifact artifact, Content content) { - artifactDisplayName = artifact.getDisplayName(); - this.content = content; - addRows(artifact); - } - - ResultsTableArtifact(String errorMsg) { - artifactDisplayName = errorMsg; - rowData = new String[1][3]; - rowData[0] = new String[]{"", errorMsg, ""}; - content = null; - } - - private String[][] getRows() { - return rowData; - } - - private void addRows(BlackboardArtifact artifact) { - List rowsToAdd = new ArrayList<>(); - try { - /* - * Add rows for each attribute. - */ - for (BlackboardAttribute attr : artifact.getAttributes()) { - /* - * Attribute value column. - */ - String value = ""; - switch (attr.getAttributeType().getValueType()) { - case STRING: - case INTEGER: - case LONG: - case DOUBLE: - case BYTE: - default: - value = attr.getDisplayString(); - break; - // Use Autopsy date formatting settings, not TSK defaults - case DATETIME: - value = epochTimeToString(attr.getValueLong()); - break; - case JSON: - // Get the attribute's JSON value and convert to indented multiline display string - String jsonVal = attr.getValueString(); - JsonParser parser = new JsonParser(); - JsonObject json = parser.parse(jsonVal).getAsJsonObject(); - - value = toJsonDisplayString(json, ""); - break; - } - /* - * Attribute sources column. - */ - String sources = StringUtils.join(attr.getSources(), ", "); - rowsToAdd.add(new String[]{attr.getAttributeType().getDisplayName(), value, sources}); - } - /* - * Add a row for the source content path. - */ - String path = ""; - try { - if (null != content) { - path = content.getUniquePath(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format("Error getting source content path for artifact (artifact_id=%d, obj_id=%d)", artifact.getArtifactID(), artifact.getObjectID()), ex); - path = Bundle.DataContentViewerArtifact_failedToGetSourcePath_message(); - } - rowsToAdd.add(new String[]{"Source File Path", path, ""}); - /* - * Add a row for the artifact id. - */ - rowsToAdd.add(new String[]{"Artifact ID", Long.toString(artifact.getArtifactID()), ""}); - } catch (TskCoreException ex) { - rowsToAdd.add(new String[]{"", Bundle.DataContentViewerArtifact_failedToGetAttributes_message(), ""}); - } - rowData = rowsToAdd.toArray(new String[0][0]); - } - - /** - * @return the artifactDisplayName - */ - String getArtifactDisplayName() { - return artifactDisplayName; - } + private ArtifactContentViewer getSupportingViewer(BlackboardArtifact artifact) { - private static final String INDENT_RIGHT = " "; - private static final String NEW_LINE = "\n"; - - /** - * Recursively converts a JSON element into an indented multi-line - * display string. - * - * @param element JSON element to convert - * @param startIndent Starting indentation for the element. - * - * @return A multi-line display string. - */ - private String toJsonDisplayString(JsonElement element, String startIndent) { - - StringBuilder sb = new StringBuilder(""); - JsonObject obj = element.getAsJsonObject(); - - for (Map.Entry entry : obj.entrySet()) { - appendJsonElementToString(entry.getKey(), entry.getValue(), startIndent, sb ); - } - - String returnString = sb.toString(); - if (startIndent.length() == 0 && returnString.startsWith(NEW_LINE)) { - returnString = returnString.substring(NEW_LINE.length()); - } - return returnString; - } - - - /** - * Converts the given JSON element into string and appends to the given string builder. - * - * @param jsonKey - * @param jsonElement - * @param startIndent Starting indentation for the element. - * @param sb String builder to append to. - */ - private void appendJsonElementToString(String jsonKey, JsonElement jsonElement, String startIndent, StringBuilder sb) { - if (jsonElement.isJsonArray()) { - JsonArray jsonArray = jsonElement.getAsJsonArray(); - if (jsonArray.size() > 0) { - int count = 1; - sb.append(NEW_LINE).append(String.format("%s%s", startIndent, jsonKey)); - for (JsonElement arrayMember : jsonArray) { - sb.append(NEW_LINE).append(String.format("%s%d", startIndent.concat(INDENT_RIGHT), count)); - sb.append(toJsonDisplayString(arrayMember, startIndent.concat(INDENT_RIGHT).concat(INDENT_RIGHT))); - count++; - } - } - } else if (jsonElement.isJsonObject()) { - sb.append(NEW_LINE).append(String.format("%s%s %s", startIndent, jsonKey, toJsonDisplayString(jsonElement.getAsJsonObject(), startIndent + INDENT_RIGHT))); - } else if (jsonElement.isJsonPrimitive()) { - String attributeName = jsonKey; - String attributeValue; - if (attributeName.toUpperCase().contains("DATETIME")) { - attributeValue = epochTimeToString(Long.parseLong(jsonElement.getAsString())); - } else { - attributeValue = jsonElement.getAsString(); - } - sb.append(NEW_LINE).append(String.format("%s%s = %s", startIndent, attributeName, attributeValue)); - } else if (jsonElement.isJsonNull()) { - sb.append(NEW_LINE).append(String.format("%s%s = null", startIndent, jsonKey)); - } - } - - /** - * Converts epoch time to readable string. - * - * @param epochTime epoch time value to be converted to string. - * @return String with human readable time. - */ - private String epochTimeToString(long epochTime) { - String dateTimeString = "0000-00-00 00:00:00"; - if (null != content && 0 != epochTime) { - dateFormatter.setTimeZone(ContentUtils.getTimeZone(content)); - dateTimeString = dateFormatter.format(new java.util.Date(epochTime * 1000)); - } - return dateTimeString; - } - + return this.KNOWN_ARTIFACT_VIEWERS.stream() + .filter(knownViewer -> knownViewer.isSupported(artifact)) + .findAny() + .orElse(new DefaultArtifactContentViewer()); + } /** @@ -708,18 +387,21 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat int numberOfPages; int currentPage; - ResultsTableArtifact tableContents; + BlackboardArtifact artifact; + String errorMsg; - ViewUpdate(int numberOfPages, int currentPage, ResultsTableArtifact contents) { + ViewUpdate(int numberOfPages, int currentPage, BlackboardArtifact artifact) { this.currentPage = currentPage; this.numberOfPages = numberOfPages; - this.tableContents = contents; + this.artifact = artifact; + this.errorMsg = null; } ViewUpdate(int numberOfPages, int currentPage, String errorMsg) { this.currentPage = currentPage; this.numberOfPages = numberOfPages; - this.tableContents = new ResultsTableArtifact(errorMsg); + this.errorMsg = errorMsg; + this.artifact = null; } } @@ -738,14 +420,26 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat currentPage = viewUpdate.currentPage; totalPageLabel.setText(Integer.toString(viewUpdate.numberOfPages)); currentPageLabel.setText(Integer.toString(currentPage)); - artifactLabel.setText(viewUpdate.tableContents.getArtifactDisplayName()); - DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); - tModel.setDataVector(viewUpdate.tableContents.getRows(), COLUMN_HEADERS); - updateColumnSizes(); - updateRowHeights(); - resultsTable.clearSelection(); + + + artifactContentPanel.removeAll(); + + if (viewUpdate.artifact != null) { + artifactLabel.setText(viewUpdate.artifact.getDisplayName()); + BlackboardArtifact artifact = viewUpdate.artifact; + ArtifactContentViewer viewer = this.getSupportingViewer(artifact); + viewer.setArtifact(artifact); + + artifactContentPanel.add(viewer.getComponent()); + } else { + artifactLabel.setText(viewUpdate.errorMsg); + } + + artifactContentPanel.revalidate(); this.setCursor(null); + + this.revalidate(); } /** @@ -755,13 +449,7 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat * @param task A new SwingWorker object to execute as a background thread. */ private synchronized void startNewTask(SwingWorker task) { - String[][] waitRow = new String[1][3]; - waitRow[0] = new String[]{"", WAIT_TEXT, ""}; - DefaultTableModel tModel = ((DefaultTableModel) resultsTable.getModel()); - tModel.setDataVector(waitRow, COLUMN_HEADERS); - updateColumnSizes(); - updateRowHeights(); - resultsTable.clearSelection(); + // The output of the previous task is no longer relevant. if (currentTask != null) { // This call sets a cancellation flag. It does not terminate the background thread running the task. @@ -775,12 +463,12 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } /** - * Populate the cache of artifact represented as ResultsTableArtifacts. + * Populate the cache of artifacts represented as ResultsTableArtifacts. * * @param artifactList A list of ResultsTableArtifact representations of * artifacts. */ - private void setArtifactContents(List artifactList) { + private void setArtifactContents(List artifactList) { synchronized (lock) { this.artifactTableContents = artifactList; } @@ -789,11 +477,11 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat /** * Retrieve the cache of artifact represented as ResultsTableArtifacts. * - * @return A list of ResultsTableArtifact representations of artifacts. + * @return A list of artifacts. */ - private List getArtifactContents() { + private List getArtifactContents() { synchronized (lock) { - return artifactTableContents; + return Collections.unmodifiableList(artifactTableContents); } } @@ -843,9 +531,9 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } // Build the new artifact contents cache. - ArrayList artifactContents = new ArrayList<>(); + ArrayList artifactContents = new ArrayList<>(); for (BlackboardArtifact artifact : artifacts) { - artifactContents.add(new ResultsTableArtifact(artifact, underlyingContent)); + artifactContents.add(artifact); } // If the node has an underlying blackboard artifact, show it. If not, @@ -932,14 +620,14 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat protected ViewUpdate doInBackground() { // Get the artifact content to display from the cache. Note that one must be subtracted from the // page index to get the corresponding artifact content index. - List artifactContents = getArtifactContents(); - ResultsTableArtifact artifactContent = artifactContents.get(pageIndex - 1); - + List artifactContents = getArtifactContents(); + // It may take a considerable amount of time to fetch the attributes of the selected artifact so check for cancellation. if (isCancelled()) { return null; } + BlackboardArtifact artifactContent = artifactContents.get(pageIndex - 1); return new ViewUpdate(artifactContents.size(), pageIndex, artifactContent); } @@ -957,27 +645,4 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat } } } - - /** - * TableCellRenderer for displaying multiline text. - */ - private class MultiLineTableCellRenderer implements javax.swing.table.TableCellRenderer { - - @Override - public Component getTableCellRendererComponent(javax.swing.JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - javax.swing.JTextArea jtex = new javax.swing.JTextArea(); - if (value instanceof String) { - jtex.setText((String) value); - jtex.setLineWrap(true); - jtex.setWrapStyleWord(false); - } - //cell backgroud color when selected - if (isSelected) { - jtex.setBackground(javax.swing.UIManager.getColor("Table.selectionBackground")); - } else { - jtex.setBackground(javax.swing.UIManager.getColor("Table.background")); - } - return jtex; - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java index 99eecf50d2..73d1a02314 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java @@ -30,6 +30,7 @@ import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.datamodel.HashEntry; @@ -46,7 +47,14 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { private final HashDb hashDb; private final List hashes; private final List invalidHashes; - private final Pattern md5Pattern; + + // Matches hash with optional comma separated comment. + private static final Pattern HASH_LINE_PATTERN = Pattern.compile("^([a-fA-F0-9]{32})(,(.*))?$"); + // The regex group for the hash. + private static final int HASH_GROUP = 1; + // The regex group for the comment. + private static final int COMMENT_GROUP = 3; + private String errorTitle; private String errorMessage; private final String text; @@ -64,7 +72,6 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { display(parent); this.hashes = new ArrayList<>(); this.invalidHashes = new ArrayList<>(); - this.md5Pattern = Pattern.compile("^([a-fA-F0-9]{32})"); // NON-NLS this.parentRef = parent; this.hashDb = hashDb; this.text = text; @@ -161,17 +168,15 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { // These entries may be of or format for (String hashEntry : linesInTextArea) { hashEntry = hashEntry.trim(); - Matcher m = md5Pattern.matcher(hashEntry); + Matcher m = HASH_LINE_PATTERN.matcher(hashEntry); if (m.find()) { - // Is there any text left on this line? If so, treat it as a comment. - String comment = hashEntry.substring(m.end()).trim(); - if (comment.length() > 0) { - comment = (comment.charAt(0) == ',') ? comment.substring(1) : comment; - hashes.add(new HashEntry(null, m.group(0), null, null, comment)); - } else { - // more information can be added to the HashEntry - sha-1, sha-512, comment - hashes.add(new HashEntry(null, m.group(0), null, null, null)); - } + String hash = m.group(HASH_GROUP); + + // if there was a match and the match is not empty, assign to comment + String comment = StringUtils.isNotBlank(m.group(COMMENT_GROUP)) ? + m.group(COMMENT_GROUP).trim() : null; + + hashes.add(new HashEntry(null, hash, null, null, comment)); } else { if (!hashEntry.isEmpty()) { invalidHashes.add(hashEntry); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties index d9fb142cf4..e7ed2dedd7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties @@ -94,8 +94,6 @@ HashDbIngestModule.fileReadErrorMsg=Read Error: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0} ({1}). HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error: {0} HashDbIngestModule.settingKnownBadStateErr=Error encountered while setting notable state for {0}. -HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. -HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. HashDbIngestModule.postToBB.fileName=File Name HashDbIngestModule.postToBB.md5Hash=MD5 Hash HashDbIngestModule.postToBB.hashsetName=Hash Set Name @@ -145,8 +143,6 @@ HashDbManager.hashDbFileExistsExceptionMsg=A file already exists at\n{0} HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash set at\n{0}\nhas already been created or imported. HashDbManager.illegalHashDbFileNameExtensionMsg=The hash set file name must have a .{0} extension. HashDbManager.moduleErr=Module Error -HashDbManager.knownBad.text=Notable -HashDbManager.known.text=Known HashDbManager.fileNameExtensionFilter.title=Hash Set File HashDbSearchAction.dlgMsg.title=File Search by MD5 Hash HashDbSearchAction.getName.text=Hash Search @@ -162,8 +158,6 @@ AddContentToHashDbAction.multipleSelectionNameEmpty=Add Files to Hash Set (Empty HashDbManager.ingestRunningExceptionMsg=Ingest is ongoing; this service will be unavailable until it finishes. HashDbManager.saveErrorExceptionMsg=Error saving hash configuration HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash set is selected -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash sets to use: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets to use: AddContentToHashDbAction.addFilesToHashSet.files=files AddContentToHashDbAction.addFilesToHashSet.file=file HashDbManager.errCreatingIndex.title=Error creating index @@ -241,3 +235,7 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=Copy hash set into user configuration folder HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=In Live Triage situations, this option ensures that path to the hash set will be valid HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=Select hash sets to use: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=No Change +HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText= +HashDbImportDatabaseDialog.noChangeRadioButton.text=No Change diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED index 44057d0016..853a23d0db 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED @@ -9,6 +9,7 @@ HashDbImportDatabaseDialog.missingOrg=An organization must be selected HashDbImportDatabaseDialog.missingVersion=A version must be entered HashDbImportDatabaseDialog.mustEnterHashSetNameMsg=A hash set name must be entered. HashDbImportDatabaseDialog.populateOrgsError.message=Failure loading organizations. +HashDbIngestModule.complete.noChangesFound=No Change items found: # {0} - File name HashDbIngestModule.dialogTitle.errorFindingArtifacts=Error Finding Artifacts: {0} # {0} - File name @@ -16,10 +17,21 @@ HashDbIngestModule.errorMessage.lookingForFileArtifacts=Error encountered while HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search. HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed. HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed. +# {0} - fileName +HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. +# {0} - fileName +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 HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets +HashDbManager.known.text=Known +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} HashDbSearchAction.noOpenCase.errMsg=No open case available. @@ -49,7 +61,10 @@ ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash se ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Hash Set ingest module. \n\nThe ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\nThe module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. +OpenIDE-Module-Long-Description=\ + Hash Set ingest module. \n\n\ + The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\ + The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. OpenIDE-Module-Name=HashDatabases OptionsCategory_Name_HashDatabase=Hash Sets OptionsCategory_Keywords_HashDatabase=Hash Sets @@ -141,8 +156,6 @@ HashDbIngestModule.fileReadErrorMsg=Read Error: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0} ({1}). HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error: {0} HashDbIngestModule.settingKnownBadStateErr=Error encountered while setting notable state for {0}. -HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. -HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. HashDbIngestModule.postToBB.fileName=File Name HashDbIngestModule.postToBB.md5Hash=MD5 Hash HashDbIngestModule.postToBB.hashsetName=Hash Set Name @@ -178,7 +191,10 @@ HashDbSearchThread.name.searching=Searching HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found. ModalNoButtons.indexingDbsTitle=Indexing hash sets ModalNoButtons.indexingDbTitle=Indexing hash set -ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \nThe generated index will be left unusable. If you choose to continue,\nplease delete the corresponding -md5.idx file in the hash folder.\nExit indexing? +ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\ +The generated index will be left unusable. If you choose to continue,\n\ + please delete the corresponding -md5.idx file in the hash folder.\n\ + Exit indexing? ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0} @@ -189,8 +205,6 @@ HashDbManager.hashDbFileExistsExceptionMsg=A file already exists at\n{0} HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash set at\n{0}\nhas already been created or imported. HashDbManager.illegalHashDbFileNameExtensionMsg=The hash set file name must have a .{0} extension. HashDbManager.moduleErr=Module Error -HashDbManager.knownBad.text=Notable -HashDbManager.known.text=Known HashDbManager.fileNameExtensionFilter.title=Hash Set File HashDbSearchAction.dlgMsg.title=File Search by MD5 Hash HashDbSearchAction.getName.text=Hash Search @@ -205,13 +219,7 @@ AddContentToHashDbAction.singleSelectionNameEmpty=Add File to Hash Set (Empty Fi AddContentToHashDbAction.multipleSelectionNameEmpty=Add Files to Hash Set (Empty File) HashDbManager.ingestRunningExceptionMsg=Ingest is ongoing; this service will be unavailable until it finishes. HashDbManager.saveErrorExceptionMsg=Error saving hash configuration -HashLookupSettingsPanel.jButton3.text=Import Hash Set -HashLookupSettingsPanel.jLabel6.text=Type: -HashLookupSettingsPanel.jLabel4.text=Location: -HashLookupSettingsPanel.jLabel2.text=Name: HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash set is selected -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash sets to use: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets to use: AddContentToHashDbAction.addFilesToHashSet.files=files AddContentToHashDbAction.addFilesToHashSet.file=file HashDbManager.errCreatingIndex.title=Error creating index @@ -289,3 +297,7 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=Copy hash set into user configuration folder HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=In Live Triage situations, this option ensures that path to the hash set will be valid HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=Select hash sets to use: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=No Change +HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText= +HashDbImportDatabaseDialog.noChangeRadioButton.text=No Change diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties index 3f5ea43a61..c800aca094 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties @@ -216,8 +216,6 @@ HashLookupSettingsPanel.jLabel6.text=\u30bf\u30a4\u30d7: HashLookupSettingsPanel.jLabel4.text=\u5834\u6240: HashLookupSettingsPanel.jLabel2.text=\u540d\u524d: HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u3067\u3082MD5\u3092\u8a08\u7b97 -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u65e2\u77e5\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u9855\u8457\u306a\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: AddContentToHashDbAction.addFilesToHashSet.files=\u30d5\u30a1\u30a4\u30eb AddContentToHashDbAction.addFilesToHashSet.file=\u30d5\u30a1\u30a4\u30eb HashDbManager.errCreatingIndex.title=\u7d22\u5f15\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f @@ -295,3 +293,6 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u30e6\u30fc\u30b6\u30fc\u69cb\u6210\u30d5\u30a1\u30a4\u30eb\u306b\u30b3\u30d4\u30fc HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=\u30e9\u30a4\u30d6\u30c8\u30ea\u30a2\u30fc\u30b8\u306e\u72b6\u6cc1\u3067\u306f\u3001\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u3088\u3063\u3066\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3078\u306e\u30d1\u30b9\u304c\u6709\u52b9\u3067\u3042\u308b\u3053\u3068\u304c\u4fdd\u8a3c\u3055\u308c\u307e\u3059\u3002 HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u65e2\u77e5\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=\u9855\u8457 +HashDbImportDatabaseDialog.noChangeRadioButton.text=\u9855\u8457 diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form index 7c74b4b583..b274f93d3c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form @@ -78,6 +78,7 @@ + @@ -125,19 +126,21 @@ - + - - - - + + + + + + @@ -313,5 +316,18 @@ + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 5470b385fb..63a8c84e93 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -198,6 +198,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { lbOrg = new javax.swing.JLabel(); orgComboBox = new javax.swing.JComboBox<>(); orgButton = new javax.swing.JButton(); + noChangeRadioButton = new javax.swing.JRadioButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -292,6 +293,14 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { } }); + buttonGroup1.add(noChangeRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(noChangeRadioButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.noChangeRadioButton.text")); // NOI18N + noChangeRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + noChangeRadioButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -334,7 +343,8 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { .addGap(32, 32, 32) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(knownRadioButton) - .addComponent(knownBadRadioButton))) + .addComponent(knownBadRadioButton) + .addComponent(noChangeRadioButton))) .addGroup(layout.createSequentialGroup() .addGap(12, 12, 12) .addComponent(jLabel2)) @@ -374,16 +384,18 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { .addComponent(knownRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(knownBadRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(sendIngestMessagesCheckbox) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) - .addComponent(okButton)))) + .addComponent(okButton))) + .addGroup(layout.createSequentialGroup() + .addComponent(noChangeRadioButton) + .addGap(24, 24, 24) + .addComponent(sendIngestMessagesCheckbox) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); @@ -391,13 +403,13 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { }// //GEN-END:initComponents private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(false); - sendIngestMessagesCheckbox.setEnabled(false); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN.isInboxMessagesAllowed()); }//GEN-LAST:event_knownRadioButtonActionPerformed private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(true); - sendIngestMessagesCheckbox.setEnabled(true); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN_BAD.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN_BAD.isInboxMessagesAllowed()); }//GEN-LAST:event_knownBadRadioButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed @@ -476,15 +488,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { } KnownFilesType type; - TskData.FileKnown fileKnown; + if (knownRadioButton.isSelected()) { type = KnownFilesType.KNOWN; - fileKnown = TskData.FileKnown.KNOWN; + } else if (noChangeRadioButton.isSelected()) { + type = KnownFilesType.NO_CHANGE; } else { type = KnownFilesType.KNOWN_BAD; - fileKnown = TskData.FileKnown.BAD; } + TskData.FileKnown fileKnown = type.getFileKnown(); + String errorMessage = NbBundle .getMessage(this.getClass(), "HashDbCreateDatabaseDialog.errMsg.hashDbCreationErr"); @@ -586,6 +600,11 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { enableComponents(); }//GEN-LAST:event_centralRepoRadioButtonActionPerformed + private void noChangeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noChangeRadioButtonActionPerformed + sendIngestMessagesCheckbox.setSelected(KnownFilesType.NO_CHANGE.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.NO_CHANGE.isInboxMessagesAllowed()); + }//GEN-LAST:event_noChangeRadioButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JButton cancelButton; @@ -600,6 +619,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private javax.swing.JRadioButton knownBadRadioButton; private javax.swing.JRadioButton knownRadioButton; private javax.swing.JLabel lbOrg; + private javax.swing.JRadioButton noChangeRadioButton; private javax.swing.JButton okButton; private javax.swing.JButton orgButton; private javax.swing.JComboBox orgComboBox; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form index e285e99a12..13ed7fbf57 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form @@ -29,7 +29,7 @@ - + @@ -54,10 +54,6 @@ - - - - @@ -76,7 +72,16 @@ - + + + + + + + + + + @@ -86,16 +91,17 @@ - - + + + @@ -113,52 +119,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -367,5 +375,21 @@
+ + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index a08f324a44..c854cb165d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -182,6 +182,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { centralRepoRadioButton = new javax.swing.JRadioButton(); jLabel4 = new javax.swing.JLabel(); saveInUserConfigFolderCheckbox = new javax.swing.JCheckBox(); + noChangeRadioButton = new javax.swing.JRadioButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -291,6 +292,15 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { org.openide.awt.Mnemonics.setLocalizedText(saveInUserConfigFolderCheckbox, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text")); // NOI18N saveInUserConfigFolderCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText")); // NOI18N + buttonGroup1.add(noChangeRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(noChangeRadioButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.noChangeRadioButton.text")); // NOI18N + noChangeRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText")); // NOI18N + noChangeRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + noChangeRadioButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -315,9 +325,6 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(openButton)))) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(sendIngestMessagesCheckbox) - .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(lbOrg) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -331,7 +338,13 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addGap(40, 40, 40) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(versionTextField) - .addComponent(hashSetNameTextField))) + .addComponent(hashSetNameTextField)))) + .addGap(81, 81, 81)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(sendIngestMessagesCheckbox) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addComponent(okButton))) @@ -339,14 +352,15 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(cancelButton)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(saveInUserConfigFolderCheckbox) .addComponent(jLabel2) - .addComponent(readOnlyCheckbox) .addGroup(layout.createSequentialGroup() .addGap(19, 19, 19) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(knownRadioButton) - .addComponent(knownBadRadioButton)))) + .addComponent(knownBadRadioButton) + .addComponent(noChangeRadioButton))) + .addComponent(saveInUserConfigFolderCheckbox) + .addComponent(readOnlyCheckbox)) .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); @@ -361,44 +375,46 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(databasePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel3) .addComponent(openButton)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fileTypeRadioButton) + .addComponent(centralRepoRadioButton) + .addComponent(jLabel4)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbVersion) + .addComponent(versionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(orgButton) + .addComponent(orgComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbOrg)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownBadRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(noChangeRadioButton) + .addGap(5, 5, 5) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(fileTypeRadioButton) - .addComponent(centralRepoRadioButton) - .addComponent(jLabel4)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbVersion) - .addComponent(versionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(orgButton) - .addComponent(orgComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbOrg)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(knownRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(knownBadRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(readOnlyCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(sendIngestMessagesCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(saveInUserConfigFolderCheckbox) - .addGap(0, 29, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) + .addGap(81, 81, 81) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)))) - .addContainerGap()) + .addGap(18, 18, 18)) ); pack(); @@ -436,13 +452,13 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { }//GEN-LAST:event_openButtonActionPerformed private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(false); - sendIngestMessagesCheckbox.setEnabled(false); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN.isInboxMessagesAllowed()); }//GEN-LAST:event_knownRadioButtonActionPerformed private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(true); - sendIngestMessagesCheckbox.setEnabled(true); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN_BAD.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN_BAD.isInboxMessagesAllowed()); }//GEN-LAST:event_knownBadRadioButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed @@ -531,6 +547,8 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { KnownFilesType type; if (knownRadioButton.isSelected()) { type = KnownFilesType.KNOWN; + } else if (noChangeRadioButton.isSelected()) { + type = KnownFilesType.NO_CHANGE; } else { type = KnownFilesType.KNOWN_BAD; } @@ -629,6 +647,11 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { enableComponents(); }//GEN-LAST:event_readOnlyCheckboxActionPerformed + private void noChangeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noChangeRadioButtonActionPerformed + sendIngestMessagesCheckbox.setSelected(KnownFilesType.NO_CHANGE.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.NO_CHANGE.isInboxMessagesAllowed()); + }//GEN-LAST:event_noChangeRadioButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JButton cancelButton; @@ -644,6 +667,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { private javax.swing.JRadioButton knownRadioButton; private javax.swing.JLabel lbOrg; private javax.swing.JLabel lbVersion; + private javax.swing.JRadioButton noChangeRadioButton; private javax.swing.JButton okButton; private javax.swing.JButton openButton; private javax.swing.JButton orgButton; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index a562ab2a22..873f891ad0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -24,7 +24,9 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import java.util.logging.Level; +import java.util.stream.Stream; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -57,12 +59,26 @@ import org.sleuthkit.datamodel.TskException; @Messages({ "HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.", "HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.", + "HashDbIngestModule.noChangeHashDbSetMsg=No 'No Change' hash set.", + "HashDbIngestModule.noChangeFileSearchWillNotExecuteWarn='No Change' file search will not be executed.", "HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.", - "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed." -}) + "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed.", + "# {0} - fileName", "HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}.", + "# {0} - fileName", "HashDbIngestModule.lookingUpNoChangeHashValueErr=Error encountered while looking up no change hash value for {0}.", + "# {0} - fileName", "HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}.",}) public class HashDbIngestModule implements FileIngestModule { private static final Logger logger = Logger.getLogger(HashDbIngestModule.class.getName()); + + private final Function knownBadLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpKnownBadHashValueErr(file.getName()); + + private final Function noChangeLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpNoChangeHashValueErr(file.getName()); + + private final Function knownLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpKnownHashValueErr(file.getName()); + private static final int MAX_COMMENT_SIZE = 500; private final IngestServices services = IngestServices.getInstance(); private final SleuthkitCase skCase; @@ -70,6 +86,7 @@ public class HashDbIngestModule implements FileIngestModule { private final HashLookupModuleSettings settings; private final List knownBadHashSets = new ArrayList<>(); private final List knownHashSets = new ArrayList<>(); + private final List noChangeHashSets = new ArrayList<>(); private long jobId; private static final HashMap totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); @@ -81,6 +98,7 @@ public class HashDbIngestModule implements FileIngestModule { private static class IngestJobTotals { private final AtomicLong totalKnownBadCount = new AtomicLong(0); + private final AtomicLong totalNoChangeCount = new AtomicLong(0); private final AtomicLong totalCalctime = new AtomicLong(0); private final AtomicLong totalLookuptime = new AtomicLong(0); } @@ -114,8 +132,8 @@ public class HashDbIngestModule implements FileIngestModule { if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) { throw new IngestModuleException("Could not load all hash sets"); } - updateEnabledHashSets(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets); - updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets); + + initializeHashsets(hashDbManager.getAllHashSets()); if (refCounter.incrementAndGet(jobId) == 1) { // initialize job totals @@ -128,6 +146,13 @@ public class HashDbIngestModule implements FileIngestModule { Bundle.HashDbIngestModule_noKnownBadHashDbSetMsg(), Bundle.HashDbIngestModule_knownBadFileSearchWillNotExecuteWarn())); } + + if (noChangeHashSets.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage( + HashLookupModuleFactory.getModuleName(), + Bundle.HashDbIngestModule_noChangeHashDbSetMsg(), + Bundle.HashDbIngestModule_noChangeFileSearchWillNotExecuteWarn())); + } if (knownHashSets.isEmpty()) { services.postMessage(IngestMessage.createWarningMessage( @@ -139,18 +164,29 @@ public class HashDbIngestModule implements FileIngestModule { } /** - * Cycle through list of hashsets and return the subset that is enabled. + * Cycle through list of hashsets and place each HashDB in the appropriate + * list based on KnownFilesType. * - * @param allHashSets List of all hashsets from DB manager - * @param enabledHashSets List of enabled ones to return. + * @param allHashSets List of all hashsets from DB manager */ - private void updateEnabledHashSets(List allHashSets, List enabledHashSets) { - enabledHashSets.clear(); + private void initializeHashsets(List allHashSets) { for (HashDb db : allHashSets) { if (settings.isHashSetEnabled(db)) { try { if (db.isValid()) { - enabledHashSets.add(db); + switch (db.getKnownFilesType()) { + case KNOWN: + knownHashSets.add(db); + break; + case KNOWN_BAD: + knownBadHashSets.add(db); + break; + case NO_CHANGE: + noChangeHashSets.add(db); + break; + default: + throw new TskCoreException("Unknown KnownFilesType: " + db.getKnownFilesType()); + } } } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName() + " hash set", ex); //NON-NLS @@ -174,128 +210,37 @@ public class HashDbIngestModule implements FileIngestModule { return ProcessResult.ERROR; } - // Skip unallocated space files. - if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { - return ProcessResult.OK; - } - - /* - * Skip directories. One reason for this is because we won't accurately - * calculate hashes of NTFS directories that have content that spans the - * IDX_ROOT and IDX_ALLOC artifacts. So we disable that until a solution - * for it is developed. - */ - if (file.isDir()) { - return ProcessResult.OK; - } - - // bail out if we have no hashes set - if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) { + if (shouldSkip(file)) { return ProcessResult.OK; } // Safely get a reference to the totalsForIngestJobs object IngestJobTotals totals = getTotalsForIngestJobs(jobId); - // calc hash value - String name = file.getName(); - long fileId = file.getId(); - String md5Hash = file.getMd5Hash(); - if (md5Hash == null || md5Hash.isEmpty()) { - try { - TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); - long calcstart = System.currentTimeMillis(); - md5Hash = HashUtility.calculateMd5Hash(file); - if (file.getSize() > 0) { - // Surprisingly, the hash calculation does not seem to be correlated that - // strongly with file size until the files get large. - // Only normalize if the file size is greater than ~1MB. - if (file.getSize() < 1000000) { - HealthMonitor.submitTimingMetric(metric); - } else { - // In testing, this normalization gave reasonable resuls - HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); - } - } - file.setMd5Hash(md5Hash); - long delta = (System.currentTimeMillis() - calcstart); - totals.totalCalctime.addAndGet(delta); - - } catch (IOException ex) { - logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr", - file.getParentPath() + file.getName(), - file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)?"Allocated File" : "Deleted File"))); - return ProcessResult.ERROR; - } + // calc hash value + String md5Hash = getHash(file, totals); + if (md5Hash == null) { + return ProcessResult.ERROR; } - // look up in notable first - boolean foundBad = false; + // the processing result of handling this file ProcessResult ret = ProcessResult.OK; - for (HashDb db : knownBadHashSets) { - try { - long lookupstart = System.currentTimeMillis(); - HashHitInfo hashInfo = db.lookupMD5(file); - if (null != hashInfo) { - foundBad = true; - totals.totalKnownBadCount.incrementAndGet(); - file.setKnown(TskData.FileKnown.BAD); + // look up in notable first + FindInHashsetsResult knownBadResult = findInHashsets(file, totals.totalKnownBadCount, + totals.totalLookuptime, knownBadHashSets, TskData.FileKnown.BAD, knownBadLookupError); - String hashSetName = db.getDisplayName(); + boolean foundBad = knownBadResult.isFound(); + if (knownBadResult.isError()) { + ret = ProcessResult.ERROR; + } - String comment = ""; - ArrayList comments = hashInfo.getComments(); - int i = 0; - for (String c : comments) { - if (++i > 1) { - comment += " "; - } - comment += c; - if (comment.length() > MAX_COMMENT_SIZE) { - comment = comment.substring(0, MAX_COMMENT_SIZE) + "..."; - break; - } - } + // look up no change items next + FindInHashsetsResult noChangeResult = findInHashsets(file, totals.totalNoChangeCount, + totals.totalLookuptime, noChangeHashSets, TskData.FileKnown.UNKNOWN, noChangeLookupError); - /* - * We have a match. Now create an artifact if it is - * determined that one hasn't been created yet. - */ - List attributesList = new ArrayList<>(); - attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, HashLookupModuleFactory.getModuleName(), hashSetName)); - try { - org.sleuthkit.datamodel.Blackboard tskBlackboard = skCase.getBlackboard(); - if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) == false) { - postHashSetHitToBlackboard(file, md5Hash, hashSetName, comment, db.getSendIngestMessages()); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format( - "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(name), - Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(name))); - ret = ProcessResult.ERROR; - } - } - long delta = (System.currentTimeMillis() - lookupstart); - totals.totalLookuptime.addAndGet(delta); - - } catch (TskException ex) { - logger.log(Level.WARNING, String.format( - "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownBadHashValueErr", name))); - ret = ProcessResult.ERROR; - } + if (noChangeResult.isError()) { + ret = ProcessResult.ERROR; } // If the file is not in the notable sets, search for it in the known sets. @@ -313,12 +258,7 @@ public class HashDbIngestModule implements FileIngestModule { totals.totalLookuptime.addAndGet(delta); } catch (TskException ex) { - logger.log(Level.WARNING, String.format( - "Couldn't lookup known hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownHashValueErr", name))); + reportLookupError(ex, file, knownLookupError); ret = ProcessResult.ERROR; } } @@ -327,6 +267,245 @@ public class HashDbIngestModule implements FileIngestModule { return ret; } + /** + * Returns true if this file should be skipped for processing. + * + * @param file The file to potentially skip. + * + * @return True if this file should be skipped. + */ + private boolean shouldSkip(AbstractFile file) { + // Skip unallocated space files. + if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + return true; + } + + /* + * Skip directories. One reason for this is because we won't accurately + * calculate hashes of NTFS directories that have content that spans the + * IDX_ROOT and IDX_ALLOC artifacts. So we disable that until a solution + * for it is developed. + */ + if (file.isDir()) { + return true; + } + + // bail out if we have no hashes set + if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) { + return true; + } + + return false; + } + + /** + * Reports an error when an issue is encountered looking up a file. + * + * @param ex The exception thrown in the error. + * @param file The file for which this error applies. + * @param lookupErrorMessage The function that generates an error message + * specific to which piece of the ingest + * processing failed. + */ + private void reportLookupError(TskException ex, AbstractFile file, Function lookupErrorMessage) { + logger.log(Level.WARNING, String.format( + "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", file.getName()), + lookupErrorMessage.apply(file))); + } + + /** + * The result of attempting to find a file in a list of HashDB objects. + */ + private static class FindInHashsetsResult { + + private final boolean found; + private final boolean error; + + FindInHashsetsResult(boolean found, boolean error) { + this.found = found; + this.error = error; + } + + /** + * Returns true if the file was found in the HashDB. + * + * @return True if the file was found in the HashDB. + */ + boolean isFound() { + return found; + } + + /** + * Returns true if there was an error in the process of finding a file + * in a HashDB. + * + * @return True if there was an error in the process of finding a file + * in a HashDB. + */ + boolean isError() { + return error; + } + } + + /** + * Attempts to find an abstract file in a list of HashDB objects. + * + * @param file The file to find. + * @param totalCount The total cound of files found in this type + * @param totalLookupTime The counter tracking the total amount of run + * time for this operation. + * @param hashSets The HashDB objects to cycle through looking for + * a hash hit. + * @param statusIfFound The FileKnown status to set on the file if the + * file is found in the hashSets. + * @param lookupErrorMessage The function that generates a message should + * there be an error in looking up the file in the + * hashSets. + * + * @return Whether or not the file was found and whether or not there was an + * error during the operation. + */ + private FindInHashsetsResult findInHashsets(AbstractFile file, AtomicLong totalCount, AtomicLong totalLookupTime, + List hashSets, TskData.FileKnown statusIfFound, Function lookupErrorMessage) { + + boolean found = false; + boolean wasError = false; + for (HashDb db : hashSets) { + try { + long lookupstart = System.currentTimeMillis(); + HashHitInfo hashInfo = db.lookupMD5(file); + if (null != hashInfo) { + found = true; + + totalCount.incrementAndGet(); + file.setKnown(statusIfFound); + String hashSetName = db.getDisplayName(); + String comment = generateComment(hashInfo); + if (!createArtifactIfNotExists(hashSetName, file, comment, db)) { + wasError = true; + } + } + long delta = (System.currentTimeMillis() - lookupstart); + totalLookupTime.addAndGet(delta); + + } catch (TskException ex) { + reportLookupError(ex, file, lookupErrorMessage); + wasError = true; + } + } + + return new FindInHashsetsResult(found, wasError); + } + + /** + * Generates a formatted comment. + * + * @param hashInfo The HashHitInfo. + * + * @return The formatted comment. + */ + private String generateComment(HashHitInfo hashInfo) { + String comment = ""; + ArrayList comments = hashInfo.getComments(); + int i = 0; + for (String c : comments) { + if (++i > 1) { + comment += " "; + } + comment += c; + if (comment.length() > MAX_COMMENT_SIZE) { + comment = comment.substring(0, MAX_COMMENT_SIZE) + "..."; + break; + } + } + return comment; + } + + /** + * Creates a BlackboardArtifact if artifact does not already exist. + * + * @param hashSetName The name of the hashset found. + * @param file The file that had a hash hit. + * @param comment The comment to associate with this artifact. + * @param db the database in which this file was found. + * + * @return True if the operation occurred successfully and without error. + */ + private boolean createArtifactIfNotExists(String hashSetName, AbstractFile file, String comment, HashDb db) { + /* + * We have a match. Now create an artifact if it is determined that one + * hasn't been created yet. + */ + List attributesList = new ArrayList<>(); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, HashLookupModuleFactory.getModuleName(), hashSetName)); + try { + Blackboard tskBlackboard = skCase.getBlackboard(); + if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) == false) { + postHashSetHitToBlackboard(file, file.getMd5Hash(), hashSetName, comment, db.getSendIngestMessages()); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format( + "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(file.getName()), + Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(file.getName()))); + return false; + } + return true; + } + + /** + * Retrieves the md5 hash for a file or generates one if no one exists on + * the file. + * + * @param file The file in order to determine the hash. + * @param totals The timing metrics for this process. + * + * @return The found or determined md5 hash or null if none could be + * determined. + */ + private String getHash(AbstractFile file, IngestJobTotals totals) { + String md5Hash = file.getMd5Hash(); + if (md5Hash != null && md5Hash.isEmpty()) { + return md5Hash; + } + + try { + TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); + long calcstart = System.currentTimeMillis(); + md5Hash = HashUtility.calculateMd5Hash(file); + if (file.getSize() > 0) { + // Surprisingly, the hash calculation does not seem to be correlated that + // strongly with file size until the files get large. + // Only normalize if the file size is greater than ~1MB. + if (file.getSize() < 1000000) { + HealthMonitor.submitTimingMetric(metric); + } else { + // In testing, this normalization gave reasonable resuls + HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + } + } + file.setMd5Hash(md5Hash); + long delta = (System.currentTimeMillis() - calcstart); + totals.totalCalctime.addAndGet(delta); + return md5Hash; + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr", + file.getParentPath() + file.getName(), + file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File"))); + return null; + } + } + /** * Post a hash set hit to the blackboard. * @@ -413,35 +592,35 @@ public class HashDbIngestModule implements FileIngestModule { * @param knownBadHashSets The list of hash sets for "known bad" files. * @param knownHashSets The list of hash sets for "known" files. */ - private static synchronized void postSummary(long jobId, - List knownBadHashSets, List knownHashSets) { + @Messages("HashDbIngestModule.complete.noChangesFound=No Change items found:") + private static synchronized void postSummary(long jobId, List knownBadHashSets, + List noChangeHashSets, List knownHashSets) { + IngestJobTotals jobTotals = getTotalsForIngestJobs(jobId); totalsForIngestJobs.remove(jobId); - if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty())) { + if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty()) || (!noChangeHashSets.isEmpty())) { StringBuilder detailsSb = new StringBuilder(); //details - detailsSb.append(""); //NON-NLS + detailsSb.append( + "
" + + "" + + "" + + + "" + + "" + + + "\n" + + + "\n
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.knownBadsFound") + "" + jobTotals.totalKnownBadCount.get() + "
" + Bundle.HashDbIngestModule_complete_noChangesFound() + "" + jobTotals.totalNoChangeCount.get() + "
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalCalcTime") + + "" + jobTotals.totalCalctime.get() + "
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalLookupTime") + + "" + jobTotals.totalLookuptime.get() + "
" + - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.knownBadsFound")) - .append(""); //NON-NLS - detailsSb.append("").append(jobTotals.totalKnownBadCount.get()).append(""); //NON-NLS - - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalCalcTime")) - .append("").append(jobTotals.totalCalctime.get()).append("\n"); //NON-NLS - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalLookupTime")) - .append("").append(jobTotals.totalLookuptime.get()).append("\n"); //NON-NLS - detailsSb.append(""); //NON-NLS - - detailsSb.append("

") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.databasesUsed")) - .append("

\n
    "); //NON-NLS - for (HashDb db : knownBadHashSets) { - detailsSb.append("
  • ").append(db.getHashSetName()).append("
  • \n"); //NON-NLS - } + "

    " + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.databasesUsed") + "

    \n
      "); //NON-NLS + + Stream.concat(knownBadHashSets.stream(), noChangeHashSets.stream()).forEach((db) -> { + detailsSb.append("
    • " + db.getHashSetName() + "
    • \n"); //NON-NLS + }); detailsSb.append("
    "); //NON-NLS @@ -456,7 +635,7 @@ public class HashDbIngestModule implements FileIngestModule { @Override public void shutDown() { if (refCounter.decrementAndGet(jobId) == 0) { - postSummary(jobId, knownBadHashSets, knownHashSets); + postSummary(jobId, knownBadHashSets, noChangeHashSets, knownHashSets); } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index c6ae0d5ef4..7559649689 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,7 +23,6 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -31,6 +30,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Stream; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.SwingWorker; @@ -58,6 +58,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; /** * This class implements a singleton that manages the set of hash databases used @@ -103,8 +104,8 @@ public class HashDbManager implements PropertyChangeListener { public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } - - synchronized boolean verifyAllDatabasesLoadedCorrectly(){ + + synchronized boolean verifyAllDatabasesLoadedCorrectly() { return allDatabasesLoadedCorrectly; } @@ -238,7 +239,7 @@ public class HashDbManager implements PropertyChangeListener { } return hashDb; } - + 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); @@ -273,22 +274,22 @@ public class HashDbManager implements PropertyChangeListener { } return hashDb; } - - CentralRepoHashSet addExistingCentralRepoHashSet(String hashSetName, String version, int referenceSetID, - boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType, - boolean readOnly) throws TskCoreException{ - - if(! CentralRepository.isEnabled()){ + + CentralRepoHashSet addExistingCentralRepoHashSet(String hashSetName, String version, int referenceSetID, + boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType, + boolean readOnly) throws TskCoreException { + + if (!CentralRepository.isEnabled()) { throw new TskCoreException("Could not load central repository hash set " + hashSetName + " - central repository is not enabled"); } - + CentralRepoHashSet db = new CentralRepoHashSet(hashSetName, version, referenceSetID, searchDuringIngest, - sendIngestMessages, knownFilesType, readOnly); - - if(! db.isValid()){ + sendIngestMessages, knownFilesType, readOnly); + + if (!db.isValid()) { throw new TskCoreException("Error finding hash set " + hashSetName + " in central repository"); } - + // Add the hash database to the collection hashSets.add(db); @@ -302,8 +303,8 @@ public class HashDbManager implements PropertyChangeListener { NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErrorListeningToUpdatesMsg"), MessageNotifyUtil.MessageType.ERROR); } - return db; - + return db; + } synchronized void indexHashDatabase(SleuthkitHashSet hashDb) { @@ -341,7 +342,7 @@ public class HashDbManager implements PropertyChangeListener { this.removeHashDatabaseNoSave(hashDb); this.save(); } - + public synchronized void removeHashDatabaseNoSave(HashDb hashDb) throws HashDbManagerException { // Don't remove a database if ingest is running boolean ingestIsRunning = IngestManager.getInstance().isIngestRunning(); @@ -357,17 +358,16 @@ public class HashDbManager implements PropertyChangeListener { hashSets.remove(hashDb); // Now undertake the operations that could throw. - // Indexing is only relevanet for sleuthkit hashsets - if(hashDb instanceof SleuthkitHashSet){ - SleuthkitHashSet hashDatabase = (SleuthkitHashSet)hashDb; + if (hashDb instanceof SleuthkitHashSet) { + SleuthkitHashSet hashDatabase = (SleuthkitHashSet) hashDb; try { - if(hashDatabase.hasIndex()){ + if (hashDatabase.hasIndex()) { hashSetPaths.remove(hashDatabase.getIndexPath()); } } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS - } + } try { if (!hashDatabase.hasIndexOnly()) { @@ -376,7 +376,7 @@ public class HashDbManager implements PropertyChangeListener { } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS } - + try { hashDatabase.close(); } catch (TskCoreException ex) { @@ -405,7 +405,7 @@ public class HashDbManager implements PropertyChangeListener { throw new HashDbManagerException(NbBundle.getMessage(this.getClass(), "HashDbManager.saveErrorExceptionMsg")); } } - + /** * Gets all of the hash databases used to classify files as known or known * bad. Will add any new central repository databases to the list before @@ -414,12 +414,12 @@ public class HashDbManager implements PropertyChangeListener { * @return A list, possibly empty, of hash databases. */ public synchronized List getAllHashSets() { - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } - + List hashDbs = new ArrayList<>(); hashDbs.addAll(this.hashSets); return hashDbs; @@ -432,9 +432,9 @@ public class HashDbManager implements PropertyChangeListener { */ public synchronized List getKnownFileHashSets() { List hashDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } 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) -> { @@ -450,9 +450,9 @@ public class HashDbManager implements PropertyChangeListener { */ public synchronized List getKnownBadFileHashSets() { List hashDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } 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) -> { @@ -472,9 +472,9 @@ public class HashDbManager implements PropertyChangeListener { private List getUpdateableHashSets(List hashDbs) { ArrayList updateableDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } for (HashDb db : hashDbs) { @@ -488,34 +488,27 @@ public class HashDbManager implements PropertyChangeListener { } return updateableDbs; } - - private List getCentralRepoHashSetsFromDatabase(){ + + private List getCentralRepoHashSetsFromDatabase() { List crHashSets = new ArrayList<>(); - if(CentralRepository.isEnabled()){ - try{ + if (CentralRepository.isEnabled()) { + try { List crSets = CentralRepository.getInstance().getAllReferenceSets(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - for(CentralRepoFileSet globalSet:crSets){ - + for (CentralRepoFileSet globalSet : crSets) { + // Defaults for fields not stored in the central repository: // searchDuringIngest: false // sendIngestMessages: true if the hash set is notable - boolean sendIngestMessages = convertFileKnown(globalSet.getFileKnownStatus()).equals(HashDb.KnownFilesType.KNOWN_BAD); + boolean sendIngestMessages = KnownFilesType.fromFileKnown(globalSet.getFileKnownStatus()).equals(HashDb.KnownFilesType.KNOWN_BAD); crHashSets.add(new HashDbInfo(globalSet.getSetName(), globalSet.getVersion(), - globalSet.getGlobalSetID(), convertFileKnown(globalSet.getFileKnownStatus()), globalSet.isReadOnly(), false, sendIngestMessages)); - } - } catch (CentralRepoException ex){ + globalSet.getGlobalSetID(), KnownFilesType.fromFileKnown(globalSet.getFileKnownStatus()), globalSet.isReadOnly(), false, sendIngestMessages)); + } + } catch (CentralRepoException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } } return crHashSets; } - - private static HashDb.KnownFilesType convertFileKnown(TskData.FileKnown fileKnown){ - if(fileKnown.equals(TskData.FileKnown.BAD)){ - return HashDb.KnownFilesType.KNOWN_BAD; - } - return HashDb.KnownFilesType.KNOWN; - } /** * Restores the last saved hash sets configuration. This supports @@ -531,9 +524,9 @@ public class HashDbManager implements PropertyChangeListener { private void closeHashDatabases(List hashDatabases) { for (HashDb database : hashDatabases) { - if(database instanceof SleuthkitHashSet){ + if (database instanceof SleuthkitHashSet) { try { - ((SleuthkitHashSet)database).close(); + ((SleuthkitHashSet) database).close(); } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + database.getHashSetName() + " hash set", ex); //NON-NLS } @@ -558,13 +551,13 @@ public class HashDbManager implements PropertyChangeListener { * @param settings The settings to configure. */ @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"}) + "HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets"}) private void configureSettings(HashLookupSettings settings) { allDatabasesLoadedCorrectly = true; List hashDbInfoList = settings.getHashDbInfo(); for (HashDbInfo hashDbInfo : hashDbInfoList) { try { - if(hashDbInfo.isFileDatabaseType()){ + 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()); @@ -573,10 +566,10 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } else { - if(CentralRepository.isEnabled()){ - addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), - hashDbInfo.getReferenceSetID(), - hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), + if (CentralRepository.isEnabled()) { + addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), + hashDbInfo.getReferenceSetID(), + hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), hashDbInfo.isReadOnly()); } } @@ -590,13 +583,13 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } - - if(CentralRepository.isEnabled()){ - try{ + + if (CentralRepository.isEnabled()) { + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } 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"), @@ -604,14 +597,17 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } - - /* NOTE: When RuntimeProperties.coreComponentsAreActive() is "false", - I don't think we should overwrite hash db settings file because we - were unable to load a database. The user should have to fix the issue or - remove the database from settings. Overwiting the settings effectively removes - the database from HashLookupSettings and the user may not know about this - because the dialogs are not being displayed. The next time user starts Autopsy, HashDB - will load without errors and the user may think that the problem was solved.*/ + + /* + * NOTE: When RuntimeProperties.coreComponentsAreActive() is "false", I + * don't think we should overwrite hash db settings file because we were + * unable to load a database. The user should have to fix the issue or + * remove the database from settings. Overwiting the settings + * effectively removes the database from HashLookupSettings and the user + * may not know about this because the dialogs are not being displayed. + * The next time user starts Autopsy, HashDB will load without errors + * and the user may think that the problem was solved. + */ if (!allDatabasesLoadedCorrectly && RuntimeProperties.runningWithGUI()) { try { HashLookupSettings.writeSettings(new HashLookupSettings(HashLookupSettings.convertHashSetList(this.hashSets))); @@ -622,31 +618,31 @@ public class HashDbManager implements PropertyChangeListener { } } } - + private void updateHashSetsFromCentralRepository() throws TskCoreException { - if(CentralRepository.isEnabled()){ + if (CentralRepository.isEnabled()) { List crHashDbInfoList = getCentralRepoHashSetsFromDatabase(); - for(HashDbInfo hashDbInfo : crHashDbInfoList) { - if(hashDbInfoIsNew(hashDbInfo)){ - addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), - hashDbInfo.getReferenceSetID(), - hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), - hashDbInfo.isReadOnly()); + for (HashDbInfo hashDbInfo : crHashDbInfoList) { + if (hashDbInfoIsNew(hashDbInfo)) { + addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), + hashDbInfo.getReferenceSetID(), + hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), + hashDbInfo.isReadOnly()); } } } } - - private boolean hashDbInfoIsNew(HashDbInfo dbInfo){ - for(HashDb db:this.hashSets){ - if(dbInfo.matches(db)){ + + private boolean hashDbInfoIsNew(HashDbInfo dbInfo) { + for (HashDb db : this.hashSets) { + if (dbInfo.matches(db)) { return false; } } return true; } - private String getValidFilePath(String hashSetName, String configuredPath) { + private String getValidFilePath(String hashSetName, String configuredPath) { // Check the configured path. File database = new File(configuredPath); if (database.exists()) { @@ -655,12 +651,12 @@ public class HashDbManager implements PropertyChangeListener { // Give the user an opportunity to find the desired file. String newPath = null; - if (RuntimeProperties.runningWithGUI() && - JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", - hashSetName, configuredPath), - NbBundle.getMessage(this.getClass(), "HashDbManager.dlgTitle.MissingDb"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + if (RuntimeProperties.runningWithGUI() + && JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", + hashSetName, configuredPath), + NbBundle.getMessage(this.getClass(), "HashDbManager.dlgTitle.MissingDb"), + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { newPath = searchForFile(); if (null != newPath && !newPath.isEmpty()) { database = new File(newPath); @@ -692,26 +688,89 @@ public class HashDbManager implements PropertyChangeListener { } return filePath; } - + public static abstract class HashDb { - + /** * Indicates how files with hashes stored in a particular hash database * object should be classified. */ + @Messages({ + "HashDbManager.noChange.text=No Change", + "HashDbManager.known.text=Known", + "HashDbManager.knownBad.text=Notable" + }) public enum KnownFilesType { - KNOWN(NbBundle.getMessage(HashDbManager.class, "HashDbManager.known.text")), - KNOWN_BAD(NbBundle.getMessage(HashDbManager.class, "HashDbManager.knownBad.text")); - private final String displayName; + 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); - private KnownFilesType(String displayName) { + private final String displayName; + private final TskData.FileKnown fileKnown; + private final boolean allowSendInboxMessages; + private final boolean defaultSendInboxMessages; + + KnownFilesType(String displayName, TskData.FileKnown fileKnown, boolean allowSendInboxMessages, boolean defaultSendInboxMessages) { this.displayName = displayName; + this.fileKnown = fileKnown; + this.allowSendInboxMessages = allowSendInboxMessages; + this.defaultSendInboxMessages = defaultSendInboxMessages; + } + + /** + * Returns whether or not it is allowable to send inbox messages + * with this known files type. + * + * @return Whether or not it is allowable to send inbox messages + * with this known files type. + */ + boolean isInboxMessagesAllowed() { + return allowSendInboxMessages; + } + + /** + * Returns whether or not by default for this type is to send inbox + * messages. + * + * @return Whether or not by default for this type is to send inbox + * messages. + */ + boolean isDefaultInboxMessages() { + return defaultSendInboxMessages; } public String getDisplayName() { return this.displayName; } + + /** + * Retrieves the corresponding TskData.FileKnown enum type that + * relates to this. + * + * @return The corresponding TskData.FileKnown. + */ + TskData.FileKnown getFileKnown() { + return this.fileKnown; + } + + /** + * Converts a TskData.FileKnown to the corresponding KnownFilesType. + * + * @param fileKnown The TskData.FileKnown type. + * + * @return The corresponding KnownFilesType. + */ + static KnownFilesType fromFileKnown(TskData.FileKnown fileKnown) { + if (fileKnown == null) { + return null; + } + + return Stream.of(KnownFilesType.values()) + .filter((type) -> type.getFileKnown() == fileKnown) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown TskData.FileKnown type: " + fileKnown)); + } } /** @@ -721,9 +780,9 @@ public class HashDbManager implements PropertyChangeListener { INDEXING_DONE } - + public abstract String getHashSetName(); - + abstract String getDisplayName(); public abstract String getDatabasePath() throws TskCoreException; @@ -731,7 +790,7 @@ public class HashDbManager implements PropertyChangeListener { public abstract HashDb.KnownFilesType getKnownFilesType(); public abstract boolean getSearchDuringIngest(); - + abstract void setSearchDuringIngest(boolean useForIngest); public abstract boolean getSendIngestMessages(); @@ -764,28 +823,30 @@ public class HashDbManager implements PropertyChangeListener { public abstract boolean lookupMD5Quick(Content content) throws TskCoreException; public abstract HashHitInfo lookupMD5(Content content) throws TskCoreException; - + /** - * Returns whether this database can be enabled. - * For file type, this is the same as checking that it has an index + * Returns whether this database can be enabled. For file type, this is + * the same as checking that it has an index + * * @return true if is valid, false otherwise - * @throws TskCoreException + * + * @throws TskCoreException */ abstract boolean isValid() throws TskCoreException; - + public abstract String getIndexPath() throws TskCoreException; - + public abstract boolean hasIndexOnly() throws TskCoreException; - + public abstract void firePropertyChange(String propertyName, Object oldValue, Object newValue); - + public abstract void addPropertyChangeListener(PropertyChangeListener pcl); - + public abstract void removePropertyChangeListener(PropertyChangeListener pcl); - + @Override public abstract String toString(); - + } /** @@ -793,13 +854,13 @@ public class HashDbManager implements PropertyChangeListener { * as known or know bad. */ class SleuthkitHashSet extends HashDb { - + private static final long serialVersionUID = 1L; private final int handle; private final String hashSetName; private boolean searchDuringIngest; private boolean sendIngestMessages; - private final HashDb.KnownFilesType knownFilesType; + private final HashDb.KnownFilesType knownFilesType; private boolean indexing; private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); @@ -813,8 +874,8 @@ public class HashDbManager implements PropertyChangeListener { } /** - * Adds a listener for the events defined in HashDb.Event. - * Listeners are used during indexing. + * Adds a listener for the events defined in HashDb.Event. Listeners are + * used during indexing. * * @param pcl */ @@ -832,8 +893,8 @@ public class HashDbManager implements PropertyChangeListener { public void removePropertyChangeListener(PropertyChangeListener pcl) { propertyChangeSupport.removePropertyChangeListener(pcl); } - - int getHandle(){ + + int getHandle() { return handle; } @@ -841,9 +902,9 @@ public class HashDbManager implements PropertyChangeListener { public String getHashSetName() { return hashSetName; } - + @Override - String getDisplayName(){ + String getDisplayName() { return getHashSetName(); } @@ -851,9 +912,9 @@ public class HashDbManager implements PropertyChangeListener { public String getDatabasePath() throws TskCoreException { return SleuthkitJNI.getHashDatabasePath(handle); } - - public void setIndexing(boolean indexing){ - this.indexing = indexing; + + public void setIndexing(boolean indexing) { + this.indexing = indexing; } @Override @@ -989,12 +1050,14 @@ public class HashDbManager implements PropertyChangeListener { } return result; } - + /** - * Returns whether this database can be enabled. - * For file type, this is the same as checking that it has an index + * Returns whether this database can be enabled. For file type, this is + * the same as checking that it has an index + * * @return true if is valid, false otherwise - * @throws TskCoreException + * + * @throws TskCoreException */ @Override boolean isValid() throws TskCoreException { @@ -1017,21 +1080,20 @@ public class HashDbManager implements PropertyChangeListener { boolean isIndexing() { return indexing; } - + @Override - public void firePropertyChange(String propertyName, Object oldValue, Object newValue){ + public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } private void close() throws TskCoreException { SleuthkitJNI.closeHashDatabase(handle); } - + @Override - public String toString(){ + public String toString() { return getHashSetName(); } - @Override public int hashCode() { @@ -1066,13 +1128,13 @@ public class HashDbManager implements PropertyChangeListener { * Instances of this class represent hash databases used to classify files * as known or know bad. */ - class CentralRepoHashSet extends HashDb{ + class CentralRepoHashSet extends HashDb { private static final long serialVersionUID = 1L; private final String hashSetName; private boolean searchDuringIngest; private boolean sendIngestMessages; - private final HashDb.KnownFilesType knownFilesType; + private final HashDb.KnownFilesType knownFilesType; private final int referenceSetID; private final String version; private String orgName; @@ -1080,10 +1142,10 @@ public class HashDbManager implements PropertyChangeListener { private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); @Messages({"HashDbManager.CentralRepoHashDb.orgError=Error loading organization"}) - private CentralRepoHashSet(String hashSetName, String version, int referenceSetID, - boolean useForIngest, boolean sendHitMessages, HashDb.KnownFilesType knownFilesType, + private CentralRepoHashSet(String hashSetName, String version, int referenceSetID, + boolean useForIngest, boolean sendHitMessages, HashDb.KnownFilesType knownFilesType, boolean readOnly) - throws TskCoreException{ + throws TskCoreException { this.hashSetName = hashSetName; this.version = version; this.referenceSetID = referenceSetID; @@ -1091,18 +1153,18 @@ public class HashDbManager implements PropertyChangeListener { this.sendIngestMessages = sendHitMessages; this.knownFilesType = knownFilesType; this.readOnly = readOnly; - - try{ + + try { orgName = CentralRepository.getInstance().getReferenceSetOrganization(referenceSetID).getName(); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error looking up central repository organization for reference set " + referenceSetID, ex); //NON-NLS orgName = Bundle.HashDbManager_CentralRepoHashDb_orgError(); } } /** - * Adds a listener for the events defined in HashDb.Event. - * Listeners are used during indexing. + * Adds a listener for the events defined in HashDb.Event. Listeners are + * used during indexing. * * @param pcl */ @@ -1120,9 +1182,9 @@ public class HashDbManager implements PropertyChangeListener { public void removePropertyChangeListener(PropertyChangeListener pcl) { propertyChangeSupport.removePropertyChangeListener(pcl); } - + @Override - public boolean hasIndexOnly() throws TskCoreException{ + public boolean hasIndexOnly() throws TskCoreException { return true; } @@ -1130,25 +1192,25 @@ public class HashDbManager implements PropertyChangeListener { public String getHashSetName() { return hashSetName; } - + @Override - public String getDisplayName(){ - if(! getVersion().isEmpty()){ + public String getDisplayName() { + if (!getVersion().isEmpty()) { return getHashSetName() + " " + getVersion() + " (remote)"; } else { return getHashSetName() + " (remote)"; } } - - String getVersion(){ + + String getVersion() { return version; } - - String getOrgName(){ + + String getOrgName() { return orgName; } - - int getReferenceSetID(){ + + int getReferenceSetID() { return referenceSetID; } @@ -1196,7 +1258,7 @@ public class HashDbManager implements PropertyChangeListener { */ @Override public boolean isUpdateable() throws TskCoreException { - return (! readOnly); + return (!readOnly); } /** @@ -1229,18 +1291,13 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - TskData.FileKnown type; - if(knownFilesType.equals(HashDb.KnownFilesType.KNOWN_BAD)){ - type = TskData.FileKnown.BAD; - } else { - type = TskData.FileKnown.KNOWN; - } - - try{ + TskData.FileKnown type = knownFilesType.getFileKnown(); + + try { CentralRepoFileInstance fileInstance = new CentralRepoFileInstance(referenceSetID, file.getMd5Hash(), - type, comment); - CentralRepository.getInstance().addReferenceInstance(fileInstance,CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + type, comment); + CentralRepository.getInstance().addReferenceInstance(fileInstance, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); //NON-NLS } } @@ -1257,24 +1314,20 @@ public class HashDbManager implements PropertyChangeListener { @Override public void addHashes(List hashes) throws TskCoreException { Set globalFileInstances = new HashSet<>(); - for(HashEntry hashEntry:hashes){ - TskData.FileKnown type; - if(knownFilesType.equals(HashDb.KnownFilesType.KNOWN_BAD)){ - type = TskData.FileKnown.BAD; - } else { - type = TskData.FileKnown.KNOWN; - } + for (HashEntry hashEntry : hashes) { + TskData.FileKnown type = knownFilesType.getFileKnown(); + try { globalFileInstances.add(new CentralRepoFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment())); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); } } - - try{ - CentralRepository.getInstance().bulkInsertReferenceTypeEntries(globalFileInstances, + + try { + CentralRepository.getInstance().bulkInsertReferenceTypeEntries(globalFileInstances, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); } } @@ -1295,9 +1348,9 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - try{ + try { return CentralRepository.getInstance().isFileHashInReferenceSet(file.getMd5Hash(), this.referenceSetID); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash " + file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS throw new TskCoreException("Error performing central reposiotry hash lookup", ex); @@ -1324,12 +1377,9 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - try{ - if(CentralRepository.getInstance().isFileHashInReferenceSet(file.getMd5Hash(), this.referenceSetID)){ - // Make a bare-bones HashHitInfo for now - result = new HashHitInfo(file.getMd5Hash(), "", ""); - } - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + try { + return CentralRepository.getInstance().lookupHash(file.getMd5Hash(), referenceSetID); + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash " + file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS throw new TskCoreException("Error performing central reposiotry hash lookup", ex); @@ -1338,35 +1388,34 @@ public class HashDbManager implements PropertyChangeListener { } return result; } - + /** * Returns whether this database can be enabled. - * + * * @return true if is valid, false otherwise */ @Override boolean isValid() { - if(! CentralRepository.isEnabled()) { + if (!CentralRepository.isEnabled()) { return false; } - try{ + try { return CentralRepository.getInstance().referenceSetIsValid(this.referenceSetID, this.hashSetName, this.version); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(CentralRepoHashSet.class.getName()).log(Level.SEVERE, "Error validating hash set " + hashSetName, ex); //NON-NLS return false; } } - + @Override - public void firePropertyChange(String propertyName, Object oldValue, Object newValue){ + public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } - + @Override - public String toString(){ + public String toString() { return getDisplayName(); } - @Override public int hashCode() { @@ -1398,8 +1447,8 @@ public class HashDbManager implements PropertyChangeListener { } return true; } - } - + } + /** * Worker thread to make an index of a database */ diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form index c0a486fce9..3e2e525560 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form @@ -25,16 +25,12 @@ - + - - - - - + @@ -46,22 +42,25 @@ - + - + - - - - - + - + + + + + + + + @@ -75,7 +74,7 @@ - + @@ -86,20 +85,6 @@ - - - - - - - - - - - - - - @@ -121,39 +106,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java index 6dcd567226..e804e0bea7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,13 +27,13 @@ import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; +import org.apache.commons.lang.StringUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; - /** * Ingest job settings panel for hash lookup file ingest modules. */ @@ -42,10 +42,8 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe private static final long serialVersionUID = 1L; private final HashDbManager hashDbManager = HashDbManager.getInstance(); - private final List knownHashSetModels = new ArrayList<>(); - private final HashSetsTableModel knownHashSetsTableModel = new HashSetsTableModel(knownHashSetModels); - private final List knownBadHashSetModels = new ArrayList<>(); - private final HashSetsTableModel knownBadHashSetsTableModel = new HashSetsTableModel(knownBadHashSetModels); + private final List hashSetModels = new ArrayList<>(); + private final HashSetsTableModel hashSetsTableModel = new HashSetsTableModel(hashSetModels); HashLookupModuleSettingsPanel(HashLookupModuleSettings settings) { initializeHashSetModels(settings); @@ -54,11 +52,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe } private void initializeHashSetModels(HashLookupModuleSettings settings) { - initializeHashSetModels(settings, validSetsOnly(hashDbManager.getKnownFileHashSets()), knownHashSetModels); - initializeHashSetModels(settings, validSetsOnly(hashDbManager.getKnownBadFileHashSets()), knownBadHashSetModels); - } - - private void initializeHashSetModels(HashLookupModuleSettings settings, List hashDbs, List hashSetModels) { + List hashDbs = validSetsOnly(hashDbManager.getAllHashSets()); hashSetModels.clear(); for (HashDb db : hashDbs) { hashSetModels.add(new HashSetModel(db, settings.isHashSetEnabled(db), isHashDbValid(db))); @@ -66,8 +60,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe } private void customizeComponents(HashLookupModuleSettings settings) { - customizeHashSetsTable(jScrollPane1, knownHashTable, knownHashSetsTableModel); - customizeHashSetsTable(jScrollPane2, knownBadHashTable, knownBadHashSetsTableModel); + customizeHashSetsTable(hashDbsScrollPane, hashTable, hashSetsTableModel); alwaysCalcHashesCheckbox.setSelected(settings.shouldCalculateHashes()); hashDbManager.addPropertyChangeListener(this); alwaysCalcHashesCheckbox.setText("" + org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text") + ""); // NOI18N NON-NLS @@ -78,7 +71,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe table.setTableHeader(null); table.setRowSelectionAllowed(false); final int width1 = scrollPane.getPreferredSize().width; - knownHashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + hashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); TableColumn column; for (int i = 0; i < table.getColumnCount(); i++) { column = table.getColumnModel().getColumn(i); @@ -103,8 +96,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe public IngestModuleIngestJobSettings getSettings() { List enabledHashSets = new ArrayList<>(); List disabledHashSets = new ArrayList<>(); - addHashSets(knownHashSetModels, enabledHashSets, disabledHashSets); - addHashSets(knownBadHashSetModels, enabledHashSets, disabledHashSets); + addHashSets(hashSetModels, enabledHashSets, disabledHashSets); return new HashLookupModuleSettings(alwaysCalcHashesCheckbox.isSelected(), enabledHashSets, disabledHashSets); } @@ -121,46 +113,41 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe void update() { updateHashSetModels(); - knownHashSetsTableModel.fireTableDataChanged(); - knownBadHashSetsTableModel.fireTableDataChanged(); + hashSetsTableModel.fireTableDataChanged(); } - private void updateHashSetModels() { - updateHashSetModels(validSetsOnly(hashDbManager.getKnownFileHashSets()), knownHashSetModels); - updateHashSetModels(validSetsOnly(hashDbManager.getKnownBadFileHashSets()), knownBadHashSetModels); - } - - private List validSetsOnly(List hashDbs){ + private List validSetsOnly(List hashDbs) { List validDbs = new ArrayList<>(); - for(HashDb db:hashDbs){ - try{ - if(db.isValid()){ + for (HashDb db : hashDbs) { + try { + if (db.isValid()) { validDbs.add(db); } - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashLookupModuleSettingsPanel.class.getName()).log(Level.SEVERE, "Error checking validity for hash set (name = " + db.getHashSetName() + ")", ex); //NON-NLS } } return validDbs; } - void updateHashSetModels(List hashDbs, List hashSetModels) { - + void updateHashSetModels() { + List hashDbs = validSetsOnly(hashDbManager.getAllHashSets()); + List hashDatabases = new ArrayList<>(hashDbs); - + // Update the hash sets and detect deletions. List deletedHashSetModels = new ArrayList<>(); for (HashSetModel model : hashSetModels) { boolean foundDatabase = false; - for(HashDb db : hashDatabases){ - if(model.getDatabase().equals(db)){ + for (HashDb db : hashDatabases) { + if (model.getDatabase().equals(db)) { model.setValid(isHashDbValid(db)); hashDatabases.remove(db); foundDatabase = true; break; } } - if(! foundDatabase){ + if (!foundDatabase) { deletedHashSetModels.add(model); } } @@ -179,8 +166,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe void reset(HashLookupModuleSettings newSettings) { initializeHashSetModels(newSettings); alwaysCalcHashesCheckbox.setSelected(newSettings.shouldCalculateHashes()); - knownHashSetsTableModel.fireTableDataChanged(); - knownBadHashSetsTableModel.fireTableDataChanged(); + hashSetsTableModel.fireTableDataChanged(); } private boolean isHashDbValid(HashDb hashDb) { @@ -204,8 +190,8 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe this.enabled = enabled; this.valid = valid; } - - HashDb getDatabase(){ + + HashDb getDatabase() { return db; } @@ -213,6 +199,16 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe return db.getDisplayName(); } + String getFormattedName() { + String knownTypeName = (db != null && db.getKnownFilesType() != null) ? db.getKnownFilesType().getDisplayName() : ""; + if (!StringUtils.isBlank(knownTypeName)) { + knownTypeName = String.format(" (%s)", knownTypeName); + } + + String displayName = db != null ? db.getDisplayName() : ""; + return displayName + knownTypeName; + } + void setEnabled(boolean enabled) { this.enabled = enabled; } @@ -254,7 +250,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe if (columnIndex == 0) { return hashSets.get(rowIndex).isEnabled(); } else { - return hashSets.get(rowIndex).getName(); + return hashSets.get(rowIndex).getFormattedName(); } } @@ -285,26 +281,21 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe // //GEN-BEGIN:initComponents private void initComponents() { - jScrollPane1 = new javax.swing.JScrollPane(); - knownHashTable = new javax.swing.JTable(); - knownBadHashDbsLabel = new javax.swing.JLabel(); - knownHashDbsLabel = new javax.swing.JLabel(); + hashDbsLabel = new javax.swing.JLabel(); + hashDbsScrollPane = new javax.swing.JScrollPane(); + hashTable = new javax.swing.JTable(); alwaysCalcHashesCheckbox = new javax.swing.JCheckBox(); - jScrollPane2 = new javax.swing.JScrollPane(); - knownBadHashTable = new javax.swing.JTable(); setPreferredSize(new java.awt.Dimension(292, 150)); - jScrollPane1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + hashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.hashDbsLabel.text")); // NOI18N - knownHashTable.setBackground(new java.awt.Color(240, 240, 240)); - knownHashTable.setShowHorizontalLines(false); - knownHashTable.setShowVerticalLines(false); - jScrollPane1.setViewportView(knownHashTable); + hashDbsScrollPane.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - knownBadHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text")); // NOI18N - - knownHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.knownHashDbsLabel.text")); // NOI18N + hashTable.setBackground(new java.awt.Color(240, 240, 240)); + hashTable.setShowHorizontalLines(false); + hashTable.setShowVerticalLines(false); + hashDbsScrollPane.setViewportView(hashTable); alwaysCalcHashesCheckbox.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text")); // NOI18N alwaysCalcHashesCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.toolTipText")); // NOI18N @@ -314,21 +305,6 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe alwaysCalcHashesCheckbox.setVerticalAlignment(javax.swing.SwingConstants.TOP); alwaysCalcHashesCheckbox.setVerticalTextPosition(javax.swing.SwingConstants.TOP); - jScrollPane2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - - knownBadHashTable.setBackground(new java.awt.Color(240, 240, 240)); - knownBadHashTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - - } - )); - knownBadHashTable.setShowHorizontalLines(false); - knownBadHashTable.setShowVerticalLines(false); - jScrollPane2.setViewportView(knownBadHashTable); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -337,14 +313,11 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(knownHashDbsLabel) + .addComponent(hashDbsLabel) .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(knownBadHashDbsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 290, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addGap(10, 10, 10) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))) + .addComponent(hashDbsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 494, Short.MAX_VALUE)) .addComponent(alwaysCalcHashesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); @@ -352,26 +325,19 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(2, 2, 2) - .addComponent(knownHashDbsLabel) + .addComponent(hashDbsLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 29, Short.MAX_VALUE) + .addComponent(hashDbsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 207, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(knownBadHashDbsLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 29, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(alwaysCalcHashesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0)) + .addContainerGap()) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox alwaysCalcHashesCheckbox; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JScrollPane jScrollPane2; - private javax.swing.JLabel knownBadHashDbsLabel; - private javax.swing.JTable knownBadHashTable; - private javax.swing.JLabel knownHashDbsLabel; - private javax.swing.JTable knownHashTable; + private javax.swing.JLabel hashDbsLabel; + private javax.swing.JScrollPane hashDbsScrollPane; + private javax.swing.JTable hashTable; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 0f1f97ba97..fade3e47c5 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -49,7 +49,6 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SleuthkitHashSet; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.CentralRepoHashSet; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SetEvt; @@ -95,16 +94,16 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan } } }); - + HashDbManager.getInstance().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String propName = evt.getPropertyName(); - if(propName.equals(SetEvt.DB_ADDED.toString()) || - propName.equals(SetEvt.DB_DELETED.toString())) { + if (propName.equals(SetEvt.DB_ADDED.toString()) + || propName.equals(SetEvt.DB_DELETED.toString())) { hashSetTableModel.refreshModel(); } - } + } }); } @@ -282,7 +281,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan // Update ingest option components. sendIngestMessagesCheckBox.setSelected(db.getSendIngestMessages()); - sendIngestMessagesCheckBox.setEnabled(!ingestIsRunning && db.getKnownFilesType().equals(KnownFilesType.KNOWN_BAD)); + sendIngestMessagesCheckBox.setEnabled(!ingestIsRunning && db.getKnownFilesType().isInboxMessagesAllowed()); // Update database action buttons. createDatabaseButton.setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java index 8a7a3ae034..85d28231a9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.modules.hashdatabase; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.HashEntry; interface HashSetParser { @@ -26,7 +27,8 @@ interface HashSetParser { * Get the next hash to import * * @return The hash as a string, or null if the end of file was reached - * without error + * without error + * * @throws TskCoreException */ String getNextHash() throws TskCoreException; @@ -50,4 +52,20 @@ interface HashSetParser { * Closes the import file */ void close(); + + /** + * Get the next hash to import as a HashEntry object. + * + * @return A new hash entry for the next item parsed. + * + * @throws TskCoreException + */ + default HashEntry getNextHashEntry() throws TskCoreException { + String next = getNextHash(); + if (next == null) { + return null; + } + + return new HashEntry(null, next, null, null, null); + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java index f25471b0e0..d868b39189 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.HashEntry; /** * Imports a hash set into the central repository and updates a progress dialog @@ -186,7 +187,7 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P * Get the newly created database * * @return the imported database. May be null if an error occurred or - * the user canceled + * the user canceled */ synchronized HashDbManager.CentralRepoHashSet getDatabase() { return newHashDb; @@ -205,7 +206,7 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P * Check if the import was successful or if there was an error. * * @return true if the import process completed without error, false - * otherwise + * otherwise */ boolean getImportSuccess() { return importSuccess.get(); @@ -231,16 +232,11 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P try { // Conver to the FileKnown enum used by EamGlobalSet - TskData.FileKnown knownStatus; - if (knownFilesType.equals(HashDbManager.HashDb.KnownFilesType.KNOWN)) { - knownStatus = TskData.FileKnown.KNOWN; - } else { - knownStatus = TskData.FileKnown.BAD; - } + TskData.FileKnown knownStatus = knownFilesType.getFileKnown(); // Create an empty hashset in the central repository CentralRepository dbManager = CentralRepository.getInstance(); - referenceSetID.set(dbManager.newReferenceSet(new CentralRepoFileSet(orgId, hashSetName, version, knownStatus, + referenceSetID.set(dbManager.newReferenceSet(new CentralRepoFileSet(orgId, hashSetName, version, knownStatus, readOnly, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)))); // Get the "FILES" content type. This is a database lookup so we @@ -255,14 +251,14 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P return null; } - String newHash = hashSetParser.getNextHash(); + HashEntry newHash = hashSetParser.getNextHashEntry(); if (newHash != null) { CentralRepoFileInstance eamGlobalFileInstance = new CentralRepoFileInstance( referenceSetID.get(), - newHash, + newHash.getMd5Hash(), knownStatus, - ""); + newHash.getComment()); globalInstances.add(eamGlobalFileInstance); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java index 94d2724995..55bd974be4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java @@ -25,6 +25,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.HashEntry; import org.sleuthkit.datamodel.TskCoreException; /** @@ -68,7 +69,9 @@ public class KdbHashSetParser implements HashSetParser { } // Get the hashes - resultSet = statement.executeQuery("SELECT md5 FROM hashes"); + resultSet = statement.executeQuery("SELECT h.md5 as md5, " + + " (SELECT group_concat(c.comment, ' ') FROM comments c WHERE h.id = c.hash_id) as comment " + + " from hashes h"); // At this point, getNextHash can read each hash from the result set } catch (ClassNotFoundException | SQLException ex) { @@ -77,15 +80,21 @@ public class KdbHashSetParser implements HashSetParser { } + /** * Get the next hash to import * * @return The hash as a string + * * @throws TskCoreException */ @Override public String getNextHash() throws TskCoreException { + return getNextHashEntry().getMd5Hash(); + } + @Override + public HashEntry getNextHashEntry() throws TskCoreException { try { if (resultSet.next()) { byte[] hashBytes = resultSet.getBytes("md5"); @@ -98,13 +107,15 @@ public class KdbHashSetParser implements HashSetParser { throw new TskCoreException("Hash has incorrect length: " + sb.toString()); } + String md5Hash = sb.toString(); + String comment = resultSet.getString("comment"); totalHashesRead++; - return sb.toString(); + return new HashEntry(null, md5Hash, null, null, comment); } else { throw new TskCoreException("Could not read expected number of hashes from hash set " + filename); } } catch (SQLException ex) { - throw new TskCoreException("Error reading hash from result set for hash set " + filename, ex); + throw new TskCoreException("Error opening/reading hash set " + filename, ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties index 59ec5eaeed..508c0d1370 100644 --- a/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties @@ -1,14 +1,15 @@ CTL_OpenPersonas=Personas CTL_PersonasTopComponentAction=PersonasTopComponent CTL_PersonasTopComponent=Personas -PersonasTopComponent.searchField.text=John Doe -PersonasTopComponent.searchBtn.text=Search -PersonasTopComponent.searchNameRadio.text=Name -PersonasTopComponent.searchAccountRadio.text=Account -PersonasTopComponent.filterResultsTable.columnModel.title1=Name -PersonasTopComponent.filterResultsTable.columnModel.title0=ID -PersonasTopComponent.resultAccountsLbl.text=Accounts: -PersonasTopComponent.resultAliasesLbl.text=Aliases: -PersonasTopComponent.resultNameLbl.text=Name: -PersonasTopComponent.resultCasesLbl.text=Cases found in: -PersonasTopComponent.resultNameField.text=Johnathan Dough +PersonaDetailsTopComponent.resultNameLbl.text=Name: +PersonaDetailsTopComponent.resultCasesLbl.text=Cases found in: +PersonaDetailsTopComponent.resultAccountsLbl.text=Accounts: +PersonaDetailsTopComponent.resultAliasesLbl.text=Aliases: +PersonaDetailsTopComponent.resultNameField.text=Johnathan Dough +PersonaSearchTopComponent.searchAccountRadio.text=Account +PersonaSearchTopComponent.searchNameRadio.text=Name +PersonaSearchTopComponent.searchField.text= +PersonaSearchTopComponent.searchBtn.text=Search +PersonaSearchTopComponent.filterResultsTable.columnModel.title1=Name +PersonaSearchTopComponent.filterResultsTable.columnModel.title0=ID +PersonaSearchTopComponent.filterResultsTable.toolTipText= diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form new file mode 100644 index 0000000000..fbfd7ded76 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form @@ -0,0 +1,230 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.java b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java similarity index 55% rename from Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.java rename to Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java index 5631f80a78..fdc0af6e0d 100644 --- a/Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java @@ -18,33 +18,26 @@ */ package org.sleuthkit.autopsy.persona; +import org.openide.windows.TopComponent; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; -import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; /** - * Top component for the Personas tool - * + * Top component for persona details */ @TopComponent.Description(preferredID = "PersonasTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) -@TopComponent.Registration(mode = "personas", openAtStartup = false) -@RetainLocation("personas") +@TopComponent.Registration(mode = "personadetails", openAtStartup = false) +@RetainLocation("personadetails") @SuppressWarnings("PMD.SingularField") -public final class PersonasTopComponent extends TopComponent { +public final class PersonaDetailsTopComponent extends TopComponent { @Messages({ - "PTopComponent_Name=Personas" + "PDTopComponent_Name=Persona Details" }) - public PersonasTopComponent() { + public PersonaDetailsTopComponent() { initComponents(); - setName(Bundle.PTopComponent_Name()); - } - - @Override - public void componentOpened() { - super.componentOpened(); - WindowManager.getDefault().setTopComponentFloating(this, true); + setName(Bundle.PDTopComponent_Name()); } /** @@ -55,15 +48,6 @@ public final class PersonasTopComponent extends TopComponent { // //GEN-BEGIN:initComponents private void initComponents() { - searchButtonGroup = new javax.swing.ButtonGroup(); - splitPane = new javax.swing.JSplitPane(); - searchPanel = new javax.swing.JPanel(); - searchField = new javax.swing.JTextField(); - searchNameRadio = new javax.swing.JRadioButton(); - searchAccountRadio = new javax.swing.JRadioButton(); - filterResultsPane = new javax.swing.JScrollPane(); - filterResultsTable = new javax.swing.JTable(); - searchBtn = new javax.swing.JButton(); detailsPanel = new javax.swing.JPanel(); resultNameLbl = new javax.swing.JLabel(); resultNameField = new javax.swing.JTextField(); @@ -77,102 +61,19 @@ public final class PersonasTopComponent extends TopComponent { aliasesListPane = new javax.swing.JScrollPane(); aliasesList = new javax.swing.JList<>(); - setMinimumSize(new java.awt.Dimension(400, 400)); - - searchField.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchField.text")); // NOI18N - - searchButtonGroup.add(searchNameRadio); - searchNameRadio.setSelected(true); - org.openide.awt.Mnemonics.setLocalizedText(searchNameRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchNameRadio.text")); // NOI18N - searchNameRadio.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - searchNameRadioActionPerformed(evt); - } - }); - - searchButtonGroup.add(searchAccountRadio); - org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchAccountRadio.text")); // NOI18N - - filterResultsTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - {"0", "Johnathn Dough"}, - {"3", "Joe Schmoe"}, - {"2", "Michael Schmoe"}, - {"1", "Ethan Schmoe"} - }, - new String [] { - "ID", "Name" - } - ) { - Class[] types = new Class [] { - java.lang.String.class, java.lang.String.class - }; - - public Class getColumnClass(int columnIndex) { - return types [columnIndex]; - } - }); - filterResultsPane.setViewportView(filterResultsTable); - if (filterResultsTable.getColumnModel().getColumnCount() > 0) { - filterResultsTable.getColumnModel().getColumn(0).setMaxWidth(25); - filterResultsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.filterResultsTable.columnModel.title0")); // NOI18N - filterResultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.filterResultsTable.columnModel.title1")); // NOI18N - } - - org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N - - javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); - searchPanel.setLayout(searchPanelLayout); - searchPanelLayout.setHorizontalGroup( - searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(searchPanelLayout.createSequentialGroup() - .addContainerGap() - .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(filterResultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGroup(searchPanelLayout.createSequentialGroup() - .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(searchField) - .addGroup(searchPanelLayout.createSequentialGroup() - .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(searchPanelLayout.createSequentialGroup() - .addComponent(searchNameRadio) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(searchAccountRadio)) - .addComponent(searchBtn)) - .addGap(0, 25, Short.MAX_VALUE))) - .addContainerGap()))) - ); - searchPanelLayout.setVerticalGroup( - searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(searchPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(searchField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(searchNameRadio) - .addComponent(searchAccountRadio)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(searchBtn) - .addGap(18, 18, 18) - .addComponent(filterResultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addContainerGap()) - ); - - splitPane.setLeftComponent(searchPanel); - - org.openide.awt.Mnemonics.setLocalizedText(resultNameLbl, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultNameLbl.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(resultNameLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultNameLbl.text")); // NOI18N resultNameField.setEditable(false); - resultNameField.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultNameField.text")); // NOI18N + resultNameField.setText(org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultNameField.text")); // NOI18N resultNameField.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { resultNameFieldActionPerformed(evt); } }); - org.openide.awt.Mnemonics.setLocalizedText(resultAliasesLbl, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultAliasesLbl.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(resultAliasesLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultAliasesLbl.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(resultAccountsLbl, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultAccountsLbl.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(resultAccountsLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultAccountsLbl.text")); // NOI18N accountsTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { @@ -202,7 +103,7 @@ public final class PersonasTopComponent extends TopComponent { }); accountsTablePane.setViewportView(accountsTable); - org.openide.awt.Mnemonics.setLocalizedText(resultCasesLbl, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultCasesLbl.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(resultCasesLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultCasesLbl.text")); // NOI18N casesList.setModel(new javax.swing.AbstractListModel() { String[] strings = { "Investigation 13", "Scene 5" }; @@ -259,26 +160,30 @@ public final class PersonasTopComponent extends TopComponent { .addContainerGap()) ); - splitPane.setRightComponent(detailsPanel); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(splitPane) + .addGap(0, 505, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(detailsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(splitPane) + .addGap(0, 555, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(detailsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) ); }// //GEN-END:initComponents - private void searchNameRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchNameRadioActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_searchNameRadioActionPerformed - private void resultNameFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resultNameFieldActionPerformed - // TODO add your handling code here: + }//GEN-LAST:event_resultNameFieldActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -289,20 +194,16 @@ public final class PersonasTopComponent extends TopComponent { private javax.swing.JList casesList; private javax.swing.JScrollPane casesListPane; private javax.swing.JPanel detailsPanel; - private javax.swing.JScrollPane filterResultsPane; - private javax.swing.JTable filterResultsTable; private javax.swing.JLabel resultAccountsLbl; private javax.swing.JLabel resultAliasesLbl; private javax.swing.JLabel resultCasesLbl; private javax.swing.JTextField resultNameField; private javax.swing.JLabel resultNameLbl; - private javax.swing.JRadioButton searchAccountRadio; - private javax.swing.JButton searchBtn; - private javax.swing.ButtonGroup searchButtonGroup; - private javax.swing.JTextField searchField; - private javax.swing.JRadioButton searchNameRadio; - private javax.swing.JPanel searchPanel; - private javax.swing.JSplitPane splitPane; // End of variables declaration//GEN-END:variables - + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } } diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form new file mode 100644 index 0000000000..ec91c8bb53 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form @@ -0,0 +1,178 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonaSearchTopComponent.filterResultsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonaSearchTopComponent.filterResultsTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java new file mode 100644 index 0000000000..7cf22279db --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java @@ -0,0 +1,217 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.persona; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Collections; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableModel; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.RetainLocation; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; + +/** + * Top component for the Personas tool + * + */ +@TopComponent.Description(preferredID = "PersonasTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "personasearch", openAtStartup = false) +@RetainLocation("personasearch") +@SuppressWarnings("PMD.SingularField") +public final class PersonaSearchTopComponent extends TopComponent { + + @Messages({ + "PSTopComponent_Name=Persona Search" + }) + public PersonaSearchTopComponent() { + initComponents(); + setName(Bundle.PSTopComponent_Name()); + executeSearch(); + + searchBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + executeSearch(); + } + }); + } + + /** + * Table model for the persona search results + */ + final class PersonaFilterTableModel extends DefaultTableModel { + private static final long serialVersionUID = 1L; + + PersonaFilterTableModel(Object[][] rows, String[] colNames) { + super(rows, colNames); + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + } + + private void updateResultsTable(Collection results) { + Object[][] rows = new Object[results.size()][2]; + int i = 0; + for (Persona result : results) { + rows[i] = new String[]{String.valueOf(result.getId()), result.getName()}; + i++; + } + DefaultTableModel updatedTableModel = new PersonaFilterTableModel( + rows, + new String[]{"ID", "Name"} + ); + + filterResultsTable.setModel(updatedTableModel); + + // Formatting + filterResultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + filterResultsTable.getColumnModel().getColumn(0).setMaxWidth(100); + filterResultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + } + + private void executeSearch() { + Collection results = Collections.EMPTY_LIST; + try { + results = Persona.getPersonaByName(searchField.getText()); + } catch (CentralRepoException ex) { + Exceptions.printStackTrace(ex); + } + updateResultsTable(results); + } + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + searchButtonGroup = new javax.swing.ButtonGroup(); + searchPanel = new javax.swing.JPanel(); + searchField = new javax.swing.JTextField(); + searchNameRadio = new javax.swing.JRadioButton(); + searchAccountRadio = new javax.swing.JRadioButton(); + filterResultsPane = new javax.swing.JScrollPane(); + filterResultsTable = new javax.swing.JTable(); + searchBtn = new javax.swing.JButton(); + + setMinimumSize(new java.awt.Dimension(400, 400)); + + searchField.setText(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchField.text")); // NOI18N + + searchButtonGroup.add(searchNameRadio); + searchNameRadio.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(searchNameRadio, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchNameRadio.text")); // NOI18N + searchNameRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchNameRadioActionPerformed(evt); + } + }); + + searchButtonGroup.add(searchAccountRadio); + org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchAccountRadio.text")); // NOI18N + + filterResultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.toolTipText")); // NOI18N + filterResultsTable.getTableHeader().setReorderingAllowed(false); + filterResultsPane.setViewportView(filterResultsTable); + if (filterResultsTable.getColumnModel().getColumnCount() > 0) { + filterResultsTable.getColumnModel().getColumn(0).setMaxWidth(25); + filterResultsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.columnModel.title0")); // NOI18N + filterResultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.columnModel.title1")); // NOI18N + } + + org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchBtn.text")); // NOI18N + + javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); + searchPanel.setLayout(searchPanelLayout); + searchPanelLayout.setHorizontalGroup( + searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(searchPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filterResultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(searchField) + .addGroup(searchPanelLayout.createSequentialGroup() + .addComponent(searchNameRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(searchAccountRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 155, Short.MAX_VALUE) + .addComponent(searchBtn))) + .addContainerGap()) + ); + searchPanelLayout.setVerticalGroup( + searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(searchPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(searchField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(searchNameRadio) + .addComponent(searchAccountRadio) + .addComponent(searchBtn)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(filterResultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 320, Short.MAX_VALUE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(searchPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(searchPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void searchNameRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchNameRadioActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_searchNameRadioActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane filterResultsPane; + private javax.swing.JTable filterResultsTable; + private javax.swing.JRadioButton searchAccountRadio; + private javax.swing.JButton searchBtn; + private javax.swing.ButtonGroup searchButtonGroup; + private javax.swing.JTextField searchField; + private javax.swing.JRadioButton searchNameRadio; + private javax.swing.JPanel searchPanel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.form b/Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.form deleted file mode 100644 index 83638e2f02..0000000000 --- a/Core/src/org/sleuthkit/autopsy/persona/PersonasTopComponent.form +++ /dev/null @@ -1,381 +0,0 @@ - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - - - <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonasTopComponent.filterResultsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - - - - - - - <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonasTopComponent.filterResultsTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> - - - - - - - - - -
    -
    -
    -
    - - - - - - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index 046331ca9f..8c88f29f53 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -65,6 +65,7 @@ import org.sleuthkit.datamodel.Pool; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TaggingManager.ContentTagChange; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.TskData; @@ -740,13 +741,13 @@ public class PortableCaseReportModule implements ReportModule { if (! oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } - ContentTag newContentTag = portableSkCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset()); + ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset()); // Get the image tag data associated with this tag (empty string if there is none) // and save it if present String appData = getImageTagDataForContentTag(tag); if (! appData.isEmpty()) { - addImageTagToPortableCase(newContentTag, appData); + addImageTagToPortableCase(newContentTag.getAddedTag(), appData); } } } @@ -847,7 +848,7 @@ public class PortableCaseReportModule implements ReportModule { if (! oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } - portableSkCase.addBlackboardArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment()); + portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 5ab3b8c84b..9505c22c98 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -57,6 +57,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -74,6 +75,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; @@ -108,8 +110,6 @@ public final class ImageGalleryController { Case.Events.CONTENT_TAG_DELETED, Case.Events.DATA_SOURCE_DELETED ); - - private static final String CATEGORY_TAG_SET_PREFIX = "Project VIC"; /* * There is an image gallery controller per case. It is created during the @@ -725,19 +725,19 @@ public final class ImageGalleryController { private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException { return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile); } - + /** * Returns the TagSet with the image gallery categories. - * + * * @return Category TagSet. - * - * @throws TskCoreException + * + * @throws TskCoreException */ private TagSet getCategoryTagSet() throws TskCoreException { List tagSetList = getCaseDatabase().getTaggingManager().getTagSets(); if (tagSetList != null && !tagSetList.isEmpty()) { for (TagSet set : tagSetList) { - if (set.getName().startsWith(CATEGORY_TAG_SET_PREFIX)) { + if (set.getName().equals(TagsManager.getCategoryTagSetName())) { return set; } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 13703f417b..6eaddeef9d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -82,7 +82,6 @@ public class CategorizeAction extends Action { this.tagName = tagName; setGraphic(getGraphic(tagName)); setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs)); - setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(getCategoryNumberFromTagName(tagName)))); } static public Menu getCategoriesMenu(ImageGalleryController controller) { @@ -94,16 +93,6 @@ public class CategorizeAction extends Action { controller.queueDBTask(new CategorizeDrawableFileTask(ids, tagName, createUndo)); } - private String getCategoryNumberFromTagName(TagName tagName) { - String displayName = tagName.getDisplayName(); - if (displayName.contains("CAT")) { - String[] split = displayName.split(":"); - split = split[0].split("-"); - return split[1]; - } - return ""; - } - /** * Instances of this class implement a context menu user interface for * selecting a category diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Bundle.properties-MERGED b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Bundle.properties-MERGED index 6686b27994..b10e8bbb17 100755 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Bundle.properties-MERGED +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Bundle.properties-MERGED @@ -13,6 +13,4 @@ DrawableAttribute.name=Name DrawableAttribute.path=Path DrawableAttribute.tags=Tags DrawableAttribute.width=Width -DrawableTagsManager.bookMark=Bookmark -DrawableTagsManager.followUp=Follow Up VideoFile.getMedia.progress=writing temporary file to disk diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index 684346b41d..8419fb40fe 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -38,7 +38,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -62,9 +61,7 @@ public class CategoryManager { private static final Logger LOGGER = Logger.getLogger(CategoryManager.class.getName()); /** - * the DrawableDB that backs the category counts cache. The counts are - * initialized from this, and the counting of CAT-0 is always delegated to - * this db. + * the DrawableDB that backs the category counts cache. */ private final DrawableDB drawableDb; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index ec06c6e343..abf2c02586 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -29,7 +29,6 @@ import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.TagsManager; @@ -44,8 +43,6 @@ import org.sleuthkit.datamodel.TskCoreException; * Manages Tags, Tagging, and the relationship between Categories and Tags in * the autopsy Db. Delegates some work to the backing autopsy TagsManager. */ -@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up", - "DrawableTagsManager.bookMark=Bookmark"}) public final class DrawableTagsManager { private static final Logger logger = Logger.getLogger(DrawableTagsManager.class.getName()); @@ -78,8 +75,8 @@ public final class DrawableTagsManager { public DrawableTagsManager(ImageGalleryController controller) throws TskCoreException { this.autopsyTagsManager = controller.getCase().getServices().getTagsManager(); - followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp()); - bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark()); + followUpTagName = getTagName(TagsManager.getFollowUpDisplayString()); + bookmarkTagName = getTagName(TagsManager.getBookmarkDisplayString()); this.controller = controller; compareByDisplayName = new Comparator() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index 5dcd725381..d01cea5c08 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -61,7 +61,7 @@ -