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.jarext/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.jarext/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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -285,5 +269,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
index fbfc28e493..94fb1bca86 100755
--- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java
@@ -18,8 +18,14 @@
*/
package org.sleuthkit.autopsy.communications.relationships;
-import java.util.Set;
+import java.awt.CardLayout;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import javax.swing.DefaultListModel;
import javax.swing.JPanel;
+import javax.swing.SwingWorker;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.openide.explorer.view.OutlineView;
@@ -27,8 +33,11 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.datamodel.AccountFileInstance;
/**
* Account Summary View Panel. This panel shows a list of various counts related
@@ -39,6 +48,9 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
public class SummaryViewer extends javax.swing.JPanel implements RelationshipsViewer {
private final Lookup lookup;
+ private final DefaultListModel fileRefListModel;
+
+ private static final Logger logger = Logger.getLogger(SummaryViewer.class.getName());
@Messages({
"SummaryViewer_TabTitle=Summary",
@@ -60,14 +72,11 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
lookup = Lookup.getDefault();
initComponents();
- OutlineView outlineView = fileReferencesPanel.getOutlineView();
+ fileRefListModel = new DefaultListModel<>();
+ fileRefList.setModel(fileRefListModel);
+
+ OutlineView outlineView = caseReferencesPanel.getOutlineView();
Outline outline = outlineView.getOutline();
-
- outline.setRootVisible(false);
- ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.SummaryViewer_FileRefNameColumn_Title());
-
- outlineView = caseReferencesPanel.getOutlineView();
- outline = outlineView.getOutline();
outlineView.setPropertyColumns("creationDate", Bundle.SummaryViewer_Creation_Date_Title()); //NON-NLS
outline.setRootVisible(false);
@@ -76,7 +85,6 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
clearControls();
caseReferencesPanel.hideOutlineView(Bundle.SummaryViewer_CentralRepository_Message());
- fileReferencesPanel.hideOutlineView(Bundle.SummaryViewer_FileRef_Message());
}
@Override
@@ -98,19 +106,24 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
caseReferencesPanel.showOutlineView();
}
+ CardLayout cardLayout = (CardLayout) fileRefPane.getLayout();
+ cardLayout.show(fileRefPane, "selectAccountCard");
+
+ fileRefListModel.removeAllElements();
+
// Request is that the SummaryViewer only show information if one
// account is selected
- if (info.getAccounts().size() != 1) {
+ if (info == null || info.getAccounts().size() != 1) {
setEnabled(false);
clearControls();
-
- accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description_MuliSelect());
- fileReferencesPanel.hideOutlineView(Bundle.SummaryViewer_FileRef_Message());
+ accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description_MuliSelect());
+ selectAccountFileRefLabel.setText(Bundle.SummaryViewer_FileRef_Message());
+
} else {
Account[] accountArray = info.getAccounts().toArray(new Account[1]);
Account account = accountArray[0];
-
+
if (account.getAccountType().getTypeName().contains("PHONE")) {
String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID());
accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID()));
@@ -121,13 +134,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
accountCountry.setText("");
accountCountry.setEnabled(false);
}
-
+
if (account.getAccountType().equals(Account.Type.DEVICE)) {
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description());
} else {
accoutDescriptionLabel.setText(Bundle.SummaryViewer_Device_Account_Description());
}
-
+
AccountSummary summaryDetails = new AccountSummary(account, info.getArtifacts());
thumbnailsDataLabel.setText(Integer.toString(summaryDetails.getThumbnailCnt()));
@@ -138,11 +151,10 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
referencesDataLabel.setText(Integer.toString(summaryDetails.getReferenceCnt()));
contactsDataLabel.setText(Integer.toString(summaryDetails.getContactsCnt()));
- fileReferencesPanel.showOutlineView();
-
- fileReferencesPanel.setNode(new AbstractNode(Children.create(new AccountSourceContentChildNodeFactory(info.getAccounts()), true)));
caseReferencesPanel.setNode(new AbstractNode(Children.create(new CorrelationCaseChildNodeFactory(info.getAccounts()), true)));
+ updateFileReferences(account);
+
setEnabled(true);
}
}
@@ -165,7 +177,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
contactsLabel.setEnabled(enabled);
messagesLabel.setEnabled(enabled);
caseReferencesPanel.setEnabled(enabled);
- fileReferencesPanel.setEnabled(enabled);
+ fileRefList.setEnabled(enabled);
countsPanel.setEnabled(enabled);
attachmentsLabel.setEnabled(enabled);
referencesLabel.setEnabled(enabled);
@@ -184,29 +196,46 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
accoutDescriptionLabel.setText("");
referencesDataLabel.setText("");
accountCountry.setText("");
-
- fileReferencesPanel.setNode(new AbstractNode(Children.LEAF));
+
+ fileRefListModel.clear();
caseReferencesPanel.setNode(new AbstractNode(Children.LEAF));
}
- /**
- * For the given accounts create a comma separated string of all of the
- * names (TypeSpecificID).
- *
- * @param accounts Set of selected accounts
- *
- * @return String listing the account names
- */
- private String createAccountLabel(Set accounts) {
- StringBuilder buffer = new StringBuilder();
- accounts.stream().map((account) -> {
- buffer.append(account.getTypeSpecificID());
- return account;
- }).forEachOrdered((_item) -> {
- buffer.append(", ");
- });
+ @Messages({
+ "SummaryViewer_Fetching_References="
+ })
+ private void updateFileReferences(final Account account) {
+ SwingWorker, Void> worker = new SwingWorker, Void>() {
+ @Override
+ protected List doInBackground() throws Exception {
+ List stringList = new ArrayList<>();
+ List accountFileInstanceList = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().getAccountFileInstances(account);
+ for (AccountFileInstance instance : accountFileInstanceList) {
+ stringList.add(instance.getFile().getUniquePath());
+ }
+ return stringList;
+ }
- return buffer.toString().substring(0, buffer.length() - 2);
+ @Override
+ protected void done() {
+ try {
+ List fileRefList = get();
+
+ fileRefList.forEach(value -> {
+ fileRefListModel.addElement(value);
+ });
+
+ CardLayout cardLayout = (CardLayout) fileRefPane.getLayout();
+ cardLayout.show(fileRefPane, "listPanelCard");
+
+ } catch (InterruptedException | ExecutionException ex) {
+ logger.log(Level.WARNING, String.format(("Failed to get file references for account: %d"), account.getAccountID()), ex);
+ }
+ }
+ };
+
+ selectAccountFileRefLabel.setText(Bundle.SummaryViewer_Fetching_References());
+ worker.execute();
}
/**
@@ -237,8 +266,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
contactsDataLabel = new javax.swing.JLabel();
referencesLabel = new javax.swing.JLabel();
referencesDataLabel = new javax.swing.JLabel();
- fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
+ fileRefPane = new javax.swing.JPanel();
+ javax.swing.JPanel fileRefScrolPanel = new javax.swing.JPanel();
+ javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
+ fileRefList = new javax.swing.JList<>();
+ javax.swing.JPanel selectAccountPane = new javax.swing.JPanel();
+ selectAccountFileRefLabel = new javax.swing.JLabel();
setLayout(new java.awt.GridBagLayout());
@@ -393,17 +427,6 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
add(contanctsPanel, gridBagConstraints);
- fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N
- gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 3;
- gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
- gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
- gridBagConstraints.weightx = 1.0;
- gridBagConstraints.weighty = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
- add(fileReferencesPanel, gridBagConstraints);
-
caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
@@ -414,6 +437,38 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
add(caseReferencesPanel, gridBagConstraints);
+
+ fileRefPane.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileRefPane.border.title"))); // NOI18N
+ fileRefPane.setLayout(new java.awt.CardLayout());
+
+ fileRefScrolPanel.setLayout(new java.awt.BorderLayout());
+
+ fileRefList.setModel(new javax.swing.AbstractListModel() {
+ String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
+ public int getSize() { return strings.length; }
+ public String getElementAt(int i) { return strings[i]; }
+ });
+ scrollPane.setViewportView(fileRefList);
+
+ fileRefScrolPanel.add(scrollPane, java.awt.BorderLayout.CENTER);
+
+ fileRefPane.add(fileRefScrolPanel, "listPanelCard");
+
+ selectAccountPane.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(selectAccountFileRefLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.selectAccountFileRefLabel.text")); // NOI18N
+ selectAccountFileRefLabel.setEnabled(false);
+ selectAccountPane.add(selectAccountFileRefLabel, new java.awt.GridBagConstraints());
+
+ fileRefPane.add(selectAccountPane, "selectAccountCard");
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weighty = 1.0;
+ add(fileRefPane, gridBagConstraints);
}// //GEN-END:initComponents
@@ -430,11 +485,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
private javax.swing.JLabel contactsLabel;
private javax.swing.JPanel contanctsPanel;
private javax.swing.JPanel countsPanel;
- private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel fileReferencesPanel;
+ private javax.swing.JList fileRefList;
+ private javax.swing.JPanel fileRefPane;
private javax.swing.JLabel messagesDataLabel;
private javax.swing.JLabel messagesLabel;
private javax.swing.JLabel referencesDataLabel;
private javax.swing.JLabel referencesLabel;
+ private javax.swing.JLabel selectAccountFileRefLabel;
private javax.swing.JPanel summaryPanel;
private javax.swing.JLabel thumbnailCntLabel;
private javax.swing.JLabel thumbnailsDataLabel;
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
index 734a7ec180..95da68ca10 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java
@@ -633,21 +633,18 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
return new HashMap<>();
}
+ // make a list of all unique accounts for this contact
+ if (!account.getAccountType().equals(Account.Type.DEVICE)) {
+ CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName());
+ CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID());
+
+ if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) {
+ uniqueAccountsList.add(crAccount);
+ }
+ }
+
Collection personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
if (personaAccounts != null && !personaAccounts.isEmpty()) {
-
- // look for unique accounts
- Collection accountCandidates
- = personaAccounts
- .stream()
- .map(PersonaAccount::getAccount)
- .collect(Collectors.toList());
- for (CentralRepoAccount crAccount : accountCandidates) {
- if (uniqueAccountsList.contains(crAccount) == false) {
- uniqueAccountsList.add(crAccount);
- }
- }
-
// get personas for the account
Collection personas
= personaAccounts
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
index f7f1f6559e..333860d600 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java
@@ -290,6 +290,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
contextContainer.add(usagePanel);
}
}
+
+ contextContainer.setBackground(javax.swing.UIManager.getDefaults().getColor("window"));
contextContainer.setEnabled(foundASource);
contextContainer.setVisible(foundASource);
jScrollPane.getViewport().setView(contextContainer);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
index 8d9943ffc4..8408c0c74a 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java
@@ -55,6 +55,7 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWO
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
/**
* Parent of the "extracted content" artifacts to be displayed in the tree.
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
index 566545eecd..7108d13a84 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java
@@ -48,6 +48,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
+import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
/**
* Filters database results by file extension.
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
index 370b4f2809..5e2f19648c 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java
@@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
+import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
/**
* Class which contains the Nodes for the 'By Mime Type' view located in the
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java
index da599ea7d7..8777b4e829 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYCallsFileParser.java
@@ -29,6 +29,7 @@ import org.sleuthkit.datamodel.Blackboard.BlackboardException;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
@@ -285,8 +286,12 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser {
// If both callerId and calleeList were non-null/non-empty, then
// it would have been a valid combination.
if (callerId != null) {
+ try {
currentCase.getCommunicationsManager().createAccountFileInstance(
Account.Type.PHONE, callerId, PARSER_NAME, parent);
+ } catch (InvalidAccountIDException ex) {
+ logger.log(Level.WARNING, String.format("Invalid account identifier %s", callerId), ex);
+ }
otherAttributes.add(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
@@ -294,8 +299,13 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser {
}
for (String phone : calleeList) {
+ try {
currentCase.getCommunicationsManager().createAccountFileInstance(
Account.Type.PHONE, phone, PARSER_NAME, parent);
+ } catch (InvalidAccountIDException ex) {
+ logger.log(Level.WARNING, String.format("Invalid account identifier %s", phone), ex);
+ }
+
otherAttributes.add(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java
index b3cd172f4a..de56bb2d47 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYMessagesFileParser.java
@@ -34,6 +34,7 @@ import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.Blackboard.BlackboardException;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
@@ -307,8 +308,13 @@ final class XRYMessagesFileParser implements XRYFileParser {
} else if(namespace == XryNamespace.TO || direction == CommunicationDirection.OUTGOING) {
recipientIdsList.add(pair.getValue());
} else {
- currentCase.getCommunicationsManager().createAccountFileInstance(
- Account.Type.PHONE, pair.getValue(), PARSER_NAME, parent);
+ try {
+ currentCase.getCommunicationsManager().createAccountFileInstance(
+ Account.Type.PHONE, pair.getValue(), PARSER_NAME, parent);
+ } catch (InvalidAccountIDException ex) {
+ logger.log(Level.WARNING, String.format("Invalid account identifier %s", pair.getValue()), ex);
+ }
+
otherAttributes.add(new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
PARSER_NAME, pair.getValue()));
diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java
index b244c88966..85fbe0e12f 100755
--- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java
+++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYUtils.java
@@ -27,7 +27,7 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import org.sleuthkit.datamodel.CommunicationsUtils;
-import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
/**
* Common utility methods shared among all XRY parser implementations.
@@ -46,7 +46,7 @@ final class XRYUtils {
try {
CommunicationsUtils.normalizePhoneNum(phoneNumber);
return true;
- } catch (TskCoreException ex) {
+ } catch (InvalidAccountIDException ex) {
return false;
}
}
@@ -55,7 +55,7 @@ final class XRYUtils {
try {
CommunicationsUtils.normalizeEmailAddress(email);
return true;
- } catch (TskCoreException ex) {
+ } catch (InvalidAccountIDException ex) {
return false;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryTopComponent.java b/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryTopComponent.java
index 607459325d..526207981c 100644
--- a/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryTopComponent.java
+++ b/Core/src/org/sleuthkit/autopsy/discovery/DiscoveryTopComponent.java
@@ -39,14 +39,14 @@ import org.sleuthkit.autopsy.discovery.FileSearchFiltering.FileFilter;
/**
* Create a dialog for displaying the Discovery results.
*/
-@TopComponent.Description(preferredID = "DiscoveryTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER)
+@TopComponent.Description(preferredID = "Discovery", persistenceType = TopComponent.PERSISTENCE_NEVER)
@TopComponent.Registration(mode = "discovery", openAtStartup = false)
@RetainLocation("discovery")
@NbBundle.Messages("DiscoveryTopComponent.name= Discovery")
public final class DiscoveryTopComponent extends TopComponent {
private static final long serialVersionUID = 1L;
- private static final String PREFERRED_ID = "DiscoveryTopComponent"; // NON-NLS
+ private static final String PREFERRED_ID = "Discovery"; // NON-NLS
private final GroupListPanel groupListPanel;
private final DetailsPanel detailsPanel;
private final ResultsPanel resultsPanel;
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/RefreshThrottler.java b/Core/src/org/sleuthkit/autopsy/guiutils/RefreshThrottler.java
old mode 100644
new mode 100755
similarity index 94%
rename from Core/src/org/sleuthkit/autopsy/datamodel/RefreshThrottler.java
rename to Core/src/org/sleuthkit/autopsy/guiutils/RefreshThrottler.java
index a8d3ed5581..2610642761
--- a/Core/src/org/sleuthkit/autopsy/datamodel/RefreshThrottler.java
+++ b/Core/src/org/sleuthkit/autopsy/guiutils/RefreshThrottler.java
@@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.sleuthkit.autopsy.datamodel;
+package org.sleuthkit.autopsy.guiutils;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
@@ -33,13 +33,13 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
* potentially expensive UI refresh events when DATA_ADDED and CONTENT_CHANGED
* ingest manager events are received.
*/
-class RefreshThrottler {
+public class RefreshThrottler {
/**
* The Refresher interface needs to be implemented by ChildFactory instances
* that wish to take advantage of throttled refresh functionality.
*/
- interface Refresher {
+ public interface Refresher {
/**
* The RefreshThrottler calls this method when the RefreshTask runs.
@@ -89,7 +89,7 @@ class RefreshThrottler {
*/
private final PropertyChangeListener pcl;
- RefreshThrottler(Refresher r) {
+ public RefreshThrottler(Refresher r) {
this.refreshTaskRef = new AtomicReference<>(null);
refresher = r;
@@ -112,14 +112,14 @@ class RefreshThrottler {
/**
* Set up listener for ingest module events of interest.
*/
- void registerForIngestModuleEvents() {
+ public void registerForIngestModuleEvents() {
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl);
}
/**
* Remove ingest module event listener.
*/
- void unregisterEventListener() {
+ public void unregisterEventListener() {
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
index ee1cffea00..4b5b431537 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
@@ -89,6 +89,7 @@ IngestJobTableModel.colName.inProgress=In Progress
IngestJobTableModel.colName.filesQueued=Files Queued
IngestJobTableModel.colName.dirQueued=Dir Queued
IngestJobTableModel.colName.rootQueued=Root Queued
+IngestJobTableModel.colName.streamingQueued=Streaming Queued
IngestJobTableModel.colName.dsQueued=DS Queued
ModuleTableModel.colName.module=Module
ModuleTableModel.colName.duration=Duration
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED
index 9e4f612b6b..157506a57f 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties-MERGED
@@ -104,6 +104,7 @@ IngestJobTableModel.colName.inProgress=In Progress
IngestJobTableModel.colName.filesQueued=Files Queued
IngestJobTableModel.colName.dirQueued=Dir Queued
IngestJobTableModel.colName.rootQueued=Root Queued
+IngestJobTableModel.colName.streamingQueued=Streaming Queued
IngestJobTableModel.colName.dsQueued=DS Queued
ModuleTableModel.colName.module=Module
ModuleTableModel.colName.duration=Duration
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java
index 67e8ff55e8..cf1fefb2d4 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestProgressSnapshotPanel.java
@@ -182,6 +182,8 @@ class IngestProgressSnapshotPanel extends javax.swing.JPanel {
NbBundle.getMessage(this.getClass(),
"IngestJobTableModel.colName.rootQueued"),
NbBundle.getMessage(this.getClass(),
+ "IngestJobTableModel.colName.streamingQueued"),
+ NbBundle.getMessage(this.getClass(),
"IngestJobTableModel.colName.dsQueued")};
private List jobSnapshots;
@@ -243,6 +245,9 @@ class IngestProgressSnapshotPanel extends javax.swing.JPanel {
cellValue = snapShot.getRootQueueSize();
break;
case 9:
+ cellValue = snapShot.getStreamingQueueSize();
+ break;
+ case 10:
cellValue = snapShot.getDsQueueSize();
break;
default:
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
index 96e1641a0f..2ec96915cc 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java
@@ -272,8 +272,9 @@ final class IngestTasksScheduler {
*/
synchronized void cancelPendingTasksForIngestJob(IngestJobPipeline ingestJobPipeline) {
long jobId = ingestJobPipeline.getId();
- IngestTasksScheduler.removeTasksForJob(this.rootFileTaskQueue, jobId);
- IngestTasksScheduler.removeTasksForJob(this.pendingFileTaskQueue, jobId);
+ IngestTasksScheduler.removeTasksForJob(rootFileTaskQueue, jobId);
+ IngestTasksScheduler.removeTasksForJob(pendingFileTaskQueue, jobId);
+ IngestTasksScheduler.removeTasksForJob(streamedTasksQueue, jobId);
}
/**
@@ -642,7 +643,8 @@ final class IngestTasksScheduler {
countTasksForJob(this.rootFileTaskQueue, jobId),
countTasksForJob(this.pendingFileTaskQueue, jobId),
this.fileIngestThreadsQueue.countQueuedTasksForJob(jobId),
- this.dataSourceIngestThreadQueue.countRunningTasksForJob(jobId) + this.fileIngestThreadsQueue.countRunningTasksForJob(jobId));
+ this.dataSourceIngestThreadQueue.countRunningTasksForJob(jobId) + this.fileIngestThreadsQueue.countRunningTasksForJob(jobId),
+ countTasksForJob(this.streamedTasksQueue, jobId));
}
/**
@@ -947,19 +949,22 @@ final class IngestTasksScheduler {
private final long dirQueueSize;
private final long fileQueueSize;
private final long runningListSize;
+ private final long streamingQueueSize;
/**
* Constructs a snapshot of ingest tasks data for an ingest job.
*
* @param jobId The identifier associated with the job.
*/
- IngestJobTasksSnapshot(long jobId, long dsQueueSize, long rootQueueSize, long dirQueueSize, long fileQueueSize, long runningListSize) {
+ IngestJobTasksSnapshot(long jobId, long dsQueueSize, long rootQueueSize, long dirQueueSize, long fileQueueSize,
+ long runningListSize, long streamingQueueSize) {
this.jobId = jobId;
this.dsQueueSize = dsQueueSize;
this.rootQueueSize = rootQueueSize;
this.dirQueueSize = dirQueueSize;
this.fileQueueSize = fileQueueSize;
this.runningListSize = runningListSize;
+ this.streamingQueueSize = streamingQueueSize;
}
/**
@@ -995,6 +1000,10 @@ final class IngestTasksScheduler {
long getFileQueueSize() {
return fileQueueSize;
}
+
+ long getStreamingQueueSize() {
+ return streamingQueueSize;
+ }
long getDsQueueSize() {
return dsQueueSize;
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Snapshot.java b/Core/src/org/sleuthkit/autopsy/ingest/Snapshot.java
index 80eebfe240..19a0e41c35 100644
--- a/Core/src/org/sleuthkit/autopsy/ingest/Snapshot.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/Snapshot.java
@@ -178,6 +178,13 @@ public final class Snapshot implements Serializable {
}
return this.tasksSnapshot.getDsQueueSize();
}
+
+ long getStreamingQueueSize() {
+ if (null == this.tasksSnapshot) {
+ return 0;
+ }
+ return this.tasksSnapshot.getStreamingQueueSize();
+ }
long getRunningListSize() {
if (null == this.tasksSnapshot) {
diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java
deleted file mode 100755
index 9cc4467835..0000000000
--- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2018-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.report.modules.caseuco;
-
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.SimpleTimeZone;
-import java.util.TimeZone;
-import org.sleuthkit.autopsy.casemodule.Case;
-import org.sleuthkit.autopsy.casemodule.Case.CaseType;
-import org.sleuthkit.autopsy.datamodel.ContentUtils;
-import org.sleuthkit.datamodel.AbstractFile;
-import org.sleuthkit.datamodel.SleuthkitCase;
-import com.fasterxml.jackson.core.JsonEncoding;
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.util.DefaultIndenter;
-import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Strings;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import org.sleuthkit.datamodel.Content;
-import org.sleuthkit.datamodel.Image;
-import org.sleuthkit.datamodel.TskCoreException;
-
-/**
- * Writes Autopsy DataModel objects to Case UCO format.
- *
- * Clients are expected to add the Case first. Then they should add each data
- * source before adding any files for that data source.
- *
- * Here is an example, where we add everything:
- *
- * Path directory = Paths.get("C:", "Reports");
- * CaseUcoReportGenerator caseUco = new CaseUcoReportGenerator(directory, "my-report");
- *
- * Case caseObj = Case.getCurrentCase();
- * caseUco.addCase(caseObj);
- * List dataSources = caseObj.getDataSources();
- * for(Content dataSource : dataSources) {
- * caseUco.addDataSource(dataSource, caseObj);
- * List files = getAllFilesInDataSource(dataSource);
- * for(AbstractFile file : files) {
- * caseUco.addFile(file, dataSource);
- * }
- * }
- *
- * Path reportOutput = caseUco.generateReport();
- * //Done. Report at - "C:\Reports\my-report.json-ld"
- *
- * Please note that the life cycle for this class ends with generateReport().
- * The underlying file handle to 'my-report.json-ld' will be closed. Any further
- * calls to addX() will result in an IOException.
- */
-public final class CaseUcoReportGenerator {
-
- private static final String EXTENSION = "json-ld";
-
- private final TimeZone timeZone;
- private final Path reportPath;
- private final JsonGenerator reportGenerator;
-
- /**
- * Creates a CaseUCO Report Generator that writes a report in the specified
- * directory.
- *
- * TimeZone is assumed to be GMT+0 for formatting file creation time,
- * accessed time and modified time.
- *
- * @param directory Directory to write the CaseUCO report file. Assumes the
- * calling thread has write access to the directory and that the directory
- * exists.
- * @param reportName Name of the CaseUCO report file.
- * @throws IOException If an I/O error occurs
- */
- public CaseUcoReportGenerator(Path directory, String reportName) throws IOException {
- this.reportPath = directory.resolve(reportName + "." + EXTENSION);
-
- JsonFactory jsonGeneratorFactory = new JsonFactory();
- reportGenerator = jsonGeneratorFactory.createGenerator(reportPath.toFile(), JsonEncoding.UTF8);
- // Puts a newline between each Key, Value pair for readability.
- reportGenerator.setPrettyPrinter(new DefaultPrettyPrinter()
- .withObjectIndenter(new DefaultIndenter(" ", "\n")));
-
- ObjectMapper mapper = new ObjectMapper();
- mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
-
- reportGenerator.setCodec(mapper);
-
- reportGenerator.writeStartObject();
- reportGenerator.writeFieldName("@graph");
- reportGenerator.writeStartArray();
-
- //Assume GMT+0
- this.timeZone = new SimpleTimeZone(0, "GMT");
- }
-
- /**
- * Adds an AbstractFile instance to the Case UCO report.
- *
- * @param file AbstractFile instance to write
- * @param parentDataSource The parent data source for this abstract file. It
- * is assumed that this parent has been written to the report (via
- * addDataSource) prior to this call. Otherwise, the report may be invalid.
- * @throws IOException If an I/O error occurs.
- * @throws TskCoreException
- */
- public void addFile(AbstractFile file, Content parentDataSource) throws IOException, TskCoreException {
- addFile(file, parentDataSource, null);
- }
-
- /**
- * Adds an AbstractFile instance to the Case UCO report.
- *
- * @param file AbstractFile instance to write
- * @param parentDataSource The parent data source for this abstract file. It
- * is assumed that this parent has been written to the report (via
- * addDataSource) prior to this call. Otherwise, the report may be invalid.
- * @param localPath The location of the file on secondary storage, somewhere
- * other than the case. Example: local disk. This value will be ignored if
- * it is null.
- * @throws IOException
- * @throws TskCoreException
- */
- public void addFile(AbstractFile file, Content parentDataSource, Path localPath) throws IOException, TskCoreException {
- String fileTraceId = getFileTraceId(file);
-
- //Create the Trace CASE node, which will contain attributes about some evidence.
- //Trace is the standard term for evidence. For us, this means file system files.
- CASENode fileTrace = new CASENode(fileTraceId, "Trace");
-
- //The bits of evidence for each Trace node are contained within Property
- //Bundles. There are a number of Property Bundles available in the CASE ontology.
-
- //Build up the File Property Bundle, as the name implies - properties of
- //the file itself.
- CASEPropertyBundle filePropertyBundle = createFileBundle(file);
- fileTrace.addBundle(filePropertyBundle);
-
- //Build up the ContentData Property Bundle, as the name implies - properties of
- //the File data itself.
- CASEPropertyBundle contentDataPropertyBundle = createContentDataBundle(file);
- fileTrace.addBundle(contentDataPropertyBundle);
-
- if(localPath != null) {
- String urlTraceId = getURLTraceId(file);
- CASENode urlTrace = new CASENode(urlTraceId, "Trace");
- CASEPropertyBundle urlPropertyBundle = new CASEPropertyBundle("URL");
- urlPropertyBundle.addProperty("fullValue", localPath.toString());
- urlTrace.addBundle(urlPropertyBundle);
-
- contentDataPropertyBundle.addProperty("dataPayloadReferenceUrl", urlTraceId);
- reportGenerator.writeObject(urlTrace);
- }
-
- //Create the Relationship CASE node. This defines how the Trace CASE node described above
- //is related to another CASE node (in this case, the parent data source).
- String relationshipID = getRelationshipId(file);
- CASENode relationship = createRelationshipNode(relationshipID,
- fileTraceId, getDataSourceTraceId(parentDataSource));
-
- //Build up the PathRelation bundle for the relationship node,
- //as the name implies - the Path of the Trace in the data source.
- CASEPropertyBundle pathRelationPropertyBundle = new CASEPropertyBundle("PathRelation");
- pathRelationPropertyBundle.addProperty("path", file.getUniquePath());
- relationship.addBundle(pathRelationPropertyBundle);
-
- //This completes the triage, write them to JSON.
- reportGenerator.writeObject(fileTrace);
- reportGenerator.writeObject(relationship);
- }
-
- private String getURLTraceId(Content content) {
- return "url-" + content.getId();
- }
-
- /**
- * All relationship nodes will be the same within our context. Namely, contained-within
- * and isDirectional as true.
- */
- private CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID) {
- CASENode relationship = new CASENode(relationshipID, "Relationship");
- relationship.addProperty("source", sourceID);
- relationship.addProperty("target", targetID);
- relationship.addProperty("kindOfRelationship", "contained-within");
- relationship.addProperty("isDirectional", true);
- return relationship;
- }
-
- /**
- * Creates a File Property Bundle with a selection of file attributes.
- */
- private CASEPropertyBundle createFileBundle(AbstractFile file) throws TskCoreException {
- CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
- String createdTime = ContentUtils.getStringTimeISO8601(file.getCrtime(), timeZone);
- String accessedTime = ContentUtils.getStringTimeISO8601(file.getAtime(), timeZone);
- String modifiedTime = ContentUtils.getStringTimeISO8601(file.getMtime(), timeZone);
- filePropertyBundle.addProperty("createdTime", createdTime);
- filePropertyBundle.addProperty("accessedTime", accessedTime);
- filePropertyBundle.addProperty("modifiedTime", modifiedTime);
- if (!Strings.isNullOrEmpty(file.getNameExtension())) {
- filePropertyBundle.addProperty("extension", file.getNameExtension());
- }
- filePropertyBundle.addProperty("fileName", file.getName());
- filePropertyBundle.addProperty("filePath", file.getUniquePath());
- filePropertyBundle.addProperty("isDirectory", file.isDir());
- filePropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
- return filePropertyBundle;
- }
-
- /**
- * Creates a Content Data Property Bundle with a selection of file attributes.
- */
- private CASEPropertyBundle createContentDataBundle(AbstractFile file) {
- CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
- if (!Strings.isNullOrEmpty(file.getMIMEType())) {
- contentDataPropertyBundle.addProperty("mimeType", file.getMIMEType());
- }
- if (!Strings.isNullOrEmpty(file.getMd5Hash())) {
- List hashPropertyBundles = new ArrayList<>();
- CASEPropertyBundle md5HashPropertyBundle = new CASEPropertyBundle("Hash");
- md5HashPropertyBundle.addProperty("hashMethod", "MD5");
- md5HashPropertyBundle.addProperty("hashValue", file.getMd5Hash());
- hashPropertyBundles.add(md5HashPropertyBundle);
- contentDataPropertyBundle.addProperty("hash", hashPropertyBundles);
- }
- contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
- return contentDataPropertyBundle;
- }
-
- /**
- * Creates a unique CASE Node file trace id.
- */
- private String getFileTraceId(AbstractFile file) {
- return "file-" + file.getId();
- }
-
- /**
- * Creates a unique CASE Node relationship id value.
- */
- private String getRelationshipId(Content content) {
- return "relationship-" + content.getId();
- }
-
- /**
- * Adds a Content instance (which is known to be a DataSource) to the CASE
- * report. This means writing a selection of attributes to a CASE or UCO
- * object.
- *
- * @param dataSource Datasource content to write
- * @param parentCase The parent case that this data source belongs in. It is
- * assumed that this parent has been written to the report (via addCase)
- * prior to this call. Otherwise, the report may be invalid.
- */
- public void addDataSource(Content dataSource, Case parentCase) throws IOException, TskCoreException {
- String dataSourceTraceId = this.getDataSourceTraceId(dataSource);
-
- CASENode dataSourceTrace = new CASENode(dataSourceTraceId, "Trace");
- CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
-
- String dataSourcePath = getDataSourcePath(dataSource);
-
- filePropertyBundle.addProperty("filePath", dataSourcePath);
- dataSourceTrace.addBundle(filePropertyBundle);
-
- if (dataSource.getSize() > 0) {
- CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
- contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(dataSource.getSize()));
- dataSourceTrace.addBundle(contentDataPropertyBundle);
- }
-
- // create a "relationship" entry between the case and the data source
- String caseTraceId = getCaseTraceId(parentCase);
- String relationshipTraceId = getRelationshipId(dataSource);
- CASENode relationship = createRelationshipNode(relationshipTraceId,
- dataSourceTraceId, caseTraceId);
-
- CASEPropertyBundle pathRelationBundle = new CASEPropertyBundle("PathRelation");
- pathRelationBundle.addProperty("path", dataSourcePath);
- relationship.addBundle(pathRelationBundle);
-
- //This completes the triage, write them to JSON.
- reportGenerator.writeObject(dataSourceTrace);
- reportGenerator.writeObject(relationship);
- }
-
- private String getDataSourcePath(Content dataSource) {
- String dataSourcePath = "";
- if (dataSource instanceof Image) {
- String[] paths = ((Image) dataSource).getPaths();
- if (paths.length > 0) {
- //Get the first data source in the path, as this will
- //be reflected in each file's uniquePath.
- dataSourcePath = paths[0];
- }
- } else {
- dataSourcePath = dataSource.getName();
- }
- dataSourcePath = dataSourcePath.replaceAll("\\\\", "/");
- return dataSourcePath;
- }
-
- /**
- * Creates a unique Case UCO trace id for a data source.
- *
- * @param dataSource
- * @return
- */
- private String getDataSourceTraceId(Content dataSource) {
- return "data-source-" + dataSource.getId();
- }
-
- /**
- * Adds a Case instance to the Case UCO report. This means writing a
- * selection of Case attributes to a CASE/UCO object.
- *
- * @param caseObj Case instance to include in the report.
- * @throws IOException If an I/O error is encountered.
- */
- public void addCase(Case caseObj) throws IOException {
- SleuthkitCase skCase = caseObj.getSleuthkitCase();
-
- String caseDirPath = skCase.getDbDirPath();
- String caseTraceId = getCaseTraceId(caseObj);
- CASENode caseTrace = new CASENode(caseTraceId, "Trace");
- CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
-
- // replace double slashes with single ones
- caseDirPath = caseDirPath.replaceAll("\\\\", "/");
-
- Case.CaseType caseType = caseObj.getCaseType();
- if (caseType.equals(CaseType.SINGLE_USER_CASE)) {
- filePropertyBundle.addProperty("filePath", caseDirPath + "/" + skCase.getDatabaseName());
- filePropertyBundle.addProperty("isDirectory", false);
- } else {
- filePropertyBundle.addProperty("filePath", caseDirPath);
- filePropertyBundle.addProperty("isDirectory", true);
- }
-
- caseTrace.addBundle(filePropertyBundle);
- reportGenerator.writeObject(caseTrace);
- }
-
- /**
- * Creates a unique Case UCO trace id for a Case.
- *
- * @param caseObj
- * @return
- */
- private String getCaseTraceId(Case caseObj) {
- return "case-" + caseObj.getName();
- }
-
- /**
- * Returns a Path to the completed Case UCO report file.
- *
- * This marks the end of the CaseUcoReportGenerator's life cycle. This
- * function will close an underlying file handles, meaning any subsequent
- * calls to addX() will result in an IOException.
- *
- * @return The Path to the finalized report.
- * @throws IOException If an I/O error occurs.
- */
- public Path generateReport() throws IOException {
- //Finalize the report.
- reportGenerator.writeEndArray();
- reportGenerator.writeEndObject();
- reportGenerator.close();
-
- return reportPath;
- }
-
- /**
- * A CASE or UCO object. CASE objects can have properties and
- * property bundles.
- */
- private final class CASENode {
-
- private final String id;
- private final String type;
-
- //Dynamic properties added to this CASENode.
- private final Map properties;
- private final List propertyBundle;
-
- public CASENode(String id, String type) {
- this.id = id;
- this.type = type;
- properties = new LinkedHashMap<>();
- propertyBundle = new ArrayList<>();
- }
-
- @JsonProperty("@id")
- public String getId() {
- return id;
- }
-
- @JsonProperty("@type")
- public String getType() {
- return type;
- }
-
- @JsonAnyGetter
- public Map getProperties() {
- return properties;
- }
-
- @JsonProperty("propertyBundle")
- public List getPropertyBundle() {
- return propertyBundle;
- }
-
- public void addProperty(String key, Object val) {
- properties.put(key, val);
- }
-
- public void addBundle(CASEPropertyBundle bundle) {
- propertyBundle.add(bundle);
- }
- }
-
- /**
- * Contains CASE or UCO properties.
- */
- private final class CASEPropertyBundle {
-
- private final Map properties;
-
- public CASEPropertyBundle(String type) {
- properties = new LinkedHashMap<>();
- addProperty("@type", type);
- }
-
- @JsonAnyGetter
- public Map getProperties() {
- return properties;
- }
-
- public void addProperty(String key, Object val) {
- properties.put(key, val);
- }
- }
-}
diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java
index d90b4a54c2..1523005d92 100755
--- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java
+++ b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java
@@ -19,7 +19,15 @@
*/
package org.sleuthkit.autopsy.report.modules.caseuco;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -39,29 +47,37 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import org.sleuthkit.autopsy.report.GeneralReportSettings;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
+import org.sleuthkit.caseuco.CaseUcoExporter;
+import org.sleuthkit.caseuco.ContentNotExportableException;
import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
+import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
/**
- * CaseUcoReportModule generates a report in CASE-UCO format. This module will
- * write all files and data sources to the report.
+ * Exports an Autopsy case to a CASE-UCO report file. This module will write all
+ * files and artifacts from the selected data sources.
*/
public final class CaseUcoReportModule implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(CaseUcoReportModule.class.getName());
private static final CaseUcoReportModule SINGLE_INSTANCE = new CaseUcoReportModule();
-
- //Supported types of TSK_FS_FILES
- private static final Set SUPPORTED_TYPES = new HashSet() {{
- add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
- add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
- add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
- }};
- private static final String REPORT_FILE_NAME = "CASE_UCO_output";
- private static final String EXTENSION = "json-ld";
+ //Supported types of TSK_FS_FILES
+ private static final Set SUPPORTED_TYPES = new HashSet() {
+ {
+ add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
+ add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
+ add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
+ }
+ };
+
+ private static final String REPORT_FILE_NAME = "CASE_UCO_output";
+ private static final String EXTENSION = "jsonld";
// Hidden constructor for the report
private CaseUcoReportModule() {
@@ -76,7 +92,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
public String getName() {
return NbBundle.getMessage(this.getClass(), "CaseUcoReportModule.getName.text");
}
-
+
@Override
public JPanel getConfigurationPanel() {
return null; // No configuration panel
@@ -84,7 +100,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
@Override
public String getRelativeFilePath() {
- return REPORT_FILE_NAME + "." + EXTENSION;
+ return REPORT_FILE_NAME + "." + EXTENSION;
}
@Override
@@ -100,7 +116,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
public static String getReportFileName() {
return REPORT_FILE_NAME;
}
-
+
@Override
public boolean supportsDataSourceSelection() {
return true;
@@ -109,7 +125,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
/**
* Generates a CASE-UCO format report for all files in the Case.
*
- * @param settings Report settings.
+ * @param settings Report settings.
* @param progressPanel panel to update the report's progress
*/
@NbBundle.Messages({
@@ -128,74 +144,123 @@ public final class CaseUcoReportModule implements GeneralReportModule {
try {
// Check if ingest has finished
warnIngest(progressPanel);
-
+
//Create report paths if they don't already exist.
Path reportDirectory = Paths.get(settings.getReportDirectoryPath());
try {
Files.createDirectories(reportDirectory);
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to create directory for CASE-UCO report.", ex);
- progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
- Bundle.CaseUcoReportModule_unableToCreateDirectories());
+ progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
+ Bundle.CaseUcoReportModule_unableToCreateDirectories());
return;
}
-
- CaseUcoReportGenerator generator =
- new CaseUcoReportGenerator(reportDirectory, REPORT_FILE_NAME);
-
- //First write the Case to the report file.
- Case caseObj = Case.getCurrentCaseThrows();
- generator.addCase(caseObj);
-
- List dataSources = caseObj.getDataSources().stream()
- .filter((dataSource) -> {
- if(settings.getSelectedDataSources() == null) {
- // Assume all data sources if list is null.
- return true;
+
+ Case currentCase = Case.getCurrentCaseThrows();
+
+ Path caseJsonReportFile = reportDirectory.resolve(REPORT_FILE_NAME + "." + EXTENSION);
+
+ try (OutputStream stream = new FileOutputStream(caseJsonReportFile.toFile());
+ JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ reportWriter.setIndent(" ");
+ reportWriter.beginObject();
+ reportWriter.name("@graph");
+ reportWriter.beginArray();
+
+ CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
+ for (JsonElement element : exporter.exportSleuthkitCase()) {
+ gson.toJson(element, reportWriter);
+ }
+
+ // Get a list of selected data sources to process.
+ List dataSources = getSelectedDataSources(currentCase, settings);
+
+ progressPanel.setIndeterminate(false);
+ progressPanel.setMaximumProgress(dataSources.size());
+ progressPanel.start();
+
+ // First stage of reporting is for files and data sources.
+ // Iterate through each data source and dump all files contained
+ // in that data source.
+ for (int i = 0; i < dataSources.size(); i++) {
+ DataSource dataSource = dataSources.get(i);
+ progressPanel.updateStatusLabel(String.format(
+ Bundle.CaseUcoReportModule_processingDataSource(),
+ dataSource.getName()));
+ // Add the data source export.
+ for (JsonElement element : exporter.exportDataSource(dataSource)) {
+ gson.toJson(element, reportWriter);
+ }
+ // Search all children of the data source.
+ performDepthFirstSearch(dataSource, gson, exporter, reportWriter);
+ progressPanel.setProgress(i + 1);
+ }
+
+ // Second stage of reporting handles artifacts.
+ Set dataSourceIds = dataSources.stream()
+ .map((datasource) -> datasource.getId())
+ .collect(Collectors.toSet());
+
+ // Write all standard artifacts that are contained within the
+ // selected data sources.
+ for (ARTIFACT_TYPE artType : currentCase.getSleuthkitCase().getBlackboardArtifactTypesInUse()) {
+ for (BlackboardArtifact artifact : currentCase.getSleuthkitCase().getBlackboardArtifacts(artType)) {
+ if (dataSourceIds.contains(artifact.getDataSource().getId())) {
+
+ try {
+ for (JsonElement element : exporter.exportBlackboardArtifact(artifact)) {
+ gson.toJson(element, reportWriter);
+ }
+ } catch (ContentNotExportableException | BlackboardJsonAttrUtil.InvalidJsonException ex) {
+ logger.log(Level.WARNING, String.format("Unable to export blackboard artifact (id: %d) to CASE/UCO. "
+ + "The artifact type is either not supported or the artifact instance does not have any "
+ + "exportable attributes.", artifact.getId()));
+ }
}
- return settings.getSelectedDataSources().contains(dataSource.getId());
- })
- .collect(Collectors.toList());
-
- progressPanel.setIndeterminate(false);
- progressPanel.setMaximumProgress(dataSources.size());
- progressPanel.start();
-
- //Then search each data source for file content.
- for(int i = 0; i < dataSources.size(); i++) {
- Content dataSource = dataSources.get(i);
- progressPanel.updateStatusLabel(String.format(
- Bundle.CaseUcoReportModule_processingDataSource(),
- dataSource.getName()));
- //Add the data source and then all children.
- generator.addDataSource(dataSource, caseObj);
- performDepthFirstSearch(dataSource, generator);
- progressPanel.setProgress(i+1);
+ }
+ }
+
+ reportWriter.endArray();
+ reportWriter.endObject();
}
-
- //Complete the report.
- Path reportPath = generator.generateReport();
- caseObj.addReport(reportPath.toString(),
- Bundle.CaseUcoReportModule_srcModuleName(),
+
+ currentCase.addReport(caseJsonReportFile.toString(),
+ Bundle.CaseUcoReportModule_srcModuleName(),
REPORT_FILE_NAME);
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
} catch (IOException ex) {
logger.log(Level.WARNING, "I/O error encountered while generating the report.", ex);
- progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
+ progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_ioError());
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No case open.", ex);
- progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
+ progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
Bundle.CaseUcoReportModule_noCaseOpen());
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TskCoreException encounted while generating the report.", ex);
- progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
+ progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
String.format(Bundle.CaseUcoReportModule_tskCoreException(), ex.toString()));
}
-
+
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
}
-
+
+ /**
+ * Get the selected data sources from the settings instance.
+ */
+ private List getSelectedDataSources(Case currentCase, GeneralReportSettings settings) throws TskCoreException {
+ return currentCase.getSleuthkitCase().getDataSources().stream()
+ .filter((dataSource) -> {
+ if (settings.getSelectedDataSources() == null) {
+ // Assume all data sources if list is null.
+ return true;
+ }
+ return settings.getSelectedDataSources().contains(dataSource.getId());
+ })
+ .collect(Collectors.toList());
+ }
+
/**
* Warn the user if ingest is still ongoing.
*/
@@ -204,30 +269,32 @@ public final class CaseUcoReportModule implements GeneralReportModule {
progressPanel.updateStatusLabel(Bundle.CaseUcoReportModule_ingestWarning());
}
}
-
+
/**
- * Perform DFS on the data sources tree, which will search it in entirety.
- * This traversal is more memory efficient than BFS (Breadth first search).
+ * Perform DFS on the data sources tree, which will search it in entirety.
*/
- private void performDepthFirstSearch(Content dataSource,
- CaseUcoReportGenerator generator) throws IOException, TskCoreException {
-
+ private void performDepthFirstSearch(DataSource dataSource,
+ Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter) throws IOException, TskCoreException {
+
Deque stack = new ArrayDeque<>();
stack.addAll(dataSource.getChildren());
//Depth First Search the data source tree.
- while(!stack.isEmpty()) {
+ while (!stack.isEmpty()) {
Content current = stack.pop();
- if(current instanceof AbstractFile) {
- AbstractFile f = (AbstractFile) (current);
- if(SUPPORTED_TYPES.contains(f.getMetaType().getValue())) {
- generator.addFile(f, dataSource);
+ if (current instanceof AbstractFile) {
+ AbstractFile file = (AbstractFile) (current);
+ if (SUPPORTED_TYPES.contains(file.getMetaType().getValue())) {
+
+ for (JsonElement element : exporter.exportAbstractFile(file)) {
+ gson.toJson(element, reportWriter);
+ }
}
}
- for(Content child : current.getChildren()) {
+ for (Content child : current.getChildren()) {
stack.push(child);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java
index 8c88f29f53..b02b95aaa6 100644
--- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java
+++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java
@@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.report.modules.portablecase;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.stream.JsonWriter;
import org.sleuthkit.autopsy.report.ReportModule;
import java.util.logging.Level;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -49,7 +56,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
-import org.sleuthkit.autopsy.report.modules.caseuco.CaseUcoReportGenerator;
+import org.sleuthkit.caseuco.CaseUcoExporter;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
@@ -76,6 +83,7 @@ import org.sleuthkit.datamodel.VolumeSystem;
* Creates a portable case from tagged files
*/
public class PortableCaseReportModule implements ReportModule {
+
private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
@@ -83,35 +91,35 @@ public class PortableCaseReportModule implements ReportModule {
private static final String CASE_UCO_FILE_NAME = "portable_CASE_UCO_output";
private static final String CASE_UCO_TMP_DIR = "case_uco_tmp";
private PortableCaseReportModuleSettings settings;
-
+
// These are the types for the exported file subfolders
private static final List FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO);
-
+
private Case currentCase = null;
private SleuthkitCase portableSkCase = null;
private String caseName = "";
private File caseFolder = null;
private File copiedFilesFolder = null;
-
+
// Maps old object ID from current case to new object in portable case
private final Map oldIdToNewContent = new HashMap<>();
-
+
// Maps new object ID to the new object
private final Map newIdToContent = new HashMap<>();
-
+
// Maps old TagName to new TagName
private final Map oldTagNameToNewTagName = new HashMap<>();
// Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
private final Map oldArtTypeIdToNewArtTypeId = new HashMap<>();
-
+
// Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
private final Map oldAttrTypeIdToNewAttrType = new HashMap<>();
-
+
// Map of old artifact ID to new artifact
private final Map oldArtifactIdToNewArtifact = new HashMap<>();
-
+
public PortableCaseReportModule() {
}
@@ -141,11 +149,11 @@ public class PortableCaseReportModule implements ReportModule {
}
return caseName;
}
-
+
/**
* Convenience method for handling cancellation
- *
- * @param progressPanel The report progress panel
+ *
+ * @param progressPanel The report progress panel
*/
private void handleCancellation(ReportProgressPanel progressPanel) {
logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
@@ -153,16 +161,16 @@ public class PortableCaseReportModule implements ReportModule {
progressPanel.complete(ReportProgressPanel.ReportStatus.CANCELED);
cleanup();
}
-
+
/**
- * Convenience method to avoid code duplication.
- * Assumes that if an exception is supplied then the error is SEVERE. Otherwise
- * it is logged as a WARNING.
- *
- * @param logWarning Warning to write to the log
- * @param dialogWarning Warning to write to a pop-up window
- * @param ex The exception (can be null)
- * @param progressPanel The report progress panel
+ * Convenience method to avoid code duplication. Assumes that if an
+ * exception is supplied then the error is SEVERE. Otherwise it is logged as
+ * a WARNING.
+ *
+ * @param logWarning Warning to write to the log
+ * @param dialogWarning Warning to write to a pop-up window
+ * @param ex The exception (can be null)
+ * @param progressPanel The report progress panel
*/
private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
if (ex == null) {
@@ -208,24 +216,24 @@ public class PortableCaseReportModule implements ReportModule {
progressPanel.setIndeterminate(true);
progressPanel.start();
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
-
+
// Clear out any old values
cleanup();
-
+
// Validate the input parameters
File outputDir = new File(reportPath);
- if (! outputDir.exists()) {
+ if (!outputDir.exists()) {
handleError("Output folder " + outputDir.toString() + " does not exist",
Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
return;
}
-
- if (! outputDir.isDirectory()) {
+
+ if (!outputDir.isDirectory()) {
handleError("Output folder " + outputDir.toString() + " is not a folder",
Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
return;
}
-
+
// Save the current case object
try {
currentCase = Case.getCurrentCaseThrows();
@@ -234,41 +242,41 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Current case has been closed",
Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
return;
- }
-
+ }
+
// Check that there will be something to copy
List tagNames;
if (options.areAllTagsSelected()) {
try {
tagNames = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse();
} catch (NoCurrentCaseException | TskCoreException ex) {
- handleError("Unable to get all tags",
- Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS
+ handleError("Unable to get all tags",
+ Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS
return;
}
} else {
tagNames = options.getSelectedTagNames();
}
-
+
List setNames;
if (options.areAllSetsSelected()) {
try {
setNames = getAllInterestingItemsSets();
} catch (NoCurrentCaseException | TskCoreException ex) {
- handleError("Unable to get all interesting items sets",
- Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS
+ handleError("Unable to get all interesting items sets",
+ Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS
return;
}
} else {
setNames = options.getSelectedSetNames();
}
-
- if (tagNames.isEmpty() && setNames.isEmpty()) {
- handleError("No content to copy",
+
+ if (tagNames.isEmpty() && setNames.isEmpty()) {
+ handleError("No content to copy",
Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
return;
}
-
+
// Create the case.
// portableSkCase and caseFolder will be set here.
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
@@ -277,13 +285,13 @@ public class PortableCaseReportModule implements ReportModule {
// The error has already been handled
return;
}
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
return;
}
-
+
// Set up the table for the image tags
try {
initializeImageTags(progressPanel);
@@ -291,11 +299,11 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS
return;
}
-
+
// Copy the selected tags
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
try {
- for(TagName tagName:tagNames) {
+ for (TagName tagName : tagNames) {
TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
oldTagNameToNewTagName.put(tagName, newTagName);
}
@@ -303,12 +311,12 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
return;
}
-
+
// Set up tracking to support any custom artifact or attribute types
- for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
+ for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) {
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
}
- for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
+ for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
try {
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
} catch (TskCoreException ex) {
@@ -316,11 +324,11 @@ public class PortableCaseReportModule implements ReportModule {
Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
ex, progressPanel); // NON-NLS
}
- }
-
+ }
+
// Copy the tagged files
try {
- for(TagName tagName:tagNames) {
+ for (TagName tagName : tagNames) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
@@ -328,7 +336,7 @@ public class PortableCaseReportModule implements ReportModule {
}
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
addFilesToPortableCase(tagName, progressPanel);
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
@@ -338,11 +346,11 @@ public class PortableCaseReportModule implements ReportModule {
} catch (TskCoreException ex) {
handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
return;
- }
-
+ }
+
// Copy the tagged artifacts and associated files
try {
- for(TagName tagName:tagNames) {
+ for (TagName tagName : tagNames) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
@@ -350,7 +358,7 @@ public class PortableCaseReportModule implements ReportModule {
}
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
addArtifactsToPortableCase(tagName, progressPanel);
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
@@ -361,18 +369,18 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
return;
}
-
+
// Copy interesting files and results
- if (! setNames.isEmpty()) {
+ if (!setNames.isEmpty()) {
try {
List interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
- for (BlackboardArtifact art:interestingFiles) {
+ for (BlackboardArtifact art : interestingFiles) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
return;
}
-
+
BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
if (setNames.contains(setAttr.getValueString())) {
copyContentToPortableCase(art, progressPanel);
@@ -385,7 +393,7 @@ public class PortableCaseReportModule implements ReportModule {
try {
List interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
- for (BlackboardArtifact art:interestingResults) {
+ for (BlackboardArtifact art : interestingResults) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
@@ -400,49 +408,49 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
return;
}
- }
-
+ }
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
return;
}
-
+
//Attempt to generate and included the CASE-UCO report.
generateCaseUcoReport(tagNames, setNames, progressPanel);
// Compress the case (if desired)
if (options.shouldCompress()) {
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
-
+
boolean success = compressCase(progressPanel);
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
handleCancellation(progressPanel);
return;
}
-
- if (! success) {
+
+ if (!success) {
// Errors have been handled already
return;
}
}
-
+
// Close the case connections and clear out the maps
cleanup();
-
+
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
-
+
}
-
+
/**
* Generates a CASE-UCO report for all files that have a specified TagName
* or TSK_INTERESTING artifacts that are flagged by the specified SET_NAMEs.
- *
+ *
* Only one copy of the file will be saved in the report if it is the source
* of more than one of the above.
- *
+ *
* @param tagNames TagNames to included in the report.
* @param setNames SET_NAMEs to include in the report.
* @param progressPanel ProgressPanel to relay progress messages.
@@ -456,17 +464,23 @@ public class PortableCaseReportModule implements ReportModule {
private void generateCaseUcoReport(List tagNames, List setNames, ReportProgressPanel progressPanel) {
//Create the 'Reports' directory to include a CASE-UCO report.
Path reportsDirectory = Paths.get(caseFolder.toString(), "Reports");
- if(!reportsDirectory.toFile().mkdir()) {
+ if (!reportsDirectory.toFile().mkdir()) {
logger.log(Level.SEVERE, "Could not make the report folder... skipping "
+ "CASE-UCO report generation for the portable case");
return;
}
- try {
- //Try to generate case uco output.
- progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration());
- CaseUcoReportGenerator reportGenerator = new CaseUcoReportGenerator(reportsDirectory, CASE_UCO_FILE_NAME);
- //Acquire references for file discovery
+ Path reportFile = reportsDirectory.resolve(CASE_UCO_FILE_NAME);
+
+ progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration());
+ try (OutputStream stream = new FileOutputStream(reportFile.toFile());
+ JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ reportWriter.setIndent(" ");
+ reportWriter.beginObject();
+ reportWriter.name("@graph");
+ reportWriter.beginArray();
+
String caseTempDirectory = currentCase.getTempDirectory();
SleuthkitCase skCase = currentCase.getSleuthkitCase();
TagsManager tagsManager = currentCase.getServices().getTagsManager();
@@ -477,43 +491,44 @@ public class PortableCaseReportModule implements ReportModule {
FileUtils.deleteDirectory(tmpDir.toFile());
Files.createDirectory(tmpDir);
- reportGenerator.addCase(currentCase);
-
+ CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
+ for (JsonElement element : exporter.exportSleuthkitCase()) {
+ gson.toJson(element, reportWriter);
+ }
+
//Load all interesting BlackboardArtifacts that belong to the selected SET_NAMEs
//binned by data source id.
Multimap artifactsWithSetName = getInterestingArtifactsBySetName(skCase, setNames);
-
+
//Search each data source looking for content tags and interesting
//items that match the selected tag names and set names.
- for (Content dataSource : currentCase.getDataSources()) {
- /**
- * It is currently believed that DataSources in a CASE-UCO report
- * should precede all file entities. Therefore, before
- * writing a file, add the data source if it
- * has yet to be included.
- */
+ for (DataSource dataSource : currentCase.getSleuthkitCase().getDataSources()) {
+ // Helper flag to ensure each data source is only written once in
+ // a report.
boolean dataSourceHasBeenIncluded = false;
+
//Search content tags and artifact tags that match
for (TagName tagName : tagNames) {
for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) {
- dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(),
- dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded);
+ dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(),
+ dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
}
for (BlackboardArtifactTag bat : tagsManager.getBlackboardArtifactTagsByTagName(tagName, dataSource.getId())) {
- dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(),
- dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded);
+ dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(),
+ dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
}
}
//Search artifacts that this data source contains
- for(BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
+ for (BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
Content sourceContent = bArt.getParent();
- dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource,
- tmpDir, reportGenerator, dataSourceHasBeenIncluded);
+ dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource,
+ tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
}
}
-
- //Create the report.
- reportGenerator.generateReport();
+
+ // Finish the report.
+ reportWriter.endArray();
+ reportWriter.endObject();
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_successCaseUcoReportGeneration());
} catch (IOException | TskCoreException ex) {
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_errorGeneratingCaseUcoReport());
@@ -522,7 +537,7 @@ public class PortableCaseReportModule implements ReportModule {
+ "completed without a CASE-UCO report.", ex);
}
}
-
+
/**
* Load all interesting BlackboardArtifacts that belong to the selected
* SET_NAME. This operation would be duplicated for every data source, since
@@ -530,15 +545,15 @@ public class PortableCaseReportModule implements ReportModule {
*/
private Multimap getInterestingArtifactsBySetName(SleuthkitCase skCase, List setNames) throws TskCoreException {
Multimap artifactsWithSetName = ArrayListMultimap.create();
- if(!setNames.isEmpty()) {
+ if (!setNames.isEmpty()) {
List allArtifacts = skCase.getBlackboardArtifacts(
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
allArtifacts.addAll(skCase.getBlackboardArtifacts(
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT));
- for(BlackboardArtifact bArt : allArtifacts) {
+ for (BlackboardArtifact bArt : allArtifacts) {
BlackboardAttribute setAttr = bArt.getAttribute(
- new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
+ new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
if (setNames.contains(setAttr.getValueString())) {
artifactsWithSetName.put(bArt.getDataSource().getId(), bArt);
}
@@ -546,7 +561,7 @@ public class PortableCaseReportModule implements ReportModule {
}
return artifactsWithSetName;
}
-
+
/**
* Adds the content if and only if it has not already been seen.
*
@@ -555,32 +570,36 @@ public class PortableCaseReportModule implements ReportModule {
* @param tmpDir Path to the tmpDir to enforce uniqueness
* @param reportGenerator Report generator instance to add the content to
* @param dataSourceHasBeenIncluded Flag determining if the data source
- * should be written before the file. False will cause the data source to be written.
+ * should be written to the report (false indicates that it should be written).
* @throws IOException If an I/O error occurs.
* @throws TskCoreException If an internal database error occurs.
*
- * return True if the data source was written during this operation.
+ * return True if the file was written during this operation.
*/
- private boolean addUniqueFile(Content content, Content dataSource,
- Path tmpDir, CaseUcoReportGenerator reportGenerator,
+ private boolean addUniqueFile(Content content, DataSource dataSource,
+ Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter,
boolean dataSourceHasBeenIncluded) throws IOException, TskCoreException {
if (content instanceof AbstractFile && !(content instanceof DataSource)) {
AbstractFile absFile = (AbstractFile) content;
Path filePath = tmpDir.resolve(Long.toString(absFile.getId()));
if (!absFile.isDir() && !Files.exists(filePath)) {
- if(!dataSourceHasBeenIncluded) {
- reportGenerator.addDataSource(dataSource, currentCase);
+ if (!dataSourceHasBeenIncluded) {
+ for (JsonElement element : exporter.exportDataSource(dataSource)) {
+ gson.toJson(element, reportWriter);
+ }
}
String subFolder = getExportSubfolder(absFile);
String fileName = absFile.getId() + "-" + FileUtil.escapeFileName(absFile.getName());
- reportGenerator.addFile(absFile, dataSource, Paths.get(FILE_FOLDER_NAME, subFolder, fileName));
+ for (JsonElement element : exporter.exportAbstractFile(absFile, Paths.get(FILE_FOLDER_NAME, subFolder, fileName).toString())) {
+ gson.toJson(element, reportWriter);
+ }
Files.createFile(filePath);
return true;
}
}
return false;
}
-
+
private List getAllInterestingItemsSets() throws NoCurrentCaseException, TskCoreException {
// Get the set names in use for the current case.
@@ -603,14 +622,13 @@ public class PortableCaseReportModule implements ReportModule {
setNames.addAll(setCounts.keySet());
return setNames;
}
-
/**
- * Create the case directory and case database.
- * portableSkCase will be set if this completes without error.
- *
- * @param outputDir The parent for the case folder
- * @param progressPanel
+ * Create the case directory and case database. portableSkCase will be set
+ * if this completes without error.
+ *
+ * @param outputDir The parent for the case folder
+ * @param progressPanel
*/
@NbBundle.Messages({
"# {0} - case folder",
@@ -618,8 +636,7 @@ public class PortableCaseReportModule implements ReportModule {
"PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
"# {0} - folder",
"PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
- "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
- })
+ "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",})
private void createCase(File outputDir, ReportProgressPanel progressPanel) {
// Create the case folder
@@ -627,66 +644,66 @@ public class PortableCaseReportModule implements ReportModule {
if (caseFolder.exists()) {
handleError("Case folder " + caseFolder.toString() + " already exists",
- Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
+ Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
return;
}
-
+
// Create the case
try {
portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
} catch (TskCoreException ex) {
handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
- Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
+ Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
return;
}
-
+
// Store the highest IDs
try {
saveHighestIds();
} catch (TskCoreException ex) {
handleError("Error storing maximum database IDs",
- Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
+ Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
return;
}
-
+
// Create the base folder for the copied files
copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
- if (! copiedFilesFolder.mkdir()) {
+ if (!copiedFilesFolder.mkdir()) {
handleError("Error creating folder " + copiedFilesFolder.toString(),
Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
return;
}
-
+
// Create subfolders for the copied files
- for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
+ for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
- if (! subFolder.mkdir()) {
+ if (!subFolder.mkdir()) {
handleError("Error creating folder " + subFolder.toString(),
- Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
+ Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
return;
}
}
File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
- if (! unknownTypeFolder.mkdir()) {
+ if (!unknownTypeFolder.mkdir()) {
handleError("Error creating folder " + unknownTypeFolder.toString(),
- Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
+ Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
return;
}
-
+
}
-
+
/**
* Save the current highest IDs to the portable case.
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
private void saveHighestIds() throws TskCoreException {
-
+
CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
-
+
String tableSchema = "( table_name TEXT PRIMARY KEY, "
- + " max_id TEXT)"; // NON-NLS
-
+ + " max_id TEXT)"; // NON-NLS
+
portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
@@ -694,51 +711,51 @@ public class PortableCaseReportModule implements ReportModule {
currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
}
-
+
/**
* Set up the image tag table in the portable case
- *
- * @param progressPanel
- *
- * @throws TskCoreException
+ *
+ * @param progressPanel
+ *
+ * @throws TskCoreException
*/
private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException {
-
+
// Create the image tags table in the portable case
CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
- if (! portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
+ if (!portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
portableDbAccessManager.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE);
}
}
-
+
/**
* Add all files with a given tag to the portable case.
- *
- * @param oldTagName The TagName object from the current case
+ *
+ * @param oldTagName The TagName object from the current case
* @param progressPanel The progress panel
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
-
+
// Get all the tags in the current case
List tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
-
+
// Copy the files into the portable case and tag
for (ContentTag tag : tags) {
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
return;
}
-
+
Content content = tag.getContent();
if (content instanceof AbstractFile) {
-
+
long newFileId = copyContentToPortableCase(content, progressPanel);
-
+
// Tag the file
- if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
+ if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
}
ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
@@ -746,21 +763,22 @@ public class PortableCaseReportModule implements ReportModule {
// Get the image tag data associated with this tag (empty string if there is none)
// and save it if present
String appData = getImageTagDataForContentTag(tag);
- if (! appData.isEmpty()) {
+ if (!appData.isEmpty()) {
addImageTagToPortableCase(newContentTag.getAddedTag(), appData);
}
}
- }
- }
-
+ }
+ }
+
/**
* Gets the image tag data for a given content tag
- *
+ *
* @param tag The ContentTag in the current case
- *
- * @return The app_data string for this content tag or an empty string if there was none
- *
- * @throws TskCoreException
+ *
+ * @return The app_data string for this content tag or an empty string if
+ * there was none
+ *
+ * @throws TskCoreException
*/
private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException {
@@ -769,7 +787,7 @@ public class PortableCaseReportModule implements ReportModule {
currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
return callback.getAppData();
}
-
+
/**
* CaseDbAccessManager callback to get the app_data string for the image tag
*/
@@ -777,7 +795,7 @@ public class PortableCaseReportModule implements ReportModule {
private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
private String appData = "";
-
+
@Override
public void process(ResultSet rs) {
try {
@@ -791,106 +809,107 @@ public class PortableCaseReportModule implements ReportModule {
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS
}
- }
-
+ }
+
/**
* Get the app_data string
- *
+ *
* @return the app_data string
*/
String getAppData() {
return appData;
}
}
-
+
/**
* Add an image tag to the portable case.
- *
+ *
* @param newContentTag The content tag in the portable case
- * @param appData The string to copy into app_data
- *
- * @throws TskCoreException
+ * @param appData The string to copy into app_data
+ *
+ * @throws TskCoreException
*/
private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException {
String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')";
portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
}
-
-
+
/**
* Add all artifacts with a given tag to the portable case.
- *
- * @param oldTagName The TagName object from the current case
+ *
+ * @param oldTagName The TagName object from the current case
* @param progressPanel The progress panel
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
-
+
List tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
-
+
// Copy the artifacts into the portable case along with their content and tag
for (BlackboardArtifactTag tag : tags) {
-
+
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
return;
}
-
+
// Copy the source content
Content content = tag.getContent();
long newContentId = copyContentToPortableCase(content, progressPanel);
-
+
// Copy the artifact
BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
-
+
// Tag the artfiact
- if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
+ if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
}
portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
- }
- }
-
+ }
+ }
+
/**
- * Copy an artifact into the new case. Will also copy any associated artifacts
- *
- * @param newContentId The content ID (in the portable case) of the source content
+ * Copy an artifact into the new case. Will also copy any associated
+ * artifacts
+ *
+ * @param newContentId The content ID (in the portable case) of the source
+ * content
* @param artifactToCopy The artifact to copy
- *
+ *
* @return The new artifact in the portable case
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
-
+
if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
}
-
+
// First create the associated artifact (if present)
BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
List newAttrs = new ArrayList<>();
if (oldAssociatedAttribute != null) {
BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
- newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
- String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
+ newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
+ String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
}
-
+
// Create the new artifact
int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId);
List oldAttrs = artifactToCopy.getAttributes();
-
+
// Copy over each attribute, making sure the type is in the new case.
- for (BlackboardAttribute oldAttr:oldAttrs) {
-
+ for (BlackboardAttribute oldAttr : oldAttrs) {
+
// The associated artifact has already been handled
if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
continue;
}
-
+
BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
switch (oldAttr.getValueType()) {
case BYTE:
@@ -905,7 +924,7 @@ public class PortableCaseReportModule implements ReportModule {
newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
oldAttr.getValueInt()));
break;
- case DATETIME:
+ case DATETIME:
case LONG:
newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
oldAttr.getValueLong()));
@@ -919,26 +938,27 @@ public class PortableCaseReportModule implements ReportModule {
throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
}
}
-
+
newArtifact.addAttributes(newAttrs);
-
+
oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
return newArtifact;
}
-
+
/**
- * Get the artifact type ID in the portable case and create new artifact type if needed.
- * For built-in artifacts this will be the same as the original.
- *
+ * Get the artifact type ID in the portable case and create new artifact
+ * type if needed. For built-in artifacts this will be the same as the
+ * original.
+ *
* @param oldArtifact The artifact in the current case
- *
+ *
* @return The corresponding artifact type ID in the portable case
*/
private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
}
-
+
BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
try {
BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
@@ -948,13 +968,14 @@ public class PortableCaseReportModule implements ReportModule {
throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
}
}
-
+
/**
- * Get the attribute type ID in the portable case and create new attribute type if needed.
- * For built-in attributes this will be the same as the original.
- *
+ * Get the attribute type ID in the portable case and create new attribute
+ * type if needed. For built-in attributes this will be the same as the
+ * original.
+ *
* @param oldAttribute The attribute in the current case
- *
+ *
* @return The corresponding attribute type in the portable case
*/
private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
@@ -962,9 +983,9 @@ public class PortableCaseReportModule implements ReportModule {
if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
}
-
+
try {
- BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(),
+ BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(),
oldAttrType.getValueType(), oldAttrType.getDisplayName());
oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
return newCustomType;
@@ -975,39 +996,38 @@ public class PortableCaseReportModule implements ReportModule {
/**
* Top level method to copy a content object to the portable case.
- *
- * @param content The content object to copy
+ *
+ * @param content The content object to copy
* @param progressPanel The progress panel
- *
+ *
* @return The object ID of the copied content in the portable case
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
@NbBundle.Messages({
"# {0} - File name",
- "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",
- })
+ "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",})
private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
return copyContent(content);
}
-
+
/**
* Returns the object ID for the given content object in the portable case.
- *
+ *
* @param content The content object to copy into the portable case
- *
+ *
* @return the new object ID for this content
- *
- * @throws TskCoreException
+ *
+ * @throws TskCoreException
*/
private long copyContent(Content content) throws TskCoreException {
-
+
// Check if we've already copied this content
if (oldIdToNewContent.containsKey(content.getId())) {
return oldIdToNewContent.get(content.getId()).getId();
}
-
+
// Otherwise:
// - Make parent of this object (if applicable)
// - Copy this content
@@ -1015,42 +1035,42 @@ public class PortableCaseReportModule implements ReportModule {
if (content.getParent() != null) {
parentId = copyContent(content.getParent());
}
-
+
Content newContent;
if (content instanceof BlackboardArtifact) {
- BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
+ BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
newContent = copyArtifact(parentId, artifactToCopy);
} else {
CaseDbTransaction trans = portableSkCase.beginTransaction();
try {
if (content instanceof Image) {
- Image image = (Image)content;
- newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
+ Image image = (Image) content;
+ newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
} else if (content instanceof VolumeSystem) {
- VolumeSystem vs = (VolumeSystem)content;
+ VolumeSystem vs = (VolumeSystem) content;
newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
} else if (content instanceof Volume) {
- Volume vs = (Volume)content;
- newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
+ Volume vs = (Volume) content;
+ newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
vs.getDescription(), vs.getFlags(), trans);
} else if (content instanceof Pool) {
- Pool pool = (Pool)content;
+ Pool pool = (Pool) content;
newContent = portableSkCase.addPool(parentId, pool.getType(), trans);
} else if (content instanceof FileSystem) {
- FileSystem fs = (FileSystem)content;
- newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
- fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
+ FileSystem fs = (FileSystem) content;
+ newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
+ fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
fs.getName(), trans);
} else if (content instanceof BlackboardArtifact) {
- BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
+ BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
newContent = copyArtifact(parentId, artifactToCopy);
} else if (content instanceof AbstractFile) {
- AbstractFile abstractFile = (AbstractFile)content;
-
+ AbstractFile abstractFile = (AbstractFile) content;
+
if (abstractFile instanceof LocalFilesDataSource) {
- LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
- newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
+ LocalFilesDataSource localFilesDS = (LocalFilesDataSource) abstractFile;
+ newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
} else {
if (abstractFile.isDir()) {
newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
@@ -1065,21 +1085,21 @@ public class PortableCaseReportModule implements ReportModule {
// Get the new parent object in the portable case database
Content oldParent = abstractFile.getParent();
- if (! oldIdToNewContent.containsKey(oldParent.getId())) {
+ if (!oldIdToNewContent.containsKey(oldParent.getId())) {
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
}
Content newParent = oldIdToNewContent.get(oldParent.getId());
// Construct the relative path to the copied file
- String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
+ String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
- true, TskData.EncodingType.NONE,
+ true, TskData.EncodingType.NONE,
newParent, trans);
} catch (IOException ex) {
- throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
+ throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
+ abstractFile.getId(), ex); // NON-NLS
}
}
@@ -1088,38 +1108,38 @@ public class PortableCaseReportModule implements ReportModule {
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
}
trans.commit();
- } catch (TskCoreException ex) {
+ } catch (TskCoreException ex) {
trans.rollback();
- throw(ex);
+ throw (ex);
}
}
-
+
// Save the new object
oldIdToNewContent.put(content.getId(), newContent);
newIdToContent.put(newContent.getId(), newContent);
return oldIdToNewContent.get(content.getId()).getId();
}
-
+
/**
* Return the subfolder name for this file based on MIME type
- *
+ *
* @param abstractFile the file
- *
- * @return the name of the appropriate subfolder for this file type
+ *
+ * @return the name of the appropriate subfolder for this file type
*/
private String getExportSubfolder(AbstractFile abstractFile) {
if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
return UNKNOWN_FILE_TYPE_FOLDER;
}
-
- for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
+
+ for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
return cat.getDisplayName();
}
}
return UNKNOWN_FILE_TYPE_FOLDER;
}
-
+
/**
* Clear out the maps and other fields and close the database connections.
*/
@@ -1132,12 +1152,12 @@ public class PortableCaseReportModule implements ReportModule {
oldArtifactIdToNewArtifact.clear();
closePortableCaseDatabase();
-
+
currentCase = null;
caseFolder = null;
copiedFilesFolder = null;
}
-
+
/**
* Close the portable case
*/
@@ -1153,15 +1173,14 @@ public class PortableCaseReportModule implements ReportModule {
configPanel = new CreatePortableCasePanel();
return configPanel;
} */
-
private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
private final String tableName;
-
+
StoreMaxIdCallback(String tableName) {
this.tableName = tableName;
}
-
+
@Override
public void process(ResultSet rs) {
@@ -1177,60 +1196,59 @@ public class PortableCaseReportModule implements ReportModule {
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
}
-
+
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
}
}
}
-
+
@NbBundle.Messages({
"PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
"# {0} - Temp folder path",
"PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
"PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
- "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",
- })
+ "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",})
private boolean compressCase(ReportProgressPanel progressPanel) {
-
+
// Close the portable case database (we still need some of the variables that would be cleared by cleanup())
closePortableCaseDatabase();
-
+
// Make a temporary folder for the compressed case
File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS
- if (! tempZipFolder.mkdir()) {
- handleError("Error creating temporary folder " + tempZipFolder.toString(),
+ if (!tempZipFolder.mkdir()) {
+ handleError("Error creating temporary folder " + tempZipFolder.toString(),
Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
return false;
}
-
+
// Find 7-Zip
File sevenZipExe = locate7ZipExecutable();
if (sevenZipExe == null) {
handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
return false;
}
-
+
// Create the chunk option
String chunkOption = "";
if (settings.getChunkSize() != PortableCaseReportModuleSettings.ChunkSize.NONE) {
chunkOption = "-v" + settings.getChunkSize().getSevenZipParam();
}
-
+
File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
ProcessBuilder procBuilder = new ProcessBuilder();
procBuilder.command(
sevenZipExe.getAbsolutePath(),
- "a", // Add to archive
+ "a", // Add to archive
zipFile.getAbsolutePath(),
caseFolder.getAbsolutePath(),
chunkOption
);
-
+
try {
Process process = procBuilder.start();
-
+
while (process.isAlive()) {
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
process.destroy();
@@ -1248,7 +1266,7 @@ public class PortableCaseReportModule implements ReportModule {
sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
}
}
-
+
handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
return false;
}
@@ -1256,7 +1274,7 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
return false;
}
-
+
// Delete everything in the case folder then copy over the compressed file(s)
try {
FileUtils.cleanDirectory(caseFolder);
@@ -1266,10 +1284,10 @@ public class PortableCaseReportModule implements ReportModule {
handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
return false;
}
-
+
return true;
}
-
+
/**
* Locate the 7-Zip executable from the release folder.
*
@@ -1292,7 +1310,7 @@ public class PortableCaseReportModule implements ReportModule {
return exeFile;
}
-
+
/**
* Processes the result sets from the interesting item set name query.
*/
@@ -1300,7 +1318,7 @@ public class PortableCaseReportModule implements ReportModule {
private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GetInterestingItemSetNamesCallback.class.getName());
private final Map setCounts = new HashMap<>();
-
+
@Override
public void process(ResultSet rs) {
try {
@@ -1310,7 +1328,7 @@ public class PortableCaseReportModule implements ReportModule {
String setName = rs.getString("set_name"); // NON-NLS
setCounts.put(setName, setCount);
-
+
} catch (SQLException ex) {
logger.log(Level.WARNING, "Unable to get data_source_obj_id or value from result set", ex); // NON-NLS
}
@@ -1318,11 +1336,11 @@ public class PortableCaseReportModule implements ReportModule {
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get next result for values by datasource", ex); // NON-NLS
}
- }
-
+ }
+
/**
* Gets the counts for each interesting items set
- *
+ *
* @return A map from each set name to the number of items in it
*/
public Map getSetCountMap() {
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java
index 465dd7ea87..ff8bd34250 100755
--- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java
@@ -28,8 +28,10 @@ import junit.framework.Test;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbModuleSuite;
+import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
/**
* Tests the Account APIs on the Central Repository.
@@ -145,7 +147,7 @@ public class CentralRepoAccountsTest extends TestCase {
// Create the account
CentralRepository.getInstance()
.getOrCreateAccount(expectedAccountType, "+1 401-231-2552");
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -167,7 +169,7 @@ public class CentralRepoAccountsTest extends TestCase {
Assert.assertEquals(expectedAccountType, actualAccount.getAccountType());
Assert.assertEquals("+1 441-231-2552", actualAccount.getIdentifier());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java
index 22beaa32d8..bbe5ae58f5 100644
--- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java
+++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java
@@ -33,6 +33,7 @@ import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbModuleSuite;
import org.openide.util.Exceptions;
import org.sleuthkit.datamodel.Account;
+import org.sleuthkit.datamodel.InvalidAccountIDException;
import org.sleuthkit.datamodel.TskData;
@@ -74,7 +75,7 @@ public class CentralRepoPersonasTest extends TestCase {
private static final String FACEBOOK_ID_CATDOG = "BalooSherkhan";
private static final String DOG_EMAIL_ID = "superpupper@junglebook.com";
- private static final String CAT_WHATSAPP_ID = "111 222 3333";
+ private static final String CAT_WHATSAPP_ID = "1112223333@s.whatsapp.net";
private static final String EMAIL_ID_1 = "rkipling@jungle.book";
private static final String HOLMES_SKYPE_ID = "live:holmes@221baker.com";
@@ -383,7 +384,7 @@ public class CentralRepoPersonasTest extends TestCase {
// Confirm the account was removed
Assert.assertTrue(catPersona.getPersonaAccounts().isEmpty());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -518,7 +519,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(0, holmesMetadataList.size());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("Didn't expect an exception here. Exception: " + ex);
}
}
@@ -795,7 +796,7 @@ public class CentralRepoPersonasTest extends TestCase {
}
- catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
+ catch (CentralRepoException | CorrelationAttributeNormalizationException | InvalidAccountIDException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
@@ -820,7 +821,7 @@ public class CentralRepoPersonasTest extends TestCase {
// Verify Persona has a default name
Assert.assertEquals(Persona.getDefaultName(), persona.getName());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -893,7 +894,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(4, personaSearchResult.size());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -1004,7 +1005,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(6, personaSearchResult.size());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
@@ -1077,7 +1078,7 @@ public class CentralRepoPersonasTest extends TestCase {
Assert.assertEquals(0, accountsWithUnknownIdentifier.size());
- } catch (CentralRepoException ex) {
+ } catch (InvalidAccountIDException | CentralRepoException ex) {
Assert.fail("No name persona test failed. Exception: " + ex);
}
}
diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py
index c67502d22b..e392fdf24c 100644
--- a/InternalPythonModules/android/whatsapp.py
+++ b/InternalPythonModules/android/whatsapp.py
@@ -177,16 +177,16 @@ class WhatsAppAnalyzer(general.AndroidComponentAnalyzer):
home_phone = contacts_parser.get_home_phone()
mobile_phone = contacts_parser.get_mobile_phone()
email = contacts_parser.get_email()
-
+ other_attributes = contacts_parser.get_other_attributes()
# add contact if we have at least one valid phone/email
- if phone or home_phone or mobile_phone or email:
+ if phone or home_phone or mobile_phone or email or other_attributes:
helper.addContact(
name,
phone,
home_phone,
mobile_phone,
email,
- contacts_parser.get_other_attributes()
+ other_attributes
)
contacts_parser.close()
except SQLException as ex:
@@ -443,10 +443,14 @@ class WhatsAppContactsParser(TskContactsParser):
return (value if general.isValidEmailAddress(value) else None)
def get_other_attributes(self):
- return [BlackboardAttribute(
+ value = self.result_set.getString("jid")
+ if value:
+ return [BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID,
self._PARENT_ANALYZER,
- self.result_set.getString("jid"))]
+ value)]
+ else:
+ return []
class WhatsAppMessagesParser(TskMessagesParser):
"""
diff --git a/NEWS.txt b/NEWS.txt
index 068408b413..aa3418c5e0 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,59 @@
+---------------- VERSION 4.16.0 --------------
+Ingest:
+- Added streaming ingest capability for disk images that allow files to be analyzed as soon as they are added to the database.
+- Changed backend code so that disk image-based files are added by Java code instead of C/C++ code.
+
+Ingest Modules:
+- Include Interesting File set rules for cloud storage, encryption, cryptocurrency and privacy programs.
+- Updated PhotoRec 7.1 and include 64-bit version
+- Updated RegRipper in Recent Activity to 2.8
+- Create artifacts for Prefetch, Background Activity Monitor, and System Resource Usage.
+- Support MBOX files greater than 2GB
+- Document metadata is saved as explicit artifacts and added to the timeline.
+- New “no change” hashset type that does not change status of file.
+
+
+Central Repository / Personas:
+- Accounts in the Central Repository can be grouped together and associated with a digital persona
+- All accounts are now stored in the Central Repository to support correlation and persona creation.
+
+Content viewers:
+- Created artifact-specific viewers in the Results viewer for contact book and call log.
+- Moved Message viewer to a Results sub-viewer and expanded to show accounts.
+- Added Application sub-viewer for PDF files based on IcePDF.
+- Annotation viewer now includes comments from hash set hit and interesting file set hit artifacts
+
+Geolocation Viewer
+- Different data types now are displayed using different colors
+- Track points in a track are now displayed as small, connected circles instead of full pins.
+- Filter panel shows only data sources with geo location data.
+- Geolocation artifact points can be tagged and commented upon
+
+File Discovery
+- Changed UI to have more of a search flow and content viewer is hidden until an item is selected.
+
+Reports
+- Can be generated for a single data source instead of the entire case.
+- CASE / UCO report module now includes artifacts in addition to files.
+- Added backend concept of Tag Sets to support Project Vic categories from different countries.
+
+Performance:
+- Add throttling of UI refreshes to ensure data is quickly displayed and the tree does not get backed up with requests.
+- Improved efficiency of adding a data source with many orphan files
+- Improved efficiency of loading file systems
+- Jython interpreter is preloaded at application startup
+
+Misc bug fixes and improvements
+- Fixed bug from last release where hex content viewer text was no longer fixed width
+- Altered locking to allow multiple data sources to be added at once more smoothly and to support batch inserts of file data
+- Central repository comments will no longer store tag descriptions
+- Account type nodes in the Accounts tree show counts
+- Full time stamps displayed for messages in ingest inbox
+- More detailed status during file exports
+- Improved efficiency of adding timeline events
+- Fixed bug with CVT most recent filter
+- Improved documentation and support for running on Linux/macOS
+
---------------- VERSION 4.15.0 --------------
New UI Features:
- Added Document view to File Discovery.
diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
index 1e6fe08d02..2d1a967efc 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java
@@ -66,7 +66,7 @@ final class ExtractPrefetch extends Extract {
private static final String MODULE_NAME = "extractPREFETCH"; //NON-NLS
private static final String PREFETCH_TSK_COMMENT = "Prefetch File";
- private static final String PREFETCH_FILE_LOCATION = "/Windows/Prefetch";
+ private static final String PREFETCH_FILE_LOCATION = "/windows/prefetch";
private static final String PREFETCH_TOOL_FOLDER = "markmckinnon"; //NON-NLS
private static final String PREFETCH_TOOL_NAME_WINDOWS_64 = "parse_prefetch_x64.exe"; //NON-NLS
private static final String PREFETCH_TOOL_NAME_WINDOWS_32 = "parse_prefetch_x32.exe"; //NON-NLS
@@ -112,9 +112,9 @@ final class ExtractPrefetch extends Extract {
return;
}
- String modOutFile = modOutPath + File.separator + PREFETCH_PARSER_DB_FILE;
+ String modOutFile = modOutPath + File.separator + dataSource.getName() + "-" + PREFETCH_PARSER_DB_FILE;
try {
- String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), PREFETCH_DIR_NAME );
+ String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME );
parsePrefetchFiles(prefetchDumper, tempDirPath, modOutFile, modOutPath);
createAppExecArtifacts(modOutFile, dataSource);
} catch (IOException ex) {
@@ -148,8 +148,8 @@ final class ExtractPrefetch extends Extract {
return;
}
- String prefetchFile = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), PREFETCH_DIR_NAME) + File.separator + pFile.getName();
- if (pFile.getParentPath().contains(PREFETCH_FILE_LOCATION)) {
+ String prefetchFile = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME) + File.separator + pFile.getName();
+ if (pFile.getParentPath().toLowerCase().contains(PREFETCH_FILE_LOCATION.toLowerCase())) {
try {
ContentUtils.writeToFile(pFile, new File(prefetchFile));
} catch (IOException ex) {
@@ -293,7 +293,7 @@ final class ExtractPrefetch extends Extract {
}
}
} else {
- logger.log(Level.SEVERE, "File has a null value " + prefetchFileName);//NON-NLS
+ logger.log(Level.WARNING, "File has a null value " + prefetchFileName);//NON-NLS
}
}
@@ -371,17 +371,21 @@ final class ExtractPrefetch extends Extract {
FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
try {
- files = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS
+ files = fileManager.findFiles(dataSource, fileName); //NON-NLS
+
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to find prefetch files.", ex); //NON-NLS
return null; // No need to continue
}
- if (!files.isEmpty()) {
- return files.get(0);
- } else {
- return null;
+ for (AbstractFile pFile : files) {
+
+ if (pFile.getParentPath().toLowerCase().contains(filePath.toLowerCase())) {
+ return pFile;
+ }
}
+
+ return null;
}
diff --git a/TSKVersion.xml b/TSKVersion.xml
index 38e614169a..7da9869f04 100644
--- a/TSKVersion.xml
+++ b/TSKVersion.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
index f4d5248a8b..35138509d8 100644
--- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
+++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
@@ -1,5 +1,5 @@
#Updated by build script
-#Fri, 19 Jun 2020 10:14:47 -0400
+#Wed, 08 Jul 2020 15:15:46 -0400
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538
@@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
SplashRunningTextColor=0x0
SplashRunningTextFontSize=19
-currentVersion=Autopsy 4.15.0
+currentVersion=Autopsy 4.16.0
diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
index 52d17e0e96..cf36e85b33 100644
--- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
+++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
@@ -1,4 +1,4 @@
#Updated by build script
-#Fri, 19 Jun 2020 10:14:47 -0400
-CTL_MainWindow_Title=Autopsy 4.15.0
-CTL_MainWindow_Title_No_Project=Autopsy 4.15.0
+#Wed, 08 Jul 2020 15:15:46 -0400
+CTL_MainWindow_Title=Autopsy 4.16.0
+CTL_MainWindow_Title_No_Project=Autopsy 4.16.0
diff --git a/docs/doxygen-user/data_sources.dox b/docs/doxygen-user/data_sources.dox
index 6aeed0bbb1..9a5dcd3692 100644
--- a/docs/doxygen-user/data_sources.dox
+++ b/docs/doxygen-user/data_sources.dox
@@ -47,10 +47,11 @@ Data sources can be removed from cases created with Autopsy 4.14.0 and later. Se
\section ds_img Adding a Disk Image
Autopsy supports disk images in the following formats:
-- Raw Single (For example: *.img, *.dd, *.raw, *.bin)
-- Raw Split (For example: *.001, *.002, *.aa, *.ab, etc)
-- EnCase (For example: *.e01, *.e02, etc)
-- Virtual Machines (For example: *.vmdk, *.vhd)
+- Raw Single (*.img, *.dd, *.raw, *.bin)
+- Raw Split (*.001, *.aa)
+- EnCase (*.e01)
+- Virtual Machine Disk (*.vmdk)
+- Virtual Hard Disk (*.vhd)
\image html data_source_disk_image.png
diff --git a/docs/doxygen-user/file_discovery.dox b/docs/doxygen-user/file_discovery.dox
index f711cb0d01..92f2e5a4e9 100644
--- a/docs/doxygen-user/file_discovery.dox
+++ b/docs/doxygen-user/file_discovery.dox
@@ -1,12 +1,12 @@
-/*! \page file_discovery_page File Discovery
+/*! \page discovery_page Discovery
\section file_disc_overview Overview
-The file discovery tool shows images, videos, or documents that match a set of filters configured by the user. You can choose how to group and order your results in order to see the most relevant data first.
+The discovery tool shows images, videos, or documents that match a set of filters configured by the user. You can choose how to group and order your results in order to see the most relevant data first.
\section file_disc_prereq Prerequisites
-We suggest running all \ref ingest_page "ingest modules" before launching file discovery, but if time is a factor the following are the modules that are the most important. You will see a warning if you open file discovery without running the \ref file_type_identification_page, the \ref hash_db_page, and the \ref EXIF_parser_page.
+We suggest running all \ref ingest_page "ingest modules" before launching discovery, but if time is a factor the following are the modules that are the most important. You will see a warning if you open discovery without running the \ref file_type_identification_page, the \ref hash_db_page, and the \ref EXIF_parser_page.
Required ingest modules:
@@ -24,22 +24,24 @@ Optional ingest modules:
\ref embedded_file_extractor_page - Allows display of an image contained in a document
-\section file_disc_run Running File Discovery
+\section file_disc_run Running Discovery
-To launch file discovery, either click the "File Discovery" icon near the top of the Autopsy UI or go to "Tools", "File Discovery". There are three steps when setting up file discovery, which flow from the top of the panel to the bottom:
+To launch discovery, either click the "Discovery" icon near the top of the Autopsy UI or go to "Tools", "Discovery". There are three steps when setting up discovery, which flow from the top of the panel to the bottom:
\ref file_disc_type "Choose the file type"
\ref file_disc_filtering "Set up filters"
\ref file_disc_grouping "Choose how to group and sort the results
-Once everything is set up, use the "Show" button at the bottom of the left panel to display your results. If you want to cancel a search in progress you can use the "Cancel" button.
+\image html FileDiscovery/fd_setup.png
+
+Once everything is set up, use the "Show" button at the bottom right to display your results.
\image html FileDiscovery/fd_main.png
\subsection file_disc_type File Type
-The first step is choosing whether you want to display images, videos, or documents. The file type is determined by the MIME type of the file, which is why the \ref file_type_identification_page must be run to see any results. Switching between the file types will clear any results being displayed and reset the filters.
+The first step is choosing whether you want to display images, videos, or documents. The file type is determined by the MIME type of the file, which is why the \ref file_type_identification_page must be run to see any results. Switching between the file types will reset the filters.
\image html FileDiscovery/fd_fileType.png
@@ -79,13 +81,13 @@ This means the file must have a "User Content Suspected" result associated with
\subsubsection file_disc_hash_filter Hash Set Filter
-The hash set filter restricts the results to files found in the selected hash sets. Only notable hash sets that have hits in the current case are listed (though those hits may not be images or videos). See the \ref hash_db_page page for more information on creating and using hash sets.
+The hash set filter restricts the results to files found in the selected hash sets. Only notable hash sets that have hits in the current case are listed. See the \ref hash_db_page page for more information on creating and using hash sets.
\image html FileDiscovery/fd_hashSetFilter.png
\subsubsection file_disc_int_filter Interesting Item Filter
-The interesting item filter restricts the results to files found in the selected interesting item rule sets. Only interesting file rule sets that have results in the current case are listed (though those matches may not be images or videos). See the \ref interesting_files_identifier_page page for more information on creating and using interesting item rule sets.
+The interesting item filter restricts the results to files found in the selected interesting item rule sets. Only interesting file rule sets that have results in the current case are listed. See the \ref interesting_files_identifier_page page for more information on creating and using interesting item rule sets.
\image html FileDiscovery/fd_interestingItemsFilter.png
@@ -125,7 +127,7 @@ The final options are for how you want to group and sort your results.
\image html FileDiscovery/fd_grouping.png
-The first option lets you choose the top level grouping for your results and the second option lets you choose how to sort them. The groups appear in the middle column of the file discovery panel. Note that some of the grouping options may not always appear - for example, grouping by past occurrences will only be present if the \ref central_repo_page is enabled, and grouping by hash set will only be present if there are hash set hits in your current case. The example below shows the groups created using the default options (group by file size, order groups by group name):
+The first option lets you choose the top level grouping for your results and the second option lets you choose how to sort them. The groups appear in the left column of the results window. Note that some of the grouping options may not always appear - for example, grouping by past occurrences will only be present if the \ref central_repo_page is enabled, and grouping by hash set will only be present if there are hash set hits in your current case. The example below shows the groups created using the default options (group by file size, order groups by group name):
\image html FileDiscovery/fd_groupingSize.png
@@ -135,13 +137,15 @@ In the case of file size and past occurrences, ordering by group name is based o
The interesting items filter was not enabled so most images ended up in the "None" group, meaning they have no interesting file result associated with them. The final group in the list contains a file that matched both interesting item rule sets.
-The last grouping and sorting option is choosing how to sort the results within a group. This is the order of the results in the top right panel after selecting a group from the middle column. Note that due to the merging of results with the same hash in that panel, ordering by file name, path, or data source can vary. See the \ref file_disc_dedupe section below for more information.
+The last grouping and sorting option is choosing how to sort the results within a group. This is the order of the results on the right side of the results window after selecting a group from the left column. Note that due to the merging of results with the same hash in that panel, ordering by file name, path, or data source can vary. See the \ref file_disc_dedupe section below for more information.
\section file_disc_results Viewing Results
\subsection file_disc_results_overview Overview
-Once you select your options and click "Show", you'll see a list of groups in the middle panel. Selecting one of these groups will display the results from that group in the right panel. If your results are images, you'll see thumbnails for each image in the top area of the right panel.
+Once you select your options and click "Search", you'll see a new window with the list of groups on the left side. Selecting one of these groups will display the results from that group on the right side. Selecting a result will cause a panel to rise showing more details about each instance of that result. You can manually raise and lower this panel using the large arrows on the right side of the divider.
+
+If your results are images, you'll see thumbnails for each image in the top area of the right panel.
\image html FileDiscovery/fd_resultGroups.png
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_documents.png b/docs/doxygen-user/images/FileDiscovery/fd_documents.png
index 7170798ce7..3f60954eae 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_documents.png and b/docs/doxygen-user/images/FileDiscovery/fd_documents.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png b/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png
index 4d15a718e2..e94e3d1339 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png and b/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_grouping.png b/docs/doxygen-user/images/FileDiscovery/fd_grouping.png
index 7d1441bee1..1a671c8999 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_grouping.png and b/docs/doxygen-user/images/FileDiscovery/fd_grouping.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_main.png b/docs/doxygen-user/images/FileDiscovery/fd_main.png
index 6e44376a10..69e7a109a9 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_main.png and b/docs/doxygen-user/images/FileDiscovery/fd_main.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png b/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png
index b8362e610f..5872da0ad1 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png and b/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_setup.png b/docs/doxygen-user/images/FileDiscovery/fd_setup.png
new file mode 100644
index 0000000000..32018808a3
Binary files /dev/null and b/docs/doxygen-user/images/FileDiscovery/fd_setup.png differ
diff --git a/docs/doxygen-user/images/FileDiscovery/fd_videos.png b/docs/doxygen-user/images/FileDiscovery/fd_videos.png
index 53dfbd8339..4cf58e29a6 100644
Binary files a/docs/doxygen-user/images/FileDiscovery/fd_videos.png and b/docs/doxygen-user/images/FileDiscovery/fd_videos.png differ
diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox
index 63114401bd..0d808519cd 100644
--- a/docs/doxygen-user/main.dox
+++ b/docs/doxygen-user/main.dox
@@ -70,7 +70,7 @@ The following topics are available here:
- \subpage timeline_page
- \subpage communications_page
- \subpage geolocation_page
- - \subpage file_discovery_page
+ - \subpage discovery_page
- Reporting
- \subpage tagging_page
diff --git a/release_scripts/update_sleuthkit_version.pl b/release_scripts/update_sleuthkit_version.pl
index 26d8be5073..1d6bdc7e72 100755
--- a/release_scripts/update_sleuthkit_version.pl
+++ b/release_scripts/update_sleuthkit_version.pl
@@ -131,10 +131,14 @@ sub update_core_project_properties {
my $found = 0;
while () {
- if (/^file\.reference\.sleuthkit\-/) {
+ if (/^file\.reference\.sleuthkit\-4/) {
print CONF_OUT "file.reference.sleuthkit-${VER}.jar=release/modules/ext/sleuthkit-${VER}.jar\n";
$found++;
}
+ elsif (/^file\.reference\.sleuthkit\-caseuco-4/) {
+ print CONF_OUT "file.reference.sleuthkit-caseuco-${VER}.jar=release/modules/ext/sleuthkit-caseuco-${VER}.jar\n";
+ $found++;
+ }
else {
print CONF_OUT $_;
@@ -143,8 +147,8 @@ sub update_core_project_properties {
close (CONF_IN);
close (CONF_OUT);
- if ($found != 1) {
- die "$found (instead of 1) occurrences of version found in ${orig}";
+ if ($found != 2) {
+ die "$found (instead of 2) occurrences of version found in core ${orig}";
}
unlink ($orig) or die "Error deleting ${orig}";
@@ -167,14 +171,22 @@ sub update_core_project_xml {
my $found = 0;
while () {
- if (/ext\/sleuthkit-/) {
+ if (/ext\/sleuthkit-4/) {
print CONF_OUT " ext/sleuthkit-${VER}.jar\n";
$found++;
}
- elsif (/release\/modules\/ext\/sleuthkit-/) {
+ elsif (/release\/modules\/ext\/sleuthkit-4/) {
print CONF_OUT " release/modules/ext/sleuthkit-${VER}.jar\n";
$found++;
}
+ elsif (/ext\/sleuthkit-caseuco-4/) {
+ print CONF_OUT " ext/sleuthkit-caseuco-${VER}.jar\n";
+ $found++;
+ }
+ elsif (/release\/modules\/ext\/sleuthkit-caseuco-4/) {
+ print CONF_OUT " release/modules/ext/sleuthkit-caseuco-${VER}.jar\n";
+ $found++;
+ }
else {
print CONF_OUT $_;
}
@@ -182,8 +194,8 @@ sub update_core_project_xml {
close (CONF_IN);
close (CONF_OUT);
- if ($found != 2) {
- die "$found (instead of 2) occurrences of version found in ${orig}";
+ if ($found != 4) {
+ die "$found (instead of 4) occurrences of version found in case ${orig}";
}
unlink ($orig) or die "Error deleting ${orig}";
diff --git a/unix_setup.sh b/unix_setup.sh
index 06fc655e32..a9d01739f6 100644
--- a/unix_setup.sh
+++ b/unix_setup.sh
@@ -5,7 +5,7 @@
# NOTE: update_sleuthkit_version.pl updates this value and relies
# on it keeping the same name and whitespace. Don't change it.
-TSK_VERSION=4.9.0
+TSK_VERSION=4.10.0
# In the beginning...