diff --git a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java index f468d6a5f63239c9195fdd6e51362bc2aec87833..e4bdea274df1d5b1d1ce04ec707a93c08d278523 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/CommunicationsManager.java @@ -1,7 +1,7 @@ /* * Sleuth Kit Data Model * - * Copyright 2017-18 Basis Technology Corp. + * Copyright 2017-2020 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; import static org.sleuthkit.datamodel.SleuthkitCase.closeResultSet; import static org.sleuthkit.datamodel.SleuthkitCase.closeStatement; @@ -48,20 +49,18 @@ public final class CommunicationsManager { private final SleuthkitCase db; private final Map<Account.Type, Integer> accountTypeToTypeIdMap - = new ConcurrentHashMap<Account.Type, Integer>(); + = new ConcurrentHashMap<>(); private final Map<String, Account.Type> typeNameToAccountTypeMap - = new ConcurrentHashMap<String, Account.Type>(); - - // Artifact types that represent a relationship between accounts - private final static Set<Integer> RELATIONSHIP_ARTIFACT_TYPE_IDS - = new HashSet<Integer>(Arrays.asList( - BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), - BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), - BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID(), - BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() - )); - private static final String RELATIONSHIP_ARTIFACT_TYPE_IDS_CSV_STR - = StringUtils.buildCSVString(RELATIONSHIP_ARTIFACT_TYPE_IDS); + = new ConcurrentHashMap<>(); + + // Artifact types that can represent a relationship between accounts. + private static final Set<Integer> RELATIONSHIP_ARTIFACT_TYPE_IDS = new HashSet<Integer>(Arrays.asList( + BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), + BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(), + BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID(), + BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() + )); + private static final String RELATIONSHIP_ARTIFACT_TYPE_IDS_CSV_STR = StringUtils.buildCSVString(RELATIONSHIP_ARTIFACT_TYPE_IDS); /** * Construct a CommunicationsManager for the given SleuthkitCase. @@ -328,7 +327,7 @@ public Account getAccount(org.sleuthkit.datamodel.Account.Type accountType, Stri /** * Adds relationships between the sender and each of the recipient account - * instances and between all recipient account instances. All account + * instances and between all recipient account instances. All account * instances must be from the same data source. * * @param sender sender account @@ -450,36 +449,36 @@ private Account getOrCreateAccount(Account.Type accountType, String accountUniqu } /** - * Get the blackboard artifact for the given account type, account ID, and - * source file. Create an artifact if it doesn't already exist. + * Gets or creates an account artifact for an instance of an account found + * in a file. * - * @param accountType account type - * @param accountUniqueID Unique account ID (such as email address) - * @param moduleName module name that found this instance (for the - * artifact) - * @param sourceFile Source file (for the artifact) + * @param accountType The account type of the account instance. + * @param accountUniqueID The account ID of the account instance, should be + * unique for the account type (e.g., an email + * address for an email account). + * @param moduleName The name of the module that found the account + * instance. + * @param sourceFile The file in which the account instance was found. * - * @return blackboard artifact for the account file instance + * @return The account artifact. * - * @throws TskCoreException exception thrown if a critical error occurs - * within TSK core + * @throws TskCoreException If there is an error querying or updating the + * case database. */ - BlackboardArtifact getOrCreateAccountFileInstanceArtifact(Account.Type accountType, String accountUniqueID, String moduleName, Content sourceFile) throws TskCoreException { - - // see if it already exists + private BlackboardArtifact getOrCreateAccountFileInstanceArtifact(Account.Type accountType, String accountUniqueID, String moduleName, Content sourceFile) throws TskCoreException { BlackboardArtifact accountArtifact = getAccountFileInstanceArtifact(accountType, accountUniqueID, sourceFile); - if (null != accountArtifact) { - return accountArtifact; + if (accountArtifact == null) { + accountArtifact = db.newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT, sourceFile.getId()); + Collection<BlackboardAttribute> attributes = new ArrayList<>(); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, moduleName, accountType.getTypeName())); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID, moduleName, accountUniqueID)); + accountArtifact.addAttributes(attributes); + try { + db.getBlackboard().postArtifact(accountArtifact, moduleName); + } catch (BlackboardException ex) { + LOGGER.log(Level.SEVERE, String.format("Error posting new account artifact to the blackboard (object ID = %d)", accountArtifact.getId()), ex); + } } - - // Create a new artifact. - accountArtifact = db.newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT, sourceFile.getId()); - - Collection<BlackboardAttribute> attributes = new ArrayList<BlackboardAttribute>(); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, moduleName, accountType.getTypeName())); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID, moduleName, accountUniqueID)); - accountArtifact.addAttributes(attributes); - return accountArtifact; } @@ -662,11 +661,11 @@ public List<AccountDeviceInstance> getAccountDeviceInstancesWithRelationships(Co CommunicationsFilter.RelationshipTypeFilter.class.getName() )); String relationshipFilterSQL = getCommunicationsFilterSQL(filter, applicableInnerQueryFilters); - + String relationshipLimitSQL = getMostRecentFilterLimitSQL(filter); - - String relTblfilterQuery = - "SELECT * " + + String relTblfilterQuery + = "SELECT * " + "FROM account_relationships as relationships" + (relationshipFilterSQL.isEmpty() ? "" : " WHERE " + relationshipFilterSQL) + (relationshipLimitSQL.isEmpty() ? "" : relationshipLimitSQL); @@ -901,17 +900,17 @@ public long getRelationshipSourcesCount(AccountDeviceInstance accountDeviceInsta try { s = connection.createStatement(); - + String innerQuery = " account_relationships AS relationships"; String limitStr = getMostRecentFilterLimitSQL(filter); - - if(!limitStr.isEmpty()) { + + if (!limitStr.isEmpty()) { innerQuery = "(SELECT * FROM account_relationships as relationships " + limitStr + ") as relationships"; } String queryStr = "SELECT count(DISTINCT relationships.relationship_source_obj_id) as count " - + " FROM" + innerQuery + + " FROM" + innerQuery + " WHERE relationships.data_source_obj_id IN ( " + datasourceObjIdsCSV + " )" + " AND ( relationships.account1_id = " + account_id + " OR relationships.account2_id = " + account_id + " )" @@ -935,7 +934,8 @@ public long getRelationshipSourcesCount(AccountDeviceInstance accountDeviceInsta * with accounts on specific devices (AccountDeviceInstance) that meet the * filter criteria. * - * Applicable filters: RelationshipTypeFilter, DateRangeFilter, MostRecentFilter + * Applicable filters: RelationshipTypeFilter, DateRangeFilter, + * MostRecentFilter * * @param accountDeviceInstanceList set of account device instances for * which to get the relationship sources. @@ -985,10 +985,10 @@ public Set<Content> getRelationshipSources(Set<AccountDeviceInstance> accountDev .getName() )); String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters); - + String limitQuery = " account_relationships AS relationships"; String limitStr = getMostRecentFilterLimitSQL(filter); - if(!limitStr.isEmpty()) { + if (!limitStr.isEmpty()) { limitQuery = "(SELECT * FROM account_relationships as relationships " + limitStr + ") as relationships"; } @@ -1152,8 +1152,8 @@ public List<AccountDeviceInstance> getRelatedAccountDeviceInstances(AccountDevic * Get the sources (artifacts, content) of relationships between the given * account device instances. * - * Applicable filters: DeviceFilter, DateRangeFilter, RelationshipTypeFilter, - * MostRecentFilter + * Applicable filters: DeviceFilter, DateRangeFilter, + * RelationshipTypeFilter, MostRecentFilter * * @param account1 First AccountDeviceInstance * @param account2 Second AccountDeviceInstance @@ -1172,13 +1172,13 @@ public List<Content> getRelationshipSources(AccountDeviceInstance account1, Acco CommunicationsFilter.DeviceFilter.class.getName(), CommunicationsFilter.RelationshipTypeFilter.class.getName() )); - + String limitQuery = " account_relationships AS relationships"; String limitStr = getMostRecentFilterLimitSQL(filter); - if(!limitStr.isEmpty()) { + if (!limitStr.isEmpty()) { limitQuery = "(SELECT * FROM account_relationships as relationships " + limitStr + ") as relationships"; } - + String filterSQL = getCommunicationsFilterSQL(filter, applicableFilters); final String queryString = "SELECT artifacts.artifact_id AS artifact_id," + " artifacts.obj_id AS obj_id," @@ -1187,7 +1187,7 @@ public List<Content> getRelationshipSources(AccountDeviceInstance account1, Acco + " artifacts.artifact_type_id AS artifact_type_id," + " artifacts.review_status_id AS review_status_id" + " FROM blackboard_artifacts AS artifacts" - + " JOIN " + limitQuery + + " JOIN " + limitQuery + " ON artifacts.artifact_obj_id = relationships.relationship_source_obj_id" + " WHERE (( relationships.account1_id = " + account1.getAccount().getAccountID() + " AND relationships.account2_id = " + account2.getAccount().getAccountID() @@ -1220,44 +1220,44 @@ public List<Content> getRelationshipSources(AccountDeviceInstance account1, Acco db.releaseSingleUserCaseReadLock(); } } - + /** * Get a list AccountFileInstance for the given accounts. - * + * * @param account List of accounts - * - * @return A lit of AccountFileInstances for the given accounts or null if - * none are found. - * - * @throws org.sleuthkit.datamodel.TskCoreException + * + * @return A lit of AccountFileInstances for the given accounts or null if + * none are found. + * + * @throws org.sleuthkit.datamodel.TskCoreException */ public List<AccountFileInstance> getAccountFileInstances(Account account) throws TskCoreException { List<AccountFileInstance> accountFileInstanceList = new ArrayList<>(); - + List<BlackboardArtifact> artifactList = getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID, account.getTypeSpecificID()); - - if(artifactList != null && !artifactList.isEmpty()) { - for(BlackboardArtifact artifact : artifactList) { + + if (artifactList != null && !artifactList.isEmpty()) { + for (BlackboardArtifact artifact : artifactList) { accountFileInstanceList.add(new AccountFileInstance(artifact, account)); } } - - if(!accountFileInstanceList.isEmpty()) { + + if (!accountFileInstanceList.isEmpty()) { return accountFileInstanceList; } else { return null; } } - + /** * Gets a list of the distinct account types that can currently be found in * the case db. - * + * * @return A list of distinct accounts or an empty list. - * - * @throws TskCoreException + * + * @throws TskCoreException */ - public List<Account.Type> getAccountTypesInUse() throws TskCoreException{ + public List<Account.Type> getAccountTypesInUse() throws TskCoreException { CaseDbConnection connection = db.getConnection(); db.acquireSingleUserCaseReadLock(); Statement s = null; @@ -1272,12 +1272,12 @@ public List<Account.Type> getAccountTypesInUse() throws TskCoreException{ while (rs.next()) { String accountTypeName = rs.getString("type_name"); accountType = this.typeNameToAccountTypeMap.get(accountTypeName); - - if(accountType == null) { + + if (accountType == null) { accountType = new Account.Type(accountTypeName, rs.getString("display_name")); this.accountTypeToTypeIdMap.put(accountType, rs.getInt("account_type_id")); } - + inUseAccounts.add(accountType); } return inUseAccounts; @@ -1341,8 +1341,8 @@ private String normalizePhoneNum(String phoneNum) { if (phoneNum.startsWith("+")) { normailzedPhoneNum = "+" + normailzedPhoneNum; } - - if(normailzedPhoneNum.isEmpty()) { + + if (normailzedPhoneNum.isEmpty()) { normailzedPhoneNum = phoneNum; } @@ -1405,27 +1405,28 @@ private String getCommunicationsFilterSQL(CommunicationsFilter commFilter, Set<S } return sqlStr; } - + /** * Builds the SQL for the MostRecentFilter. - * + * * @param filter The CommunicationsFilter to get the SQL for. - * @return Order BY and LIMIT clause or empty - * string if no filter is available. + * + * @return Order BY and LIMIT clause or empty string if no filter is + * available. */ private String getMostRecentFilterLimitSQL(CommunicationsFilter filter) { String limitStr = ""; - + if (filter != null && !filter.getAndFilters().isEmpty()) { for (CommunicationsFilter.SubFilter subFilter : filter.getAndFilters()) { - if(subFilter.getClass().getName().equals(CommunicationsFilter.MostRecentFilter.class.getName())) { - limitStr = subFilter.getSQL(this); + if (subFilter.getClass().getName().equals(CommunicationsFilter.MostRecentFilter.class.getName())) { + limitStr = subFilter.getSQL(this); break; } } } - + return limitStr; } }