Merge branch 'develop' of github.com:sleuthkit/autopsy into develop
@@ -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).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -472,8 +472,8 @@
|
||||
<binary-origin>release/modules/ext/commons-pool2-2.4.2.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sleuthkit-4.9.0.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sleuthkit-4.9.0.jar</binary-origin>
|
||||
<runtime-relative-path>ext/sleuthkit-4.10.0.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sleuthkit-4.10.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/jxmapviewer2-2.4.jar</runtime-relative-path>
|
||||
@@ -780,8 +780,8 @@
|
||||
<binary-origin>release/modules/ext/curator-client-2.8.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sleuthkit-caseuco-4.9.0.jar</runtime-relative-path>
|
||||
<binary-origin>release\modules\ext\sleuthkit-caseuco-4.9.0.jar</binary-origin>
|
||||
<runtime-relative-path>ext/sleuthkit-caseuco-4.10.0.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/sleuthkit-caseuco-4.10.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/fontbox-2.0.13.jar</runtime-relative-path>
|
||||
|
||||
@@ -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<CentralRepoAccount> 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<CentralRepoAccount> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> 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...
|
||||
*/
|
||||
|
||||
@@ -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<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException {
|
||||
private static void makeCorrAttrsFromCommunicationArtifacts(List<CorrelationAttributeInstance> 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<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException {
|
||||
private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> 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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<Persona> 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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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<Integer> 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<String, DataSource> 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<Account.Type> 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<String, DataSource> dataSourceMap;
|
||||
private final Integer startTime;
|
||||
private final Integer endTime;
|
||||
private final List<Account.Type> accountTypesInUse;
|
||||
|
||||
FilterPanelData(Map<String, DataSource> dataSourceMap, List<Account.Type> accountTypesInUse, Integer startTime, Integer endTime) {
|
||||
this.dataSourceMap = dataSourceMap;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.accountTypesInUse = accountTypesInUse;
|
||||
}
|
||||
|
||||
Map<String, DataSource> getDataSourceMap() {
|
||||
return dataSourceMap;
|
||||
}
|
||||
|
||||
Integer getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
Integer getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
List<Account.Type> getAccountTypesInUse() {
|
||||
return accountTypesInUse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="scrollPane">
|
||||
<Properties>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="autoscrolls" type="boolean" value="true"/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="null"/>
|
||||
</Property>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="autoscrolls" type="boolean" value="true"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
|
||||
@@ -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<Account.Type> accountTypesInUse, boolean checkNewOnes) {
|
||||
boolean newOneFound = false;
|
||||
try {
|
||||
List<Account.Type> 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<String, DataSource> 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<String, DataSource> 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<Map<String, Integer>, Void> {
|
||||
|
||||
@Override
|
||||
protected Map<String, Integer> doInBackground() throws Exception {
|
||||
if (openCase == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Integer> resultMap = new HashMap<>();
|
||||
String queryString = "max(date_time) as end, min(date_time) as start from account_relationships"; // NON-NLS
|
||||
|
||||
openCase.getSleuthkitCase().getCaseDbAccessManager().select(queryString, new FilterPanelQueryCallback() {
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
try {
|
||||
if (rs.next()) {
|
||||
int startDate = rs.getInt("start"); // NON-NLS
|
||||
int endDate = rs.getInt("end"); // NON-NLS
|
||||
|
||||
resultMap.put("start", startDate); // NON-NLS
|
||||
resultMap.put("end", endDate); // NON-NLS
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
// Not the end of the world if this fails.
|
||||
logger.log(Level.WARNING, String.format("SQL Exception thrown from Query: %s", queryString), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return resultMap;
|
||||
FilterPanelRefresher(boolean selectNewOptions, boolean refreshAfterUpdate) {
|
||||
this.selectNewOption = selectNewOptions;
|
||||
this.refreshAfterUpdate = refreshAfterUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
Map<String, Integer> resultMap = get();
|
||||
if (resultMap != null) {
|
||||
Integer start = resultMap.get("start");
|
||||
Integer end = resultMap.get("end");
|
||||
void updateFilterPanel(CVTFilterRefresher.FilterPanelData data) {
|
||||
updateDateTimePicker(data.getStartTime(), data.getEndTime());
|
||||
updateDeviceFilterPanel(data.getDataSourceMap(), selectNewOption);
|
||||
updateAccountTypeFilter(data.getAccountTypesInUse(), selectNewOption);
|
||||
|
||||
if (start != null && start != 0) {
|
||||
startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(start), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
FiltersPanel.this.repaint();
|
||||
|
||||
if (end != null && end != 0) {
|
||||
endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(end), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "Exception occured after date time sql query", ex);
|
||||
if (refreshAfterUpdate) {
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
if (!isEnabled()) {
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
validateFilters();
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,5 +1170,4 @@ final public class FiltersPanel extends JPanel {
|
||||
return e1.getText().toLowerCase().compareTo(e2.getText().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
|
||||
/**
|
||||
@@ -113,7 +114,7 @@ class AccountSummary {
|
||||
isReference = true;
|
||||
break;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
logger.log(Level.WARNING, String.format("Exception thrown "
|
||||
+ "in trying to normalize attribute value: %s",
|
||||
attributeValue), ex); //NON-NLS
|
||||
|
||||
@@ -9,7 +9,6 @@ SummaryViewer.callLogsLabel.text=Call Logs:
|
||||
ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages
|
||||
ThreadPane.backButton.text=<---
|
||||
SummaryViewer.caseReferencesPanel.border.title=Other Occurrences
|
||||
SummaryViewer.fileReferencesPanel.border.title=File References in Current Case
|
||||
MessageViewer.threadsLabel.text=Select a Thread to View
|
||||
MessageViewer.threadNameLabel.text=<threadName>
|
||||
MessageViewer.showingMessagesLabel.text=Showing Messages for Thread:
|
||||
@@ -27,3 +26,5 @@ SummaryViewer.referencesLabel.text=Communication References:
|
||||
SummaryViewer.referencesDataLabel.text=<reference count>
|
||||
SummaryViewer.contactsLabel.text=Book Entries:
|
||||
SummaryViewer.accountCountry.text=<account country>
|
||||
SummaryViewer.fileRefPane.border.title=File References in Current Case
|
||||
SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References>
|
||||
|
||||
@@ -49,13 +49,13 @@ SummaryViewer_CentralRepository_Message=<Enable Central Respository to see Other
|
||||
SummaryViewer_Country_Code=Country:
|
||||
SummaryViewer_Creation_Date_Title=Creation Date
|
||||
SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.
|
||||
SummaryViewer_Fetching_References=<Fetching File References>
|
||||
SummaryViewer_FileRef_Message=<Select a single account to see File References>
|
||||
SummaryViewer_FileRefNameColumn_Title=Path
|
||||
SummaryViewer_TabTitle=Summary
|
||||
ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages
|
||||
ThreadPane.backButton.text=<---
|
||||
SummaryViewer.caseReferencesPanel.border.title=Other Occurrences
|
||||
SummaryViewer.fileReferencesPanel.border.title=File References in Current Case
|
||||
MessageViewer.threadsLabel.text=Select a Thread to View
|
||||
MessageViewer.threadNameLabel.text=<threadName>
|
||||
MessageViewer.showingMessagesLabel.text=Showing Messages for Thread:
|
||||
@@ -73,3 +73,5 @@ SummaryViewer.referencesLabel.text=Communication References:
|
||||
SummaryViewer.referencesDataLabel.text=<reference count>
|
||||
SummaryViewer.contactsLabel.text=Book Entries:
|
||||
SummaryViewer.accountCountry.text=<account country>
|
||||
SummaryViewer.fileRefPane.border.title=File Referernce(s) in Current Case
|
||||
SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References>
|
||||
|
||||
@@ -49,7 +49,6 @@ SummeryViewer_FileRef_Message=<\u30a2\u30ab\u30a6\u30f3\u30c8\u30921\u3064\u9078
|
||||
ThreadRootMessagePanel.showAllCheckBox.text=\u3059\u3079\u3066\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u8868\u793a
|
||||
ThreadPane.backButton.text=<---
|
||||
SummaryViewer.caseReferencesPanel.border.title=\u305d\u306e\u4ed6\u306e\u767a\u751f
|
||||
SummaryViewer.fileReferencesPanel.border.title=\u73fe\u5728\u306e\u30b1\u30fc\u30b9\u306e\u30d5\u30a1\u30a4\u30eb\u30ec\u30d5\u30a1\u30ec\u30f3\u30b9
|
||||
MessageViewer.threadsLabel.text=\u30b9\u30ec\u30c3\u30c9\u3092\u9078\u629e\u3057\u3066\u8868\u793a
|
||||
MessageViewer.threadNameLabel.text=<threadName>
|
||||
MessageViewer.showingMessagesLabel.text=\u6b21\u306e\u30b9\u30ec\u30c3\u30c9\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u8868\u793a\u4e2d\u3067\u3059:
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications.relationships;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.nodes.Sheet;
|
||||
@@ -55,6 +57,8 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
|
||||
private final Action preferredAction;
|
||||
|
||||
private final Action defaultNoopAction = new DefaultMessageAction();
|
||||
|
||||
MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) {
|
||||
super(artifact);
|
||||
|
||||
@@ -148,7 +152,7 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
|
||||
@Override
|
||||
public Action getPreferredAction() {
|
||||
return preferredAction;
|
||||
return preferredAction != null ? preferredAction : defaultNoopAction;
|
||||
}
|
||||
|
||||
private int getAttachmentsCount() throws TskCoreException {
|
||||
@@ -171,4 +175,17 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
|
||||
return attachmentsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* A no op action to override the default action of BlackboardArtifactNode
|
||||
*/
|
||||
private class DefaultMessageAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// Do Nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,22 +253,6 @@
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="File References in Current Case">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
@@ -285,5 +269,99 @@
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="fileRefPane">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<TitledBorder title="File References in Current Case">
|
||||
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileRefPane.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="fileRefScrolPanel">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
|
||||
<CardConstraints cardName="listPanelCard"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="scrollPane">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="Center"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JList" name="fileRefList">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
|
||||
<StringArray count="5">
|
||||
<StringItem index="0" value="Item 1"/>
|
||||
<StringItem index="1" value="Item 2"/>
|
||||
<StringItem index="2" value="Item 3"/>
|
||||
<StringItem index="3" value="Item 4"/>
|
||||
<StringItem index="4" value="Item 5"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="selectAccountPane">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
|
||||
<CardConstraints cardName="selectAccountCard"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="selectAccountFileRefLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.selectAccountFileRefLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
||||
@@ -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<String> 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<Account> accounts) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
accounts.stream().map((account) -> {
|
||||
buffer.append(account.getTypeSpecificID());
|
||||
return account;
|
||||
}).forEachOrdered((_item) -> {
|
||||
buffer.append(", ");
|
||||
});
|
||||
@Messages({
|
||||
"SummaryViewer_Fetching_References=<Fetching File References>"
|
||||
})
|
||||
private void updateFileReferences(final Account account) {
|
||||
SwingWorker<List<String>, Void> worker = new SwingWorker<List<String>, Void>() {
|
||||
@Override
|
||||
protected List<String> doInBackground() throws Exception {
|
||||
List<String> stringList = new ArrayList<>();
|
||||
List<AccountFileInstance> 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<String> 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>() {
|
||||
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);
|
||||
}// </editor-fold>//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<String> 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;
|
||||
|
||||
@@ -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<PersonaAccount> personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
|
||||
if (personaAccounts != null && !personaAccounts.isEmpty()) {
|
||||
|
||||
// look for unique accounts
|
||||
Collection<CentralRepoAccount> 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<Persona> personas
|
||||
= personaAccounts
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Snapshot> 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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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<Content> dataSources = caseObj.getDataSources();
|
||||
* for(Content dataSource : dataSources) {
|
||||
* caseUco.addDataSource(dataSource, caseObj);
|
||||
* List<AbstractFile> 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<CASEPropertyBundle> 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<String, Object> properties;
|
||||
private final List<CASEPropertyBundle> 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<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@JsonProperty("propertyBundle")
|
||||
public List<CASEPropertyBundle> 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<String, Object> properties;
|
||||
|
||||
public CASEPropertyBundle(String type) {
|
||||
properties = new LinkedHashMap<>();
|
||||
addProperty("@type", type);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void addProperty(String key, Object val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Short> SUPPORTED_TYPES = new HashSet<Short>() {{
|
||||
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<Short> SUPPORTED_TYPES = new HashSet<Short>() {
|
||||
{
|
||||
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<Content> 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<DataSource> 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<Long> 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<DataSource> 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<Content> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<project name="TSK_VERSION">
|
||||
<property name="TSK_VERSION" value="4.9.0"/>
|
||||
<property name="TSK_VERSION" value="4.10.0"/>
|
||||
</project>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
<ul>
|
||||
@@ -24,22 +24,24 @@ Optional ingest modules:
|
||||
<li>\ref embedded_file_extractor_page - Allows display of an image contained in a document
|
||||
</ul>
|
||||
|
||||
\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:
|
||||
<ol>
|
||||
<li>\ref file_disc_type "Choose the file type"
|
||||
<li>\ref file_disc_filtering "Set up filters"
|
||||
<li>\ref file_disc_grouping "Choose how to group and sort the results
|
||||
</ol>
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 477 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 420 KiB After Width: | Height: | Size: 417 KiB |
@@ -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
|
||||
|
||||
@@ -131,10 +131,14 @@ sub update_core_project_properties {
|
||||
|
||||
my $found = 0;
|
||||
while (<CONF_IN>) {
|
||||
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 (<CONF_IN>) {
|
||||
if (/<runtime-relative-path>ext\/sleuthkit-/) {
|
||||
if (/<runtime-relative-path>ext\/sleuthkit-4/) {
|
||||
print CONF_OUT " <runtime-relative-path>ext/sleuthkit-${VER}.jar</runtime-relative-path>\n";
|
||||
$found++;
|
||||
}
|
||||
elsif (/<binary-origin>release\/modules\/ext\/sleuthkit-/) {
|
||||
elsif (/<binary-origin>release\/modules\/ext\/sleuthkit-4/) {
|
||||
print CONF_OUT " <binary-origin>release/modules/ext/sleuthkit-${VER}.jar</binary-origin>\n";
|
||||
$found++;
|
||||
}
|
||||
elsif (/<runtime-relative-path>ext\/sleuthkit-caseuco-4/) {
|
||||
print CONF_OUT " <runtime-relative-path>ext/sleuthkit-caseuco-${VER}.jar</runtime-relative-path>\n";
|
||||
$found++;
|
||||
}
|
||||
elsif (/<binary-origin>release\/modules\/ext\/sleuthkit-caseuco-4/) {
|
||||
print CONF_OUT " <binary-origin>release/modules/ext/sleuthkit-caseuco-${VER}.jar</binary-origin>\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}";
|
||||
|
||||
@@ -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...
|
||||
|
||||