Merge pull request #6475 from eugene7646/solr-8-upgrade

Merged develop
This commit is contained in:
eugene7646 2020-11-19 11:44:57 -05:00 committed by GitHub
commit 80bc0a5434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1217 additions and 436 deletions

View File

@ -383,6 +383,31 @@ public class CentralRepoDbManager {
saveNewCentralRepo(); saveNewCentralRepo();
} }
/**
* Set up a PostgresDb using the settings for the given database choice
* enum.
*
* @param choice Type of postgres DB to set up
* @throws CentralRepoException
*/
public void setupPostgresDb(CentralRepoDbChoice choice) throws CentralRepoException {
selectedDbChoice = choice;
DatabaseTestResult curStatus = testStatus();
if (curStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) {
createDb();
curStatus = testStatus();
}
// the only successful setup status is tested ok
if (curStatus != DatabaseTestResult.TESTED_OK) {
throw new CentralRepoException("Unable to successfully create postgres database. Test failed with: " + curStatus);
}
// if successfully got here, then save the settings
CentralRepoDbUtil.setUseCentralRepo(true);
saveNewCentralRepo();
}
/** /**
* This method returns if changes to the central repository configuration * This method returns if changes to the central repository configuration
* were successfully applied. * were successfully applied.

View File

@ -121,10 +121,12 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbConnectiv
* @return * @return
*/ */
String getConnectionURL(boolean usePostgresDb) { String getConnectionURL(boolean usePostgresDb) {
StringBuilder url = new StringBuilder(); StringBuilder url = new StringBuilder()
url.append(getJDBCBaseURI()); .append(getJDBCBaseURI())
url.append(getHost()); .append(getHost())
url.append("/"); // NON-NLS .append(":") // NON-NLS
.append(getPort())
.append("/"); // NON-NLS
if (usePostgresDb) { if (usePostgresDb) {
url.append("postgres"); // NON-NLS url.append("postgres"); // NON-NLS
} else { } else {
@ -153,7 +155,7 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbConnectiv
} catch (ClassNotFoundException | SQLException ex) { } catch (ClassNotFoundException | SQLException ex) {
// TODO: Determine why a connection failure (ConnectionException) re-throws // TODO: Determine why a connection failure (ConnectionException) re-throws
// the SQLException and does not print this log message? // the SQLException and does not print this log message?
LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to postgresql."); // NON-NLS LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to postgresql.", ex); // NON-NLS
conn = null; conn = null;
} }
return conn; return conn;

View File

@ -1,4 +1,10 @@
caseeventlistener.evidencetag=Evidence caseeventlistener.evidencetag=Evidence
CentralRepositoryNotificationDialog.bulletHeader=This data is used to:
CentralRepositoryNotificationDialog.bulletOne=Ignore common items (files, domains, and accounts)
CentralRepositoryNotificationDialog.bulletThree=Create personas that group accounts
CentralRepositoryNotificationDialog.bulletTwo=Identify where an item was previously seen
CentralRepositoryNotificationDialog.finalRemarks=To limit what is stored, use the Central Repository options panel.
CentralRepositoryNotificationDialog.header=Autopsy stores data about each case in its Central Repository.
IngestEventsListener.ingestmodule.name=Central Repository IngestEventsListener.ingestmodule.name=Central Repository
IngestEventsListener.prevCaseComment.text=Previous Case: IngestEventsListener.prevCaseComment.text=Previous Case:
# {0} - typeName # {0} - typeName
@ -7,6 +13,3 @@ IngestEventsListener.prevCount.text=Number of previous {0}: {1}
IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository) IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)
IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)
Installer.centralRepoUpgradeFailed.title=Central repository disabled Installer.centralRepoUpgradeFailed.title=Central repository disabled
Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. You can use this to ignore previously seen files and make connections between cases.
Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?
Installer.initialCreateSqlite.title=Enable Central Repository?

View File

@ -0,0 +1,73 @@
/*
* Central Repository
*
* 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.centralrepository.eventlisteners;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.Version;
/**
* Notifies new installations or old upgrades that the central repository will
* be enabled by default.
*/
public class CentralRepositoryNotificationDialog {
/**
* This dialog should display iff the mode is RELEASE and the
* application is running with a GUI.
*/
static boolean shouldDisplay() {
return Version.getBuildType() == Version.Type.RELEASE
&& RuntimeProperties.runningWithGUI();
}
/**
* Displays an informational modal dialog to the user, which is dismissed by
* pressing 'OK'.
*/
@NbBundle.Messages({
"CentralRepositoryNotificationDialog.header=Autopsy stores data about each case in its Central Repository.",
"CentralRepositoryNotificationDialog.bulletHeader=This data is used to:",
"CentralRepositoryNotificationDialog.bulletOne=Ignore common items (files, domains, and accounts)",
"CentralRepositoryNotificationDialog.bulletTwo=Identify where an item was previously seen",
"CentralRepositoryNotificationDialog.bulletThree=Create personas that group accounts",
"CentralRepositoryNotificationDialog.finalRemarks=To limit what is stored, use the Central Repository options panel."
})
static void display() {
assert shouldDisplay();
MessageNotifyUtil.Message.info(
"<html>"
+ "<body>"
+ "<div>"
+ "<p>" + Bundle.CentralRepositoryNotificationDialog_header() + "</p>"
+ "<p>" + Bundle.CentralRepositoryNotificationDialog_bulletHeader() + "</p>"
+ "<ul>"
+ "<li>" + Bundle.CentralRepositoryNotificationDialog_bulletOne() + "</li>"
+ "<li>" + Bundle.CentralRepositoryNotificationDialog_bulletTwo() + "</li>"
+ "<li>" + Bundle.CentralRepositoryNotificationDialog_bulletThree() + "</li>"
+ "</ul>"
+ "<p>" + Bundle.CentralRepositoryNotificationDialog_finalRemarks() + "</p>"
+ "</div>"
+ "</body>"
+ "</html>"
);
}
}

View File

@ -25,14 +25,13 @@ import javax.swing.JOptionPane;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import org.openide.modules.ModuleInstall; import org.openide.modules.ModuleInstall;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.Version;
/** /**
* Adds/removes application event listeners responsible for adding data to the * Adds/removes application event listeners responsible for adding data to the
@ -81,20 +80,11 @@ public class Installer extends ModuleInstall {
* the org.sleuthkit.autopsy.core package when the already installed * the org.sleuthkit.autopsy.core package when the already installed
* Autopsy-Core module is restored (during application startup). * Autopsy-Core module is restored (during application startup).
*/ */
@NbBundle.Messages({
"Installer.initialCreateSqlite.title=Enable Central Repository?",
"Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?",
"Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. "
+ "You can use this to ignore previously seen files and make connections between cases."
})
@Override @Override
public void restored() { public void restored() {
addApplicationEventListeners(); addApplicationEventListeners();
if (Version.getBuildType() == Version.Type.RELEASE) {
setupDefaultCentralRepository(); setupDefaultCentralRepository();
} }
}
/** /**
* Adds the application event listeners responsible for adding data to the * Adds the application event listeners responsible for adding data to the
@ -107,9 +97,9 @@ public class Installer extends ModuleInstall {
/** /**
* Checks if the central repository has been set up and configured. If not, * Checks if the central repository has been set up and configured. If not,
* either offers to perform set up (running with a GUI) or does the set up * does the set up unconditionally. If the application is running with a
* unconditionally (not running with a GUI, e.g., in an automated ingest * GUI, a notification will be displayed to the user if the mode is RELEASE
* node). * (in other words, developers are exempt from seeing the notification).
*/ */
private void setupDefaultCentralRepository() { private void setupDefaultCentralRepository() {
Map<String, String> centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository"); Map<String, String> centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository");
@ -128,62 +118,30 @@ public class Installer extends ModuleInstall {
} }
} }
// if central repository hasn't been previously initialized, initialize it if(initialized) {
if (!initialized) { return; // Nothing to do
// if running with a GUI, prompt the user }
if (RuntimeProperties.runningWithGUI()) {
try {
SwingUtilities.invokeAndWait(() -> {
try {
String dialogText
= "<html><body>"
+ "<div style='width: 400px;'>"
+ "<p>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "</p>"
+ "<p style='margin-top: 10px'>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "</p>"
+ "</div>"
+ "</body></html>";
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), if (CentralRepositoryNotificationDialog.shouldDisplay()) {
dialogText, CentralRepositoryNotificationDialog.display();
NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"), }
JOptionPane.YES_NO_OPTION)) {
setupDefaultSqliteCentralRepo(); try {
CentralRepoDbManager manager = new CentralRepoDbManager();
if (UserPreferences.getIsMultiUserModeEnabled()) {
// Set up using existing multi-user settings.
manager.setupPostgresDb(CentralRepoDbChoice.POSTGRESQL_MULTIUSER);
} else {
manager.setupDefaultSqliteDb();
} }
} catch (CentralRepoException ex) { } catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
doMessageBoxIfRunningInGUI(ex); doMessageBoxIfRunningInGUI(ex);
} }
});
} catch (InterruptedException | InvocationTargetException ex) {
logger.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex);
}
} // if no GUI, just initialize
else {
try {
setupDefaultSqliteCentralRepo();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
doMessageBoxIfRunningInGUI(ex);
}
}
ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true");
} }
}
/**
* Sets up a default single-user SQLite central repository.
*
* @throws CentralRepoException If there is an error setting up teh central
* repository.
*/
private void setupDefaultSqliteCentralRepo() throws CentralRepoException {
CentralRepoDbManager manager = new CentralRepoDbManager();
manager.setupDefaultSqliteDb();
}
/** /**
* Display a central repository exception in a message box if running with a * Display a central repository exception in a message box if running with a

View File

@ -28,10 +28,11 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.stream.Collectors;
import java.util.TreeMap; import org.apache.commons.lang.StringUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
@ -93,13 +94,53 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return ARTIFACT_UPDATE_TYPE_IDS; return ARTIFACT_UPDATE_TYPE_IDS;
} }
/**
* Removes fileDetails entries with redundant paths, sorts by date
* descending and limits to the limit provided.
*
* @param fileDetails The file details list.
* @param limit The maximum number of entries to return.
* @return The sorted limited list with unique paths.
*/
private <T extends RecentFileDetails> List<T> getSortedLimited(List<T> fileDetails, int limit) {
Map<String, T> fileDetailsMap = fileDetails.stream()
.filter(details -> details != null)
.collect(Collectors.toMap(
d -> d.getPath().toUpperCase(),
d -> d,
(d1, d2) -> Long.compare(d1.getDateAsLong(), d2.getDateAsLong()) > 0 ? d1 : d2));
return fileDetailsMap.values().stream()
.sorted((a, b) -> -Long.compare(a.getDateAsLong(), b.getDateAsLong()))
.limit(limit)
.collect(Collectors.toList());
}
/**
* Returns a RecentFileDetails object as derived from the recent document
* artifact or null if no appropriate object can be made.
*
* @param artifact The artifact.
* @return The derived object or null if artifact is invalid.
*/
private RecentFileDetails getRecentlyOpenedDocument(BlackboardArtifact artifact) {
String path = DataSourceInfoUtilities.getStringOrNull(artifact, PATH_ATT);
Long lastOpened = DataSourceInfoUtilities.getLongOrNull(artifact, DATETIME_ATT);
if (StringUtils.isBlank(path) || lastOpened == null || lastOpened == 0) {
return null;
} else {
return new RecentFileDetails(path, lastOpened);
}
}
/** /**
* Return a list of the most recently opened documents based on the * Return a list of the most recently opened documents based on the
* TSK_RECENT_OBJECT artifact. * TSK_RECENT_OBJECT artifact.
* *
* @param dataSource The data source to query. * @param dataSource The data source to query.
* @param maxCount The maximum number of results to return, pass 0 to get * @param maxCount The maximum number of results to return, pass 0 to get a
* a list of all results. * list of all results.
* *
* @return A list RecentFileDetails representing the most recently opened * @return A list RecentFileDetails representing the most recently opened
* documents or an empty list if none were found. * documents or an empty list if none were found.
@ -112,38 +153,47 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); return Collections.emptyList();
} }
List<BlackboardArtifact> artifactList throwOnNonPositiveCount(maxCount);
= DataSourceInfoUtilities.getArtifacts(provider.get(),
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT),
dataSource,
DATETIME_ATT,
DataSourceInfoUtilities.SortOrder.DESCENDING,
maxCount);
List<RecentFileDetails> fileDetails = new ArrayList<>(); List<RecentFileDetails> details = provider.get().getBlackboard()
for (BlackboardArtifact artifact : artifactList) { .getArtifacts(ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID(), dataSource.getId()).stream()
Long accessedTime = null; .map(art -> getRecentlyOpenedDocument(art))
String path = ""; .filter(d -> d != null)
.collect(Collectors.toList());
// Get all the attributes in one call. return getSortedLimited(details, maxCount);
List<BlackboardAttribute> attributeList = artifact.getAttributes(); }
for (BlackboardAttribute attribute : attributeList) {
if (attribute.getAttributeType().equals(DATETIME_ATT)) { /**
accessedTime = attribute.getValueLong(); * Returns a RecentDownloadDetails object as derived from the recent
} else if (attribute.getAttributeType().equals(PATH_ATT)) { * download artifact or null if no appropriate object can be made.
path = attribute.getValueString(); *
* @param artifact The artifact.
* @return The derived object or null if artifact is invalid.
*/
private RecentDownloadDetails getRecentDownload(BlackboardArtifact artifact) {
Long accessedTime = DataSourceInfoUtilities.getLongOrNull(artifact, DATETIME_ACCESSED_ATT);
String domain = DataSourceInfoUtilities.getStringOrNull(artifact, DOMAIN_ATT);
String path = DataSourceInfoUtilities.getStringOrNull(artifact, PATH_ATT);
if (StringUtils.isBlank(path) || accessedTime == null || accessedTime == 0) {
return null;
} else {
return new RecentDownloadDetails(path, accessedTime, domain);
} }
} }
if (accessedTime != null && accessedTime != 0) { /**
fileDetails.add(new RecentFileDetails(path, accessedTime)); * Throws an IllegalArgumentException if count is less than 1.
*
* @param count The count.
*/
private void throwOnNonPositiveCount(int count) {
if (count < 1) {
throw new IllegalArgumentException("Invalid count: value must be greater than 0.");
} }
} }
return fileDetails;
}
/** /**
* Return a list of the most recent downloads based on the value of the the * Return a list of the most recent downloads based on the value of the the
* artifact TSK_DATETIME_ACCESSED attribute. * artifact TSK_DATETIME_ACCESSED attribute.
@ -163,38 +213,15 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); return Collections.emptyList();
} }
List<BlackboardArtifact> artifactList throwOnNonPositiveCount(maxCount);
= DataSourceInfoUtilities.getArtifacts(provider.get(),
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD),
dataSource,
DATETIME_ACCESSED_ATT,
DataSourceInfoUtilities.SortOrder.DESCENDING,
maxCount);
List<RecentDownloadDetails> fileDetails = new ArrayList<>(); List<RecentDownloadDetails> details = provider.get().getBlackboard()
for (BlackboardArtifact artifact : artifactList) { .getArtifacts(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID(), dataSource.getId()).stream()
// Get all the attributes in one call. .map(art -> getRecentDownload(art))
Long accessedTime = null; .filter(d -> d != null)
String domain = ""; .collect(Collectors.toList());
String path = "";
List<BlackboardAttribute> attributeList = artifact.getAttributes(); return getSortedLimited(details, maxCount);
for (BlackboardAttribute attribute : attributeList) {
if (attribute.getAttributeType().equals(DATETIME_ACCESSED_ATT)) {
accessedTime = attribute.getValueLong();
} else if (attribute.getAttributeType().equals(DOMAIN_ATT)) {
domain = attribute.getValueString();
} else if (attribute.getAttributeType().equals(PATH_ATT)) {
path = attribute.getValueString();
}
}
if (accessedTime != null && accessedTime != 0L) {
fileDetails.add(new RecentDownloadDetails(path, accessedTime, domain));
}
}
return fileDetails;
} }
/** /**
@ -214,109 +241,66 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); return Collections.emptyList();
} }
if (maxCount < 0) { throwOnNonPositiveCount(maxCount);
throw new IllegalArgumentException("Invalid maxCount passed to getRecentAttachments, value must be equal to or greater than 0");
SleuthkitCase skCase = provider.get();
List<BlackboardArtifact> associatedArtifacts = skCase.getBlackboard()
.getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId());
List<RecentAttachmentDetails> details = new ArrayList<>();
for (BlackboardArtifact artifact : associatedArtifacts) {
RecentAttachmentDetails thisDetails = getRecentAttachment(artifact, skCase);
if (thisDetails != null) {
details.add(thisDetails);
}
} }
return createListFromMap(buildAttachmentMap(dataSource), maxCount); return getSortedLimited(details, maxCount);
} }
/** /**
* Build a map of all of the message attachment sorted in date order. * Creates a RecentAttachmentDetails object from the associated object
* artifact or null if no RecentAttachmentDetails object can be derived.
* *
* @param dataSource Data source to query. * @param artifact The associated object artifact.
* * @param skCase The current case.
* @return Returns a SortedMap of details objects returned in descending * @return The derived object or null.
* order.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException * @throws TskCoreException
*/ */
private SortedMap<Long, List<RecentAttachmentDetails>> buildAttachmentMap(DataSource dataSource) throws SleuthkitCaseProviderException, TskCoreException { private RecentAttachmentDetails getRecentAttachment(BlackboardArtifact artifact, SleuthkitCase skCase) throws TskCoreException {
SleuthkitCase skCase = provider.get(); // get associated artifact or return no result
TreeMap<Long, List<RecentAttachmentDetails>> sortedMap = new TreeMap<>();
List<BlackboardArtifact> associatedArtifacts = skCase.getBlackboard().getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId());
for (BlackboardArtifact artifact : associatedArtifacts) {
BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT); BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT);
if (attribute == null) { if (attribute == null) {
continue; return null;
} }
// get associated message artifact if exists or return no result
BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong()); BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong());
if (messageArtifact != null && isMessageArtifact(messageArtifact)) { if (messageArtifact == null || !isMessageArtifact(messageArtifact)) {
Content content = artifact.getParent(); return null;
if (content instanceof AbstractFile) {
String sender;
Long date = null;
String path;
BlackboardAttribute senderAttribute = messageArtifact.getAttribute(EMAIL_FROM_ATT);
if (senderAttribute != null) {
sender = senderAttribute.getValueString();
} else {
sender = "";
} }
senderAttribute = messageArtifact.getAttribute(MSG_DATEIME_SENT_ATT);
if (senderAttribute != null) { // get abstract file if exists or return no result
date = senderAttribute.getValueLong(); Content content = artifact.getParent();
if (!(content instanceof AbstractFile)) {
return null;
} }
AbstractFile abstractFile = (AbstractFile) content; AbstractFile abstractFile = (AbstractFile) content;
path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()).toString(); // get the path, sender, and date
String path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()).toString();
String sender = DataSourceInfoUtilities.getStringOrNull(messageArtifact, EMAIL_FROM_ATT);
Long date = DataSourceInfoUtilities.getLongOrNull(messageArtifact, MSG_DATEIME_SENT_ATT);
if (date != null && date != 0) { if (date == null || date == 0 || StringUtils.isBlank(path)) {
List<RecentAttachmentDetails> list = sortedMap.get(date); return null;
if (list == null) {
list = new ArrayList<>();
sortedMap.put(date, list);
}
RecentAttachmentDetails details = new RecentAttachmentDetails(path, date, sender);
if (!list.contains(details)) {
list.add(details);
}
}
}
}
}
return sortedMap.descendingMap();
}
/**
* Create a list of detail objects from the given sorted map of the max
* size.
*
* @param sortedMap A Map of attachment details sorted by date.
* @param maxCount Maximum number of values to return.
*
* @return A list of the details of the most recent attachments or empty
* list if none where found.
*/
private List<RecentAttachmentDetails> createListFromMap(SortedMap<Long, List<RecentAttachmentDetails>> sortedMap, int maxCount) {
List<RecentAttachmentDetails> fileList = new ArrayList<>();
for (List<RecentAttachmentDetails> mapList : sortedMap.values()) {
if (maxCount == 0 || fileList.size() + mapList.size() <= maxCount) {
fileList.addAll(mapList);
continue;
}
if (maxCount == fileList.size()) {
break;
}
for (RecentAttachmentDetails details : mapList) {
if (fileList.size() < maxCount) {
fileList.add(details);
} else { } else {
break; return new RecentAttachmentDetails(path, date, sender);
} }
} }
}
return fileList;
}
/** /**
* Is the given artifact a message. * Is the given artifact a message.
@ -330,6 +314,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
final int artifactTypeID = nodeArtifact.getArtifactTypeID(); final int artifactTypeID = nodeArtifact.getArtifactTypeID();
return artifactTypeID == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() return artifactTypeID == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(); || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID();
} }
/** /**

View File

@ -280,9 +280,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* @param dataSource The datasource. * @param dataSource The datasource.
* *
* @return A tuple where the first value is the latest web history accessed * @return A tuple where the first value is the latest web history accessed
* date in milliseconds and the second value maps normalized * date in milliseconds and the second value maps normalized (lowercase;
* (lowercase; trimmed) domain names to when those domains were * trimmed) domain names to when those domains were visited.
* visited.
* *
* @throws TskCoreException * @throws TskCoreException
* @throws SleuthkitCaseProviderException * @throws SleuthkitCaseProviderException
@ -364,8 +363,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* term. * term.
* *
* @param dataSource The data source. * @param dataSource The data source.
* @param count The maximum number of records to be shown (must be > * @param count The maximum number of records to be shown (must be > 0).
* 0).
* *
* @return The list of most recent web searches where most recent search * @return The list of most recent web searches where most recent search
* appears first. * appears first.
@ -386,21 +384,22 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
.getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId()); .getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId());
// group by search string (case insensitive) // group by search string (case insensitive)
Collection<List<TopWebSearchResult>> resultGroups = webSearchArtifacts Collection<TopWebSearchResult> resultGroups = webSearchArtifacts
.stream() .stream()
// get items where search string and date is not null // get items where search string and date is not null
.map(UserActivitySummary::getWebSearchResult) .map(UserActivitySummary::getWebSearchResult)
// remove null records // remove null records
.filter(result -> result != null) .filter(result -> result != null)
// get these messages grouped by search to string // get the latest message for each search string
.collect(Collectors.groupingBy((result) -> result.getSearchString().toUpperCase())) .collect(Collectors.toMap(
(result) -> result.getSearchString().toUpperCase(),
result -> result,
(result1, result2) -> TOP_WEBSEARCH_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2))
.values(); .values();
// get the most recent date for each search term // get the most recent date for each search term
List<TopWebSearchResult> results = resultGroups List<TopWebSearchResult> results = resultGroups
.stream() .stream()
// get the most recent access per search type
.map((list) -> list.stream().max(TOP_WEBSEARCH_RESULT_DATE_COMPARE).get())
// get most recent searches first // get most recent searches first
.sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed()) .sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed())
.limit(count) .limit(count)
@ -448,12 +447,31 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return translated; return translated;
} }
/**
* Gives the most recent TopDeviceAttachedResult. If one is null, the other
* is returned.
*
* @param r1 A result.
* @param r2 Another result.
* @return The most recent one with a non-null date.
*/
private TopDeviceAttachedResult getMostRecentDevice(TopDeviceAttachedResult r1, TopDeviceAttachedResult r2) {
if (r2.getDateAccessed() == null) {
return r1;
}
if (r1.getDateAccessed() == null) {
return r2;
}
return r1.getDateAccessed().compareTo(r2.getDateAccessed()) >= 0 ? r1 : r2;
}
/** /**
* Retrieves most recent devices used by most recent date attached. * Retrieves most recent devices used by most recent date attached.
* *
* @param dataSource The data source. * @param dataSource The data source.
* @param count The maximum number of records to be shown (must be > * @param count The maximum number of records to be shown (must be > 0).
* 0).
* *
* @return The list of most recent devices attached where most recent device * @return The list of most recent devices attached where most recent device
* attached appears first. * attached appears first.
@ -469,7 +487,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return Collections.emptyList(); return Collections.emptyList();
} }
return DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED, Collection<TopDeviceAttachedResult> results = DataSourceInfoUtilities.getArtifacts(caseProvider.get(), TYPE_DEVICE_ATTACHED,
dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0) dataSource, TYPE_DATETIME, DataSourceInfoUtilities.SortOrder.DESCENDING, 0)
.stream() .stream()
.map(artifact -> { .map(artifact -> {
@ -482,9 +500,14 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
}) })
// remove Root Hub identifier // remove Root Hub identifier
.filter(result -> { .filter(result -> {
return result.getDeviceModel() == null return result.getDeviceId() == null
|| result.getDeviceModel() == null
|| !DEVICE_EXCLUDE_LIST.contains(result.getDeviceModel().trim().toUpperCase()); || !DEVICE_EXCLUDE_LIST.contains(result.getDeviceModel().trim().toUpperCase());
}) })
.collect(Collectors.toMap(result -> result.getDeviceId(), result -> result, (r1, r2) -> getMostRecentDevice(r1, r2)))
.values();
return results.stream()
.limit(count) .limit(count)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -538,8 +561,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* sent. * sent.
* *
* @param dataSource The data source. * @param dataSource The data source.
* @param count The maximum number of records to be shown (must be > * @param count The maximum number of records to be shown (must be > 0).
* 0).
* *
* @return The list of most recent accounts used where the most recent * @return The list of most recent accounts used where the most recent
* account by last message sent occurs first. * account by last message sent occurs first.
@ -585,18 +607,19 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
Stream<TopAccountResult> allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults)); Stream<TopAccountResult> allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults));
// get them grouped by account type // get them grouped by account type
Collection<List<TopAccountResult>> groupedResults = allResults Collection<TopAccountResult> groupedResults = allResults
// remove null records // remove null records
.filter(result -> result != null) .filter(result -> result != null)
// get these messages grouped by account type // get these messages grouped by account type and get the most recent of each type
.collect(Collectors.groupingBy(TopAccountResult::getAccountType)) .collect(Collectors.toMap(
result -> result.getAccountType(),
result -> result,
(result1, result2) -> TOP_ACCOUNT_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2))
.values(); .values();
// get account type sorted by most recent date // get account type sorted by most recent date
return groupedResults return groupedResults
.stream() .stream()
// get the most recent access per account type
.map((accountGroup) -> accountGroup.stream().max(TOP_ACCOUNT_RESULT_DATE_COMPARE).get())
// get most recent accounts accessed // get most recent accounts accessed
.sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed()) .sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed())
// limit to count // limit to count
@ -721,7 +744,6 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return longNum != null && longNum > 0; return longNum != null && longNum > 0;
} }
/** /**
* Retrieves the top programs results for the given data source limited to * Retrieves the top programs results for the given data source limited to
* the count provided as a parameter. The highest run times are at the top * the count provided as a parameter. The highest run times are at the top
@ -732,8 +754,8 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
* *
* @param dataSource The datasource. If the datasource is null, an empty * @param dataSource The datasource. If the datasource is null, an empty
* list will be returned. * list will be returned.
* @param count The number of results to return. This value must be > 0 * @param count The number of results to return. This value must be > 0 or
* or an IllegalArgumentException will be thrown. * an IllegalArgumentException will be thrown.
* *
* @return The sorted list and limited to the count if last run or run count * @return The sorted list and limited to the count if last run or run count
* information is available on any item. * information is available on any item.
@ -759,7 +781,9 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
// The value will be a TopProgramsResult with the max run times // The value will be a TopProgramsResult with the max run times
// and most recent last run date for each program name / program path pair. // and most recent last run date for each program name / program path pair.
.collect(Collectors.toMap( .collect(Collectors.toMap(
res -> Pair.of(res.getProgramName(), res.getProgramPath()), res -> Pair.of(
res.getProgramName() == null ? null : res.getProgramName().toUpperCase(),
res.getProgramPath() == null ? null : res.getProgramPath().toUpperCase()),
res -> res, res -> res,
(res1, res2) -> { (res1, res2) -> {
return new TopProgramsResult( return new TopProgramsResult(

View File

@ -1,3 +1,4 @@
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
ILeappAnalyzerIngestModule.processing.file=Processing file {0} ILeappAnalyzerIngestModule.processing.file=Processing file {0}
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem

View File

@ -8,6 +8,7 @@ ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled
ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}.
ILeappAnalyzerIngestModule.processing.file=Processing file {0} ILeappAnalyzerIngestModule.processing.file=Processing file {0}
ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} ILeappAnalyzerIngestModule.parsing.file=Parsing file {0}
ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem
ILeappAnalyzerIngestModule.report.name=iLeapp Html Report ILeappAnalyzerIngestModule.report.name=iLeapp Html Report
ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows. ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows.
ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp

View File

@ -18,8 +18,10 @@
*/ */
package org.sleuthkit.autopsy.modules.ileappanalyzer; package org.sleuthkit.autopsy.modules.ileappanalyzer;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -32,14 +34,17 @@ import java.util.Locale;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.io.FilenameUtils;
import org.openide.modules.InstalledFileLocator; import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase; import static org.sleuthkit.autopsy.casemodule.Case.getCurrentCase;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.casemodule.services.FileManager;
import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.ExecUtil;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
@ -50,6 +55,7 @@ import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.LocalFilesDataSource;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -61,7 +67,9 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
private static final String MODULE_NAME = ILeappAnalyzerModuleFactory.getModuleName(); private static final String MODULE_NAME = ILeappAnalyzerModuleFactory.getModuleName();
private static final String ILEAPP = "iLeapp"; //NON-NLS private static final String ILEAPP = "iLeapp"; //NON-NLS
private static final String ILEAPP_FS = "fs_"; //NON-NLS
private static final String ILEAPP_EXECUTABLE = "ileapp.exe";//NON-NLS private static final String ILEAPP_EXECUTABLE = "ileapp.exe";//NON-NLS
private static final String ILEAPP_PATHS_FILE = "iLeapp_paths.txt"; //NON-NLS
private File iLeappExecutable; private File iLeappExecutable;
@ -87,7 +95,7 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
try { try {
iLeappFileProcessor = new ILeappFileProcessor(); iLeappFileProcessor = new ILeappFileProcessor();
} catch (IOException | IngestModuleException ex) { } catch (IOException | IngestModuleException | NoCurrentCaseException ex) {
throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex); throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex);
} }
@ -112,59 +120,50 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
@Override @Override
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
if (!(context.getDataSource() instanceof LocalFilesDataSource)) { Case currentCase = Case.getCurrentCase();
return ProcessResult.OK; Path tempOutputPath = Paths.get(currentCase.getTempDirectory(), ILEAPP, ILEAPP_FS + dataSource.getId());
try {
Files.createDirectories(tempOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", tempOutputPath.toString()), ex);
return ProcessResult.ERROR;
}
List<String> iLeappPathsToProcess = new ArrayList<>();
ProcessBuilder iLeappCommand = buildiLeappListCommand(tempOutputPath);
try {
int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
if (result != 0) {
logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result));
return ProcessResult.ERROR;
}
iLeappPathsToProcess = loadIleappPathFile(tempOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program getting file paths to search"), ex);
return ProcessResult.ERROR;
} }
statusHelper.progress(Bundle.ILeappAnalyzerIngestModule_starting_iLeapp(), 0); statusHelper.progress(Bundle.ILeappAnalyzerIngestModule_starting_iLeapp(), 0);
List<AbstractFile> iLeappFilesToProcess = findiLeappFilesToProcess(dataSource); List<AbstractFile> iLeappFilesToProcess = new ArrayList<>();
if (!(context.getDataSource() instanceof LocalFilesDataSource)) {
extractFilesFromImage(dataSource, iLeappPathsToProcess, tempOutputPath);
statusHelper.switchToDeterminate(iLeappFilesToProcess.size());
processILeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
} else {
iLeappFilesToProcess = findiLeappFilesToProcess(dataSource);
statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); statusHelper.switchToDeterminate(iLeappFilesToProcess.size());
Integer filesProcessedCount = 0; Integer filesProcessedCount = 0;
Case currentCase = Case.getCurrentCase();
for (AbstractFile iLeappFile : iLeappFilesToProcess) { for (AbstractFile iLeappFile : iLeappFilesToProcess) {
processILeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, iLeappFile);
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime);
try {
Files.createDirectories(moduleOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex);
return ProcessResult.ERROR;
}
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.file", iLeappFile.getName()), filesProcessedCount);
ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, iLeappFile.getLocalAbsPath(), iLeappFile.getNameExtension());
try {
int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
if (result != 0) {
// ignore if there is an error and continue to try and process the next file if there is one
continue;
}
addILeappReportToReports(moduleOutputPath, currentCase);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file %s", iLeappFile.getLocalAbsPath()), ex);
return ProcessResult.ERROR;
}
if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
return ProcessResult.OK;
}
ProcessResult fileProcessorResult = iLeappFileProcessor.processFiles(dataSource, moduleOutputPath, iLeappFile);
if (fileProcessorResult == ProcessResult.ERROR) {
return ProcessResult.ERROR;
}
filesProcessedCount++; filesProcessedCount++;
} }
// Process the logical image as a fs in iLeapp to make sure this is not a logical fs that was added
extractFilesFromImage(dataSource, iLeappPathsToProcess, tempOutputPath);
processILeappFs(dataSource, currentCase, statusHelper, tempOutputPath.toString());
}
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
Bundle.ILeappAnalyzerIngestModule_has_run(), Bundle.ILeappAnalyzerIngestModule_has_run(),
@ -173,6 +172,99 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
return ProcessResult.OK; return ProcessResult.OK;
} }
/**
* Process each tar/zip file that is found in a logical image that contains xLeapp data
* @param dataSource Datasource where the file has been found
* @param currentCase current case
* @param statusHelper Progress bar for messages to show user
* @param filesProcessedCount count that is incremented for progress bar
* @param iLeappFile abstract file that will be processed
*/
private void processILeappFile(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, int filesProcessedCount,
AbstractFile iLeappFile) {
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime);
try {
Files.createDirectories(moduleOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex);
return;
}
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.file", iLeappFile.getName()), filesProcessedCount);
ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, iLeappFile.getLocalAbsPath(), iLeappFile.getNameExtension());
try {
int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
if (result != 0) {
logger.log(Level.WARNING, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result));
return;
}
addILeappReportToReports(moduleOutputPath, currentCase);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file %s", iLeappFile.getLocalAbsPath()), ex);
return;
}
if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
return;
}
ProcessResult fileProcessorResult = iLeappFileProcessor.processFiles(dataSource, moduleOutputPath, iLeappFile);
if (fileProcessorResult == ProcessResult.ERROR) {
return;
}
}
/**
* Process extracted files from a disk image using xLeapp
* @param dataSource Datasource where the file has been found
* @param currentCase current case
* @param statusHelper Progress bar for messages to show user
* @param directoryToProcess
*/
private void processILeappFs(Content dataSource, Case currentCase, DataSourceIngestModuleProgress statusHelper, String directoryToProcess) {
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), ILEAPP, currentTime);
try {
Files.createDirectories(moduleOutputPath);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error creating iLeapp output directory %s", moduleOutputPath.toString()), ex);
return;
}
statusHelper.progress(NbBundle.getMessage(this.getClass(), "ILeappAnalyzerIngestModule.processing.filesystem"));
ProcessBuilder iLeappCommand = buildiLeappCommand(moduleOutputPath, directoryToProcess, "fs");
try {
int result = ExecUtil.execute(iLeappCommand, new DataSourceIngestModuleProcessTerminator(context, true));
if (result != 0) {
logger.log(Level.WARNING, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result));
return;
}
addILeappReportToReports(moduleOutputPath, currentCase);
} catch (IOException ex) {
logger.log(Level.SEVERE, String.format("Error when trying to execute iLeapp program against file system"), ex);
return;
}
if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
return;
}
ProcessResult fileProcessorResult = iLeappFileProcessor.processFileSystem(dataSource, moduleOutputPath);
if (fileProcessorResult == ProcessResult.ERROR) {
return;
}
}
/** /**
* Find the files that will be processed by the iLeapp program * Find the files that will be processed by the iLeapp program
* *
@ -208,6 +300,13 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
return iLeappFilesToProcess; return iLeappFilesToProcess;
} }
/**
* Build the command to run xLeapp
* @param moduleOutputPath output path for xLeapp
* @param sourceFilePath path where the xLeapp file is
* @param iLeappFileSystem type of file to process tar/zip/fs
* @return process to run
*/
private ProcessBuilder buildiLeappCommand(Path moduleOutputPath, String sourceFilePath, String iLeappFileSystemType) { private ProcessBuilder buildiLeappCommand(Path moduleOutputPath, String sourceFilePath, String iLeappFileSystemType) {
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
@ -221,10 +320,26 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
return processBuilder; return processBuilder;
} }
/**
* Command to run xLeapp using the path option
* @param moduleOutputPath path where the file paths output will reside
* @return process to run
*/
private ProcessBuilder buildiLeappListCommand(Path moduleOutputPath) {
ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
"\"" + iLeappExecutable + "\"", //NON-NLS
"-p"
);
processBuilder.redirectError(moduleOutputPath.resolve("iLeapp_paths_error.txt").toFile()); //NON-NLS
processBuilder.redirectOutput(moduleOutputPath.resolve("iLeapp_paths.txt").toFile()); //NON-NLS
return processBuilder;
}
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) { static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
ProcessBuilder processBuilder = new ProcessBuilder(commandLine); ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
/* /*
* Add an environment variable to force log2timeline/psort to run with * Add an environment variable to force iLeapp to run with
* the same permissions Autopsy uses. * the same permissions Autopsy uses.
*/ */
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
@ -254,8 +369,13 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
.filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList()); .filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList());
if (!allIndexFiles.isEmpty()) { if (!allIndexFiles.isEmpty()) {
// Check for existance of directory that holds report data if does not exist then report contains no data
String filePath = FilenameUtils.getFullPathNoEndSeparator(allIndexFiles.get(0));
File dataFilesDir = new File(Paths.get(filePath, "_TSV Exports").toString());
if (dataFilesDir.exists()) {
currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ILeappAnalyzerIngestModule_report_name()); currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ILeappAnalyzerIngestModule_report_name());
} }
}
} catch (IOException | UncheckedIOException | TskCoreException ex) { } catch (IOException | UncheckedIOException | TskCoreException ex) {
// catch the error and continue on as report is not added // catch the error and continue on as report is not added
@ -264,4 +384,129 @@ public class ILeappAnalyzerIngestModule implements DataSourceIngestModule {
} }
/*
* Reads the iLeapp paths file to get the paths that we want to extract
*
* @param moduleOutputPath path where the file paths output will reside
*/
private List<String> loadIleappPathFile(Path moduleOutputPath) throws FileNotFoundException, IOException {
List<String> iLeappPathsToProcess = new ArrayList<>();
Path filePath = Paths.get(moduleOutputPath.toString(), ILEAPP_PATHS_FILE);
try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) {
String line = reader.readLine();
while (line != null) {
if (line.contains("path list generation") || line.length() < 2) {
line = reader.readLine();
continue;
}
iLeappPathsToProcess.add(line.trim());
line = reader.readLine();
}
}
return iLeappPathsToProcess;
}
/**
* Extract files from a disk image to process with xLeapp
* @param dataSource Datasource of the image
* @param iLeappPathsToProcess List of paths to extract content from
* @param moduleOutputPath path to write content to
*/
private void extractFilesFromImage(Content dataSource, List<String> iLeappPathsToProcess, Path moduleOutputPath) {
FileManager fileManager = getCurrentCase().getServices().getFileManager();
for (String fullFilePath : iLeappPathsToProcess) {
if (context.dataSourceIngestIsCancelled()) {
logger.log(Level.INFO, "ILeapp Analyser ingest module run was canceled"); //NON-NLS
break;
}
String ffp = fullFilePath.replaceAll("\\*", "%");
ffp = FilenameUtils.normalize(ffp, true);
String fileName = FilenameUtils.getName(ffp);
String filePath = FilenameUtils.getPath(ffp);
List<AbstractFile> iLeappFiles = new ArrayList<>();
try {
if (filePath.isEmpty()) {
iLeappFiles = fileManager.findFiles(dataSource, fileName); //NON-NLS
} else {
iLeappFiles = fileManager.findFiles(dataSource, fileName, filePath); //NON-NLS
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "No files found to process"); //NON-NLS
return;
}
for (AbstractFile iLeappFile : iLeappFiles) {
Path parentPath = Paths.get(moduleOutputPath.toString(), iLeappFile.getParentPath());
File fileParentPath = new File(parentPath.toString());
extractFileToOutput(dataSource, iLeappFile, fileParentPath, parentPath);
}
}
}
/**
* Create path and file from datasource in temp
* @param dataSource datasource of the image
* @param iLeappFile abstract file to write out
* @param fileParentPath parent file path
* @param parentPath parent file
*/
private void extractFileToOutput(Content dataSource, AbstractFile iLeappFile, File fileParentPath, Path parentPath) {
if (fileParentPath.exists()) {
if (!iLeappFile.isDir()) {
writeiLeappFile(dataSource, iLeappFile, fileParentPath.toString());
} else {
try {
Files.createDirectories(Paths.get(parentPath.toString(), iLeappFile.getName()));
} catch (IOException ex) {
logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex);
}
}
} else {
try {
Files.createDirectories(parentPath);
} catch (IOException ex) {
logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex);
}
if (!iLeappFile.isDir()) {
writeiLeappFile(dataSource, iLeappFile, fileParentPath.toString());
} else {
try {
Files.createDirectories(Paths.get(parentPath.toString(), iLeappFile.getName()));
} catch (IOException ex) {
logger.log(Level.INFO, String.format("Error creating iLeapp output directory %s", parentPath.toString()), ex);
}
}
}
}
/**
* Write out file to output
* @param dataSource datasource of disk image
* @param iLeappFile acstract file to write out
* @param parentPath path to write file to
*/
private void writeiLeappFile(Content dataSource, AbstractFile iLeappFile, String parentPath) {
String fileName = iLeappFile.getName().replace(":", "-");
if (!fileName.matches(".") && !fileName.matches("..") && !fileName.toLowerCase().endsWith("-slack")) {
Path filePath = Paths.get(parentPath, fileName);
File localFile = new File(filePath.toString());
try {
ContentUtils.writeToFile(iLeappFile, localFile, context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).",
iLeappFile.getName(), iLeappFile.getId()), ex); //NON-NLS
} catch (IOException ex) {
logger.log(Level.WARNING, String.format("Error writing file local file '%s' (id=%d).",
filePath.toString(), iLeappFile.getId()), ex); //NON-NLS
}
}
}
} }

View File

@ -44,6 +44,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
@ -76,12 +77,16 @@ public final class ILeappFileProcessor {
private final Map<String, String> tsvFileArtifactComments; private final Map<String, String> tsvFileArtifactComments;
private final Map<String, List<List<String>>> tsvFileAttributes; private final Map<String, List<List<String>>> tsvFileAttributes;
public ILeappFileProcessor() throws IOException, IngestModuleException { Blackboard blkBoard;
public ILeappFileProcessor() throws IOException, IngestModuleException, NoCurrentCaseException {
this.tsvFiles = new HashMap<>(); this.tsvFiles = new HashMap<>();
this.tsvFileArtifacts = new HashMap<>(); this.tsvFileArtifacts = new HashMap<>();
this.tsvFileArtifactComments = new HashMap<>(); this.tsvFileArtifactComments = new HashMap<>();
this.tsvFileAttributes = new HashMap<>(); this.tsvFileAttributes = new HashMap<>();
blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
configExtractor(); configExtractor();
loadConfigFile(); loadConfigFile();
@ -110,6 +115,19 @@ public final class ILeappFileProcessor {
return ProcessResult.OK; return ProcessResult.OK;
} }
public ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath) {
try {
List<String> iLeappTsvOutputFiles = findTsvFiles(moduleOutputPath);
processiLeappFiles(iLeappTsvOutputFiles, dataSource);
} catch (IOException | IngestModuleException ex) {
logger.log(Level.SEVERE, String.format("Error trying to process iLeapp output files in directory %s. ", moduleOutputPath.toString()), ex); //NON-NLS
return ProcessResult.ERROR;
}
return ProcessResult.OK;
}
/** /**
* Find the tsv files in the iLeapp output directory and match them to files * Find the tsv files in the iLeapp output directory and match them to files
* we know we want to process and return the list to process those files. * we know we want to process and return the list to process those files.
@ -124,7 +142,7 @@ public final class ILeappFileProcessor {
.filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList()); .filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList());
for (String tsvFile : allTsvFiles) { for (String tsvFile : allTsvFiles) {
if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile))) { if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile.toLowerCase()))) {
foundTsvFiles.add(tsvFile); foundTsvFiles.add(tsvFile);
} }
} }
@ -160,7 +178,41 @@ public final class ILeappFileProcessor {
processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, iLeappImageFile); processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, iLeappImageFile);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
// check this throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex);
}
}
}
if (!bbartifacts.isEmpty()) {
postArtifacts(bbartifacts);
}
}
/**
* Process the iLeapp files that were found that match the xml mapping file
*
* @param iLeappFilesToProcess List of files to process
* @param iLeappImageFile Abstract file to create artifact for
*
* @throws FileNotFoundException
* @throws IOException
*/
private void processiLeappFiles(List<String> iLeappFilesToProcess, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException {
List<BlackboardArtifact> bbartifacts = new ArrayList<>();
for (String iLeappFileName : iLeappFilesToProcess) {
String fileName = FilenameUtils.getName(iLeappFileName);
File iLeappFile = new File(iLeappFileName);
if (tsvFileAttributes.containsKey(fileName)) {
List<List<String>> attrList = tsvFileAttributes.get(fileName);
try {
BlackboardArtifact.Type artifactType = Case.getCurrentCase().getSleuthkitCase().getArtifactType(tsvFileArtifacts.get(fileName));
processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, dataSource);
} catch (TskCoreException ex) {
throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex); throw new IngestModuleException(String.format("Error getting Blackboard Artifact Type for %s", tsvFileArtifacts.get(fileName)), ex);
} }
} }
@ -174,7 +226,8 @@ public final class ILeappFileProcessor {
} }
private void processFile(File iLeappFile, List<List<String>> attrList, String fileName, BlackboardArtifact.Type artifactType, private void processFile(File iLeappFile, List<List<String>> attrList, String fileName, BlackboardArtifact.Type artifactType,
List<BlackboardArtifact> bbartifacts, AbstractFile iLeappImageFile) throws FileNotFoundException, IOException, IngestModuleException { List<BlackboardArtifact> bbartifacts, Content dataSource) throws FileNotFoundException, IOException, IngestModuleException,
TskCoreException {
try (BufferedReader reader = new BufferedReader(new FileReader(iLeappFile))) { try (BufferedReader reader = new BufferedReader(new FileReader(iLeappFile))) {
String line = reader.readLine(); String line = reader.readLine();
// Check first line, if it is null then no heading so nothing to match to, close and go to next file. // Check first line, if it is null then no heading so nothing to match to, close and go to next file.
@ -183,8 +236,8 @@ public final class ILeappFileProcessor {
line = reader.readLine(); line = reader.readLine();
while (line != null) { while (line != null) {
Collection<BlackboardAttribute> bbattributes = processReadLine(line, columnNumberToProcess, fileName); Collection<BlackboardAttribute> bbattributes = processReadLine(line, columnNumberToProcess, fileName);
if (!bbattributes.isEmpty()) { if (!bbattributes.isEmpty() && !blkBoard.artifactExists(dataSource, BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactType.getTypeID()), bbattributes)) {
BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), iLeappImageFile, bbattributes); BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes);
if (bbartifact != null) { if (bbartifact != null) {
bbartifacts.add(bbartifact); bbartifacts.add(bbartifact);
} }
@ -340,7 +393,7 @@ public final class ILeappFileProcessor {
for (int i = 0; i < nlist.getLength(); i++) { for (int i = 0; i < nlist.getLength(); i++) {
NamedNodeMap nnm = nlist.item(i).getAttributes(); NamedNodeMap nnm = nlist.item(i).getAttributes();
tsvFiles.put(nnm.getNamedItem("filename").getNodeValue(), nnm.getNamedItem("description").getNodeValue()); tsvFiles.put(nnm.getNamedItem("filename").getNodeValue().toLowerCase(), nnm.getNamedItem("description").getNodeValue());
} }
@ -393,16 +446,17 @@ public final class ILeappFileProcessor {
} }
} }
/** /**
* Generic method for creating a blackboard artifact with attributes * Generic method for creating a blackboard artifact with attributes
* *
* @param type is a blackboard.artifact_type enum to determine * @param type is a blackboard.artifact_type enum to determine which
* which type the artifact should be * type the artifact should be
* @param abstractFile is the AbstractFile object that needs to have the * @param abstractFile is the AbstractFile object that needs to have the
* artifact added for it * artifact added for it
* @param bbattributes is the collection of blackboard attributes that * @param bbattributes is the collection of blackboard attributes that need
* need to be added to the artifact after the * to be added to the artifact after the artifact has
* artifact has been created * been created
* *
* @return The newly-created artifact, or null on error * @return The newly-created artifact, or null on error
*/ */
@ -417,6 +471,30 @@ public final class ILeappFileProcessor {
return null; return null;
} }
/**
* Generic method for creating a blackboard artifact with attributes
*
* @param type is a blackboard.artifact_type enum to determine which
* type the artifact should be
* @param datasource is the Content object that needs to have the artifact
* added for it
* @param bbattributes is the collection of blackboard attributes that need
* to be added to the artifact after the artifact has
* been created
*
* @return The newly-created artifact, or null on error
*/
private BlackboardArtifact createArtifactWithAttributes(int type, Content dataSource, Collection<BlackboardAttribute> bbattributes) {
try {
BlackboardArtifact bbart = dataSource.newArtifact(type);
bbart.addAttributes(bbattributes);
return bbart;
} catch (TskException ex) {
logger.log(Level.WARNING, Bundle.ILeappFileProcessor_error_creating_new_artifacts(), ex); //NON-NLS
}
return null;
}
/** /**
* Method to post a list of BlackboardArtifacts to the blackboard. * Method to post a list of BlackboardArtifacts to the blackboard.
* *

View File

@ -47,6 +47,15 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="App Snapshots.tsv" description="App Snapshots (screenshots)">
<ArtifactName artifactname="TSK_SCREEN_SHOTS" comment="null">
<AttributeName attributename="TSK_PROG_NAME" columnName="App Name" required="yes" />
<AttributeName attributename="TSK_PATH" columnName="SOurce Path" required="yes" />
<AttributeName attributename="TSK_DATETIME" columnName="Date Modified" required="yes" />
<AttributeName attributename="null" columnName="Source File Located" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Bluetooth Other.tsv" description="Bluetooth Other"> <FileName filename="Bluetooth Other.tsv" description="Bluetooth Other">
<ArtifactName artifactname="TSK_BLUETOOTH_ADAPTER" comment="Bluetooth Other"> <ArtifactName artifactname="TSK_BLUETOOTH_ADAPTER" comment="Bluetooth Other">
<AttributeName attributename="TSK_NAME" columnName="Name" required="yes" /> <AttributeName attributename="TSK_NAME" columnName="Name" required="yes" />
@ -120,6 +129,13 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="DHCP Received List.tsv" description="DHCP Received List" >
<ArtifactName artifactname="TSK_IP_DHCP" comment="null">
<AttributeName attributename="TSK_NAME" columnName="Key" required="yes" />
<AttributeName attributename="TSK_VALUE" columnName="Value" required="yes" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC App Activity.tsv" description="KnowledgeC App Activity"> <FileName filename="KnowledgeC App Activity.tsv" description="KnowledgeC App Activity">
<ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Activity"> <ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Activity">
<AttributeName attributename="TSK_DATETIME" columnName="Entry Creation" required="yes" /> <AttributeName attributename="TSK_DATETIME" columnName="Entry Creation" required="yes" />
@ -189,6 +205,36 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="KnowledgeC Device is Backlit.tsv" description="KnowledgeC Device is Backlit">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Device Backlit">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Screen is Backlit" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Start" required="no" />
<AttributeName attributename="null" columnName="End" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="ZOBJECT Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Battery Level.tsv" description="KnowledgeC Battery Level">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Battery Level">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Battery Level" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Day of the Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName=" ZOBJECT Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Bluetooth Connections.tsv" description="KnowledgeC Bluetooth Connections"> <FileName filename="KnowledgeC Bluetooth Connections.tsv" description="KnowledgeC Bluetooth Connections">
<ArtifactName artifactname="TSK_BLUETOOTH_PAIRING" comment="KnowledgeC Bluetooth Connections"> <ArtifactName artifactname="TSK_BLUETOOTH_PAIRING" comment="KnowledgeC Bluetooth Connections">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" /> <AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
@ -207,15 +253,61 @@
<FileName filename="KnowledgeC Car Play Connections.tsv" description="KnowledgeC Car Play Connections"> <FileName filename="KnowledgeC Car Play Connections.tsv" description="KnowledgeC Car Play Connections">
<ArtifactName artifactname="TSK_DEVICE_INFO" comment="KnowledgeC Car Play Connections"> <ArtifactName artifactname="TSK_DEVICE_INFO" comment="KnowledgeC Car Play Connections">
<AttributeName attributename="TSK_DATETIME" columnName="Start" required="no" /> <AttributeName attributename="TSK_DATETIME" columnName="Start" required="yes" />
<AttributeName attributename="null" columnName="End" required="no" /> <AttributeName attributename="null" columnName="End" required="no" />
<AttributeName attributename="null" columnName="Car Play Connected" required="no" /> <AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Car Play Connected" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" /> <AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" /> <AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" /> <AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" /> <AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" /> <AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="TSK_DEVICE_ID" columnName="UUID" required="no" /> <AttributeName attributename="TSK_DEVICE_ID" columnName="UUID" required="yes" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Disk Subsystem Access.tsv" description="KnowledgeC Disk Subsystem Access">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="disk Subsystem">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="Bundle ID" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Value String" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Do Not Disturb.tsv" description="KnowledgeC Do Not Disturb">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Do Not Disturb">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Value" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Inferred Motion.tsv" description="KnowledgeC Inferred Motion">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Inferred Motion">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Value" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" /> <AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName> </ArtifactName>
</FileName> </FileName>
@ -248,6 +340,19 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="KnowledgeC Device Locked.tsv" description="KnowledgeC Device Locked">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Device Locked">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Is Locked?" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Day of the Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName=" ZOBJECT Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Media Playing.tsv" description="KnowledgeC Media Playing"> <FileName filename="Media Playing.tsv" description="KnowledgeC Media Playing">
<ArtifactName artifactname="TSK_RECENT_OBJ" comment="KnowledgeC Media Playing"> <ArtifactName artifactname="TSK_RECENT_OBJ" comment="KnowledgeC Media Playing">
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" /> <AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" />
@ -288,6 +393,36 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="KnowledgeC Screen Orientation.tsv" description="KnowledgeC Screen Orientation">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Screen Orientation">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Orientation" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Usage in Minutes" required="no" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Plugged In.tsv" description="KnowledgeC Plugged In">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Plugged In">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Is Plugged In?" required="yes" />
<AttributeName attributename="null" columnName="Usage in Seconds" required="no" />
<AttributeName attributename="null" columnName="Day of the Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Start" required="no" />
<AttributeName attributename="null" columnName="End" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName=" ZOBJECT Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Safari Browsing.tsv" description="KnowledgeC Safari Browsing"> <FileName filename="KnowledgeC Safari Browsing.tsv" description="KnowledgeC Safari Browsing">
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Safari Browsing"> <ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Safari Browsing">
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" /> <AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" />
@ -302,6 +437,18 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="KnowledgeC Siri Usage.tsv" description="KnowledgeC Siri Usage">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Siri Usage">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="App Name" required="yes" />
<AttributeName attributename="null" columnName="Weekday" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="ZOBJECT Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC App Usage.tsv" description="KnowledgeC App Usage"> <FileName filename="KnowledgeC App Usage.tsv" description="KnowledgeC App Usage">
<ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Usage"> <ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Usage">
<AttributeName attributename="TSK_DATETIME" columnName="Start" required="yes" /> <AttributeName attributename="TSK_DATETIME" columnName="Start" required="yes" />
@ -318,6 +465,18 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
<FileName filename="KnowledgeC User Waking Events.tsv" description="KnowledgeC User Waking Event">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="User Waking">
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="null" columnName="Day of Week" required="no" />
<AttributeName attributename="null" columnName="GMT Offset" required="no" />
<AttributeName attributename="null" columnName="Entry Creation" required="no" />
<AttributeName attributename="null" columnName="UUID" required="no" />
<AttributeName attributename="null" columnName="Zobject Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="KnowledgeC Web Usage.tsv" description="KnowledgeC Web Usage"> <FileName filename="KnowledgeC Web Usage.tsv" description="KnowledgeC Web Usage">
<ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Web Usage"> <ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Web Usage">
<AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" /> <AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" />
@ -433,6 +592,102 @@
</ArtifactName> </ArtifactName>
</FileName> </FileName>
--> -->
<FileName filename="Notifications.tsv" description="iOS Notificatons">
<ArtifactName artifactname="TSK_PROG_NOTIFICATIONS" comment="iOS Notificatons">
<AttributeName attributename="TSK_DATETIME" columnName="Creation Time" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName=" Bundle" required="yes" />
<AttributeName attributename="TSK_TITLE" columnName=" Title[Subtitle]" required="yes" />
<AttributeName attributename="TSK_VALUE" columnName=" Message" required="yes" />
<AttributeName attributename="null" columnName=" Other Details" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Agg Bulletins.tsv" description="Powerlog Aggregate Bulletins">
<ArtifactName artifactname="TSK_PROG_NOTIFICATIONS" comment="Powerlog Aggregate Bulletins">
<AttributeName attributename="TSK_DATETIME" columnName="Timestamp" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="Bulletin Bundle ID" required="yes" />
<AttributeName attributename="null" columnName="Time Interval in Seconds" required="no" />
<AttributeName attributename="null" columnName="Count" required="no" />
<AttributeName attributename="null" columnName="Post Type" required="no" />
<AttributeName attributename="null" columnName="Aggregate Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Agg Notifications.tsv" description="Powerlog Aggregate Notifications">
<ArtifactName artifactname="TSK_PROG_NOTIFICATIONS" comment="Powerlog Aggregate Notifications">
<AttributeName attributename="TSK_DATETIME" columnName="Timestamp" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="Notification Bundle ID" required="yes" />
<AttributeName attributename="null" columnName="Time Interval in Seconds" required="no" />
<AttributeName attributename="null" columnName="Count" required="no" />
<AttributeName attributename="null" columnName="Notification Type" required="no" />
<AttributeName attributename="null" columnName="Aggregate Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Backup Info.tsv" description="Powerlog Backup Info">
<ArtifactName artifactname="TSK_BACKUP_EVENT" comment="null">
<AttributeName attributename="TSK_DATETIME" columnName="Timestamp" required="yes" />
<AttributeName attributename="TSK_DATETIME_START" columnName="Start" required="yes" />
<AttributeName attributename="TSK_DATETIME_END" columnName="End" required="yes" />
<AttributeName attributename="null" columnName="State" required="no" />
<AttributeName attributename="null" columnName="Finished" required="no" />
<AttributeName attributename="null" columnName="Has error" required="no" />
<AttributeName attributename="null" columnName="Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Deleted Apps.tsv" description="Powerlog Deleted Apps">
<ArtifactName artifactname="TSK_DELETED_PROG" comment="Powerlog Deleted Apps">
<AttributeName attributename="TSK_DATETIME_DELETED" columnName="App Deleted Date" required="yes" />
<AttributeName attributename="TSK_DATETIME" columnName="Timestamp" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="App Name" required="yes" />
<AttributeName attributename="null" columnName="App Executable Name" required="no" />
<AttributeName attributename="TSK_PATH" columnName="Bundle ID" required="yes" />
<AttributeName attributename="null" columnName="Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Lightning Connector.tsv" description="Powerlog Lightning Connector Status">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Powerlog Lightning Connector Status">
<AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Accesory Power Mode" required="yes" />
<AttributeName attributename="null" columnName="Original Lightnint Connector Timestamp" required="no" />
<AttributeName attributename="null" columnName="Offset Timestamp" required="no" />
<AttributeName attributename="null" columnName="Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Push Message Received.tsv" description="Powerlog Push Message Received">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Powerlog Push Message Received">
<AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" />
<AttributeName attributename="TSK_PROG_NAME" columnName="Bundle ID" required="yes" />
<AttributeName attributename="TSK_VALUE" columnName="Connection Type" required="yes" />
<AttributeName attributename="null" columnName="Is Dropped" required="no" />
<AttributeName attributename="null" columnName="Link Quality" required="no" />
<AttributeName attributename="null" columnName="Priority" required="no" />
<AttributeName attributename="null" columnName="Topic" required="no" />
<AttributeName attributename="null" columnName="Server Hostname" required="no" />
<AttributeName attributename="null" columnName="Server IP" required="no" />
<AttributeName attributename="null" columnName="Original Timestamp" required="no" />
<AttributeName attributename="null" columnName="Offset Timestamp" required="no" />
<AttributeName attributename="null" columnName="Time Offset" required="no" />
<AttributeName attributename="null" columnName="Aggregate Table ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Torch.tsv" description="Powerlog Torch">
<ArtifactName artifactname="TSK_USER_DEVICE_EVENT" comment="Powerlog Torch">
<AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" />
<AttributeName attributename="null" columnName="Bundle ID" required="no" />
<AttributeName attributename="TSK_USER_DEVICE_EVENT_TYPE" columnName="Status" required="yes" />
<AttributeName attributename="null" columnName="Original Torch Timestamp" required="no" />
<AttributeName attributename="null" columnName="Offset Timestamp" required="no" />
<AttributeName attributename="null" columnName="Time Offset" required="no" />
<AttributeName attributename="null" columnName="Torch ID" required="no" />
</ArtifactName>
</FileName>
<FileName filename="Powerlog Wifi Network Connections.tsv" description="Powerlog WiFi Network Connections"> <FileName filename="Powerlog Wifi Network Connections.tsv" description="Powerlog WiFi Network Connections">
<ArtifactName artifactname="TSK_WIFI_NETWORK" comment="Powerlog WiFi Network Connections"> <ArtifactName artifactname="TSK_WIFI_NETWORK" comment="Powerlog WiFi Network Connections">
<AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" /> <AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" />

View File

@ -292,6 +292,26 @@ public class RecentFilesSummaryTest {
} }
} }
@Test
public void getRecentlyOpenedDocuments_uniquePaths() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact item1 = getRecentDocumentArtifact(dataSource, 1001, DAY_SECONDS, "/a/path");
BlackboardArtifact item2 = getRecentDocumentArtifact(dataSource, 1002, DAY_SECONDS + 1, "/a/path");
BlackboardArtifact item3 = getRecentDocumentArtifact(dataSource, 1003, DAY_SECONDS + 2, "/a/path");
List<BlackboardArtifact> artifacts = Arrays.asList(item2, item3, item1);
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentFileDetails> results = summary.getRecentlyOpenedDocuments(dataSource, 10);
// verify results (only successItem)
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
Assert.assertEquals((Long) (DAY_SECONDS + 2), results.get(0).getDateAsLong());
Assert.assertTrue("/a/path".equalsIgnoreCase(results.get(0).getPath()));
}
@Test @Test
public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1); DataSource dataSource = TskMockUtils.getDataSource(1);
@ -368,6 +388,30 @@ public class RecentFilesSummaryTest {
} }
} }
@Test
public void getRecentDownloads_uniquePaths() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact item1 = getRecentDownloadArtifact(dataSource, 1001, DAY_SECONDS, "domain1.com", "/a/path1");
BlackboardArtifact item1a = getRecentDownloadArtifact(dataSource, 10011, DAY_SECONDS + 1, "domain1.com", "/a/path1");
BlackboardArtifact item2 = getRecentDownloadArtifact(dataSource, 1002, DAY_SECONDS + 2, "domain2.com", "/a/path1");
BlackboardArtifact item3 = getRecentDownloadArtifact(dataSource, 1003, DAY_SECONDS + 3, "domain2a.com", "/a/path1");
List<BlackboardArtifact> artifacts = Arrays.asList(item2, item3, item1);
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// call method
List<RecentDownloadDetails> results = summary.getRecentDownloads(dataSource, 10);
// verify results
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
Assert.assertEquals((Long) (DAY_SECONDS + 3), results.get(0).getDateAsLong());
Assert.assertTrue("/a/path1".equalsIgnoreCase(results.get(0).getPath()));
Assert.assertTrue("domain2a.com".equalsIgnoreCase(results.get(0).getWebDomain()));
}
@Test @Test
public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1); DataSource dataSource = TskMockUtils.getDataSource(1);
@ -410,16 +454,14 @@ public class RecentFilesSummaryTest {
* *
* @param messageArtifactTypeId The type id for the artifact or null if * @param messageArtifactTypeId The type id for the artifact or null if
* no message artifact to be created. * no message artifact to be created.
* @param emailFrom Who the message is from or null not to * @param emailFrom Who the message is from or null not to include
* include attribute. * attribute.
* @param messageTime Time in seconds from epoch or null not * @param messageTime Time in seconds from epoch or null not to include
* to include attribute. * attribute.
* @param fileParentPath The parent AbstractFile's path value. * @param fileParentPath The parent AbstractFile's path value.
* @param fileName The parent AbstractFile's filename * @param fileName The parent AbstractFile's filename value.
* value.
* @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT * @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT
* artifact has no attribute (even though * artifact has no attribute (even though it is required).
* it is required).
* @param hasParent Whether or not the artifact has a parent * @param hasParent Whether or not the artifact has a parent
* AbstractFile. * AbstractFile.
*/ */
@ -442,13 +484,12 @@ public class RecentFilesSummaryTest {
* *
* @param messageArtifactTypeId The type id for the artifact or null if * @param messageArtifactTypeId The type id for the artifact or null if
* no message artifact to be created. * no message artifact to be created.
* @param emailFrom Who the message is from or null not to * @param emailFrom Who the message is from or null not to include
* include attribute. * attribute.
* @param messageTime Time in seconds from epoch or null not * @param messageTime Time in seconds from epoch or null not to include
* to include attribute. * attribute.
* @param fileParentPath The parent AbstractFile's path value. * @param fileParentPath The parent AbstractFile's path value.
* @param fileName The parent AbstractFile's filename * @param fileName The parent AbstractFile's filename value.
* value.
*/ */
AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) { AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) {
this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true); this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true);
@ -678,4 +719,34 @@ public class RecentFilesSummaryTest {
.toString().equalsIgnoreCase(successItem2Details.getPath())); .toString().equalsIgnoreCase(successItem2Details.getPath()));
Assert.assertTrue(successItem2.getEmailFrom().equalsIgnoreCase(successItem2Details.getSender())); Assert.assertTrue(successItem2.getEmailFrom().equalsIgnoreCase(successItem2Details.getSender()));
} }
@Test
public void getRecentAttachments_uniquePath() throws SleuthkitCaseProviderException, TskCoreException {
// setup data
DataSource dataSource = TskMockUtils.getDataSource(1);
AttachmentArtifactItem item1 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person@sleuthkit.com", DAY_SECONDS, "/parent/path", "msg.pdf");
AttachmentArtifactItem item2 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
"person_on_skype", DAY_SECONDS + 1, "/parent/path", "msg.pdf");
AttachmentArtifactItem item3 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person2@sleuthkit.com", DAY_SECONDS + 2, "/parent/path", "msg.pdf");
List<AttachmentArtifactItem> items = Arrays.asList(item1, item2, item3);
Pair<SleuthkitCase, Blackboard> casePair = getRecentAttachmentArtifactCase(items);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// get data
List<RecentAttachmentDetails> results = summary.getRecentAttachments(dataSource, 10);
// verify results
Assert.assertNotNull(results);
Assert.assertEquals(1, results.size());
Assert.assertEquals(results.get(0).getDateAsLong(), (Long) (DAY_SECONDS + 2));
Assert.assertTrue(Paths.get(item3.getFileParentPath(), item3.getFileName())
.toString().equalsIgnoreCase(results.get(0).getPath()));
Assert.assertTrue(results.get(0).getSender().equalsIgnoreCase(item3.getEmailFrom()));
}
} }

View File

@ -66,12 +66,16 @@ import org.sleuthkit.datamodel.TskCoreException;
* Tests for UserActivitySummary. * Tests for UserActivitySummary.
*/ */
public class UserActivitySummaryTest { public class UserActivitySummaryTest {
/** /**
* Function to retrieve data from UserActivitySummary with the provided arguments. * Function to retrieve data from UserActivitySummary with the provided
* arguments.
*/ */
private interface DataFunction<T> { private interface DataFunction<T> {
/** /**
* A UserActivitySummary method encapsulated in a uniform manner. * A UserActivitySummary method encapsulated in a uniform manner.
*
* @param userActivitySummary The UserActivitySummary class to use. * @param userActivitySummary The UserActivitySummary class to use.
* @param datasource The data source. * @param datasource The data source.
* @param count The count. * @param count The count.
@ -333,6 +337,28 @@ public class UserActivitySummaryTest {
} }
} }
@Test
public void getRecentDevices_uniqueByDeviceId()
throws TskCoreException, NoServiceProviderException, SleuthkitCaseProviderException, TskCoreException, TranslationException {
long dataSourceId = 1L;
DataSource dataSource = TskMockUtils.getDataSource(dataSourceId);
BlackboardArtifact item1 = getRecentDeviceArtifact(1001, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS);
BlackboardArtifact item2 = getRecentDeviceArtifact(1002, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS + 1);
BlackboardArtifact item3 = getRecentDeviceArtifact(1003, dataSource, "ID1", "MAKE1", "MODEL1", DAY_SECONDS + 2);
Pair<SleuthkitCase, Blackboard> tskPair = getArtifactsTSKMock(Arrays.asList(item1, item2, item3));
UserActivitySummary summary = getTestClass(tskPair.getLeft(), false, null);
List<TopDeviceAttachedResult> results = summary.getRecentDevices(dataSource, 10);
Assert.assertEquals(1, results.size());
Assert.assertEquals((long) (DAY_SECONDS + 2), results.get(0).getDateAccessed().getTime() / 1000);
Assert.assertTrue("ID1".equalsIgnoreCase(results.get(0).getDeviceId()));
Assert.assertTrue("MAKE1".equalsIgnoreCase(results.get(0).getDeviceMake()));
Assert.assertTrue("MODEL1".equalsIgnoreCase(results.get(0).getDeviceModel()));
}
private static BlackboardArtifact getWebSearchArtifact(long artifactId, DataSource dataSource, String query, Long date) { private static BlackboardArtifact getWebSearchArtifact(long artifactId, DataSource dataSource, String query, Long date) {
try { try {
return TskMockUtils.getArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY), artifactId, dataSource, return TskMockUtils.getArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY), artifactId, dataSource,
@ -797,8 +823,8 @@ public class UserActivitySummaryTest {
* @param dataSource The datasource to use as parameter. * @param dataSource The datasource to use as parameter.
* @param count The count to use as a parameter. * @param count The count to use as a parameter.
* @param retArtifacts The artifacts to return from * @param retArtifacts The artifacts to return from
* SleuthkitCase.getArtifacts. This method filters * SleuthkitCase.getArtifacts. This method filters based on artifact type
* based on artifact type from the call. * from the call.
* @param expectedResults The expected results. * @param expectedResults The expected results.
* *
* @throws TskCoreException * @throws TskCoreException

View File

@ -76,6 +76,10 @@
<sysproperty key="solrPort" value="${solrPort}"/> <sysproperty key="solrPort" value="${solrPort}"/>
<sysproperty key="messageServiceHost" value="${messageServiceHost}"/> <sysproperty key="messageServiceHost" value="${messageServiceHost}"/>
<sysproperty key="messageServicePort" value="${messageServicePort}"/> <sysproperty key="messageServicePort" value="${messageServicePort}"/>
<sysproperty key="crHost" value="${crHost}"/>
<sysproperty key="crPort" value="${crPort}"/>
<sysproperty key="crUserName" value="${crUserName}"/>
<sysproperty key="crPassword" value="${crPassword}"/>
<sysproperty key="isMultiUser" value="${isMultiUser}"/> <sysproperty key="isMultiUser" value="${isMultiUser}"/>
<!--needed to have tests NOT to steal focus when running, works in latest apple jdk update only.--> <!--needed to have tests NOT to steal focus when running, works in latest apple jdk update only.-->
<sysproperty key="apple.awt.UIElement" value="@{disable.apple.ui}"/> <sysproperty key="apple.awt.UIElement" value="@{disable.apple.ui}"/>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2018 Basis Technology Corp. * Copyright 2011-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -25,6 +25,9 @@ import junit.framework.Test;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.netbeans.jemmy.Timeouts; import org.netbeans.jemmy.Timeouts;
import org.netbeans.junit.NbModuleSuite; import org.netbeans.junit.NbModuleSuite;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
/** /**
* This test expects the following system properties to be set: img_path: The * This test expects the following system properties to be set: img_path: The
@ -100,6 +103,21 @@ public class RegressionTest extends TestCase {
public void setUp() { public void setUp() {
logger.info("######## " + AutopsyTestCases.getEscapedPath(System.getProperty("img_path")) + " #######"); logger.info("######## " + AutopsyTestCases.getEscapedPath(System.getProperty("img_path")) + " #######");
Timeouts.setDefault("ComponentOperator.WaitComponentTimeout", 1000000); Timeouts.setDefault("ComponentOperator.WaitComponentTimeout", 1000000);
try {
if (Boolean.parseBoolean(System.getProperty("isMultiUser"))) {
// Set up a custom postgres CR using the configuration passed
// to system properties.
CentralRepoDbManager manager = new CentralRepoDbManager();
manager.getDbSettingsPostgres().setHost(System.getProperty("crHost"));
manager.getDbSettingsPostgres().setPort(Integer.parseInt(System.getProperty("crPort")));
manager.getDbSettingsPostgres().setUserName(System.getProperty("crUserName"));
manager.getDbSettingsPostgres().setPassword(System.getProperty("crPassword"));
manager.setupPostgresDb(CentralRepoDbChoice.POSTGRESQL_CUSTOM);
}
} catch (CentralRepoException ex) {
throw new RuntimeException("Error setting up multi user CR", ex);
}
} }
/** /**

View File

@ -471,6 +471,10 @@ class TestRunner(object):
test_data.ant.append("-DsolrPort=" + str(test_config.solrPort)) test_data.ant.append("-DsolrPort=" + str(test_config.solrPort))
test_data.ant.append("-DmessageServiceHost=" + test_config.messageServiceHost) test_data.ant.append("-DmessageServiceHost=" + test_config.messageServiceHost)
test_data.ant.append("-DmessageServicePort=" + str(test_config.messageServicePort)) test_data.ant.append("-DmessageServicePort=" + str(test_config.messageServicePort))
test_data.ant.append("-DcrHost=" + str(test_config.crHost))
test_data.ant.append("-DcrPort=" + str(test_config.crPort))
test_data.ant.append("-DcrUserName=" + str(test_config.crUserName))
test_data.ant.append("-DcrPassword=" + str(test_config.crPassword))
if test_data.isMultiUser: if test_data.isMultiUser:
test_data.ant.append("-DisMultiUser=true") test_data.ant.append("-DisMultiUser=true")
# Note: test_data has autopys_version attribute, but we couldn't see it from here. It's set after run ingest. # Note: test_data has autopys_version attribute, but we couldn't see it from here. It's set after run ingest.
@ -854,6 +858,14 @@ class TestConfiguration(object):
self.messageServicePort = parsed_config.getElementsByTagName("messageServicePort")[0].getAttribute("value").encode().decode("utf_8") self.messageServicePort = parsed_config.getElementsByTagName("messageServicePort")[0].getAttribute("value").encode().decode("utf_8")
if parsed_config.getElementsByTagName("multiUser_outdir"): if parsed_config.getElementsByTagName("multiUser_outdir"):
self.multiUser_outdir = parsed_config.getElementsByTagName("multiUser_outdir")[0].getAttribute("value").encode().decode("utf_8") self.multiUser_outdir = parsed_config.getElementsByTagName("multiUser_outdir")[0].getAttribute("value").encode().decode("utf_8")
if parsed_config.getElementsByTagName("crHost"):
self.crHost = parsed_config.getElementsByTagName("crHost")[0].getAttribute("value").encode().decode("utf_8")
if parsed_config.getElementsByTagName("crPort"):
self.crPort = parsed_config.getElementsByTagName("crPort")[0].getAttribute("value").encode().decode("utf_8")
if parsed_config.getElementsByTagName("crUserName"):
self.crUserName = parsed_config.getElementsByTagName("crUserName")[0].getAttribute("value").encode().decode("utf_8")
if parsed_config.getElementsByTagName("crPassword"):
self.crPassword = parsed_config.getElementsByTagName("crPassword")[0].getAttribute("value").encode().decode("utf_8")
self._init_imgs(parsed_config) self._init_imgs(parsed_config)
self._init_build_info(parsed_config) self._init_build_info(parsed_config)

View File

@ -27,7 +27,7 @@ def make_os_path(platform, *dirs):
path += str(dir).replace('\\', '/') + '/' path += str(dir).replace('\\', '/') + '/'
return path_fix(path) return path_fix(path)
elif platform == "win32": elif platform == "win32":
return make_path(dirs) return make_path(*dirs)
else: else:
print("Couldn't make path, because we only support Windows and Cygwin at this time.") print("Couldn't make path, because we only support Windows and Cygwin at this time.")
sys.exit(1) sys.exit(1)

Binary file not shown.