diff --git a/Core/build.xml b/Core/build.xml index d2e9b00f169d395d418f76fd3b574fdbf0d2a68c..811ff30772a66858cc0d47872833740e7e5b1668 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -102,6 +102,7 @@ <copy todir="${basedir}/release/yara" > <fileset dir="${thirdparty.dir}/yara/bin"/> </copy> + <copy file="${thirdparty.dir}/yara/bin/YaraJNIWrapper.jar" todir="${ext.dir}" /> </target> diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 2b8155b7b20bcd3409fda1ca5ea19b027e09b6aa..6986362a9056635565480fdbc837a90715e57d37 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -118,7 +118,7 @@ file.reference.StixLib.jar=release\\modules\\ext\\StixLib.jar file.reference.threetenbp-1.3.3.jar=release\\modules\\ext\\threetenbp-1.3.3.jar file.reference.webp-imageio-sejda-0.1.0.jar=release\\modules\\ext\\webp-imageio-sejda-0.1.0.jar file.reference.xmpcore-5.1.3.jar=release\\modules\\ext\\xmpcore-5.1.3.jar -file.reference.YaraJNIWrapper.jar=release/modules/ext/YaraJNIWrapper.jar +file.reference.YaraJNIWrapper.jar=release\\modules\\ext\\YaraJNIWrapper.jar file.reference.zookeeper-3.4.6.jar=release\\modules\\ext\\zookeeper-3.4.6.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java index b0f288673bf3cb81f4913b808e8f5fca1c12b6bf..d3a0e61ecf14998448f4b3f4c349bab9b3703541 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java @@ -382,6 +382,31 @@ public void setupDefaultSqliteDb() throws CentralRepoException { CentralRepoDbUtil.setUseCentralRepo(true); 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 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java index 165d4ca6e083a8f4cd62a1f2b41eb69b118b5eec..dfb855a0b2b0468adf4b23f630e78af66df110bf 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java @@ -121,10 +121,12 @@ String getJDBCBaseURI() { * @return */ String getConnectionURL(boolean usePostgresDb) { - StringBuilder url = new StringBuilder(); - url.append(getJDBCBaseURI()); - url.append(getHost()); - url.append("/"); // NON-NLS + StringBuilder url = new StringBuilder() + .append(getJDBCBaseURI()) + .append(getHost()) + .append(":") // NON-NLS + .append(getPort()) + .append("/"); // NON-NLS if (usePostgresDb) { url.append("postgres"); // NON-NLS } else { @@ -153,7 +155,7 @@ Connection getEphemeralConnection(boolean usePostgresDb) { } catch (ClassNotFoundException | SQLException ex) { // TODO: Determine why a connection failure (ConnectionException) re-throws // 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; } return conn; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED index d4cca6c407f6956bbff37299339a84b9c1fa8e43..e95a759c4fa87f103243551220454efa1e86aa0d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED @@ -1,4 +1,10 @@ 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.prevCaseComment.text=Previous Case: # {0} - typeName @@ -7,6 +13,3 @@ IngestEventsListener.prevCount.text=Number of previous {0}: {1} IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository) IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) 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? diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java new file mode 100755 index 0000000000000000000000000000000000000000..883d979c908673d1861f06a74bc239a9e9bafb41 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CentralRepositoryNotificationDialog.java @@ -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>" + ); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index d4f0253cd2f92b82f2113f5ce46b775ada31ff65..b2ef0d437ed05d3c81eb73c76228b667ea19af52 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -25,14 +25,13 @@ import javax.swing.SwingUtilities; import org.openide.modules.ModuleInstall; import org.openide.util.NbBundle; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.autopsy.coreutils.Version; /** * Adds/removes application event listeners responsible for adding data to the @@ -81,19 +80,10 @@ private Installer() { * the org.sleuthkit.autopsy.core package when the already installed * 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 public void restored() { addApplicationEventListeners(); - - if (Version.getBuildType() == Version.Type.RELEASE) { - setupDefaultCentralRepository(); - } + setupDefaultCentralRepository(); } /** @@ -107,9 +97,9 @@ private void addApplicationEventListeners() { /** * 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 - * unconditionally (not running with a GUI, e.g., in an automated ingest - * node). + * does the set up unconditionally. If the application is running with a + * GUI, a notification will be displayed to the user if the mode is RELEASE + * (in other words, developers are exempt from seeing the notification). */ private void setupDefaultCentralRepository() { Map<String, String> centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository"); @@ -127,62 +117,30 @@ private void setupDefaultCentralRepository() { ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); } } + + if(initialized) { + return; // Nothing to do + } - // if central repository hasn't been previously initialized, initialize it - if (!initialized) { - // 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(), - dialogText, - NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"), - JOptionPane.YES_NO_OPTION)) { - - setupDefaultSqliteCentralRepo(); - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "There was an error while initializing the central repository database", 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); - } + if (CentralRepositoryNotificationDialog.shouldDisplay()) { + CentralRepositoryNotificationDialog.display(); + } + + 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) { + logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); - ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); + doMessageBoxIfRunningInGUI(ex); } - } - /** - * 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(); + ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java index 6c4601af88bb0ba46ad2e904f0b6484da334679c..651f5fe758ba697b5da9e6fe7d29f2a5fcff44fd 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummary.java @@ -28,10 +28,11 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -93,16 +94,56 @@ public Set<Integer> getArtifactTypeIdsForRefresh() { 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 * TSK_RECENT_OBJECT artifact. * * @param dataSource The data source to query. - * @param maxCount The maximum number of results to return, pass 0 to get - * a list of all results. + * @param maxCount The maximum number of results to return, pass 0 to get a + * list of all results. * * @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. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -112,36 +153,45 @@ public List<RecentFileDetails> getRecentlyOpenedDocuments(DataSource dataSource, return Collections.emptyList(); } - List<BlackboardArtifact> artifactList - = DataSourceInfoUtilities.getArtifacts(provider.get(), - new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT), - dataSource, - DATETIME_ATT, - DataSourceInfoUtilities.SortOrder.DESCENDING, - maxCount); - - List<RecentFileDetails> fileDetails = new ArrayList<>(); - for (BlackboardArtifact artifact : artifactList) { - Long accessedTime = null; - String path = ""; - - // Get all the attributes in one call. - List<BlackboardAttribute> attributeList = artifact.getAttributes(); - for (BlackboardAttribute attribute : attributeList) { - - if (attribute.getAttributeType().equals(DATETIME_ATT)) { - accessedTime = attribute.getValueLong(); - } else if (attribute.getAttributeType().equals(PATH_ATT)) { - path = attribute.getValueString(); - } - } + throwOnNonPositiveCount(maxCount); - if (accessedTime != null && accessedTime != 0) { - fileDetails.add(new RecentFileDetails(path, accessedTime)); - } + List<RecentFileDetails> details = provider.get().getBlackboard() + .getArtifacts(ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID(), dataSource.getId()).stream() + .map(art -> getRecentlyOpenedDocument(art)) + .filter(d -> d != null) + .collect(Collectors.toList()); + + return getSortedLimited(details, maxCount); + } + + /** + * Returns a RecentDownloadDetails object as derived from the recent + * download 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 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); } + } - return fileDetails; + /** + * 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."); + } } /** @@ -149,11 +199,11 @@ public List<RecentFileDetails> getRecentlyOpenedDocuments(DataSource dataSource, * artifact TSK_DATETIME_ACCESSED attribute. * * @param dataSource Data source to query. - * @param maxCount Maximum number of results to return, passing 0 will - * return all results. + * @param maxCount Maximum number of results to return, passing 0 will + * return all results. * * @return A list of RecentFileDetails objects or empty list if none were - * found. + * found. * * @throws TskCoreException * @throws SleuthkitCaseProviderException @@ -163,46 +213,23 @@ public List<RecentDownloadDetails> getRecentDownloads(DataSource dataSource, int return Collections.emptyList(); } - List<BlackboardArtifact> artifactList - = 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<>(); - for (BlackboardArtifact artifact : artifactList) { - // Get all the attributes in one call. - Long accessedTime = null; - String domain = ""; - String path = ""; - - List<BlackboardAttribute> attributeList = artifact.getAttributes(); - 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)); - } - } + throwOnNonPositiveCount(maxCount); + + List<RecentDownloadDetails> details = provider.get().getBlackboard() + .getArtifacts(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID(), dataSource.getId()).stream() + .map(art -> getRecentDownload(art)) + .filter(d -> d != null) + .collect(Collectors.toList()); - return fileDetails; + return getSortedLimited(details, maxCount); } /** * Returns a list of the most recent message attachments. * * @param dataSource Data source to query. - * @param maxCount Maximum number of results to return, passing 0 will - * return all results. + * @param maxCount Maximum number of results to return, passing 0 will + * return all results. * * @return A list of RecentFileDetails of the most recent attachments. * @@ -214,115 +241,72 @@ public List<RecentAttachmentDetails> getRecentAttachments(DataSource dataSource, return Collections.emptyList(); } - if (maxCount < 0) { - throw new IllegalArgumentException("Invalid maxCount passed to getRecentAttachments, value must be equal to or greater than 0"); - } - - return createListFromMap(buildAttachmentMap(dataSource), maxCount); - } + throwOnNonPositiveCount(maxCount); - /** - * Build a map of all of the message attachment sorted in date order. - * - * @param dataSource Data source to query. - * - * @return Returns a SortedMap of details objects returned in descending - * order. - * - * @throws SleuthkitCaseProviderException - * @throws TskCoreException - */ - private SortedMap<Long, List<RecentAttachmentDetails>> buildAttachmentMap(DataSource dataSource) throws SleuthkitCaseProviderException, TskCoreException { SleuthkitCase skCase = provider.get(); - TreeMap<Long, List<RecentAttachmentDetails>> sortedMap = new TreeMap<>(); - List<BlackboardArtifact> associatedArtifacts = skCase.getBlackboard().getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId()); + List<BlackboardArtifact> associatedArtifacts = skCase.getBlackboard() + .getArtifacts(ASSOCATED_OBJ_ART.getTypeID(), dataSource.getId()); + + List<RecentAttachmentDetails> details = new ArrayList<>(); for (BlackboardArtifact artifact : associatedArtifacts) { - BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT); - if (attribute == null) { - continue; - } + RecentAttachmentDetails thisDetails = getRecentAttachment(artifact, skCase); - BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong()); - if (messageArtifact != null && isMessageArtifact(messageArtifact)) { - Content content = artifact.getParent(); - 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) { - date = senderAttribute.getValueLong(); - } - - AbstractFile abstractFile = (AbstractFile) content; - - path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()).toString(); - - if (date != null && date != 0) { - List<RecentAttachmentDetails> list = sortedMap.get(date); - if (list == null) { - list = new ArrayList<>(); - sortedMap.put(date, list); - } - RecentAttachmentDetails details = new RecentAttachmentDetails(path, date, sender); - if (!list.contains(details)) { - list.add(details); - } - } - } + if (thisDetails != null) { + details.add(thisDetails); } } - return sortedMap.descendingMap(); + + return getSortedLimited(details, maxCount); } /** - * Create a list of detail objects from the given sorted map of the max - * size. + * Creates a RecentAttachmentDetails object from the associated object + * artifact or null if no RecentAttachmentDetails object can be derived. * - * @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. + * @param artifact The associated object artifact. + * @param skCase The current case. + * @return The derived object or null. + * @throws TskCoreException */ - 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; - } + private RecentAttachmentDetails getRecentAttachment(BlackboardArtifact artifact, SleuthkitCase skCase) throws TskCoreException { + // get associated artifact or return no result + BlackboardAttribute attribute = artifact.getAttribute(ASSOCATED_ATT); + if (attribute == null) { + return null; + } - if (maxCount == fileList.size()) { - break; - } + // get associated message artifact if exists or return no result + BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong()); + if (messageArtifact == null || !isMessageArtifact(messageArtifact)) { + return null; + } - for (RecentAttachmentDetails details : mapList) { - if (fileList.size() < maxCount) { - fileList.add(details); - } else { - break; - } - } + // get abstract file if exists or return no result + Content content = artifact.getParent(); + if (!(content instanceof AbstractFile)) { + return null; } - return fileList; + AbstractFile abstractFile = (AbstractFile) content; + + // 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 || StringUtils.isBlank(path)) { + return null; + } else { + return new RecentAttachmentDetails(path, date, sender); + } } /** * Is the given artifact a message. * * @param nodeArtifact An artifact that might be a message. Must not be - * null. + * null. * * @return True if the given artifact is a message artifact */ @@ -330,6 +314,7 @@ private boolean isMessageArtifact(BlackboardArtifact nodeArtifact) { final int artifactTypeID = nodeArtifact.getArtifactTypeID(); return artifactTypeID == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() || artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(); + } /** @@ -391,8 +376,8 @@ public static class RecentDownloadDetails extends RecentFileDetails { /** * Constructor for files with just a path and date. * - * @param path File path. - * @param date File access date\time in seconds with java epoch. + * @param path File path. + * @param date File access date\time in seconds with java epoch. * @param webDomain The webdomain from which the file was downloaded. */ RecentDownloadDetails(String path, long date, String webDomain) { @@ -404,7 +389,7 @@ public static class RecentDownloadDetails extends RecentFileDetails { * Returns the web domain. * * @return The web domain or empty string if not available or - * applicable. + * applicable. */ public String getWebDomain() { return webDomain; @@ -422,10 +407,10 @@ public static class RecentAttachmentDetails extends RecentFileDetails { * Constructor for recent download files which have a path, date and * domain value. * - * @param path File path. - * @param date File crtime. + * @param path File path. + * @param date File crtime. * @param sender The sender of the message from which the file was - * attached. + * attached. */ RecentAttachmentDetails(String path, long date, String sender) { super(path, date); @@ -436,7 +421,7 @@ public static class RecentAttachmentDetails extends RecentFileDetails { * Return the sender of the attached file. * * @return The sender of the attached file or empty string if not - * available. + * available. */ public String getSender() { return sender; diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java index 16da6f5c4bf1f41b01d2198c80f2b52f680e387e..b7efda6e29c442ada5860182281ac5a218935c58 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummary.java @@ -137,7 +137,7 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor { return (a.getProgramName() == null ? "" : a.getProgramName()) .compareToIgnoreCase((b.getProgramName() == null ? "" : b.getProgramName())); }; - + private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList( ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(), @@ -172,9 +172,9 @@ public UserActivitySummary() { * is designed with unit testing in mind since mocked dependencies can be * utilized. * - * @param provider The object providing the current SleuthkitCase. + * @param provider The object providing the current SleuthkitCase. * @param translationService The translation service. - * @param logger The logger to use. + * @param logger The logger to use. */ public UserActivitySummary( SleuthkitCaseProvider provider, @@ -206,7 +206,7 @@ private void assertValidCount(int count) { * Gets a list of recent domains based on the datasource. * * @param dataSource The datasource to query for recent domains. - * @param count The max count of items to return. + * @param count The max count of items to return. * * @return The list of items retrieved from the database. * @@ -242,12 +242,12 @@ public List<TopDomainsResult> getRecentDomains(DataSource dataSource, int count) * Creates a TopDomainsResult from data or null if no visit date exists * within DOMAIN_WINDOW_MS of mostRecentMs. * - * @param domain The domain. - * @param visits The number of visits. + * @param domain The domain. + * @param visits The number of visits. * @param mostRecentMs The most recent visit of any domain. * * @return The TopDomainsResult or null if no visits to this domain within - * 30 days of mostRecentMs. + * 30 days of mostRecentMs. */ private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long mostRecentMs) { long visitCount = 0; @@ -280,9 +280,8 @@ private TopDomainsResult getDomainsResult(String domain, List<Long> visits, long * @param dataSource The datasource. * * @return A tuple where the first value is the latest web history accessed - * date in milliseconds and the second value maps normalized - * (lowercase; trimmed) domain names to when those domains were - * visited. + * date in milliseconds and the second value maps normalized (lowercase; + * trimmed) domain names to when those domains were visited. * * @throws TskCoreException * @throws SleuthkitCaseProviderException @@ -349,7 +348,7 @@ private static Long getMax(Long num1, Long num2) { * @param artifact The artifact. * * @return The TopWebSearchResult or null if the search string or date - * accessed cannot be determined. + * accessed cannot be determined. */ private static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact) { String searchString = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_TEXT); @@ -364,11 +363,10 @@ private static TopWebSearchResult getWebSearchResult(BlackboardArtifact artifact * term. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @return The list of most recent web searches where most recent search - * appears first. + * appears first. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -386,21 +384,22 @@ public List<TopWebSearchResult> getMostRecentWebSearches(DataSource dataSource, .getArtifacts(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID(), dataSource.getId()); // group by search string (case insensitive) - Collection<List<TopWebSearchResult>> resultGroups = webSearchArtifacts + Collection<TopWebSearchResult> resultGroups = webSearchArtifacts .stream() // get items where search string and date is not null .map(UserActivitySummary::getWebSearchResult) // remove null records .filter(result -> result != null) - // get these messages grouped by search to string - .collect(Collectors.groupingBy((result) -> result.getSearchString().toUpperCase())) + // get the latest message for each search string + .collect(Collectors.toMap( + (result) -> result.getSearchString().toUpperCase(), + result -> result, + (result1, result2) -> TOP_WEBSEARCH_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2)) .values(); // get the most recent date for each search term List<TopWebSearchResult> results = resultGroups .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 .sorted(TOP_WEBSEARCH_RESULT_DATE_COMPARE.reversed()) .limit(count) @@ -424,7 +423,7 @@ public List<TopWebSearchResult> getMostRecentWebSearches(DataSource dataSource, * @param original The original text. * * @return The translated text or null if no translation can be determined - * or exists. + * or exists. */ private String getTranslationOrNull(String original) { if (!translationService.hasProvider() || StringUtils.isBlank(original)) { @@ -448,15 +447,34 @@ private String getTranslationOrNull(String original) { 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. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @return The list of most recent devices attached where most recent device - * attached appears first. + * attached appears first. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -469,7 +487,7 @@ public List<TopDeviceAttachedResult> getRecentDevices(DataSource dataSource, int 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) .stream() .map(artifact -> { @@ -482,9 +500,14 @@ public List<TopDeviceAttachedResult> getRecentDevices(DataSource dataSource, int }) // remove Root Hub identifier .filter(result -> { - return result.getDeviceModel() == null + return result.getDeviceId() == null + || result.getDeviceModel() == null || !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) .collect(Collectors.toList()); } @@ -495,7 +518,7 @@ public List<TopDeviceAttachedResult> getRecentDevices(DataSource dataSource, int * @param artifact The artifact. * * @return The TopAccountResult or null if the account type or message date - * cannot be determined. + * cannot be determined. */ private static TopAccountResult getMessageAccountResult(BlackboardArtifact artifact) { String type = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_MESSAGE_TYPE); @@ -509,12 +532,12 @@ private static TopAccountResult getMessageAccountResult(BlackboardArtifact artif * Obtains a TopAccountResult from a blackboard artifact. The date is * maximum of any found dates for attribute types provided. * - * @param artifact The artifact. + * @param artifact The artifact. * @param messageType The type of message this is. - * @param dateAttrs The date attribute types. + * @param dateAttrs The date attribute types. * * @return The TopAccountResult or null if the account type or max date are - * not provided. + * not provided. */ private static TopAccountResult getAccountResult(BlackboardArtifact artifact, String messageType, BlackboardAttribute.Type... dateAttrs) { String type = messageType; @@ -538,11 +561,10 @@ private static TopAccountResult getAccountResult(BlackboardArtifact artifact, St * sent. * * @param dataSource The data source. - * @param count The maximum number of records to be shown (must be > - * 0). + * @param count The maximum number of records to be shown (must be > 0). * * @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. * * @throws * org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException @@ -585,18 +607,19 @@ public List<TopAccountResult> getRecentAccounts(DataSource dataSource, int count Stream<TopAccountResult> allResults = Stream.concat(messageResults, Stream.concat(emailResults, calllogResults)); // get them grouped by account type - Collection<List<TopAccountResult>> groupedResults = allResults + Collection<TopAccountResult> groupedResults = allResults // remove null records .filter(result -> result != null) - // get these messages grouped by account type - .collect(Collectors.groupingBy(TopAccountResult::getAccountType)) + // get these messages grouped by account type and get the most recent of each type + .collect(Collectors.toMap( + result -> result.getAccountType(), + result -> result, + (result1, result2) -> TOP_ACCOUNT_RESULT_DATE_COMPARE.compare(result1, result2) >= 0 ? result1 : result2)) .values(); // get account type sorted by most recent date return groupedResults .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 .sorted(TOP_ACCOUNT_RESULT_DATE_COMPARE.reversed()) // limit to count @@ -608,7 +631,7 @@ public List<TopAccountResult> getRecentAccounts(DataSource dataSource, int count /** * Determines a short folder name if any. Otherwise, returns empty string. * - * @param strPath The string path. + * @param strPath The string path. * @param applicationName The application name. * * @return The short folder name or empty string if not found. @@ -659,7 +682,7 @@ private TopProgramsResult getTopProgramsResult(BlackboardArtifact artifact) { if (StringUtils.startsWithIgnoreCase(path, WINDOWS_PREFIX)) { return null; } - + Integer count = DataSourceInfoUtilities.getIntOrNull(artifact, TYPE_COUNT); Long longCount = (count == null) ? null : (long) count; @@ -696,7 +719,7 @@ private static Date getMax(Date date1, Date date2) { * @param long2 Second possibly null long. * * @return Returns the compare value: 1,0,-1 favoring the higher non-null - * value. + * value. */ private static int nullableCompare(Long long1, Long long2) { if (long1 == null && long2 == null) { @@ -721,7 +744,6 @@ private static boolean isPositiveNum(Long longNum) { return longNum != null && longNum > 0; } - /** * 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 @@ -731,12 +753,12 @@ private static boolean isPositiveNum(Long longNum) { * be ignored and all items will be returned. * * @param dataSource The datasource. If the datasource is null, an empty - * list will be returned. - * @param count The number of results to return. This value must be > 0 - * or an IllegalArgumentException will be thrown. + * list will be returned. + * @param count The number of results to return. This value must be > 0 or + * an IllegalArgumentException will be thrown. * * @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. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -759,7 +781,9 @@ public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) // The value will be a TopProgramsResult with the max run times // and most recent last run date for each program name / program path pair. .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, (res1, res2) -> { return new TopProgramsResult( @@ -852,10 +876,10 @@ public static class TopDeviceAttachedResult { /** * Main constructor. * - * @param deviceId The device id. + * @param deviceId The device id. * @param dateAccessed The date last attached. - * @param deviceMake The device make. - * @param deviceModel The device model. + * @param deviceMake The device make. + * @param deviceModel The device model. */ public TopDeviceAttachedResult(String deviceId, Date dateAccessed, String deviceMake, String deviceModel) { this.deviceId = deviceId; @@ -906,7 +930,7 @@ public static class TopAccountResult { * Main constructor. * * @param accountType The account type. - * @param lastAccess The date the account was last accessed. + * @param lastAccess The date the account was last accessed. */ public TopAccountResult(String accountType, Date lastAccess) { this.accountType = accountType; @@ -940,9 +964,9 @@ public static class TopDomainsResult { /** * Describes a top domain result. * - * @param domain The domain. + * @param domain The domain. * @param visitTimes The number of times it was visited. - * @param lastVisit The date of the last visit. + * @param lastVisit The date of the last visit. */ public TopDomainsResult(String domain, Long visitTimes, Date lastVisit) { this.domain = domain; @@ -987,7 +1011,7 @@ public static class TopProgramsResult { * * @param programName The name of the program. * @param programPath The path of the program. - * @param runTimes The number of runs. + * @param runTimes The number of runs. */ TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) { this.programName = programName; diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED index bf61ad9be0477e90eaf6c9c54fcc230c45d9f9a3..cd35320f717a67570b41cc3c7df9d8c0ffa3ed01 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED @@ -1,6 +1,6 @@ DATExtractor_process_message=Processing DJI DAT file: %s DATFileExtractor_Extractor_Name=DAT File Extractor -DroneIngestModule_Description=Analyzes files generated by drones. -DroneIngestModule_Name=Drone Analyzer +DroneIngestModule_Description=Analyzes files generated by some DJI drones. +DroneIngestModule_Name=DJI Drone Analyzer # {0} - AbstractFileName DroneIngestModule_process_start=Started {0} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java index 1213ffedd5e8df1650661911cbd2acfd27632d04..0e8f7107a5e372734a2a43cc59b8bd2bc35d6c76 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java @@ -33,8 +33,8 @@ public class DroneIngestModuleFactory extends IngestModuleFactoryAdapter { @Messages({ - "DroneIngestModule_Name=Drone Analyzer", - "DroneIngestModule_Description=Analyzes files generated by drones." + "DroneIngestModule_Name=DJI Drone Analyzer", + "DroneIngestModule_Description=Analyzes files generated by some DJI drones." }) /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties index 0036d4dd6ff5999a3866a5c73eba1f40c8c3c3de..15698d736c21d37e86e4f15d71d1a12ca9ef333a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties @@ -1,3 +1,4 @@ ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.processing.file=Processing file {0} -ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} \ No newline at end of file +ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} +ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED index b4f350f478f63a91207ccc1b4b11f70ebe84ca02..b4e8226a91cffd9666b2d44f0030d8fe07275a57 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/Bundle.properties-MERGED @@ -8,6 +8,7 @@ ILeappAnalyzerIngestModule.iLeapp.cancelled=iLeapp run was canceled ILeappAnalyzerIngestModule.init.exception.msg=Unable to find {0}. ILeappAnalyzerIngestModule.processing.file=Processing file {0} ILeappAnalyzerIngestModule.parsing.file=Parsing file {0} +ILeappAnalyzerIngestModule.processing.filesystem=Processing filesystem ILeappAnalyzerIngestModule.report.name=iLeapp Html Report ILeappAnalyzerIngestModule.requires.windows=iLeapp module requires windows. ILeappAnalyzerIngestModule.running.iLeapp=Running iLeapp diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java index f859919bae7aa35e96fe3ca6ee9c935c7cbd5ea0..abec90e6c0eed909fa8512e1a5ce941aa099f132 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappAnalyzerIngestModule.java @@ -18,8 +18,10 @@ */ package org.sleuthkit.autopsy.modules.ileappanalyzer; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -32,14 +34,17 @@ import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.io.FilenameUtils; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; 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.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; @@ -50,6 +55,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.ReadContentInputStream; 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 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_PATHS_FILE = "iLeapp_paths.txt"; //NON-NLS private File iLeappExecutable; @@ -87,7 +95,7 @@ public void startUp(IngestJobContext context) throws IngestModuleException { try { iLeappFileProcessor = new ILeappFileProcessor(); - } catch (IOException | IngestModuleException ex) { + } catch (IOException | IngestModuleException | NoCurrentCaseException ex) { throw new IngestModuleException(Bundle.ILeappAnalyzerIngestModule_error_ileapp_file_processor_init(), ex); } @@ -112,65 +120,149 @@ public void startUp(IngestJobContext context) throws IngestModuleException { @Override public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) { - if (!(context.getDataSource() instanceof LocalFilesDataSource)) { - return ProcessResult.OK; + Case currentCase = Case.getCurrentCase(); + 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); - List<AbstractFile> iLeappFilesToProcess = findiLeappFilesToProcess(dataSource); + List<AbstractFile> iLeappFilesToProcess = new ArrayList<>(); - statusHelper.switchToDeterminate(iLeappFilesToProcess.size()); + 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()); + + Integer filesProcessedCount = 0; + for (AbstractFile iLeappFile : iLeappFilesToProcess) { + processILeappFile(dataSource, currentCase, statusHelper, filesProcessedCount, iLeappFile); + 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()); + } - Integer filesProcessedCount = 0; + IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, + Bundle.ILeappAnalyzerIngestModule_has_run(), + Bundle.ILeappAnalyzerIngestModule_completed()); + IngestServices.getInstance().postMessage(message); + return ProcessResult.OK; + } - Case currentCase = Case.getCurrentCase(); - for (AbstractFile iLeappFile : iLeappFilesToProcess) { + /** + * 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; + } - 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) { + logger.log(Level.WARNING, String.format("Error when trying to execute iLeapp program getting file paths to search for result is %d", result)); + 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) { - // ignore if there is an error and continue to try and process the next file if there is one - continue; - } + addILeappReportToReports(moduleOutputPath, currentCase); - 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; + } - } 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; + } - 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); - ProcessResult fileProcessorResult = iLeappFileProcessor.processFiles(dataSource, moduleOutputPath, iLeappFile); + if (fileProcessorResult == ProcessResult.ERROR) { + return; + } + } - if (fileProcessorResult == ProcessResult.ERROR) { - return ProcessResult.ERROR; + /** + * 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; } - filesProcessedCount++; + 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; } - IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, - Bundle.ILeappAnalyzerIngestModule_has_run(), - Bundle.ILeappAnalyzerIngestModule_completed()); - IngestServices.getInstance().postMessage(message); - return ProcessResult.OK; } /** @@ -197,17 +289,24 @@ private List<AbstractFile> findiLeappFilesToProcess(Content dataSource) { List<AbstractFile> iLeappFilesToProcess = new ArrayList<>(); for (AbstractFile iLeappFile : iLeappFiles) { if (((iLeappFile.getLocalAbsPath() != null) - && (!iLeappFile.getNameExtension().isEmpty() && (!iLeappFile.isVirtual()))) - && ((iLeappFile.getName().toLowerCase().contains(".zip") || (iLeappFile.getName().toLowerCase().contains(".tar"))) - || iLeappFile.getName().toLowerCase().contains(".tgz"))) { - iLeappFilesToProcess.add(iLeappFile); - + && (!iLeappFile.getNameExtension().isEmpty() && (!iLeappFile.isVirtual()))) + && ((iLeappFile.getName().toLowerCase().contains(".zip") || (iLeappFile.getName().toLowerCase().contains(".tar"))) + || iLeappFile.getName().toLowerCase().contains(".tgz"))) { + iLeappFilesToProcess.add(iLeappFile); + } } 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) { ProcessBuilder processBuilder = buildProcessWithRunAsInvoker( @@ -221,10 +320,26 @@ private ProcessBuilder buildiLeappCommand(Path moduleOutputPath, String sourceFi 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) { 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. */ processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS @@ -248,13 +363,18 @@ private static File locateExecutable(String executableName) throws FileNotFoundE private void addILeappReportToReports(Path iLeappOutputDir, Case currentCase) { List<String> allIndexFiles = new ArrayList<>(); - try (Stream<Path> walk = Files.walk(iLeappOutputDir)) { + try (Stream<Path> walk = Files.walk(iLeappOutputDir)) { allIndexFiles = walk.map(x -> x.toString()) .filter(f -> f.toLowerCase().endsWith("index.html")).collect(Collectors.toList()); if (!allIndexFiles.isEmpty()) { - currentCase.addReport(allIndexFiles.get(0), MODULE_NAME, Bundle.ILeappAnalyzerIngestModule_report_name()); + // 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()); + } } } catch (IOException | UncheckedIOException | TskCoreException ex) { @@ -264,4 +384,129 @@ private void addILeappReportToReports(Path iLeappOutputDir, Case currentCase) { } + /* + * 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 + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java index cb5ab516a66ac386625e81f1051dfcc5247228eb..058b0aa28d23d6449cb16eeaf9b5a7d052ee0697 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ILeappFileProcessor.java @@ -44,6 +44,7 @@ import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; 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, List<List<String>>> tsvFileAttributes; - public ILeappFileProcessor() throws IOException, IngestModuleException { + Blackboard blkBoard; + + public ILeappFileProcessor() throws IOException, IngestModuleException, NoCurrentCaseException { this.tsvFiles = new HashMap<>(); this.tsvFileArtifacts = new HashMap<>(); this.tsvFileArtifactComments = new HashMap<>(); this.tsvFileAttributes = new HashMap<>(); + blkBoard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard(); + configExtractor(); loadConfigFile(); @@ -110,6 +115,19 @@ public ProcessResult processFiles(Content dataSource, Path moduleOutputPath, Abs 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 * we know we want to process and return the list to process those files. @@ -124,7 +142,7 @@ private List<String> findTsvFiles(Path iLeappOutputDir) throws IngestModuleExcep .filter(f -> f.toLowerCase().endsWith(".tsv")).collect(Collectors.toList()); for (String tsvFile : allTsvFiles) { - if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile))) { + if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile.toLowerCase()))) { foundTsvFiles.add(tsvFile); } } @@ -160,7 +178,41 @@ private void processiLeappFiles(List<String> iLeappFilesToProcess, AbstractFile processFile(iLeappFile, attrList, fileName, artifactType, bbartifacts, iLeappImageFile); } 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); } } @@ -174,7 +226,8 @@ private void processiLeappFiles(List<String> iLeappFilesToProcess, AbstractFile } 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))) { 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. @@ -183,8 +236,8 @@ private void processFile(File iLeappFile, List<List<String>> attrList, String fi line = reader.readLine(); while (line != null) { Collection<BlackboardAttribute> bbattributes = processReadLine(line, columnNumberToProcess, fileName); - if (!bbattributes.isEmpty()) { - BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), iLeappImageFile, bbattributes); + if (!bbattributes.isEmpty() && !blkBoard.artifactExists(dataSource, BlackboardArtifact.ARTIFACT_TYPE.fromID(artifactType.getTypeID()), bbattributes)) { + BlackboardArtifact bbartifact = createArtifactWithAttributes(artifactType.getTypeID(), dataSource, bbattributes); if (bbartifact != null) { bbartifacts.add(bbartifact); } @@ -234,8 +287,8 @@ private Collection<BlackboardAttribute> processReadLine(String line, Map<Integer } - private void checkAttributeType(Collection<BlackboardAttribute> bbattributes, String attrType, String[] columnValues, Integer columnNumber, BlackboardAttribute.Type attributeType, - String fileName) { + private void checkAttributeType(Collection<BlackboardAttribute> bbattributes, String attrType, String[] columnValues, Integer columnNumber, BlackboardAttribute.Type attributeType, + String fileName) { if (attrType.matches("STRING")) { bbattributes.add(new BlackboardAttribute(attributeType, MODULE_NAME, columnValues[columnNumber])); } else if (attrType.matches("INTEGER")) { @@ -340,7 +393,7 @@ private void getFileNode(Document xmlinput) { for (int i = 0; i < nlist.getLength(); i++) { 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,19 +446,20 @@ private void getAttributeNodes(Document xmlinput) { } } - /** - * 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 abstractFile is the AbstractFile 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 - */ + + /** + * 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 abstractFile is the AbstractFile 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, AbstractFile abstractFile, Collection<BlackboardAttribute> bbattributes) { try { BlackboardArtifact bbart = abstractFile.newArtifact(type); @@ -417,6 +471,30 @@ private BlackboardArtifact createArtifactWithAttributes(int type, AbstractFile a 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. * diff --git a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml index 0959cbdcc48f85d5034efaa5a7a380c794ab44f7..a4169395aae21cce3b9cb1360d4bca0d14a11200 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml +++ b/Core/src/org/sleuthkit/autopsy/modules/ileappanalyzer/ileap-artifact-attribute-reference.xml @@ -47,6 +47,15 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_BLUETOOTH_ADAPTER" comment="Bluetooth Other"> <AttributeName attributename="TSK_NAME" columnName="Name" required="yes" /> @@ -120,6 +129,13 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Activity"> <AttributeName attributename="TSK_DATETIME" columnName="Entry Creation" required="yes" /> @@ -189,6 +205,36 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_BLUETOOTH_PAIRING" comment="KnowledgeC Bluetooth Connections"> <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"> <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="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 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="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" /> </ArtifactName> </FileName> @@ -248,6 +340,19 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_RECENT_OBJ" comment="KnowledgeC Media Playing"> <AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" /> @@ -288,6 +393,36 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Safari Browsing"> <AttributeName attributename="TSK_DATETIME_ACCESSED" columnName="Start" required="yes" /> @@ -302,6 +437,18 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_PROG_RUN" comment="KnowledgeC App Usage"> <AttributeName attributename="TSK_DATETIME" columnName="Start" required="yes" /> @@ -317,6 +464,18 @@ <AttributeName attributename="null" columnName="Zobject Table ID" required="no" /> </ArtifactName> </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"> <ArtifactName artifactname="TSK_WEB_HISTORY" comment="KnowledgeC Web Usage"> @@ -433,6 +592,102 @@ </ArtifactName> </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"> <ArtifactName artifactname="TSK_WIFI_NETWORK" comment="Powerlog WiFi Network Connections"> <AttributeName attributename="TSK_DATETIME" columnName="Adjusted Timestamp" required="yes" /> diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index 67b253a1fa66b6e420ecd4742aaa8af73a582838..7221ee4a5537b459be3c804234c642f2f2301876 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -60,6 +60,7 @@ import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.caseuco.CaseUcoExporter; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -983,10 +984,10 @@ private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreE BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName()); try { - BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName()); + BlackboardArtifact.Type newCustomType = portableSkCase.getBlackboard().getOrAddArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName()); oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID()); return newCustomType.getTypeID(); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS } } @@ -1007,11 +1008,11 @@ private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttr } try { - BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(), + BlackboardAttribute.Type newCustomType = portableSkCase.getBlackboard().getOrAddAttributeType(oldAttrType.getTypeName(), oldAttrType.getValueType(), oldAttrType.getDisplayName()); oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType); return newCustomType; - } catch (TskDataException ex) { + } catch (BlackboardException ex) { throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS } } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java index 068f0c3904f8e517842d60500d4f12de018cb4ce..ff73c428b9689ee23e1d167f9551dab72cc2789e 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/RecentFilesSummaryTest.java @@ -71,8 +71,8 @@ private interface RecentFilesMethod<T> { * Means of acquiring data from a method in RecentFilesSummary. * * @param recentFilesSummary The RecentFilesSummary object. - * @param dataSource The datasource. - * @param count The number of items to retrieve. + * @param dataSource The datasource. + * @param count The number of items to retrieve. * * @return The method's return data. * @@ -95,7 +95,7 @@ List<T> fetch(RecentFilesSummary recentFilesSummary, DataSource dataSource, int /** * If -1 count passed to method, should throw IllegalArgumentException. * - * @param method The method to call. + * @param method The method to call. * @param methodName The name of the metho * * @throws TskCoreException @@ -137,7 +137,7 @@ public void getRecentAttachments_nonPositiveCount_ThrowsError() throws TskCoreEx * SleuthkitCase isn't called. * * @param recentFilesMethod The method to call. - * @param methodName The name of the method + * @param methodName The name of the method * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -175,7 +175,7 @@ public void getRecentAttachments_noDataSource_ReturnsEmptyList() throws TskCoreE * If SleuthkitCase returns no results, an empty list is returned. * * @param recentFilesMethod The method to call. - * @param methodName The name of the method. + * @param methodName The name of the method. * * @throws SleuthkitCaseProviderException * @throws TskCoreException @@ -220,11 +220,11 @@ public void getRecentAttachments_testNoDataSource_ReturnsEmptyList() throws TskC /** * Gets a mock BlackboardArtifact. * - * @param ds The data source to which the artifact belongs. - * @param artifactId The artifact id. - * @param artType The artifact type. + * @param ds The data source to which the artifact belongs. + * @param artifactId The artifact id. + * @param artType The artifact type. * @param attributeArgs The mapping of attribute type to value for each - * attribute in the artifact. + * attribute in the artifact. * * @return The mock artifact. */ @@ -247,10 +247,10 @@ private BlackboardArtifact getArtifact(DataSource ds, long artifactId, ARTIFACT_ /** * Returns a mock artifact for getRecentlyOpenedDocuments. * - * @param ds The datasource for the artifact. + * @param ds The datasource for the artifact. * @param artifactId The artifact id. - * @param dateTime The time in seconds from epoch. - * @param path The path for the document. + * @param dateTime The time in seconds from epoch. + * @param path The path for the document. * * @return The mock artifact with pertinent attributes. */ @@ -292,13 +292,33 @@ public void getRecentlyOpenedDocuments_sortedByDateTimeAndLimited() throws Sleut } } + @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 public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { DataSource dataSource = TskMockUtils.getDataSource(1); BlackboardArtifact successItem = getRecentDocumentArtifact(dataSource, 1001, DAY_SECONDS, "/a/path"); BlackboardArtifact nullTime = getRecentDocumentArtifact(dataSource, 1002, null, "/a/path2"); - BlackboardArtifact zeroTime = getRecentDocumentArtifact(dataSource, 10021, 0L, "/a/path2a"); + BlackboardArtifact zeroTime = getRecentDocumentArtifact(dataSource, 10021, 0L, "/a/path2a"); List<BlackboardArtifact> artifacts = Arrays.asList(nullTime, zeroTime, successItem); Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts)); @@ -315,11 +335,11 @@ public void getRecentlyOpenedDocuments_filtersMissingData() throws SleuthkitCase /** * Creates a mock blackboard artifact for getRecentDownloads. * - * @param ds The datasource. + * @param ds The datasource. * @param artifactId The artifact id. - * @param dateTime The time in seconds from epoch. - * @param domain The domain. - * @param path The path for the download. + * @param dateTime The time in seconds from epoch. + * @param domain The domain. + * @param path The path for the download. * * @return The mock artifact. */ @@ -368,6 +388,30 @@ public void getRecentDownloads_sortedByDateTimeAndLimited() throws SleuthkitCase } } + @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 public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException { DataSource dataSource = TskMockUtils.getDataSource(1); @@ -409,19 +453,17 @@ private class AttachmentArtifactItem { * Constructor with all parameters. * * @param messageArtifactTypeId The type id for the artifact or null if - * no message artifact to be created. - * @param emailFrom Who the message is from or null not to - * include attribute. - * @param messageTime Time in seconds from epoch or null not - * to include attribute. - * @param fileParentPath The parent AbstractFile's path value. - * @param fileName The parent AbstractFile's filename - * value. - * @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT - * artifact has no attribute (even though - * it is required). - * @param hasParent Whether or not the artifact has a parent - * AbstractFile. + * no message artifact to be created. + * @param emailFrom Who the message is from or null not to include + * attribute. + * @param messageTime Time in seconds from epoch or null not to include + * attribute. + * @param fileParentPath The parent AbstractFile's path value. + * @param fileName The parent AbstractFile's filename value. + * @param associatedAttrFormed If false, the TSK_ASSOCIATED_OBJECT + * artifact has no attribute (even though it is required). + * @param hasParent Whether or not the artifact has a parent + * AbstractFile. */ AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName, @@ -441,14 +483,13 @@ private class AttachmentArtifactItem { * SleuthkitCase assumed. * * @param messageArtifactTypeId The type id for the artifact or null if - * no message artifact to be created. - * @param emailFrom Who the message is from or null not to - * include attribute. - * @param messageTime Time in seconds from epoch or null not - * to include attribute. - * @param fileParentPath The parent AbstractFile's path value. - * @param fileName The parent AbstractFile's filename - * value. + * no message artifact to be created. + * @param emailFrom Who the message is from or null not to include + * attribute. + * @param messageTime Time in seconds from epoch or null not to include + * attribute. + * @param fileParentPath The parent AbstractFile's path value. + * @param fileName The parent AbstractFile's filename value. */ AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) { this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true); @@ -486,11 +527,11 @@ Integer getMessageArtifactTypeId() { /** * Sets up the associated artifact message for the TSK_ASSOCIATED_OBJECT. * - * @param artifacts The mapping of artifact id to artifact. - * @param item The record to setup. - * @param dataSource The datasource. + * @param artifacts The mapping of artifact id to artifact. + * @param item The record to setup. + * @param dataSource The datasource. * @param associatedId The associated attribute id. - * @param artifactId The artifact id. + * @param artifactId The artifact id. * * @return The associated Artifact blackboard attribute. * @@ -504,7 +545,7 @@ private BlackboardAttribute setupAssociatedMessage(Map<Long, BlackboardArtifact> if (item.getMessageArtifactTypeId() == null) { return associatedAttr; } - + // find the artifact type or null if not found ARTIFACT_TYPE messageType = Stream.of(ARTIFACT_TYPE.values()) .filter((artType) -> artType.getTypeID() == item.getMessageArtifactTypeId()) @@ -534,7 +575,7 @@ private BlackboardAttribute setupAssociatedMessage(Map<Long, BlackboardArtifact> * to return pertinent data. * * @param items Each attachment item where each item could represent a - * return result if fully formed. + * return result if fully formed. * * @return The mock SleuthkitCase and Blackboard. */ @@ -678,4 +719,34 @@ public void getRecentAttachments_filterData() throws SleuthkitCaseProviderExcept .toString().equalsIgnoreCase(successItem2Details.getPath())); 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())); + } } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java index 0aff8a56b5b2c8c3d41ec828ab757c8e97933166..c7f3246a4db626630f0f83ac60f32c27750fc4a5 100644 --- a/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/UserActivitySummaryTest.java @@ -66,18 +66,22 @@ * Tests for UserActivitySummary. */ 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> { + /** * A UserActivitySummary method encapsulated in a uniform manner. + * * @param userActivitySummary The UserActivitySummary class to use. * @param datasource The data source. * @param count The count. * @return The list of objects to return. * @throws SleuthkitCaseProviderException - * @throws TskCoreException + * @throws TskCoreException */ List<T> retrieve(UserActivitySummary userActivitySummary, DataSource datasource, int count) throws SleuthkitCaseProviderException, TskCoreException; @@ -117,8 +121,8 @@ private static void verifyCalled(Blackboard mockBlackboard, int artifactType, lo /** * Gets a UserActivitySummary class to test. * - * @param tskCase The SleuthkitCase. - * @param hasTranslation Whether the translation service is functional. + * @param tskCase The SleuthkitCase. + * @param hasTranslation Whether the translation service is functional. * @param translateFunction Function for translation. * * @return The UserActivitySummary class to use for testing. @@ -333,6 +337,28 @@ public void getRecentDevices_limitedToCount() } } + @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) { try { return TskMockUtils.getArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY), artifactId, dataSource, @@ -708,8 +734,8 @@ public void getRecentDomains_limitedAppropriately() * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param dateRcvd The date received in seconds or null to exclude. - * @param dateSent The date sent in seconds or null to exclude. + * @param dateRcvd The date received in seconds or null to exclude. + * @param dateSent The date sent in seconds or null to exclude. * * @return The mock artifact. */ @@ -738,8 +764,8 @@ private static BlackboardArtifact getEmailArtifact(long artifactId, DataSource d * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param dateStart The date start in seconds or null to exclude. - * @param dateEnd The date end in seconds or null to exclude. + * @param dateStart The date start in seconds or null to exclude. + * @param dateEnd The date end in seconds or null to exclude. * * @return The mock artifact. */ @@ -768,8 +794,8 @@ private static BlackboardArtifact getCallogArtifact(long artifactId, DataSource * * @param artifactId The artifact id. * @param dataSource The datasource. - * @param type The account type. - * @param dateSent The date of the message in seconds. + * @param type The account type. + * @param dateSent The date of the message in seconds. */ private static BlackboardArtifact getMessageArtifact(long artifactId, DataSource dataSource, String type, Long dateTime) { List<BlackboardAttribute> attributes = new ArrayList<>(); @@ -794,11 +820,11 @@ private static BlackboardArtifact getMessageArtifact(long artifactId, DataSource /** * Performs a test on UserActivitySummary.getRecentAccounts. * - * @param dataSource The datasource to use as parameter. - * @param count The count to use as a parameter. - * @param retArtifacts The artifacts to return from - * SleuthkitCase.getArtifacts. This method filters - * based on artifact type from the call. + * @param dataSource The datasource to use as parameter. + * @param count The count to use as a parameter. + * @param retArtifacts The artifacts to return from + * SleuthkitCase.getArtifacts. This method filters based on artifact type + * from the call. * @param expectedResults The expected results. * * @throws TskCoreException diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index a842d76f7431114977ca0191bc2c1145756ae8d5..7927d43415af28eb598a640d1d92ef09a4c308ba 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -61,6 +61,7 @@ ExtractOS_progressMessage=Checking for OS ExtractPrefetch_errMsg_prefetchParsingFailed={0}: Error analyzing prefetch files ExtractPrefetch_module_name=Windows Prefetch Extractor ExtractRecycleBin_module_name=Recycle Bin +ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin ExtractSafari_Error_Getting_History=An error occurred while processing Safari history files. ExtractSafari_Error_Parsing_Bookmark=An error occured while processing Safari Bookmark files ExtractSafari_Error_Parsing_Cookies=An error occured while processing Safari Cookies files @@ -84,16 +85,9 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Trusted=Trusted Sites Zone OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. +OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Short-Description=Recent Activity finder ingest module -Browser.name.Microsoft.Edge=Microsoft Edge -Browser.name.Yandex=Yandex -Browser.name.Opera=Opera -Browser.name.SalamWeb=SalamWeb -Browser.name.UC.Browser=UC Browser -Browser.name.Brave=Brave -Browser.name.Google.Chrome=Google Chrome Chrome.moduleName=Chromium Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files. Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files. diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java index adc71704826898f79781418c86e4791a47bb6267..175a47044ef731e9ca06d0ed33931ac398e5d769 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRecycleBin.java @@ -45,6 +45,7 @@ import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -415,6 +416,9 @@ private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID()))); } + @Messages({ + "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin" + }) /** * Create TSK_RECYCLE_BIN artifact type. * @@ -422,9 +426,9 @@ private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, */ private void createRecycleBinArtifactType() throws TskCoreException { try { - tskCase.addBlackboardArtifactType(RECYCLE_BIN_ARTIFACT_NAME, "Recycle Bin"); //NON-NLS - } catch (TskDataException ex) { - logger.log(Level.INFO, String.format("%s may have already been defined for this case", RECYCLE_BIN_ARTIFACT_NAME)); + tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); //NON-NLS + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 9d232cdc841d16bd1b30fc1dadcacdc6baae70bb..13ede70c90c12537af17bd60384583b8f186fac4 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -68,6 +68,7 @@ import java.util.HashSet; import static java.util.Locale.US; import static java.util.TimeZone.getTimeZone; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.recentactivity.ShellBagParser.ShellBag; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT; @@ -1961,18 +1963,11 @@ void createShellBagArtifacts(AbstractFile regFile, List<ShellBag> shellbags) thr */ private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException { if (shellBagArtifactType == null) { - shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME); - - if (shellBagArtifactType == null) { - try { - tskCase.addBlackboardArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); //NON-NLS - } catch (TskDataException ex) { - // Artifact already exists - logger.log(Level.INFO, String.format("%s may have already been defined for this case", SHELLBAG_ARTIFACT_NAME)); - } - - shellBagArtifactType = tskCase.getArtifactType(SHELLBAG_ARTIFACT_NAME); - } + try { + shellBagArtifactType = tskCase.getBlackboard().getOrAddArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name()); + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("Failed to get shell bag artifact type", SHELLBAG_ARTIFACT_NAME), ex); + } } return shellBagArtifactType; @@ -1989,12 +1984,12 @@ private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException { private BlackboardAttribute.Type getLastWriteAttribute() throws TskCoreException { if (shellBagLastWriteAttributeType == null) { try { - shellBagLastWriteAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE, + shellBagLastWriteAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, Bundle.Shellbag_Last_Write_Attribute_Display_Name()); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { // Attribute already exists get it from the case - shellBagLastWriteAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE); + throw new TskCoreException(String.format("Failed to get custom attribute %s", SHELLBAG_ATTRIBUTE_LAST_WRITE), ex); } } return shellBagLastWriteAttributeType; @@ -2011,12 +2006,11 @@ private BlackboardAttribute.Type getLastWriteAttribute() throws TskCoreException private BlackboardAttribute.Type getKeyAttribute() throws TskCoreException { if (shellBagKeyAttributeType == null) { try { - shellBagKeyAttributeType = tskCase.addArtifactAttributeType(SHELLBAG_ATTRIBUTE_KEY, + shellBagKeyAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_KEY, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, Bundle.Shellbag_Key_Attribute_Display_Name()); - } catch (TskDataException ex) { - // The attribute already exists get it from the case - shellBagKeyAttributeType = tskCase.getAttributeType(SHELLBAG_ATTRIBUTE_KEY); + } catch (BlackboardException ex) { + throw new TskCoreException(String.format("Failed to get key attribute %s", SHELLBAG_ATTRIBUTE_KEY), ex); } } return shellBagKeyAttributeType; diff --git a/Testing/build.xml b/Testing/build.xml index f0ba6531a4ad9122aa5bdbaf314f101246e7c27c..f8eff4b82111a8ca3b3ac036cb2699c9ef4b0eab 100644 --- a/Testing/build.xml +++ b/Testing/build.xml @@ -76,6 +76,10 @@ <sysproperty key="solrPort" value="${solrPort}"/> <sysproperty key="messageServiceHost" value="${messageServiceHost}"/> <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}"/> <!--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}"/> diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 6f6e04d7bd5fab2951a84e2d4fcec2785cb2ba6e..f2aa476590858a56029587ada3b983fca55def08 100644 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,9 @@ import junit.framework.TestCase; import org.netbeans.jemmy.Timeouts; 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 @@ -100,6 +103,21 @@ public static Test suite() { public void setUp() { logger.info("######## " + AutopsyTestCases.getEscapedPath(System.getProperty("img_path")) + " #######"); 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); + } } /** diff --git a/docs/doxygen-user/multi-user/installPostgres.dox b/docs/doxygen-user/multi-user/installPostgres.dox index fd7753281be7433c37a38f5b69b07cb1c94d3d7d..d56afe931b8b34e739e37f0ec96f116ad0f54533 100644 --- a/docs/doxygen-user/multi-user/installPostgres.dox +++ b/docs/doxygen-user/multi-user/installPostgres.dox @@ -13,7 +13,7 @@ You should ensure that the database folder is backed up. To install PostgreSQL, perform the following steps: -1. Download a 64-bit PostgreSQL installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_. Autopsy has been tested with PostgreSQL version 9.5. +1. Download a 64-bit PostgreSQL installer from https://www.enterprisedb.com/downloads/postgres-postgresql-downloads Choose one under Windows x86-64. Autopsy has been tested with PostgreSQL version 9.5. 2. Run the installer. The name will be similar to _postgresql-9.5.3-1-windows-x64.exe_. diff --git a/test/script/regression.py b/test/script/regression.py index 9999a5c5635604f938bca935a145c75c476783de..94b50c244f6be0a70b2a0ff6d67fbfa1f74f24f8 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -471,6 +471,10 @@ def _run_ant(test_data): test_data.ant.append("-DsolrPort=" + str(test_config.solrPort)) test_data.ant.append("-DmessageServiceHost=" + test_config.messageServiceHost) 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: 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. @@ -854,6 +858,14 @@ def _load_config_file(self, config_file): self.messageServicePort = parsed_config.getElementsByTagName("messageServicePort")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("multiUser_outdir"): 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_build_info(parsed_config) diff --git a/test/script/regression_utils.py b/test/script/regression_utils.py index 51fa3eb1c4a2a3f2c9296368af25e2ea8fe8b132..0c0229beb24a3ecf96c94f188d830144aacc012f 100644 --- a/test/script/regression_utils.py +++ b/test/script/regression_utils.py @@ -27,7 +27,7 @@ def make_os_path(platform, *dirs): path += str(dir).replace('\\', '/') + '/' return path_fix(path) elif platform == "win32": - return make_path(dirs) + return make_path(*dirs) else: print("Couldn't make path, because we only support Windows and Cygwin at this time.") sys.exit(1) diff --git a/thirdparty/iLeapp/ileapp.exe b/thirdparty/iLeapp/ileapp.exe index 8176b4f6793a424a0e9d94a3b0796f9497f59f9d..d17ab28f4d1018e2ebc1e08f15a4427b14015b7c 100644 Binary files a/thirdparty/iLeapp/ileapp.exe and b/thirdparty/iLeapp/ileapp.exe differ diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java index d662c5f5e65b3158d349d518a00ed47a0c893a4a..886a3bc41f5a5069969093cd6492641a55a2cb68 100755 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/VcardParser.java @@ -53,6 +53,7 @@ import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountFileInstance; import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; @@ -421,14 +422,16 @@ private void addPhoneAttributes(Telephone telephone, AbstractFile abstractFile, if (attributeType == null) { try{ // Add this attribute type to the case database. - attributeType = tskCase.addArtifactAttributeType(attributeTypeName, + attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, String.format("Phone Number (%s)", StringUtils.capitalize(splitType.toLowerCase()))); - }catch (TskDataException ex) { - attributeType = tskCase.getAttributeType(attributeTypeName); + + ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); + }catch (BlackboardException ex) { + logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } } - ThunderbirdMboxFileIngestModule.addArtifactAttribute(telephoneText, attributeType, attributes); + } catch (TskCoreException ex) { logger.log(Level.WARNING, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } @@ -474,14 +477,14 @@ private void addEmailAttributes(Email email, AbstractFile abstractFile, Collecti BlackboardAttribute.Type attributeType = tskCase.getAttributeType(attributeTypeName); if (attributeType == null) { // Add this attribute type to the case database. - attributeType = tskCase.addArtifactAttributeType(attributeTypeName, + attributeType = tskCase.getBlackboard().getOrAddAttributeType(attributeTypeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, String.format("Email (%s)", StringUtils.capitalize(splitType.toLowerCase()))); } ThunderbirdMboxFileIngestModule.addArtifactAttribute(email.getValue(), attributeType, attributes); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Unable to retrieve attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); - } catch (TskDataException ex) { + } catch (BlackboardException ex) { logger.log(Level.SEVERE, String.format("Unable to add custom attribute type '%s' for file '%s' (id=%d).", attributeTypeName, abstractFile.getName(), abstractFile.getId()), ex); } }