diff --git a/BUILDING.txt b/BUILDING.txt index 570cadbf87..a77d6c8add 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -37,16 +37,16 @@ to the root 64-bit JRE directory. 2) Get Sleuth Kit Setup 2a) Download and build a Release version of Sleuth Kit (TSK) 4.0. See win32\BUILDING.txt in the TSK package for more information. You need to - build the tsk_jni project. Select the Release_PostgreSQL Win32 or x64 target, + build the tsk_jni project. Select the Release Win32 or x64 target, depending upon your target build. You can use a released version or download the latest from github: - git://github.com/sleuthkit/sleuthkit.git -2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in +2b) Build the TSK JAR file by typing 'ant dist' in bindings/java in the TSK source code folder from a command line. Note it is case sensitive. You can also add the code to a NetBeans project and build - it from there, selecting the dist-PostgreSQL target. + it from there, selecting the dist target. 2c) Set TSK_HOME environment variable to the root directory of TSK @@ -103,7 +103,7 @@ the build process. - The Sleuth Kit Java datamodel JAR file has native JNI libraries that are copied into it. These JNI libraries have dependencies on -libewf, zlib, libpq, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows +libewf, zlib, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows platforms, the JNI library also has a dependency on libtsk (on Windows, it is compiled into libtsk_jni). diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 1898db811c..8dbd7f8d92 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -83,7 +83,7 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sis-metadata-0.8.jar=release\\modules\\ext\\sis-metadata-0.8.jar file.reference.sis-netcdf-0.8.jar=release\\modules\\ext\\sis-netcdf-0.8.jar file.reference.sis-utility-0.8.jar=release\\modules\\ext\\sis-utility-0.8.jar -file.reference.sleuthkit-caseuco-4.9.0.jar=release\\modules\\ext\\sleuthkit-caseuco-4.9.0.jar +file.reference.sleuthkit-caseuco-4.10.0.jar=release/modules/ext/sleuthkit-caseuco-4.10.0.jar file.reference.slf4j-api-1.7.25.jar=release\\modules\\ext\\slf4j-api-1.7.25.jar file.reference.sqlite-jdbc-3.25.2.jar=release/modules/ext/sqlite-jdbc-3.25.2.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar @@ -91,7 +91,7 @@ file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0 file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar -file.reference.sleuthkit-4.9.0.jar=release/modules/ext/sleuthkit-4.9.0.jar +file.reference.sleuthkit-4.10.0.jar=release/modules/ext/sleuthkit-4.10.0.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar file.reference.curator-framework-2.8.0.jar=release/modules/ext/curator-framework-2.8.0.jar file.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index b751ffbf07..61e6a86b04 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -472,8 +472,8 @@ release/modules/ext/commons-pool2-2.4.2.jar - ext/sleuthkit-4.9.0.jar - release/modules/ext/sleuthkit-4.9.0.jar + ext/sleuthkit-4.10.0.jar + release/modules/ext/sleuthkit-4.10.0.jar ext/jxmapviewer2-2.4.jar @@ -780,8 +780,8 @@ release/modules/ext/curator-client-2.8.0.jar - ext/sleuthkit-caseuco-4.9.0.jar - release\modules\ext\sleuthkit-caseuco-4.9.0.jar + ext/sleuthkit-caseuco-4.10.0.jar + release/modules/ext/sleuthkit-caseuco-4.10.0.jar ext/fontbox-2.0.13.jar diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java index 28e968d516..90e61fbd0d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java @@ -25,10 +25,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import org.apache.commons.lang.StringUtils; import org.sleuthkit.datamodel.Account; -import org.sleuthkit.datamodel.CommunicationsUtils; -import static org.sleuthkit.datamodel.CommunicationsUtils.normalizeEmailAddress; -import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.InvalidAccountIDException; /** * This class abstracts an Account as stored in the CR database. @@ -246,16 +245,9 @@ public final class CentralRepoAccount { * @throws CentralRepoException If there is an error in getting the * accounts. */ - public static Collection getAccountsWithIdentifier(String accountIdentifier) throws CentralRepoException { - - String normalizedAccountIdentifier; - - try { - normalizedAccountIdentifier = normalizeAccountIdentifier(accountIdentifier); - } catch (TskCoreException ex) { - throw new CentralRepoException("Failed to normalize account identifier.", ex); - } + public static Collection getAccountsWithIdentifier(String accountIdentifier) throws InvalidAccountIDException, CentralRepoException { + String normalizedAccountIdentifier = normalizeAccountIdentifier(accountIdentifier); String queryClause = ACCOUNTS_QUERY_CLAUSE + " WHERE LOWER(accounts.account_unique_identifier) = LOWER(?)"; @@ -296,15 +288,57 @@ public final class CentralRepoAccount { * @param accountIdentifier Account identifier to be normalized. * @return normalized identifier * - * @throws TskCoreException + * @throws InvalidAccountIDException If the account identifier is not valid. */ - private static String normalizeAccountIdentifier(String accountIdentifier) throws TskCoreException { - String normalizedAccountIdentifier = accountIdentifier; - if (CommunicationsUtils.isValidPhoneNumber(accountIdentifier)) { - normalizedAccountIdentifier = CommunicationsUtils.normalizePhoneNum(accountIdentifier); - } else if (CommunicationsUtils.isValidEmailAddress(accountIdentifier)) { - normalizedAccountIdentifier = normalizeEmailAddress(accountIdentifier); + private static String normalizeAccountIdentifier(String accountIdentifier) throws InvalidAccountIDException { + if (StringUtils.isEmpty(accountIdentifier)) { + throw new InvalidAccountIDException("Account id is null or empty."); + } + + String normalizedAccountIdentifier; + try { + if (CorrelationAttributeNormalizer.isValidPhoneNumber(accountIdentifier)) { + normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizePhone(accountIdentifier); + } else if (CorrelationAttributeNormalizer.isValidEmailAddress(accountIdentifier)) { + normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizeEmail(accountIdentifier); + } else { + normalizedAccountIdentifier = accountIdentifier.toLowerCase().trim(); + } + } catch (CorrelationAttributeNormalizationException ex) { + throw new InvalidAccountIDException("Failed to normalize the account idenitier.", ex); } return normalizedAccountIdentifier; } + + /** + * Normalizes an account identifier, based on the given account type. + * + * @param crAccountType Account type. + * @param accountIdentifier Account identifier to be normalized. + * @return Normalized identifier. + * + * @throws InvalidAccountIDException If the account identifier is invalid. + */ + public static String normalizeAccountIdentifier(CentralRepoAccountType crAccountType, String accountIdentifier) throws InvalidAccountIDException { + + if (StringUtils.isBlank(accountIdentifier)) { + throw new InvalidAccountIDException("Account identifier is null or empty."); + } + + String normalizedAccountIdentifier; + try { + if (crAccountType.getAcctType().equals(Account.Type.PHONE)) { + normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizePhone(accountIdentifier); + } else if (crAccountType.getAcctType().equals(Account.Type.EMAIL)) { + normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizeEmail(accountIdentifier); + } else { + // convert to lowercase + normalizedAccountIdentifier = accountIdentifier.toLowerCase(); + } + } catch (CorrelationAttributeNormalizationException ex) { + throw new InvalidAccountIDException("Invalid account identifier", ex); + } + + return normalizedAccountIdentifier; + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java index 6ba23b65b5..5105aed2e9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java @@ -262,9 +262,7 @@ public class CentralRepoDbUtil { * used */ public static void setUseCentralRepo(boolean centralRepoCheckBoxIsSelected) { - if (!centralRepoCheckBoxIsSelected) { - closePersonasTopComponent(); - } + closePersonasTopComponent(); ModuleSettings.setConfigSetting(CENTRAL_REPO_NAME, CENTRAL_REPO_USE_KEY, Boolean.toString(centralRepoCheckBoxIsSelected)); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index bdae5a727b..842c8e3f04 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.datamodel.HashHitInfo; +import org.sleuthkit.datamodel.InvalidAccountIDException; /** * Main interface for interacting with the database @@ -880,9 +881,24 @@ public interface CentralRepository { * @param crAccountType CR account type to look for or create * @param accountUniqueID type specific unique account id * @return CR account - * - * @throws CentralRepoException + * + * @throws CentralRepoException If there is an error accessing Central Repository. + * @throws InvalidAccountIDException If the account identifier is not valid. */ - CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException; + CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException; + + /** + * Gets an account from the accounts table matching the given type/ID, if + * one exists. + * + * @param crAccountType CR account type to look for or create + * @param accountUniqueID type specific unique account id + * + * @return CR account, if found, null otherwise. + * + * @throws CentralRepoException If there is an error accessing Central Repository. + * @throws InvalidAccountIDException If the account identifier is not valid. + */ + CentralRepoAccount getAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java index d762e74945..51b9b80f84 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java @@ -19,12 +19,14 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.EmailValidator; -import org.sleuthkit.datamodel.CommunicationsUtils; -import org.sleuthkit.datamodel.TskCoreException; /** * Provides functions for normalizing data by attribute type before insertion or @@ -40,7 +42,7 @@ final public class CorrelationAttributeNormalizer { * data is a valid string of the format expected given the attributeType. * * @param attributeType correlation type of data - * @param data data to normalize + * @param data data to normalize * * @return normalized data */ @@ -94,7 +96,7 @@ final public class CorrelationAttributeNormalizer { } catch (CentralRepoException ex) { throw new CorrelationAttributeNormalizationException("Failed to get default correlation types.", ex); } - } + } } /** @@ -102,7 +104,7 @@ final public class CorrelationAttributeNormalizer { * is a valid string of the format expected given the attributeType. * * @param attributeTypeId correlation type of data - * @param data data to normalize + * @param data data to normalize * * @return normalized data */ @@ -155,25 +157,43 @@ final public class CorrelationAttributeNormalizer { /** * Verify and normalize email address. + * + * @param emailAddress Address to normalize. + * @return Normalized email address. + * @throws CorrelationAttributeNormalizationExceptions If the input is not a + * valid email address. + * */ - private static String normalizeEmail(String data) throws CorrelationAttributeNormalizationException { - try { - return CommunicationsUtils.normalizeEmailAddress(data); - } - catch(TskCoreException ex) { - throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", data), ex); - } + static String normalizeEmail(String emailAddress) throws CorrelationAttributeNormalizationException { + if (isValidEmailAddress(emailAddress)) { + return emailAddress.toLowerCase().trim(); + } else { + throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", emailAddress)); + } } /** * Verify and normalize phone number. + * + * @param phoneNumber Phone number to normalize. + * @return Normalized phone number. + * @throws CorrelationAttributeNormalizationExceptions If the input is not a + * valid phone number. + * */ - private static String normalizePhone(String data) throws CorrelationAttributeNormalizationException { - try { - return CommunicationsUtils.normalizePhoneNum(data); - } - catch(TskCoreException ex) { - throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", data)); + static String normalizePhone(String phoneNumber) throws CorrelationAttributeNormalizationException { + if (isValidPhoneNumber(phoneNumber)) { + String normalizedNumber = phoneNumber.replaceAll("\\s+", ""); // remove spaces. + normalizedNumber = normalizedNumber.replaceAll("[\\-()]", ""); // remove parens & dashes. + + // ensure a min length + if (normalizedNumber.length() < MIN_PHONENUMBER_LEN) { + throw new CorrelationAttributeNormalizationException(String.format("Phone number string %s is too short ", phoneNumber)); + } + return normalizedNumber; + + } else { + throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", phoneNumber)); } } @@ -196,7 +216,7 @@ final public class CorrelationAttributeNormalizer { * @return the unmodified data if the data was a valid length to be an SSID * * @throws CorrelationAttributeNormalizationException if the data was not a - * valid SSID + * valid SSID */ private static String verifySsid(String data) throws CorrelationAttributeNormalizationException { if (data.length() <= 32) { @@ -223,10 +243,10 @@ final public class CorrelationAttributeNormalizer { * @param data The string to normalize and validate * * @return the data with common number seperators removed and lower cased if - * the data was determined to be a possible ICCID + * the data was determined to be a possible ICCID * * @throws CorrelationAttributeNormalizationException if the data was not a - * valid ICCID + * valid ICCID */ private static String normalizeIccid(String data) throws CorrelationAttributeNormalizationException { final String validIccidRegex = "^89[f0-9]{17,22}$"; @@ -250,10 +270,10 @@ final public class CorrelationAttributeNormalizer { * @param data The string to normalize and validate * * @return the data with common number seperators removed if the data was - * determined to be a possible IMSI + * determined to be a possible IMSI * * @throws CorrelationAttributeNormalizationException if the data was not a - * valid IMSI + * valid IMSI */ private static String normalizeImsi(String data) throws CorrelationAttributeNormalizationException { final String validImsiRegex = "^[0-9]{14,15}$"; @@ -274,10 +294,10 @@ final public class CorrelationAttributeNormalizer { * @param data The string to normalize and validate * * @return the data with common number seperators removed and lowercased if - * the data was determined to be a possible MAC + * the data was determined to be a possible MAC * * @throws CorrelationAttributeNormalizationException if the data was not a - * valid MAC + * valid MAC */ private static String normalizeMac(String data) throws CorrelationAttributeNormalizationException { final String validMacRegex = "^([a-f0-9]{12}|[a-f0-9]{16})$"; @@ -303,10 +323,10 @@ final public class CorrelationAttributeNormalizer { * @param data The string to normalize and validate * * @return the data with common number seperators removed if the data was - * determined to be a possible IMEI + * determined to be a possible IMEI * * @throws CorrelationAttributeNormalizationException if the data was not a - * valid IMEI + * valid IMEI */ private static String normalizeImei(String data) throws CorrelationAttributeNormalizationException { final String validImeiRegex = "^[0-9]{14,16}$"; @@ -318,6 +338,58 @@ final public class CorrelationAttributeNormalizer { } } + // These symbols are allowed in written form of phone numbers. + // A '+' is allowed only as a leading digit and hence not inlcuded here. + // While a dialed sequence may have additonal special characters, such as #, * or ',', + // CR attributes represent accounts and hence those chatracter are not allowed. + private static final Set PHONENUMBER_CHARS = new HashSet<>(Arrays.asList( + "-", "(", ")" + )); + + private static final int MIN_PHONENUMBER_LEN = 5; + + /** + * Checks if the given string is a valid phone number. + * + * @param phoneNumber String to check. + * + * @return True if the given string is a valid phone number, false + * otherwise. + */ + static boolean isValidPhoneNumber(String phoneNumber) { + + // A phone number may have a leading '+', special telephony chars, or digits. + // Anything else implies an invalid phone number. + for (int i = 0; i < phoneNumber.length(); i++) { + if ( !((i == 0 && phoneNumber.charAt(i) == '+') + || Character.isSpaceChar(phoneNumber.charAt(i)) + || Character.isDigit(phoneNumber.charAt(i)) + || PHONENUMBER_CHARS.contains(String.valueOf(phoneNumber.charAt(i))))) { + return false; + } + } + + // ensure a min length + return phoneNumber.length() >= MIN_PHONENUMBER_LEN; + } + + /** + * Checks if the given string is a valid email address. + * + * @param emailAddress String to check. + * + * @return True if the given string is a valid email address, false + * otherwise. + */ + static boolean isValidEmailAddress(String emailAddress) { + if (!StringUtils.isEmpty(emailAddress)) { + EmailValidator validator = EmailValidator.getInstance(true, true); + return validator.isValid(emailAddress); + } + + return false; + } + /** * This is a utility class - no need for constructing or subclassing, etc... */ diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index c025308b00..71efaa52b8 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -34,8 +34,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; -import org.sleuthkit.datamodel.CommunicationsUtils; import org.sleuthkit.datamodel.HashUtility; +import org.sleuthkit.datamodel.InvalidAccountIDException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -184,7 +184,15 @@ public class CorrelationAttributeUtil { makeCorrAttrsFromCommunicationArtifacts(correlationAttrs, sourceArtifact); } } - } catch (CentralRepoException ex) { + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.SEVERE, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS + return correlationAttrs; + } + catch (InvalidAccountIDException ex) { + logger.log(Level.SEVERE, String.format("Invalid account identifier (%s)", artifact), ex); // NON-NLS + return correlationAttrs; + } + catch (CentralRepoException ex) { logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS return correlationAttrs; } catch (TskCoreException ex) { @@ -198,18 +206,19 @@ public class CorrelationAttributeUtil { } /** - * Makes a correlation attribute instance from a phone number attribute of an - * artifact. + * Makes a correlation attribute instance from a phone number attribute of + * an artifact. * * @param corrAttrInstances Correlation attributes will be added to this. * @param artifact An artifact with a phone number attribute. * - * @throws TskCoreException If there is an error querying the case - * database. + * @throws TskCoreException If there is an error querying the case database. * @throws CentralRepoException If there is an error querying the central - * repository. + * repository. + * @throws CorrelationAttributeNormalizationException If there is an error + * in normalizing the attribute. */ - private static void makeCorrAttrsFromCommunicationArtifacts(List corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException { + private static void makeCorrAttrsFromCommunicationArtifacts(List corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException, CorrelationAttributeNormalizationException { CorrelationAttributeInstance corrAttr = null; /* @@ -227,13 +236,13 @@ public class CorrelationAttributeUtil { /* * Normalize the phone number. */ - if (value != null) { - if(CommunicationsUtils.isValidPhoneNumber(value)) { - value = CommunicationsUtils.normalizePhoneNum(value); - corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value); - if(corrAttr != null) { - corrAttrInstances.add(corrAttr); - } + if (value != null + && CorrelationAttributeNormalizer.isValidPhoneNumber(value)) { + + value = CorrelationAttributeNormalizer.normalizePhone(value); + corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value); + if (corrAttr != null) { + corrAttrInstances.add(corrAttr); } } } @@ -277,7 +286,7 @@ public class CorrelationAttributeUtil { * * @return The correlation attribute instance. */ - private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException { + private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact) throws InvalidAccountIDException, TskCoreException, CentralRepoException { // Get the account type from the artifact BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index 7e07afb4c2..b48797e3fc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -52,6 +52,7 @@ import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.HashHitInfo; +import org.sleuthkit.datamodel.InvalidAccountIDException; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; @@ -1080,34 +1081,37 @@ abstract class RdbmsCentralRepo implements CentralRepository { * within TSK core */ @Override - public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { - - // TBD: normalize the account id - waiting for a PR to be merged + public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException { // Get the account fom the accounts table - CentralRepoAccount account = getAccount(crAccountType, accountUniqueID); + String normalizedAccountID = CentralRepoAccount.normalizeAccountIdentifier(crAccountType, accountUniqueID); - // account not found in the table, create it - if (null == account) { - - String insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) " - + "VALUES (?, ?)"; - - try (Connection connection = connect(); - PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);) { - - preparedStatement.setInt(1, crAccountType.getAccountTypeId()); - preparedStatement.setString(2, accountUniqueID); // TBD: fill in the normalized ID - - preparedStatement.executeUpdate(); - - // get the account from the db - should exist now. - account = getAccount(crAccountType, accountUniqueID); - } catch (SQLException ex) { - throw new CentralRepoException("Error adding an account to CR database.", ex); - } + // insert the account. If there is a conflict, ignore it. + String insertSQL; + switch (CentralRepoDbManager.getSavedDbChoice().getDbPlatform()) { + case POSTGRESQL: + insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) " + getConflictClause(); //NON-NLS + break; + case SQLITE: + insertSQL = "INSERT OR IGNORE INTO accounts (account_type_id, account_unique_identifier) VALUES (?, ?) "; //NON-NLS + break; + default: + throw new CentralRepoException(String.format("Cannot add account to currently selected CR database platform %s", CentralRepoDbManager.getSavedDbChoice().getDbPlatform())); //NON-NLS } + - return account; + try (Connection connection = connect(); + PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);) { + + preparedStatement.setInt(1, crAccountType.getAccountTypeId()); + preparedStatement.setString(2, normalizedAccountID); + + preparedStatement.executeUpdate(); + + // get the account from the db - should exist now. + return getAccount(crAccountType, normalizedAccountID); + } catch (SQLException ex) { + throw new CentralRepoException("Error adding an account to CR database.", ex); + } } @Override @@ -1187,15 +1191,17 @@ abstract class RdbmsCentralRepo implements CentralRepository { * @return CentralRepoAccount for the give type/id. May return null if not * found. * - * @throws CentralRepoException + * @throws CentralRepoException If there is an error accessing Central Repository. + * @throws InvalidAccountIDException If the account identifier is not valid. */ - private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { - - CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID)); + @Override + public CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws InvalidAccountIDException, CentralRepoException { + String normalizedAccountID = CentralRepoAccount.normalizeAccountIdentifier(crAccountType, accountUniqueID); + CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, normalizedAccountID)); if (crAccount == null) { - crAccount = getCRAccountFromDb(crAccountType, accountUniqueID); + crAccount = getCRAccountFromDb(crAccountType, normalizedAccountID); if (crAccount != null) { - accountsCache.put(Pair.of(crAccountType, accountUniqueID), crAccount); + accountsCache.put(Pair.of(crAccountType, normalizedAccountID), crAccount); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 675e7c8807..f30c402513 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -43,6 +43,7 @@ import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; +import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; @@ -660,6 +661,8 @@ public class EamDbSettingsDialog extends JDialog { * found. */ private static boolean testStatusAndCreate(Component parent, CentralRepoDbManager manager, EamDbSettingsDialog dialog) { + closePersonasTopComponent(); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); manager.testStatus(); @@ -690,6 +693,21 @@ public class EamDbSettingsDialog extends JDialog { parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); return true; } + + + + /** + * Closes Personas top component if it exists. + */ + private static void closePersonasTopComponent() { + SwingUtilities.invokeLater(() -> { + TopComponent personasWindow = WindowManager.getDefault().findTopComponent("PersonasTopComponent"); + if (personasWindow != null && personasWindow.isOpened()) { + personasWindow.close(); + } + }); + } + /** * This method returns if changes to the central repository configuration diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED index a9085b6558..123bd71800 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED @@ -8,6 +8,8 @@ CreatePersonaAccountDialog_error_msg=Failed to create account. CreatePersonaAccountDialog_error_title=Account failure CreatePersonaAccountDialog_success_msg=Account added. CreatePersonaAccountDialog_success_title=Account added +CreatePersonaAccountDialog_invalid_account_msg=Account identifier is not valid. +CreatePersonaAccountDialog_invalid_account_Title=Invalid account identifier CTL_OpenPersonas=Personas CTL_PersonasTopComponentAction=Personas CTL_PersonaDetailsTopComponent=Persona Details @@ -19,6 +21,8 @@ PersonaAccountDialog_get_types_exception_msg=Failed to access central repository PersonaAccountDialog_get_types_exception_Title=Central Repository failure PersonaAccountDialog_identifier_empty_msg=The identifier field cannot be empty. PersonaAccountDialog_identifier_empty_Title=Empty identifier +PersonaAccountDialog_invalid_account_msg=Account identifier is not valid. +PersonaAccountDialog_invalid_account_Title=Invalid account identifier PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type. PersonaAccountDialog_search_empty_Title=Account not found PersonaAccountDialog_search_failure_msg=Central Repository account search failed. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java index ecb848da61..cfdf990710 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.Cent import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.InvalidAccountIDException; /** * Configuration dialog for creating an account. @@ -216,7 +217,8 @@ public class CreatePersonaAccountDialog extends JDialog { @Messages({ "CreatePersonaAccountDialog_error_title=Account failure", "CreatePersonaAccountDialog_error_msg=Failed to create account.", - }) + "CreatePersonaAccountDialog_invalid_account_Title=Invalid account identifier", + "CreatePersonaAccountDialog_invalid_account_msg=Account identifier is not valid.",}) private CentralRepoAccount createAccount(CentralRepoAccount.CentralRepoAccountType type, String identifier) { CentralRepoAccount ret = null; try { @@ -227,8 +229,14 @@ public class CreatePersonaAccountDialog extends JDialog { } catch (CentralRepoException e) { logger.log(Level.SEVERE, "Failed to create account", e); JOptionPane.showMessageDialog(this, - Bundle.CreatePersonaAccountDialog_error_title(), Bundle.CreatePersonaAccountDialog_error_msg(), + Bundle.CreatePersonaAccountDialog_error_title(), + JOptionPane.ERROR_MESSAGE); + } catch (InvalidAccountIDException e) { + logger.log(Level.WARNING, "Invalid account identifier", e); + JOptionPane.showMessageDialog(this, + Bundle.CreatePersonaAccountDialog_invalid_account_msg(), + Bundle.CreatePersonaAccountDialog_invalid_account_Title(), JOptionPane.ERROR_MESSAGE); } return ret; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaAccountDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaAccountDialog.java index 1606f07da6..558f92619e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaAccountDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonaAccountDialog.java @@ -37,6 +37,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.InvalidAccountIDException; /** * Configuration dialog for adding an account to a persona. @@ -277,7 +278,10 @@ public class PersonaAccountDialog extends JDialog { "PersonaAccountDialog_search_failure_Title=Account add failure", "PersonaAccountDialog_search_failure_msg=Central Repository account search failed.", "PersonaAccountDialog_search_empty_Title=Account not found", - "PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type.",}) + "PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type.", + "PersonaAccountDialog_invalid_account_Title=Invalid account identifier", + "PersonaAccountDialog_invalid_account_msg=Account identifier is not valid.", + }) private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed if (StringUtils.isBlank(identifierTextField.getText())) { JOptionPane.showMessageDialog(this, @@ -304,6 +308,14 @@ public class PersonaAccountDialog extends JDialog { JOptionPane.ERROR_MESSAGE); return; } + catch (InvalidAccountIDException e) { + logger.log(Level.SEVERE, "Invalid account identifier", e); + JOptionPane.showMessageDialog(this, + Bundle.PersonaAccountDialog_invalid_account_msg(), + Bundle.PersonaAccountDialog_invalid_account_Title(), + JOptionPane.ERROR_MESSAGE); + return; + } if (candidates.isEmpty()) { JOptionPane.showMessageDialog(this, Bundle.PersonaAccountDialog_search_empty_msg(), diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java index d38b379078..e051529f11 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java @@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.centralrepository.persona; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -60,28 +62,6 @@ public final class PersonasTopComponent extends TopComponent { private List currentResults = null; private Persona selectedPersona = null; - /** - * Listens for when this component will be rendered and executes a search to - * update gui when it is displayed. - */ - private final AncestorListener onAddListener = new AncestorListener() { - @Override - public void ancestorAdded(AncestorEvent event) { - resetSearchControls(); - setKeywordSearchEnabled(false, true); - } - - @Override - public void ancestorRemoved(AncestorEvent event) { - //Empty - } - - @Override - public void ancestorMoved(AncestorEvent event) { - //Empty - } - }; - @Messages({ "PersonasTopComponent_Name=Personas", "PersonasTopComponent_delete_exception_Title=Delete failure", @@ -165,7 +145,17 @@ public final class PersonasTopComponent extends TopComponent { } }); - addAncestorListener(onAddListener); + /** + * Listens for when this component will be rendered and executes a + * search to update gui when it is displayed. + */ + addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + resetSearchControls(); + setKeywordSearchEnabled(false, true); + } + }); } /** @@ -276,7 +266,7 @@ public final class PersonasTopComponent extends TopComponent { } @Messages({ - "PersonasTopComponent_search_exception_Title=Search failure", + "PersonasTopComponent_search_exception_Title=There was a failure during the search. Try opening a case to fully initialize the central repository database.", "PersonasTopComponent_search_exception_msg=Failed to search personas.", "PersonasTopComponent_noCR_msg=Central Repository is not enabled.",}) private void executeSearch() { diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties index 747f0c82f7..9a9ebd3929 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties @@ -14,7 +14,7 @@ FiltersPanel.endCheckBox.text=End: FiltersPanel.refreshButton.text=Refresh FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one. -FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. +FiltersPanel.needsRefreshLabel.text=Displayed data may be out of date. Press Refresh to update. VisualizationPanel.jButton1.text=Fast Organic CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse diff --git a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED index 59778273ab..0d4db75372 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/Bundle.properties-MERGED @@ -26,7 +26,7 @@ FiltersPanel.endCheckBox.text=End: FiltersPanel.refreshButton.text=Refresh FiltersPanel.deviceRequiredLabel.text=Select at least one. FiltersPanel.accountTypeRequiredLabel.text=Select at least one. -FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh. +FiltersPanel.needsRefreshLabel.text=Displayed data may be out of date. Press Refresh to update. OpenCVTAction.displayName=Communications PinAccountsAction.pluralText=Add Selected Accounts to Visualization PinAccountsAction.singularText=Add Selected Account to Visualization diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTFilterRefresher.java b/Core/src/org/sleuthkit/autopsy/communications/CVTFilterRefresher.java new file mode 100755 index 0000000000..1ba9d6c81e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTFilterRefresher.java @@ -0,0 +1,161 @@ +/* + * 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.communications; + +import java.beans.PropertyChangeEvent; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Refreshes the CVTFilterPanel. + */ +abstract class CVTFilterRefresher implements RefreshThrottler.Refresher { + + private static final Logger logger = Logger.getLogger(CVTFilterRefresher.class.getName()); + /** + * contains all of the gui control specific update code. Refresh will call + * this method with an involkLater so that the updating of the swing + * controls can happen on the EDT. + * + * @param data + */ + abstract void updateFilterPanel(FilterPanelData data); + + @Override + public void refresh() { + try { + Integer startTime; + Integer endTime; + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + + // Fetch Min/Max start times + try (SleuthkitCase.CaseDbQuery dbQuery = skCase.executeQuery("SELECT MAX(date_time) as end, MIN(date_time) as start from account_relationships")) { + // ResultSet is closed by CasDBQuery + ResultSet rs = dbQuery.getResultSet(); + startTime = rs.getInt("start"); // NON-NLS + endTime = rs.getInt("end"); // NON-NLS + } + // Get the devices with CVT artifacts + List deviceObjIds = new ArrayList<>(); + try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery("SELECT DISTINCT data_source_obj_id FROM account_relationships")) { + // ResultSet is closed by CasDBQuery + ResultSet rs = queryResult.getResultSet(); + while (rs.next()) { + deviceObjIds.add(rs.getInt(1)); + } + } + + // The map key is the Content name instead of the data source name + // to match how the CVT filters work. + Map dataSourceMap = new HashMap<>(); + for (DataSource dataSource : skCase.getDataSources()) { + if (deviceObjIds.contains((int) dataSource.getId())) { + String dsName = skCase.getContentById(dataSource.getId()).getName(); + dataSourceMap.put(dsName, dataSource); + } + } + + List accountTypesInUse = skCase.getCommunicationsManager().getAccountTypesInUse(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + updateFilterPanel(new FilterPanelData(dataSourceMap, accountTypesInUse, startTime, endTime)); + } + }); + + } catch (SQLException | TskCoreException ex) { + logger.log(Level.WARNING, "Unable to update CVT filter panel.", ex); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(DATA_ADDED.toString())) { + // Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits + ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); + return (null != eventData + && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID())); + } + + return false; + } + + /** + * Class to hold the data for setting up the filter panel gui controls. + */ + class FilterPanelData { + + private final Map dataSourceMap; + private final Integer startTime; + private final Integer endTime; + private final List accountTypesInUse; + + FilterPanelData(Map dataSourceMap, List accountTypesInUse, Integer startTime, Integer endTime) { + this.dataSourceMap = dataSourceMap; + this.startTime = startTime; + this.endTime = endTime; + this.accountTypesInUse = accountTypesInUse; + } + + Map getDataSourceMap() { + return dataSourceMap; + } + + Integer getStartTime() { + return startTime; + } + + Integer getEndTime() { + return endTime; + } + + List getAccountTypesInUse() { + return accountTypesInUse; + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java index 5f62c73c67..fbd41b5840 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/communications/CVTTopComponent.java @@ -189,7 +189,7 @@ public final class CVTTopComponent extends TopComponent { * * Re-applying the filters means we will lose the selection... */ - filtersPane.updateAndApplyFilters(true); + filtersPane.initalizeFilters(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form index a3000dfdeb..8598d04494 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.form @@ -18,11 +18,11 @@ + + - - diff --git a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java index 379619a9a3..7bdf3a46e3 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/FiltersPanel.java @@ -18,12 +18,11 @@ */ package org.sleuthkit.autopsy.communications; +import org.sleuthkit.autopsy.guiutils.RefreshThrottler; import com.google.common.collect.ImmutableSet; import com.google.common.eventbus.Subscribe; import java.awt.event.ItemListener; import java.beans.PropertyChangeListener; -import java.sql.ResultSet; -import java.sql.SQLException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -37,8 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.Box; import javax.swing.BoxLayout; @@ -47,11 +44,9 @@ import javax.swing.ImageIcon; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.SwingWorker; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -61,7 +56,6 @@ import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback; import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter; import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter; @@ -71,8 +65,6 @@ import org.sleuthkit.datamodel.DataSource; import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG; import static org.sleuthkit.datamodel.Relationship.Type.CONTACT; import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * Panel that holds the Filter control widgets and triggers queries against the @@ -116,6 +108,8 @@ final public class FiltersPanel extends JPanel { */ private final ItemListener validationListener; + private final RefreshThrottler refreshThrottler; + /** * Is the device account type filter enabled or not. It should be enabled * when the Table/Brows mode is active and disabled when the visualization @@ -131,6 +125,7 @@ final public class FiltersPanel extends JPanel { initComponents(); initalizeDeviceAccountType(); + setDateTimeFiltersToDefault(); deviceRequiredLabel.setVisible(false); accountTypeRequiredLabel.setVisible(false); @@ -162,25 +157,27 @@ final public class FiltersPanel extends JPanel { if (eventType.equals(DATA_ADDED.toString())) { // Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); - if (null != eventData + if (!needsRefresh + && null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID())) { - updateFilters(true); needsRefresh = true; validateFilters(); } } }; + refreshThrottler = new RefreshThrottler(new FilterPanelRefresher(false, false)); + this.ingestJobListener = pce -> { String eventType = pce.getPropertyName(); - if (eventType.equals(COMPLETED.toString()) - && updateFilters(true)) { + if (eventType.equals(COMPLETED.toString()) && !needsRefresh) { needsRefresh = true; validateFilters(); + } }; @@ -222,39 +219,24 @@ final public class FiltersPanel extends JPanel { } } - /** - * Update the filter widgets, and apply them. - */ - void updateAndApplyFilters(boolean initialState) { - updateFilters(initialState); - applyFilters(); - initalizeDateTimeFilters(); + void initalizeFilters() { + Runnable runnable = new Runnable() { + @Override + public void run() { + new FilterPanelRefresher(true, true).refresh(); + } + }; + runnable.run(); } private void updateTimeZone() { dateRangeLabel.setText("Date Range (" + Utils.getUserPreferredZoneId().toString() + "):"); } - /** - * Updates the filter widgets to reflect he data sources/types in the case. - */ - private boolean updateFilters(boolean initialState) { - final SleuthkitCase sleuthkitCase; - try { - sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to perform filter update, update has been cancelled. Case is closed.", ex); - return false; - } - boolean newAccountType = updateAccountTypeFilter(initialState, sleuthkitCase); - boolean newDeviceFilter = updateDeviceFilter(initialState, sleuthkitCase); - // both or either are true, return true; - return newAccountType || newDeviceFilter; - } - @Override public void addNotify() { super.addNotify(); + refreshThrottler.registerForIngestModuleEvents(); IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, ingestListener); IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, ingestJobListener); Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { @@ -272,6 +254,7 @@ final public class FiltersPanel extends JPanel { @Override public void removeNotify() { super.removeNotify(); + refreshThrottler.unregisterEventListener(); IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener); } @@ -285,33 +268,25 @@ final public class FiltersPanel extends JPanel { /** * Populate the Account Types filter widgets. * - * @param selected The initial value for the account type checkbox. - * @param sleuthkitCase The sleuthkit case for containing the account - * information. + * @param accountTypesInUse List of accountTypes currently in use * * @return True, if a new accountType was found */ - private boolean updateAccountTypeFilter(boolean selected, SleuthkitCase sleuthkitCase) { + private boolean updateAccountTypeFilter(List accountTypesInUse, boolean checkNewOnes) { boolean newOneFound = false; - try { - List accountTypesInUse = sleuthkitCase.getCommunicationsManager().getAccountTypesInUse(); - for (Account.Type type : accountTypesInUse) { + for (Account.Type type : accountTypesInUse) { + if (!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)) { + CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(type, checkNewOnes); + accountTypeMap.put(type, panel.getCheckBox()); + accountTypeListPane.add(panel); - if (!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)) { - CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(type, selected); - accountTypeMap.put(type, panel.getCheckBox()); - accountTypeListPane.add(panel); - - newOneFound = true; - } + newOneFound = true; } - - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to update to update Account Types Filter", ex); } + if (newOneFound) { - accountTypeListPane.revalidate(); + accountTypeListPane.validate(); } return newOneFound; @@ -345,26 +320,20 @@ final public class FiltersPanel extends JPanel { * * @return true if a new device was found */ - private boolean updateDeviceFilter(boolean selected, SleuthkitCase sleuthkitCase) { + private void updateDeviceFilterPanel(Map dataSourceMap, boolean checkNewOnes) { boolean newOneFound = false; - try { - for (DataSource dataSource : sleuthkitCase.getDataSources()) { - String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName(); - if (devicesMap.containsKey(dataSource.getDeviceId())) { - continue; - } - - final JCheckBox jCheckBox = new JCheckBox(dsName, selected); - jCheckBox.addItemListener(validationListener); - devicesListPane.add(jCheckBox); - jCheckBox.setToolTipText(dsName); - devicesMap.put(dataSource.getDeviceId(), jCheckBox); - - newOneFound = true; - + for (Entry entry : dataSourceMap.entrySet()) { + if (devicesMap.containsKey(entry.getValue().getDeviceId())) { + continue; } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "There was a error loading the datasources for the case.", ex); + + final JCheckBox jCheckBox = new JCheckBox(entry.getKey(), checkNewOnes); + jCheckBox.addItemListener(validationListener); + jCheckBox.setToolTipText(entry.getKey()); + devicesListPane.add(jCheckBox); + devicesMap.put(entry.getValue().getDeviceId(), jCheckBox); + + newOneFound = true; } if (newOneFound) { @@ -378,8 +347,16 @@ final public class FiltersPanel extends JPanel { devicesListPane.revalidate(); } + } - return newOneFound; + private void updateDateTimePicker(Integer start, Integer end) { + if (start != null && start != 0) { + startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(start), Utils.getUserPreferredZoneId()).toLocalDate()); + } + + if (end != null && end != 0) { + endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(end), Utils.getUserPreferredZoneId()).toLocalDate()); + } } /** @@ -488,9 +465,9 @@ final public class FiltersPanel extends JPanel { setLayout(new java.awt.GridBagLayout()); - scrollPane.setBorder(null); scrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.setAutoscrolls(true); + scrollPane.setBorder(null); mainPanel.setLayout(new java.awt.GridBagLayout()); @@ -847,10 +824,11 @@ final public class FiltersPanel extends JPanel { /** * Post an event with the new filters. */ - private void applyFilters() { - CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(getFilter(), getStartControlState(), getEndControlState())); + void applyFilters() { needsRefresh = false; validateFilters(); + CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(getFilter(), getStartControlState(), getEndControlState())); + } /** @@ -969,31 +947,6 @@ final public class FiltersPanel extends JPanel { map.values().forEach(box -> box.setSelected(selected)); } - /** - * initalize the DateTimePickers by grabbing the earliest and latest time - * from the autopsy db. - */ - private void initalizeDateTimeFilters() { - Case currentCase = null; - try { - currentCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.INFO, "Tried to intialize communication filters date range filters without an open case, using default values"); - } - - if (currentCase == null) { - setDateTimeFiltersToDefault(); - openCase = null; - return; - } - - if (!currentCase.equals(openCase)) { - setDateTimeFiltersToDefault(); - openCase = currentCase; - (new DatePickerWorker()).execute(); - } - } - private void setDateTimeFiltersToDefault() { startDatePicker.setDate(LocalDate.now().minusWeeks(3)); endDatePicker.setDate(LocalDate.now()); @@ -1170,68 +1123,39 @@ final public class FiltersPanel extends JPanel { } /** - * A simple class that implements CaseDbAccessQueryCallback. Can be used as - * an anonymous innerclass with the CaseDbAccessManager select function. + * Extends the CVTFilterRefresher abstract class to add the calls to update + * the ui controls with the data found. Note that updateFilterPanel is run + * in the EDT. */ - class FilterPanelQueryCallback implements CaseDbAccessQueryCallback { + final class FilterPanelRefresher extends CVTFilterRefresher { - @Override - public void process(ResultSet rs) { - // Subclasses can implement their own process function. - } - } + private final boolean selectNewOption; + private final boolean refreshAfterUpdate; - final class DatePickerWorker extends SwingWorker, Void> { - - @Override - protected Map doInBackground() throws Exception { - if (openCase == null) { - return null; - } - - Map resultMap = new HashMap<>(); - String queryString = "max(date_time) as end, min(date_time) as start from account_relationships"; // NON-NLS - - openCase.getSleuthkitCase().getCaseDbAccessManager().select(queryString, new FilterPanelQueryCallback() { - @Override - public void process(ResultSet rs) { - try { - if (rs.next()) { - int startDate = rs.getInt("start"); // NON-NLS - int endDate = rs.getInt("end"); // NON-NLS - - resultMap.put("start", startDate); // NON-NLS - resultMap.put("end", endDate); // NON-NLS - } - } catch (SQLException ex) { - // Not the end of the world if this fails. - logger.log(Level.WARNING, String.format("SQL Exception thrown from Query: %s", queryString), ex); - } - } - }); - - return resultMap; + FilterPanelRefresher(boolean selectNewOptions, boolean refreshAfterUpdate) { + this.selectNewOption = selectNewOptions; + this.refreshAfterUpdate = refreshAfterUpdate; } @Override - protected void done() { - try { - Map resultMap = get(); - if (resultMap != null) { - Integer start = resultMap.get("start"); - Integer end = resultMap.get("end"); + void updateFilterPanel(CVTFilterRefresher.FilterPanelData data) { + updateDateTimePicker(data.getStartTime(), data.getEndTime()); + updateDeviceFilterPanel(data.getDataSourceMap(), selectNewOption); + updateAccountTypeFilter(data.getAccountTypesInUse(), selectNewOption); - if (start != null && start != 0) { - startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(start), Utils.getUserPreferredZoneId()).toLocalDate()); - } + FiltersPanel.this.repaint(); - if (end != null && end != 0) { - endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(end), Utils.getUserPreferredZoneId()).toLocalDate()); - } - } - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.WARNING, "Exception occured after date time sql query", ex); + if (refreshAfterUpdate) { + applyFilters(); } + + if (!isEnabled()) { + setEnabled(true); + } + + validateFilters(); + + repaint(); } } @@ -1246,5 +1170,4 @@ final public class FiltersPanel extends JPanel { return e1.getText().toLowerCase().compareTo(e2.getText().toLowerCase()); } } - } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java index ff6607a84e..8ac88abc51 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java @@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment; import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments; import org.sleuthkit.datamodel.CommunicationsUtils; +import org.sleuthkit.datamodel.InvalidAccountIDException; import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil; /** @@ -113,7 +114,7 @@ class AccountSummary { isReference = true; break; } - } catch (TskCoreException ex) { + } catch (InvalidAccountIDException ex) { logger.log(Level.WARNING, String.format("Exception thrown " + "in trying to normalize attribute value: %s", attributeValue), ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties index b14d8a2688..6ecb170c10 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties @@ -9,7 +9,6 @@ SummaryViewer.callLogsLabel.text=Call Logs: ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages ThreadPane.backButton.text=<--- SummaryViewer.caseReferencesPanel.border.title=Other Occurrences -SummaryViewer.fileReferencesPanel.border.title=File References in Current Case MessageViewer.threadsLabel.text=Select a Thread to View MessageViewer.threadNameLabel.text= MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: @@ -27,3 +26,5 @@ SummaryViewer.referencesLabel.text=Communication References: SummaryViewer.referencesDataLabel.text= SummaryViewer.contactsLabel.text=Book Entries: SummaryViewer.accountCountry.text= +SummaryViewer.fileRefPane.border.title=File References in Current Case +SummaryViewer.selectAccountFileRefLabel.text= SummaryViewer_FileRefNameColumn_Title=Path SummaryViewer_TabTitle=Summary ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages ThreadPane.backButton.text=<--- SummaryViewer.caseReferencesPanel.border.title=Other Occurrences -SummaryViewer.fileReferencesPanel.border.title=File References in Current Case MessageViewer.threadsLabel.text=Select a Thread to View MessageViewer.threadNameLabel.text= MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: @@ -73,3 +73,5 @@ SummaryViewer.referencesLabel.text=Communication References: SummaryViewer.referencesDataLabel.text= SummaryViewer.contactsLabel.text=Book Entries: SummaryViewer.accountCountry.text= +SummaryViewer.fileRefPane.border.title=File Referernce(s) in Current Case +SummaryViewer.selectAccountFileRefLabel.text=