diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index 2afda97c05..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; @@ -81,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() @@ -104,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. * @@ -136,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. * @@ -222,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; } @@ -1016,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()); @@ -1050,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) { @@ -1065,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 @@ -1105,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();) { @@ -1169,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); @@ -1194,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); @@ -1233,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"); @@ -1575,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++; @@ -2002,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); @@ -2017,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 { @@ -2332,8 +2321,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { HashHitInfo found = new HashHitInfo(hashFound, "", ""); found.addComment(comment); return found; - } - else { + } else { return null; } } catch (SQLException ex) { @@ -2344,8 +2332,6 @@ abstract class RdbmsCentralRepo implements CentralRepository { CentralRepoDbUtil.closeConnection(conn); } } - - /** * Check if the given value is in a specific reference set @@ -2516,7 +2502,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { CentralRepoDbUtil.closeConnection(conn); } } - + /** * Process a SELECT query * @@ -2554,7 +2540,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { CentralRepoDbUtil.closeResultSet(resultSet); CentralRepoDbUtil.closeConnection(conn); } - } + } @Override public void executeInsertSQL(String insertClause) throws CentralRepoException { @@ -2597,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) { @@ -2737,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 { @@ -2770,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)) { @@ -2790,7 +2777,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException("Error getting examiner for name = " + examinerLoginName, ex); } } - + /** * Update an existing organization. * @@ -3185,7 +3172,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { typeId = newCorrelationTypeKnownId(newType); } - synchronized(typeCache) { + synchronized (typeCache) { typeCache.put(newType.getId(), newType); } return typeId; @@ -3402,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) { @@ -3426,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) { @@ -3437,7 +3424,6 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } - /** * Get the EamArtifact.Type that has the given Type.Id from the central repo * @@ -3476,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); @@ -3502,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 * @@ -3662,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 * @@ -3781,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(); @@ -3801,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)) { @@ -3966,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 67c194fda7..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,7 @@ 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; /** @@ -67,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); @@ -90,7 +90,7 @@ final class CaseEventListener implements PropertyChangeListener { if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { return; } - + CentralRepository dbManager; try { dbManager = CentralRepository.getInstance(); @@ -98,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())) { @@ -132,7 +132,7 @@ final class CaseEventListener implements PropertyChangeListener { break; } } - + /* * Add all of our Case Event Listeners to the case. */ @@ -147,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; @@ -163,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) { @@ -239,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 { @@ -258,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 + } } @@ -439,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()); }