Merge branch 'develop' of github.com:sleuthkit/autopsy into solr-8-upgrade

This commit is contained in:
Eugene Livis
2020-11-02 12:30:29 -05:00
60 changed files with 7608 additions and 584 deletions
+44 -2
View File
@@ -162,7 +162,7 @@
<get src="https://drive.google.com/uc?id=1ns2olaWsBu_c4EoE4Seh8t_B3U5RnLKd" dest="${test-input}/CommonFilesAttrs_img1_v1.vhd" skipexisting="true"/>
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />
@@ -195,5 +195,47 @@
<globmapper from="*" to="*-MERGED"/>
</copy>
</target>
<!--sets up integration test system properties, calls underlying test-init and then sets up the pathing jar-->
<target name="test-init" depends="projectized-common.test-init,getTestDataFiles,qa-functional-pathing-jar" />
<!--
The paths specified in 'module.run.classpath' are incorporated into the manifest of a jar and then the path to the
jar is used as part of the classpath for '-do-junit' instead of 'module.run.classpath'. This was done to prevent
classpath length issues on windows. More information on this technique can be found here:
https://stackoverflow.com/a/201969.
-->
<target name="qa-functional-pathing-jar" depends="projectized-common.test-init">
<sequential>
<!--set up pathing jar based on module.run.classpath as classpath-->
<path id="test.qa-functional.pathing-jar.module-cp.classpath" path="${module.run.classpath}"/>
<pathconvert pathsep=" " refid="test.qa-functional.pathing-jar.module-cp.classpath" property="test.qa-functional.pathing-jar.module-cp.classpathstr"/>
<property name="test.qa-functional.pathing-jar.module-cp.loc" value="${cluster}/test.qa-functional.pathing.module-cp.jar"/>
<jar destfile="${test.qa-functional.pathing-jar.module-cp.loc}">
<manifest>
<attribute name="Class-Path" value="${test.qa-functional.pathing-jar.module-cp.classpathstr}"/>
</manifest>
</jar>
<!--grab properties from common.xml:test-init so that "test.qa-functional.run.cp" can be properly formed-->
<property name="build.test.qa-functional.dir" location="${build.dir}/test/qa-functional"/>
<property name="build.test.qa-functional.classes.dir" location="${build.test.qa-functional.dir}/classes"/>
<property name="test.qa-functional.cp.extra" value=""/>
<!--set up "test.qa-functional.run.cp" to be used by common.xml:-do-junit-->
<path id="test.qa-functional.run.cp">
<pathelement path="${build.test.qa-functional.classes.dir}"/>
<!-- Cannot use <path refid="cp"/> since that uses ${module.classpath} and we want ${module.run.classpath}: -->
<pathelement path="${test.qa-functional.runtime.cp}"/>
<pathelement path="${cp.extra}"/>
<pathelement location="${cluster}/${module.jar}"/>
<path refid="test.unit.lib.cp"/>
<!-- for compatibility with property based classpath-->
<pathelement path="${test.qa-functional.pathing-jar.module-cp.loc}"/>
<pathelement path="${test.qa-functional.run.cp.extra}"/>
<pathelement path="${test.qa-functional.cp.extra}"/>
<pathelement path="${test.extra.nb.javac.deps}"/>
</path>
</sequential>
</target>
</project>
@@ -96,7 +96,7 @@ Metadata.tableRowTitle.mimeType=MIME Type
Metadata.tableRowTitle.name=Name
Metadata.tableRowTitle.sectorSize=Sector Size
Metadata.tableRowTitle.sha1=SHA1
Metadata.tableRowTitle.sha256=SHA256
Metadata.tableRowTitle.sha256=SHA-256
Metadata.tableRowTitle.size=Size
Metadata.tableRowTitle.fileNameAlloc=File Name Allocation
Metadata.tableRowTitle.metadataAlloc=Metadata Allocation
@@ -137,7 +137,7 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
"Metadata.tableRowTitle.mimeType=MIME Type",
"Metadata.nodeText.truncated=(results truncated)",
"Metadata.tableRowTitle.sha1=SHA1",
"Metadata.tableRowTitle.sha256=SHA256",
"Metadata.tableRowTitle.sha256=SHA-256",
"Metadata.tableRowTitle.imageType=Type",
"Metadata.tableRowTitle.sectorSize=Sector Size",
"Metadata.tableRowTitle.timezone=Time Zone",
@@ -182,6 +182,11 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha256 = file.getSha256Hash();
if (sha256 == null) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.hashLookupResults"), file.getKnown().toString());
addAcquisitionDetails(sb, dataSource);
@@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.contentviewers;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
@@ -97,6 +98,9 @@ final class PDFViewer implements FileTypeViewer {
// Add the IcePDF view to the center of our container.
this.container.add(icePdfPanel, BorderLayout.CENTER);
// Disable all components until the document is ready to view.
enableComponents(container, false);
// Document is the 'M' in IcePDFs MVC set up. Read the data needed to
// populate the model in the background.
@@ -122,12 +126,13 @@ final class PDFViewer implements FileTypeViewer {
// will cause UI widgets to be updated.
try {
Document doc = get();
controller.openDocument(doc, null);
controller.openDocument(doc, file.getName());
// This makes the PDF viewer appear as one continuous
// document, which is the default for most popular PDF viewers.
controller.setPageViewMode(DocumentViewControllerImpl.ONE_COLUMN_VIEW, true);
// This makes it possible to select text by left clicking and dragging.
controller.setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_TEXT_SELECTION);
enableComponents(container, true);
} catch (InterruptedException ex) {
// Do nothing.
} catch (ExecutionException ex) {
@@ -140,10 +145,28 @@ final class PDFViewer implements FileTypeViewer {
file.getId(), file.getName()), ex);
showErrorDialog();
}
} catch (Throwable ex) {
logger.log(Level.WARNING, String.format("PDF content viewer "
+ "was unable to open document with id %d and name %s",
file.getId(), file.getName()), ex);
}
}
}.execute();
}
/**
* Recursively enable/disable all components in this content viewer.
* This will disable/enable all internal IcePDF Swing components too.
*/
private void enableComponents(Container container, boolean enabled) {
Component[] components = container.getComponents();
for(Component component : components) {
component.setEnabled(enabled);
if (component instanceof Container) {
enableComponents((Container)component, enabled);
}
}
}
@Override
public Component getComponent() {
@@ -278,6 +278,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
"AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)",
"AbstractAbstractFileNode.knownColLbl=Known",
"AbstractAbstractFileNode.md5HashColLbl=MD5 Hash",
"AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash",
"AbstractAbstractFileNode.objectId=Object ID",
"AbstractAbstractFileNode.mimeType=MIME Type",
"AbstractAbstractFileNode.extensionColLbl=Extension"})
@@ -305,6 +306,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
TYPE_META(AbstractAbstractFileNode_typeMetaColLbl()),
KNOWN(AbstractAbstractFileNode_knownColLbl()),
MD5HASH(AbstractAbstractFileNode_md5HashColLbl()),
SHA256HASH(AbstractAbstractFileNode_sha256HashColLbl()),
ObjectID(AbstractAbstractFileNode_objectId()),
MIMETYPE(AbstractAbstractFileNode_mimeType()),
EXTENSION(AbstractAbstractFileNode_extensionColLbl());
@@ -358,6 +360,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName()));
properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash())));
properties.add(new NodeProperty<>(SHA256HASH.toString(), SHA256HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getSha256Hash())));
properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType())));
properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension()));
@@ -577,6 +580,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
map.put(KNOWN.toString(), content.getKnown().getName());
map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
map.put(SHA256HASH.toString(), StringUtils.defaultString(content.getSha256Hash()));
map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
map.put(EXTENSION.toString(), content.getNameExtension());
}
@@ -30,6 +30,7 @@ AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time
AbstractAbstractFileNode.nameColLbl=Name
AbstractAbstractFileNode.objectId=Object ID
AbstractAbstractFileNode.originalName=Original Name
AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash
AbstractAbstractFileNode.sizeColLbl=Size
AbstractAbstractFileNode.tagsProperty.displayName=Tags
AbstractAbstractFileNode.typeDirColLbl=Type(Dir)
@@ -529,9 +529,11 @@ final public class Accounts implements AutopsyVisitableItem {
+ getRejectedArtifactFilterClause(); //NON-NLS
try (SleuthkitCase.CaseDbQuery results = skCase.executeQuery(query);
ResultSet rs = results.getResultSet();) {
List<Long> tempList = new ArrayList<>();
while (rs.next()) {
list.add(rs.getLong("artifact_id")); //NON-NLS
tempList.add(rs.getLong("artifact_id")); // NON-NLS
}
list.addAll(tempList);
} catch (TskCoreException | SQLException ex) {
LOGGER.log(Level.SEVERE, "Error querying for account artifacts.", ex); //NON-NLS
}
@@ -398,6 +398,20 @@ final class DataSourceInfoUtilities {
BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType);
return (attr == null) ? null : attr.getValueLong();
}
/**
* Retrieves the int value of a certain attribute type from an artifact.
*
* @param artifact The artifact.
* @param attributeType The attribute type.
*
* @return The 'getValueInt()' value or null if the attribute could not be
* retrieved.
*/
static Integer getIntOrNull(BlackboardArtifact artifact, Type attributeType) {
BlackboardAttribute attr = getAttributeOrNull(artifact, attributeType);
return (attr == null) ? null : attr.getValueInt();
}
/**
* Retrieves the long value of a certain attribute type from an artifact and
@@ -118,7 +118,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
dataSource,
DATETIME_ATT,
DataSourceInfoUtilities.SortOrder.DESCENDING,
10);
maxCount);
List<RecentFileDetails> fileDetails = new ArrayList<>();
for (BlackboardArtifact artifact : artifactList) {
@@ -134,12 +134,11 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
} else if (attribute.getAttributeType().equals(PATH_ATT)) {
path = attribute.getValueString();
}
if (accessedTime != null) {
fileDetails.add(new RecentFileDetails(path, accessedTime));
}
}
if (accessedTime != null && accessedTime != 0) {
fileDetails.add(new RecentFileDetails(path, accessedTime));
}
}
return fileDetails;
@@ -190,7 +189,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
path = attribute.getValueString();
}
}
if (accessedTime != null) {
if (accessedTime != null && accessedTime != 0L) {
fileDetails.add(new RecentDownloadDetails(path, accessedTime, domain));
}
}
@@ -215,6 +214,10 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
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);
}
@@ -241,7 +244,7 @@ public class RecentFilesSummary implements DefaultArtifactUpdateGovernor {
}
BlackboardArtifact messageArtifact = skCase.getBlackboardArtifact(attribute.getValueLong());
if (isMessageArtifact(messageArtifact)) {
if (messageArtifact != null && isMessageArtifact(messageArtifact)) {
Content content = artifact.getParent();
if (content instanceof AbstractFile) {
String sender;
@@ -1,370 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Provides information to populate Top Programs Summary queries.
*/
public class TopProgramsSummary implements DefaultArtifactUpdateGovernor {
private static final Set<Integer> ARTIFACT_UPDATE_TYPE_IDS = new HashSet<>(Arrays.asList(
ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID()
));
/**
* A SQL join type.
*/
private enum JoinType {
LEFT,
RIGHT,
INNER,
OUTER
}
/**
* A blackboard attribute value column.
*/
private enum AttributeColumn {
value_text,
value_int32,
value_int64
}
/**
* The suffix joined to a key name for use as an identifier of a query.
*/
private static final String QUERY_SUFFIX = "_query";
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
/**
* Creates a sql statement querying the blackboard attributes table for a
* particular attribute type and returning a specified value. That query
* also joins with the blackboard artifact table.
*
* @param joinType The type of join statement to create.
* @param attributeColumn The blackboard attribute column that should be
* returned.
* @param attrType The attribute type to query for.
* @param keyName The aliased name of the attribute to return. This
* is also used to calculate the alias of the query
* same as getFullKey.
* @param bbaName The blackboard artifact table alias.
*
* @return The generated sql statement.
*/
private static String getAttributeJoin(JoinType joinType, AttributeColumn attributeColumn, BlackboardAttribute.ATTRIBUTE_TYPE attrType, String keyName, String bbaName) {
String queryName = keyName + QUERY_SUFFIX;
String innerQueryName = "inner_attribute_" + queryName;
return "\n" + joinType + " JOIN (\n"
+ " SELECT \n"
+ " " + innerQueryName + ".artifact_id,\n"
+ " " + innerQueryName + "." + attributeColumn + " AS " + keyName + "\n"
+ " FROM blackboard_attributes " + innerQueryName + "\n"
+ " WHERE " + innerQueryName + ".attribute_type_id = " + attrType.getTypeID() + " -- " + attrType.name() + "\n"
+ ") " + queryName + " ON " + queryName + ".artifact_id = " + bbaName + ".artifact_id\n";
}
/**
* Given a column key, creates the full name for the column key.
*
* @param key The column key.
*
* @return The full identifier for the column key.
*/
private static String getFullKey(String key) {
return key + QUERY_SUFFIX + "." + key;
}
/**
* Constructs a SQL 'where' statement from a list of clauses and puts
* parenthesis around each clause.
*
* @param clauses The clauses
*
* @return The generated 'where' statement.
*/
private static String getWhereString(List<String> clauses) {
if (clauses.isEmpty()) {
return "";
}
List<String> parenthesized = clauses.stream()
.map(c -> "(" + c + ")")
.collect(Collectors.toList());
return "\nWHERE " + String.join("\n AND ", parenthesized) + "\n";
}
/**
* Generates a [column] LIKE sql clause.
*
* @param column The column identifier.
* @param likeString The string that will be used as column comparison.
* @param isLike if false, the statement becomes NOT LIKE.
*
* @return The generated statement.
*/
private static String getLikeClause(String column, String likeString, boolean isLike) {
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
}
private final SleuthkitCaseProvider provider;
public TopProgramsSummary() {
this(SleuthkitCaseProvider.DEFAULT);
}
public TopProgramsSummary(SleuthkitCaseProvider provider) {
this.provider = provider;
}
@Override
public Set<Integer> getArtifactTypeIdsForRefresh() {
return ARTIFACT_UPDATE_TYPE_IDS;
}
/**
* Retrieves a list of the top programs used on the data source. Currently
* determines this based off of which prefetch results return the highest
* count.
*
* @param dataSource The data source.
* @param count The number of programs to return.
*
* @return The top results objects found.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
* @throws SQLException
*/
public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count)
throws SleuthkitCaseProviderException, TskCoreException, SQLException {
if (dataSource == null || count <= 0) {
return Collections.emptyList();
}
// ntosboot should be ignored
final String ntosBootIdentifier = "NTOSBOOT";
// programs in windows directory to be ignored
final String windowsDir = "/WINDOWS%";
final String nameParam = "name";
final String pathParam = "path";
final String runCountParam = "run_count";
final String lastRunParam = "last_run";
String bbaQuery = "bba";
final String query = "SELECT\n"
+ " " + getFullKey(nameParam) + " AS " + nameParam + ",\n"
+ " " + getFullKey(pathParam) + " AS " + pathParam + ",\n"
+ " MAX(" + getFullKey(runCountParam) + ") AS " + runCountParam + ",\n"
+ " MAX(" + getFullKey(lastRunParam) + ") AS " + lastRunParam + "\n"
+ "FROM blackboard_artifacts " + bbaQuery + "\n"
+ getAttributeJoin(JoinType.INNER, AttributeColumn.value_text, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, nameParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_text, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, pathParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int32, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COUNT, runCountParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int64, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, lastRunParam, bbaQuery)
+ getWhereString(Arrays.asList(
bbaQuery + ".artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(),
bbaQuery + ".data_source_obj_id = " + dataSource.getId(),
// exclude ntosBootIdentifier from results
getLikeClause(getFullKey(nameParam), ntosBootIdentifier, false),
// exclude windows directory items from results
getFullKey(pathParam) + " IS NULL OR " + getLikeClause(getFullKey(pathParam), windowsDir, false)
))
+ "GROUP BY " + getFullKey(nameParam) + ", " + getFullKey(pathParam) + "\n"
+ "ORDER BY \n"
+ " MAX(" + getFullKey(runCountParam) + ") DESC,\n"
+ " MAX(" + getFullKey(lastRunParam) + ") DESC,\n"
+ " " + getFullKey(nameParam) + " ASC";
DataSourceInfoUtilities.ResultSetHandler<List<TopProgramsResult>> handler = (resultSet) -> {
List<TopProgramsResult> progResults = new ArrayList<>();
boolean quitAtCount = false;
while (resultSet.next() && (!quitAtCount || progResults.size() < count)) {
long lastRunEpoch = resultSet.getLong(lastRunParam);
Date lastRun = (resultSet.wasNull()) ? null : new Date(lastRunEpoch * 1000);
Long runCount = resultSet.getLong(runCountParam);
if (resultSet.wasNull()) {
runCount = null;
}
if (lastRun != null || runCount != null) {
quitAtCount = true;
}
progResults.add(new TopProgramsResult(
resultSet.getString(nameParam),
resultSet.getString(pathParam),
runCount,
lastRun));
}
return progResults;
};
try (SleuthkitCase.CaseDbQuery dbQuery = provider.get().executeQuery(query);
ResultSet resultSet = dbQuery.getResultSet()) {
return handler.process(resultSet);
}
}
/**
* Determines a short folder name if any. Otherwise, returns empty string.
*
* @param strPath The string path.
* @param applicationName The application name.
*
* @return The short folder name or empty string if not found.
*/
public String getShortFolderName(String strPath, String applicationName) {
if (strPath == null) {
return "";
}
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
File file = new File(strPath);
while (file != null && StringUtils.isNotBlank(file.getName())) {
pathEls.add(file.getName());
file = file.getParentFile();
}
Collections.reverse(pathEls);
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
String result = matchEntry.apply(pathEls);
if (StringUtils.isNotBlank(result)) {
return result;
}
}
return "";
}
/**
* Describes a result of a program run on a datasource.
*/
public static class TopProgramsResult {
private final String programName;
private final String programPath;
private final Long runTimes;
private final Date lastRun;
/**
* Main constructor.
*
* @param programName The name of the program.
* @param programPath The path of the program.
* @param runTimes The number of runs.
*/
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
this.programName = programName;
this.programPath = programPath;
this.runTimes = runTimes;
this.lastRun = lastRun;
}
/**
* @return The name of the program
*/
public String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
public String getProgramPath() {
return programPath;
}
/**
* @return The number of run times or null if not present.
*/
public Long getRunTimes() {
return runTimes;
}
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
}
}
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.io.File;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
import java.util.ArrayList;
import java.util.Arrays;
@@ -30,6 +31,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -54,6 +56,36 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
*/
public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
private static final BlackboardArtifact.Type TYPE_DEVICE_ATTACHED = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED);
private static final BlackboardArtifact.Type TYPE_WEB_HISTORY = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WEB_HISTORY);
@@ -69,17 +101,51 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
private static final BlackboardAttribute.Type TYPE_DATETIME_START = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START);
private static final BlackboardAttribute.Type TYPE_DATETIME_END = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_END);
private static final BlackboardAttribute.Type TYPE_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN);
private static final BlackboardAttribute.Type TYPE_PROG_NAME = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME);
private static final BlackboardAttribute.Type TYPE_PATH = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH);
private static final BlackboardAttribute.Type TYPE_COUNT = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_COUNT);
private static final String NTOS_BOOT_IDENTIFIER = "NTOSBOOT";
private static final String WINDOWS_PREFIX = "/WINDOWS";
private static final Comparator<TopAccountResult> TOP_ACCOUNT_RESULT_DATE_COMPARE = (a, b) -> a.getLastAccess().compareTo(b.getLastAccess());
private static final Comparator<TopWebSearchResult> TOP_WEBSEARCH_RESULT_DATE_COMPARE = (a, b) -> a.getDateAccessed().compareTo(b.getDateAccessed());
/**
* Sorts TopProgramsResults pushing highest run time count then most recent
* run and then the program name that comes earliest in the alphabet.
*/
private static final Comparator<TopProgramsResult> TOP_PROGRAMS_RESULT_COMPARE = (a, b) -> {
// first priority for sorting is the run times
// if non-0, this is the return value for the comparator
int runTimesCompare = nullableCompare(a.getRunTimes(), b.getRunTimes());
if (runTimesCompare != 0) {
return -runTimesCompare;
}
// second priority for sorting is the last run date
// if non-0, this is the return value for the comparator
int lastRunCompare = nullableCompare(
a.getLastRun() == null ? null : a.getLastRun().getTime(),
b.getLastRun() == null ? null : b.getLastRun().getTime());
if (lastRunCompare != 0) {
return -lastRunCompare;
}
// otherwise sort alphabetically
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(),
ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(),
ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID(),
ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()
ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID(),
ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID()
));
private static final Set<String> DEVICE_EXCLUDE_LIST = new HashSet<>(Arrays.asList("ROOT_HUB", "ROOT_HUB20"));
@@ -539,6 +605,189 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
.collect(Collectors.toList());
}
/**
* Determines a short folder name if any. Otherwise, returns empty string.
*
* @param strPath The string path.
* @param applicationName The application name.
*
* @return The short folder name or empty string if not found.
*/
public String getShortFolderName(String strPath, String applicationName) {
if (strPath == null) {
return "";
}
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
File file = new File(strPath);
while (file != null && org.apache.commons.lang.StringUtils.isNotBlank(file.getName())) {
pathEls.add(file.getName());
file = file.getParentFile();
}
Collections.reverse(pathEls);
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
String result = matchEntry.apply(pathEls);
if (org.apache.commons.lang.StringUtils.isNotBlank(result)) {
return result;
}
}
return "";
}
/**
* Creates a TopProgramsResult from a TSK_PROG_RUN blackboard artifact.
*
* @param artifact The TSK_PROG_RUN blackboard artifact.
*
* @return The generated TopProgramsResult.
*/
private TopProgramsResult getTopProgramsResult(BlackboardArtifact artifact) {
String programName = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_PROG_NAME);
// ignore items with no name or a ntos boot identifier
if (StringUtils.isBlank(programName) || NTOS_BOOT_IDENTIFIER.equalsIgnoreCase(programName)) {
return null;
}
String path = DataSourceInfoUtilities.getStringOrNull(artifact, TYPE_PATH);
// ignore windows directory
if (StringUtils.startsWithIgnoreCase(path, WINDOWS_PREFIX)) {
return null;
}
Integer count = DataSourceInfoUtilities.getIntOrNull(artifact, TYPE_COUNT);
Long longCount = (count == null) ? null : (long) count;
return new TopProgramsResult(
programName,
path,
longCount,
DataSourceInfoUtilities.getDateOrNull(artifact, TYPE_DATETIME)
);
}
/**
* Retrieves the maximum date given two (possibly null) dates.
*
* @param date1 First date.
* @param date2 Second date.
*
* @return The maximum non-null date or null if both items are null.
*/
private static Date getMax(Date date1, Date date2) {
if (date1 == null) {
return date2;
} else if (date2 == null) {
return date1;
} else {
return date1.compareTo(date2) > 0 ? date1 : date2;
}
}
/**
* Returns the compare value favoring the higher non-null number.
*
* @param long1 First possibly null long.
* @param long2 Second possibly null long.
*
* @return Returns the compare value: 1,0,-1 favoring the higher non-null
* value.
*/
private static int nullableCompare(Long long1, Long long2) {
if (long1 == null && long2 == null) {
return 0;
} else if (long1 != null && long2 == null) {
return 1;
} else if (long1 == null && long2 != null) {
return -1;
}
return Long.compare(long1, long2);
}
/**
* Returns true if number is non-null and higher than 0.
*
* @param longNum The number.
*
* @return True if non-null and higher than 0.
*/
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
* of the list. If that information isn't available the last run date is
* used. If both, the last run date and the number of run times are
* unavailable, the programs will be sorted alphabetically, the count will
* 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.
*
* @return The sorted list and limited to the count if last run or run count
* information is available on any item.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
public List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) throws SleuthkitCaseProviderException, TskCoreException {
assertValidCount(count);
if (dataSource == null) {
return Collections.emptyList();
}
// Get TopProgramsResults for each TSK_PROG_RUN artifact
Collection<TopProgramsResult> results = caseProvider.get().getBlackboard().getArtifacts(ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(), dataSource.getId())
.stream()
// convert to a TopProgramsResult object or null if missing critical information
.map((art) -> getTopProgramsResult(art))
// remove any null items
.filter((res) -> res != null)
// group by the program name and program path
// 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 -> res,
(res1, res2) -> {
return new TopProgramsResult(
res1.getProgramName(),
res1.getProgramPath(),
getMax(res1.getRunTimes(), res2.getRunTimes()),
getMax(res1.getLastRun(), res2.getLastRun()));
})).values();
List<TopProgramsResult> orderedResults = results.stream()
.sorted(TOP_PROGRAMS_RESULT_COMPARE)
.collect(Collectors.toList());
// only limit the list to count if there is no last run date and no run times.
if (!orderedResults.isEmpty()) {
TopProgramsResult topResult = orderedResults.get(0);
// if run times / last run information is available, the first item should have some value,
// and then the items should be limited accordingly.
if (isPositiveNum(topResult.getRunTimes())
|| (topResult.getLastRun() != null && isPositiveNum(topResult.getLastRun().getTime()))) {
return orderedResults.stream().limit(count).collect(Collectors.toList());
}
}
// otherwise return the alphabetized list with no limit applied.
return orderedResults;
}
/**
* Object containing information about a web search artifact.
*/
@@ -722,4 +971,57 @@ public class UserActivitySummary implements DefaultArtifactUpdateGovernor {
return lastVisit;
}
}
/**
* Describes a result of a program run on a datasource.
*/
public static class TopProgramsResult {
private final String programName;
private final String programPath;
private final Long runTimes;
private final Date lastRun;
/**
* Main constructor.
*
* @param programName The name of the program.
* @param programPath The path of the program.
* @param runTimes The number of runs.
*/
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
this.programName = programName;
this.programPath = programPath;
this.runTimes = runTimes;
this.lastRun = lastRun;
}
/**
* @return The name of the program
*/
public String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
public String getProgramPath() {
return programPath;
}
/**
* @return The number of run times or null if not present.
*/
public Long getRunTimes() {
return runTimes;
}
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
}
}
@@ -29,12 +29,11 @@ import org.apache.commons.lang.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.IngestModuleCheckUtil;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopAccountResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDeviceAttachedResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopWebSearchResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsSummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopDomainsResult;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.UserActivitySummary.TopProgramsResult;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModelTableCellRenderer.DefaultCellModel;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents;
import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel;
@@ -227,35 +226,30 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
private final IngestRunningLabel ingestRunningLabel = new IngestRunningLabel();
private final List<DataFetchComponents<DataSource, ?>> dataFetchComponents;
private final TopProgramsSummary topProgramsData;
private final UserActivitySummary userActivityData;
/**
* Creates a new UserActivityPanel.
*/
public UserActivityPanel() {
this(new TopProgramsSummary(), new UserActivitySummary());
this(new UserActivitySummary());
}
/**
* Creates a new UserActivityPanel.
*
* @param topProgramsData Class from which to obtain top programs data.
* @param userActivityData Class from which to obtain remaining user
* activity data.
*/
public UserActivityPanel(
TopProgramsSummary topProgramsData,
UserActivitySummary userActivityData) {
super(topProgramsData, userActivityData);
this.topProgramsData = topProgramsData;
public UserActivityPanel(UserActivitySummary userActivityData) {
super(userActivityData);
this.userActivityData = userActivityData;
// set up data acquisition methods
this.dataFetchComponents = Arrays.asList(
// top programs query
new DataFetchComponents<DataSource, List<TopProgramsResult>>(
(dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
(dataSource) -> userActivityData.getTopPrograms(dataSource, TOP_PROGS_COUNT),
(result) -> {
showResultWithModuleCheck(topProgramsTable, result,
IngestModuleCheckUtil.RECENT_ACTIVITY_FACTORY,
@@ -307,7 +301,7 @@ public class UserActivityPanel extends BaseDataSourceSummaryPanel {
* @return The underlying short folder name if one exists.
*/
private String getShortFolderName(String path, String appName) {
return this.topProgramsData.getShortFolderName(path, appName);
return this.userActivityData.getShortFolderName(path, appName);
}
@Override
@@ -49,6 +49,7 @@ SizeSearchPanel.sizeCompareComboBox.lessThan=less than
FileSearchPanel.searchButton.text=Search
MimeTypePanel.mimeTypeCheckBox.text=MIME Type:
HashSearchPanel.md5CheckBox.text=MD5:
Sha256HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.emptyHashMsg.text=Must enter something for hash search.
FileSearchPanel.errorLabel.text=\
DataSourcePanel.dataSourceCheckBox.label=Data Source:
@@ -58,3 +59,5 @@ DataSourcePanel.dataSourceNoteLabel.text=*Note: Multiple data sources can be sel
DateSearchPanel.noLimitLabel.text=*Empty fields mean "No Limit"
DateSearchPanel.dateFormatLabel.text=*The date format is mm/dd/yyyy
MimeTypePanel.noteLabel.text=*Note: Multiple MIME types can be selected
HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.sha256TextField.text=
@@ -5,7 +5,9 @@ FileSearchPanel.emptyNode.display.text=No results found.
HashSearchFilter.errorMessage.emptyHash=Hash data is empty.
HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters.
# {0} - hash data length
HashSearchFilter.errorMessage.wrongLength=Input length({0}), doesn''t match the MD5 length(32).
HashSearchFilter.errorMessage.wrongLengthMd5=Input length({0}), doesn''t match the MD5 length(32).
# {0} - hash data length
HashSearchFilter.errorMessage.wrongLengthSha256=Input length({0}), doesn''t match the SHA-256 length(64).
KnownStatusSearchFilter.errorMessage.noKnownStatusCheckboxSelected=At least one known status checkbox must be selected.
MimeTypeFilter.errorMessage.emptyMimeType=At least one MIME type must be selected.
NameSearchFilter.errorMessage.emtpyName=Please input a name to search.
@@ -63,6 +65,7 @@ SizeSearchPanel.sizeCompareComboBox.lessThan=less than
FileSearchPanel.searchButton.text=Search
MimeTypePanel.mimeTypeCheckBox.text=MIME Type:
HashSearchPanel.md5CheckBox.text=MD5:
Sha256HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.emptyHashMsg.text=Must enter something for hash search.
FileSearchPanel.errorLabel.text=\
DataSourcePanel.dataSourceCheckBox.label=Data Source:
@@ -72,3 +75,5 @@ DataSourcePanel.dataSourceNoteLabel.text=*Note: Multiple data sources can be sel
DateSearchPanel.noLimitLabel.text=*Empty fields mean "No Limit"
DateSearchPanel.dateFormatLabel.text=*The date format is mm/dd/yyyy
MimeTypePanel.noteLabel.text=*Note: Multiple MIME types can be selected
HashSearchPanel.sha256CheckBox.text=SHA-256:
HashSearchPanel.sha256TextField.text=
@@ -41,18 +41,36 @@ class HashSearchFilter extends AbstractFileSearchFilter<HashSearchPanel> {
@Override
public boolean isEnabled() {
return this.getComponent().getHashCheckBox().isSelected();
return (this.getComponent().getMd5HashCheckBox().isSelected()
|| this.getComponent().getSha256HashCheckBox().isSelected());
}
@Override
public String getPredicate() throws FilterValidationException {
String md5Hash = this.getComponent().getSearchTextField().getText();
String predicate = "";
if (this.getComponent().getMd5HashCheckBox().isSelected()) {
String md5Hash = this.getComponent().getMd5TextField().getText();
if (md5Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
if (md5Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
}
predicate = "md5 = '" + md5Hash.toLowerCase() + "'"; //NON-NLS
}
if (this.getComponent().getSha256HashCheckBox().isSelected()) {
String sha256Hash = this.getComponent().getSha256TextField().getText();
if (sha256Hash.isEmpty()) {
throw new FilterValidationException(EMPTY_HASH_MESSAGE);
}
if (predicate.isEmpty()) {
predicate = "sha256 = '" + sha256Hash.toLowerCase() + "'"; //NON-NLS
} else {
predicate = "( " + predicate + " AND sha256 = '" + sha256Hash.toLowerCase() + "')"; //NON-NLS
}
}
return "md5 = '" + md5Hash.toLowerCase() + "'"; //NON-NLS
return predicate;
}
@Override
@@ -63,23 +81,45 @@ class HashSearchFilter extends AbstractFileSearchFilter<HashSearchPanel> {
@Override
@Messages({
"HashSearchFilter.errorMessage.emptyHash=Hash data is empty.",
"# {0} - hash data length", "HashSearchFilter.errorMessage.wrongLength=Input length({0}), doesn''t match the MD5 length(32).",
"# {0} - hash data length",
"HashSearchFilter.errorMessage.wrongLengthMd5=Input length({0}), doesn''t match the MD5 length(32).",
"# {0} - hash data length",
"HashSearchFilter.errorMessage.wrongLengthSha256=Input length({0}), doesn''t match the SHA-256 length(64).",
"HashSearchFilter.errorMessage.wrongCharacter=MD5 contains invalid hex characters."
})
public boolean isValid() {
String inputHashData = this.getComponent().getSearchTextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
if (this.getComponent().getMd5HashCheckBox().isSelected()) {
String inputHashData = this.getComponent().getMd5TextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
}
if (inputHashData.length() != 32) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLengthMd5(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
}
}
if (inputHashData.length() != 32) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLength(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
if (this.getComponent().getSha256HashCheckBox().isSelected()) {
String inputHashData = this.getComponent().getSha256TextField().getText();
if (inputHashData.isEmpty()) {
setLastError(Bundle.HashSearchFilter_errorMessage_emptyHash());
return false;
}
if (inputHashData.length() != 64) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongLengthSha256(inputHashData.length()));
return false;
}
if (!inputHashData.matches("[0-9a-fA-F]+")) {
setLastError(Bundle.HashSearchFilter_errorMessage_wrongCharacter());
return false;
}
}
return true;
}
}
@@ -56,34 +56,67 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="hashCheckBox" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="sha256CheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="sha256TextField" pref="254" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="md5CheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="md5TextField" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="searchTextField" min="-2" pref="247" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="hashCheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="searchTextField" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="3" attributes="0">
<Component id="md5CheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="md5TextField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="sha256CheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="sha256TextField" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="hashCheckBox">
<Component class="javax.swing.JCheckBox" name="md5CheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.md5CheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="hashCheckBoxActionPerformed"/>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="md5CheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="searchTextField">
<Component class="javax.swing.JTextField" name="md5TextField">
</Component>
<Component class="javax.swing.JCheckBox" name="sha256CheckBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.sha256CheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sha256CheckBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="sha256TextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="HashSearchPanel.sha256TextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>
@@ -45,19 +45,19 @@ class HashSearchPanel extends javax.swing.JPanel {
private void customizeComponents() {
searchTextField.setComponentPopupMenu(rightClickMenu);
md5TextField.setComponentPopupMenu(rightClickMenu);
ActionListener actList = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JMenuItem jmi = (JMenuItem) e.getSource();
if (jmi.equals(cutMenuItem)) {
searchTextField.cut();
md5TextField.cut();
} else if (jmi.equals(copyMenuItem)) {
searchTextField.copy();
md5TextField.copy();
} else if (jmi.equals(pasteMenuItem)) {
searchTextField.paste();
md5TextField.paste();
} else if (jmi.equals(selectAllMenuItem)) {
searchTextField.selectAll();
md5TextField.selectAll();
}
}
};
@@ -65,7 +65,24 @@ class HashSearchPanel extends javax.swing.JPanel {
copyMenuItem.addActionListener(actList);
pasteMenuItem.addActionListener(actList);
selectAllMenuItem.addActionListener(actList);
this.searchTextField.getDocument().addDocumentListener(new DocumentListener() {
this.md5TextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void removeUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
@Override
public void changedUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}
});
this.sha256TextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
@@ -84,17 +101,27 @@ class HashSearchPanel extends javax.swing.JPanel {
}
JCheckBox getHashCheckBox() {
return hashCheckBox;
JCheckBox getMd5HashCheckBox() {
return md5CheckBox;
}
JTextField getSearchTextField() {
return searchTextField;
JTextField getMd5TextField() {
return md5TextField;
}
JCheckBox getSha256HashCheckBox() {
return sha256CheckBox;
}
JTextField getSha256TextField() {
return sha256TextField;
}
void setComponentsEnabled() {
boolean enabled = hashCheckBox.isSelected();
this.searchTextField.setEnabled(enabled);
boolean md5Enabled = md5CheckBox.isSelected();
this.md5TextField.setEnabled(md5Enabled);
boolean sha256Enabled = sha256CheckBox.isSelected();
this.sha256TextField.setEnabled(sha256Enabled);
}
/**
@@ -111,8 +138,10 @@ class HashSearchPanel extends javax.swing.JPanel {
copyMenuItem = new javax.swing.JMenuItem();
pasteMenuItem = new javax.swing.JMenuItem();
selectAllMenuItem = new javax.swing.JMenuItem();
hashCheckBox = new javax.swing.JCheckBox();
searchTextField = new javax.swing.JTextField();
md5CheckBox = new javax.swing.JCheckBox();
md5TextField = new javax.swing.JTextField();
sha256CheckBox = new javax.swing.JCheckBox();
sha256TextField = new javax.swing.JTextField();
cutMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.cutMenuItem.text")); // NOI18N
rightClickMenu.add(cutMenuItem);
@@ -126,48 +155,75 @@ class HashSearchPanel extends javax.swing.JPanel {
selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "NameSearchPanel.selectAllMenuItem.text")); // NOI18N
rightClickMenu.add(selectAllMenuItem);
hashCheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.md5CheckBox.text")); // NOI18N
hashCheckBox.addActionListener(new java.awt.event.ActionListener() {
md5CheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.md5CheckBox.text")); // NOI18N
md5CheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
hashCheckBoxActionPerformed(evt);
md5CheckBoxActionPerformed(evt);
}
});
sha256CheckBox.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.sha256CheckBox.text")); // NOI18N
sha256CheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
sha256CheckBoxActionPerformed(evt);
}
});
sha256TextField.setText(org.openide.util.NbBundle.getMessage(HashSearchPanel.class, "HashSearchPanel.sha256TextField.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(hashCheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(sha256CheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(sha256TextField, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(md5CheckBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(md5TextField)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(hashCheckBox)
.addComponent(searchTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(md5CheckBox)
.addComponent(md5TextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(sha256CheckBox)
.addComponent(sha256TextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
);
}// </editor-fold>//GEN-END:initComponents
private void hashCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hashCheckBoxActionPerformed
private void md5CheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_md5CheckBoxActionPerformed
setComponentsEnabled();
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_hashCheckBoxActionPerformed
}//GEN-LAST:event_md5CheckBoxActionPerformed
private void sha256CheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sha256CheckBoxActionPerformed
setComponentsEnabled();
firePropertyChange(FileSearchPanel.EVENT.CHECKED.toString(), null, null);
}//GEN-LAST:event_sha256CheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JMenuItem cutMenuItem;
private javax.swing.JCheckBox hashCheckBox;
private javax.swing.JCheckBox md5CheckBox;
private javax.swing.JTextField md5TextField;
private javax.swing.JMenuItem pasteMenuItem;
private javax.swing.JPopupMenu rightClickMenu;
private javax.swing.JTextField searchTextField;
private javax.swing.JMenuItem selectAllMenuItem;
private javax.swing.JCheckBox sha256CheckBox;
private javax.swing.JTextField sha256TextField;
// End of variables declaration//GEN-END:variables
void addActionListener(ActionListener l) {
searchTextField.addActionListener(l);
md5TextField.addActionListener(l);
}
}
@@ -1274,6 +1274,9 @@ final class IngestJobPipeline {
}
}
}
// If a data source had no tasks in progress it may now be complete.
checkForStageCompleted();
}
/**
@@ -18,8 +18,8 @@
*/
package org.sleuthkit.autopsy.modules.hashdatabase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -208,10 +208,17 @@ public class HashDbIngestModule implements FileIngestModule {
// Safely get a reference to the totalsForIngestJobs object
IngestJobTotals totals = getTotalsForIngestJobs(jobId);
// calc hash value
String md5Hash = getHash(file, totals);
if (md5Hash == null) {
return ProcessResult.ERROR;
// calc hash values
try {
calculateHashes(file, totals);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(
HashLookupModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr",
file.getParentPath() + file.getName(),
file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File")));
}
// the processing result of handling this file
@@ -451,50 +458,46 @@ public class HashDbIngestModule implements FileIngestModule {
}
/**
* Retrieves the md5 hash for a file or generates one if no one exists on
* the file.
* Generates hashes for the given file if they haven't already been set.
* Hashes are saved to the AbstractFile object.
*
* @param file The file in order to determine the hash.
* @param totals The timing metrics for this process.
*
* @return The found or determined md5 hash or null if none could be
* determined.
*/
private String getHash(AbstractFile file, IngestJobTotals totals) {
private void calculateHashes(AbstractFile file, IngestJobTotals totals) throws TskCoreException {
// First check if we've already calculated the hashes.
String md5Hash = file.getMd5Hash();
if (md5Hash != null && md5Hash.isEmpty()) {
return md5Hash;
String sha256Hash = file.getSha256Hash();
if ((md5Hash != null && ! md5Hash.isEmpty())
&& (sha256Hash != null && ! sha256Hash.isEmpty())) {
return;
}
try {
TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
md5Hash = HashUtility.calculateMd5Hash(file);
if (file.getSize() > 0) {
// Surprisingly, the hash calculation does not seem to be correlated that
// strongly with file size until the files get large.
// Only normalize if the file size is greater than ~1MB.
if (file.getSize() < 1000000) {
HealthMonitor.submitTimingMetric(metric);
} else {
// In testing, this normalization gave reasonable resuls
HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
}
TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation");
long calcstart = System.currentTimeMillis();
List<HashUtility.HashResult> newHashResults =
HashUtility.calculateHashes(file, Arrays.asList(HashUtility.HashType.MD5,HashUtility.HashType.SHA256 ));
if (file.getSize() > 0) {
// Surprisingly, the hash calculation does not seem to be correlated that
// strongly with file size until the files get large.
// Only normalize if the file size is greater than ~1MB.
if (file.getSize() < 1000000) {
HealthMonitor.submitTimingMetric(metric);
} else {
// In testing, this normalization gave reasonable resuls
HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000);
}
file.setMd5Hash(md5Hash);
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);
return md5Hash;
} catch (IOException ex) {
logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(
HashLookupModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()),
NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr",
file.getParentPath() + file.getName(),
file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File")));
return null;
}
for (HashUtility.HashResult hash : newHashResults) {
if (hash.getType().equals(HashUtility.HashType.MD5)) {
file.setMd5Hash(hash.getValue());
} else if (hash.getType().equals(HashUtility.HashType.SHA256)) {
file.setSha256Hash(hash.getValue());
}
}
long delta = (System.currentTimeMillis() - calcstart);
totals.totalCalctime.addAndGet(delta);
}
/**
@@ -198,7 +198,6 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
// for extracted virtual machines there is no manifest XML file to read data source ID from so use parent data source ID.
// ingest the data sources
ingestVirtualMachineImage(Paths.get(folder, file));
logger.log(Level.INFO, "Ingest complete for virtual machine file {0} in folder {1}", new Object[]{file, folder}); //NON-NLS
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Interrupted while ingesting virtual machine file " + file + " in folder " + folder, ex); //NON-NLS
} catch (IOException ex) {
@@ -287,8 +286,8 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
}
/*
* If the image was added, analyze it with the ingest modules for this
* ingest context.
* If the image was added, start analysis on it with the ingest modules for this
* ingest context. Note that this does not wait for ingest to complete.
*/
if (!dspCallback.vmDataSources.isEmpty()) {
Case.getCurrentCaseThrows().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId);
@@ -300,7 +299,7 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter {
IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO,
VMExtractorIngestModuleFactory.getModuleName(),
NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.addedVirtualMachineImage.message", vmFile.toString())));
IngestManager.getInstance().queueIngestJob(dataSourceContent, ingestJobSettings);
IngestManager.getInstance().beginIngestJob(dataSourceContent, ingestJobSettings);
} else {
Case.getCurrentCaseThrows().notifyFailedAddingDataSource(taskId);
}
@@ -1117,7 +1117,7 @@ public class PortableCaseReportModule implements ReportModule {
newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
abstractFile.getMd5Hash(), abstractFile.getSha256Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
true, TskData.EncodingType.NONE,
newParent, trans);
} catch (IOException ex) {
@@ -1 +1,3 @@
Netbeans platform does not properly scope classloaders while running qa-functional test code. The result is that NoClassDefError's occur in instances where an external jar (i.e. importing a class from common-io) is referenced in test code and the same external jar is referenced in multiple NBM's. Importing from external jars in qa-functional should be avoided. See jira issue 6954 for more information.
Netbeans platform does not properly scope classloaders while running qa-functional test code. The result is that NoClassDefError's occur in instances where an external jar (i.e. importing a class from common-io) is referenced in test code and the same external jar is referenced in multiple NBM's. Importing from external jars in qa-functional should be avoided. See jira issue 6954 for more information.
Many of the functional tests require external data sources. The ant target 'getTestDataFiles' must be run successfully to download the files. This should occur as a part of the 'test-init' ant target in build.xml.
@@ -1256,7 +1256,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getDefinedCorrelationTypes();
// We expect 11 total - 10 default and the custom one made earlier
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 11", types.size() == 11);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " entries - expected 28", types.size() == 28);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@@ -1267,7 +1268,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getEnabledCorrelationTypes();
// We expect 10 - the custom type is disabled
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 10", types.size() == 10);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " enabled entries - expected 27", types.size() == 27);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@@ -1278,7 +1280,8 @@ public class CentralRepoDatamodelTest extends TestCase {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getSupportedCorrelationTypes();
// We expect 10 - the custom type is not supported
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 10", types.size() == 10);
// Note: this test will need to be updated based on the current default items defined in the correlation_types table
assertTrue("getDefinedCorrelationTypes returned " + types.size() + " supported entries - expected 27", types.size() == 27);
} catch (CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@@ -218,7 +218,9 @@ class InterCaseTestUtils {
// kitchenSink.add(keywordSearchTemplate);
this.kitchenShink = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.ALL_MODULES, kitchenSink);
}
void setupCorrelationTypes() {
try {
Collection<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getDefinedCorrelationTypes();
@@ -254,16 +256,16 @@ class InterCaseTestUtils {
}
}
}
Map<Long, String> getDataSourceMap() throws NoCurrentCaseException, TskCoreException, SQLException {
return DataSourceLoader.getAllDataSources();
}
}
Map<String, Integer> getCaseMap() throws CentralRepoException {
if (CentralRepository.isEnabled()) {
Map<String, Integer> mapOfCaseIdsToCase = new HashMap<>();
for (CorrelationCase correlationCase : CentralRepository.getInstance().getCases()) {
mapOfCaseIdsToCase.put(correlationCase.getDisplayName(), correlationCase.getID());
}
@@ -300,7 +302,7 @@ class InterCaseTestUtils {
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, crSettings);
centralRepoSchemaFactory.initializeDatabaseSchema();
centralRepoSchemaFactory.insertDefaultDatabaseContent();
crSettings.saveSettings();
CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE);
}
@@ -313,10 +315,10 @@ class InterCaseTestUtils {
* The length of caseNames and caseDataSourcePaths should be the same, and
* cases should appear in the same order.
*
* @param caseNames list case names
* @param caseDataSourcePaths two dimensional array listing the datasources
* in each case
* @param ingestJobSettings HashLookup FileType etc...
* @param caseNames list case names
* @param caseDataSourcePaths two dimensional array listing the datasources
* in each case
* @param ingestJobSettings HashLookup FileType etc...
* @param caseReferenceToStore
*/
Case createCases(String[] caseNames, Path[][] caseDataSourcePaths, IngestJobSettings ingestJobSettings, String caseReferenceToStore) throws TskCoreException {
@@ -253,7 +253,7 @@ public class IngestFileFiltersTest extends NbTestCase {
IngestUtils.runIngestJob(currentCase.getDataSources(), ingestJobSettings);
FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> results = fileManager.findFiles("%%");
assertEquals(70, results.size());
assertEquals(71, results.size());
int carvedJpgGifFiles = 0;
for (AbstractFile file : results) {
if (file.getNameExtension().equalsIgnoreCase("jpg") || file.getNameExtension().equalsIgnoreCase("gif")) { //Unalloc file and .jpg files in dir1, dir2, $CarvedFiles, root directory should have MIME type
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
import org.junit.Rule;
@@ -35,6 +36,7 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.testutils.TskMockUtils;
import static org.mockito.Mockito.*;
import org.sleuthkit.autopsy.testutils.RandomizationUtils;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE;
@@ -42,7 +44,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALU
/**
* Unit tests for DataSourceInfoUtilities.getArtifacts
*/
public class GetArtifactsTest {
public class DataSourceInfoUtilitiesTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -145,38 +147,13 @@ public class GetArtifactsTest {
List<BlackboardArtifact> toRet = new ArrayList<>();
for (int i = 0; i < values.size(); i++) {
toRet.add(TskMockUtils.mockArtifact(new BlackboardArtifact.Type(artifactType), 1000 + i, dataSource,
toRet.add(TskMockUtils.getArtifact(new BlackboardArtifact.Type(artifactType), 1000 + i, dataSource,
attrMaker.make(attrType, "TEST SOURCE", values.get(i))));
}
return toRet;
}
/**
* Returns list in 0, n-1, 1, n-2 ... order. Deterministic so same results
* each time, but not in original order.
*
* @return Mixed up list.
*/
private <T> List<T> getMixedUp(List<T> list) {
int forward = 0;
int backward = list.size() - 1;
List<T> newList = new ArrayList<>();
while (forward <= backward) {
newList.add(list.get(forward));
if (forward < backward) {
newList.add(list.get(backward));
}
forward++;
backward--;
}
return newList;
}
/**
* Does a basic test passing a list of generated artifacts in mixed up order
* to DataSourceInfoUtilities.getArtifacts and expecting a sorted list to be
@@ -194,11 +171,11 @@ public class GetArtifactsTest {
private <T> void testSorted(ARTIFACT_TYPE artifactType, ATTRIBUTE_TYPE attrType, List<T> values,
AttrMaker<T> attrMaker, SortOrder sortOrder, int count) throws TskCoreException {
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
List<BlackboardArtifact> sortedArtifacts = getArtifacts(artifactType, new BlackboardAttribute.Type(attrType),
dataSource, values, attrMaker);
List<BlackboardArtifact> mixedUpArtifacts = getMixedUp(sortedArtifacts);
List<BlackboardArtifact> mixedUpArtifacts = RandomizationUtils.getMixedUp(sortedArtifacts);
List<BlackboardArtifact> expectedArtifacts = count == 0
? sortedArtifacts
@@ -250,17 +227,17 @@ public class GetArtifactsTest {
}
@Test
public void testSortAscending() throws TskCoreException {
public void getArtifacts_sortAscending() throws TskCoreException {
testAscDesc(SortOrder.ASCENDING);
}
@Test
public void testSortDescending() throws TskCoreException {
public void getArtifacts_sortDescending() throws TskCoreException {
testAscDesc(SortOrder.DESCENDING);
}
@Test
public void testLimits() throws TskCoreException {
public void getArtifacts_limits() throws TskCoreException {
List<Integer> integers = Arrays.asList(22, 31, 42, 50, 60);
testSorted(ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, integers, BlackboardAttribute::new, SortOrder.ASCENDING, 3);
testSorted(ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, integers, BlackboardAttribute::new, SortOrder.ASCENDING, 5);
@@ -281,11 +258,11 @@ public class GetArtifactsTest {
private <T> void testFailOnBadAttrType(BlackboardArtifact.Type artifactType, BlackboardAttribute.Type attributeType, T val,
AttrMaker<T> attrMaker) throws TskCoreException {
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
List<BlackboardArtifact> artifacts = Arrays.asList(
TskMockUtils.mockArtifact(artifactType, 2, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val)),
TskMockUtils.mockArtifact(artifactType, 3, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val))
TskMockUtils.getArtifact(artifactType, 2, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val)),
TskMockUtils.getArtifact(artifactType, 3, dataSource, attrMaker.make(attributeType, "TEST SOURCE", val))
);
test(artifactType,
dataSource,
@@ -299,7 +276,7 @@ public class GetArtifactsTest {
}
@Test
public void testFailOnJson() throws TskCoreException {
public void getArtifacts_failOnJson() throws TskCoreException {
testFailOnBadAttrType(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_GPS_ROUTE),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS),
@@ -308,7 +285,7 @@ public class GetArtifactsTest {
}
@Test
public void testFailOnBytes() throws TskCoreException {
public void getArtifacts_failOnBytes() throws TskCoreException {
testFailOnBadAttrType(
new BlackboardArtifact.Type(999, "BYTE_ARRAY_TYPE", "Byte Array Type"),
new BlackboardAttribute.Type(999, "BYTE_ARR_ATTR_TYPE", "Byte Array Attribute Type", TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE),
@@ -317,20 +294,20 @@ public class GetArtifactsTest {
}
@Test
public void testPurgeAttrNotPresent() throws TskCoreException {
public void getArtifacts_purgeAttrNotPresent() throws TskCoreException {
long day = 24 * 60 * 60;
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact.Type ART_TYPE = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_PROG_RUN);
BlackboardArtifact mock1 = TskMockUtils.mockArtifact(ART_TYPE, 10, dataSource,
BlackboardArtifact mock1 = TskMockUtils.getArtifact(ART_TYPE, 10, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 5),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", day));
BlackboardArtifact mock2 = TskMockUtils.mockArtifact(ART_TYPE, 20, dataSource,
BlackboardArtifact mock2 = TskMockUtils.getArtifact(ART_TYPE, 20, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 6));
BlackboardArtifact mock3 = TskMockUtils.mockArtifact(ART_TYPE, 30, dataSource,
BlackboardArtifact mock3 = TskMockUtils.getArtifact(ART_TYPE, 30, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 7),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", 3 * day));
@@ -346,20 +323,20 @@ public class GetArtifactsTest {
}
@Test
public void testMultAttrsPresent() throws TskCoreException {
public void getArtifacts_multipleAttrsPresent() throws TskCoreException {
long day = 24 * 60 * 60;
DataSource dataSource = TskMockUtils.mockDataSource(1);
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact.Type ART_TYPE = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_PROG_RUN);
BlackboardArtifact mock1 = TskMockUtils.mockArtifact(ART_TYPE, 10, dataSource,
BlackboardArtifact mock1 = TskMockUtils.getArtifact(ART_TYPE, 10, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 7),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", day));
BlackboardArtifact mock2 = TskMockUtils.mockArtifact(ART_TYPE, 20, dataSource,
BlackboardArtifact mock2 = TskMockUtils.getArtifact(ART_TYPE, 20, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 6));
BlackboardArtifact mock3 = TskMockUtils.mockArtifact(ART_TYPE, 30, dataSource,
BlackboardArtifact mock3 = TskMockUtils.getArtifact(ART_TYPE, 30, dataSource,
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT, "TEST SOURCE", 5),
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, "TEST SOURCE", 3 * day));
@@ -375,9 +352,9 @@ public class GetArtifactsTest {
}
@Test
public void testTskCoreExceptionThrown() throws TskCoreException {
public void getArtifacts_tskCoreExceptionThrown() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
0,
@@ -388,9 +365,9 @@ public class GetArtifactsTest {
}
@Test
public void testThrowOnLessThan0() throws TskCoreException {
public void getArtifacts_throwOnLessThan0() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
-1,
@@ -401,9 +378,9 @@ public class GetArtifactsTest {
}
@Test
public void testEmptyListReturned() throws TskCoreException {
public void getArtifacts_emptyListReturned() throws TskCoreException {
test(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ACCOUNT),
TskMockUtils.mockDataSource(1),
TskMockUtils.getDataSource(1),
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE),
SortOrder.ASCENDING,
0,
@@ -412,4 +389,76 @@ public class GetArtifactsTest {
Collections.emptyList(),
null);
}
/**
* Retrieves the value of an artifact.
*/
private interface GetAttrVal<T> {
/**
* A method for retrieving the value of an artifact.
* @param artifact The artifact.
* @param type The type of attribute.
* @return The value.
*/
T getOrNull(BlackboardArtifact artifact, BlackboardAttribute.Type type);
}
private <T> void testNullAttrValue(String id, GetAttrVal<T> getter, ARTIFACT_TYPE artifactType,
ATTRIBUTE_TYPE attributeType, T nonNullVal)
throws TskCoreException {
BlackboardAttribute.Type attrType = new BlackboardAttribute.Type(attributeType);
BlackboardArtifact.Type artType = new BlackboardArtifact.Type(artifactType);
BlackboardArtifact noAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), new ArrayList<>());
T nullValue = getter.getOrNull(noAttribute, attrType);
Assert.assertNull(String.format("Expected function %s to return null when no attribute present", id), nullValue);
BlackboardArtifact hasAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), TskMockUtils.getAttribute(attributeType, nonNullVal));
T valueReceived = getter.getOrNull(hasAttribute, attrType);
Assert.assertEquals(String.format("%s did not return the same value present in the attribute", id), nonNullVal, valueReceived);
}
@Test
public void getStringOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getStringOrNull", DataSourceInfoUtilities::getStringOrNull,
ARTIFACT_TYPE.TSK_ACCOUNT, ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE, "Skype");
}
@Test
public void getIntOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getIntOrNull", DataSourceInfoUtilities::getIntOrNull,
ARTIFACT_TYPE.TSK_PROG_RUN, ATTRIBUTE_TYPE.TSK_COUNT, 16);
}
@Test
public void getLongOrNull_handlesNull() throws TskCoreException {
testNullAttrValue("getLongOrNull", DataSourceInfoUtilities::getLongOrNull,
ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT, ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, 1001L);
}
@Test
public void getDateOrNull_handlesNull() throws TskCoreException {
BlackboardAttribute.Type attrType = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME);
BlackboardArtifact.Type artType = new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING);
long dateTime = 24 * 60 * 60 * 42;
BlackboardArtifact noAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), new ArrayList<>());
Date nullValue = DataSourceInfoUtilities.getDateOrNull(noAttribute, attrType);
Assert.assertNull(nullValue);
BlackboardArtifact hasAttribute = TskMockUtils.getArtifact(artType, 1000,
TskMockUtils.getDataSource(1), TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, dateTime));
Date curVal = DataSourceInfoUtilities.getDateOrNull(hasAttribute, attrType);
Assert.assertEquals(dateTime, curVal.getTime() / 1000);
}
}
@@ -0,0 +1,57 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Common tools for mocking in data source summary.
*/
public final class DataSourceSummaryMockUtils {
/**
* Creates a pair of a mock SleuthkitCase and mock Blackboard.
*
* @param returnArr The return result when calling getArtifacts on the
* blackboard.
*
* @return The pair of a mock SleuthkitCase and mock Blackboard.
*
* @throws TskCoreException
*/
static Pair<SleuthkitCase, Blackboard> getArtifactsTSKMock(List<BlackboardArtifact> returnArr) throws TskCoreException {
SleuthkitCase mockCase = mock(SleuthkitCase.class);
Blackboard mockBlackboard = mock(Blackboard.class);
when(mockCase.getBlackboard()).thenReturn(mockBlackboard);
when(mockBlackboard.getArtifacts(anyInt(), anyLong())).thenReturn(returnArr);
return Pair.of(mockCase, mockBlackboard);
}
private DataSourceSummaryMockUtils() {
}
}
@@ -0,0 +1,681 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp. Contact: carrier <at> sleuthkit <dot>
* org
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.sleuthkit.autopsy.datasourcesummary.datamodel;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Assert;
import static org.junit.Assert.fail;
import org.junit.Test;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentAttachmentDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentDownloadDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.RecentFilesSummary.RecentFileDetails;
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
import org.sleuthkit.autopsy.testutils.RandomizationUtils;
import org.sleuthkit.autopsy.testutils.TskMockUtils;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Tests for RecentFilesSummaryTest
*/
public class RecentFilesSummaryTest {
/**
* An interface for calling methods in RecentFilesSummary in a uniform
* manner.
*/
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.
*
* @return The method's return data.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
List<T> fetch(RecentFilesSummary recentFilesSummary, DataSource dataSource, int count)
throws SleuthkitCaseProviderException, TskCoreException;
}
private static final RecentFilesMethod<RecentFileDetails> RECENT_DOCS_FUNCT
= (summary, dataSource, count) -> summary.getRecentlyOpenedDocuments(dataSource, count);
private static final RecentFilesMethod<RecentDownloadDetails> RECENT_DOWNLOAD_FUNCT
= (summary, dataSource, count) -> summary.getRecentDownloads(dataSource, count);
private static final RecentFilesMethod<RecentAttachmentDetails> RECENT_ATTACHMENT_FUNCT
= (summary, dataSource, count) -> summary.getRecentAttachments(dataSource, count);
/**
* If -1 count passed to method, should throw IllegalArgumentException.
*
* @param method The method to call.
* @param methodName The name of the metho
*
* @throws TskCoreException
* @throws SleuthkitCaseProviderException
*/
private <T> void testNonPositiveCount_ThrowsError(RecentFilesMethod<T> method, String methodName)
throws TskCoreException, SleuthkitCaseProviderException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(null);
DataSource dataSource = TskMockUtils.getDataSource(1);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
try {
method.fetch(summary, dataSource, -1);
fail("Expected method " + methodName + " to fail on negative count.");
} catch (IllegalArgumentException ignored) {
verify(casePair.getRight(),
never().description("Expected negative count for " + methodName + " to not call any methods in SleuthkitCase."))
.getArtifacts(anyInt(), anyLong());
}
}
@Test
public void getRecentlyOpenedDocuments_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_nonPositiveCount_ThrowsError() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
/**
* Tests that if no data source provided, an empty list is returned and
* SleuthkitCase isn't called.
*
* @param recentFilesMethod The method to call.
* @param methodName The name of the method
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
private <T> void testNoDataSource_ReturnsEmptyList(RecentFilesMethod<T> recentFilesMethod, String methodName)
throws SleuthkitCaseProviderException, TskCoreException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(null);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<? extends T> items = recentFilesMethod.fetch(summary, null, 10);
Assert.assertNotNull("Expected method " + methodName + " to return an empty list.", items);
Assert.assertEquals("Expected method " + methodName + " to return an empty list.", 0, items.size());
verify(casePair.getRight(),
never().description("Expected null datasource for " + methodName + " to not call any methods in SleuthkitCase."))
.getArtifacts(anyInt(), anyLong());
}
@Test
public void getRecentlyOpenedDocuments_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoDataSource_ReturnsEmptyList(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoDataSource_ReturnsEmptyList(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_noDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNonPositiveCount_ThrowsError(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
/**
* If SleuthkitCase returns no results, an empty list is returned.
*
* @param recentFilesMethod The method to call.
* @param methodName The name of the method.
*
* @throws SleuthkitCaseProviderException
* @throws TskCoreException
*/
private <T> void testNoReturnedResults_ReturnsEmptyList(RecentFilesMethod<T> recentFilesMethod, String methodName)
throws SleuthkitCaseProviderException, TskCoreException {
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(Collections.emptyList());
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
DataSource dataSource = TskMockUtils.getDataSource(1);
List<? extends T> items = recentFilesMethod.fetch(summary, dataSource, 10);
Assert.assertNotNull("Expected method " + methodName + " to return an empty list.", items);
Assert.assertEquals("Expected method " + methodName + " to return an empty list.", 0, items.size());
verify(casePair.getRight(),
times(1).description("Expected " + methodName + " to call Blackboard once."))
.getArtifacts(anyInt(), anyLong());
}
@Test
public void getRecentlyOpenedDocuments_noReturnedResults_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_DOCS_FUNCT, "getRecentlyOpenedDocuments");
}
@Test
public void getRecentDownloads_noReturnedResults_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_DOWNLOAD_FUNCT, "getRecentDownloads");
}
@Test
public void getRecentAttachments_testNoDataSource_ReturnsEmptyList() throws TskCoreException, SleuthkitCaseProviderException {
testNoReturnedResults_ReturnsEmptyList(RECENT_ATTACHMENT_FUNCT, "getRecentAttachments");
}
private static final long DAY_SECONDS = 24 * 60 * 60;
/**
* A means of creating a number representing seconds from epoch where the
* lower the idx, the more recent the time.
*/
private static final Function<Integer, Long> dateTimeRetriever = (idx) -> (365 - idx) * DAY_SECONDS + 1;
/**
* 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 attributeArgs The mapping of attribute type to value for each
* attribute in the artifact.
*
* @return The mock artifact.
*/
private BlackboardArtifact getArtifact(DataSource ds, long artifactId, ARTIFACT_TYPE artType, List<Pair<ATTRIBUTE_TYPE, Object>> attributeArgs) {
try {
List<BlackboardAttribute> attributes = attributeArgs.stream()
.filter((arg) -> arg != null && arg.getLeft() != null && arg.getRight() != null)
.map((arg) -> {
return TskMockUtils.getAttribute(arg.getLeft(), arg.getRight());
})
.collect(Collectors.toList());
return TskMockUtils.getArtifact(new BlackboardArtifact.Type(artType), artifactId, ds, attributes);
} catch (TskCoreException ex) {
fail("There was an error mocking an artifact.");
return null;
}
}
/**
* Returns a mock artifact for getRecentlyOpenedDocuments.
*
* @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.
*
* @return The mock artifact with pertinent attributes.
*/
private BlackboardArtifact getRecentDocumentArtifact(DataSource ds, long artifactId, Long dateTime, String path) {
return getArtifact(ds, artifactId, ARTIFACT_TYPE.TSK_RECENT_OBJECT, Arrays.asList(
Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME, dateTime),
Pair.of(ATTRIBUTE_TYPE.TSK_PATH, path)
));
}
@Test
public void getRecentlyOpenedDocuments_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
Function<Integer, String> pathRetriever = (idx) -> "/path/to/downloads/" + idx;
DataSource dataSource = TskMockUtils.getDataSource(1);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// generate artifacts for each artifact
List<BlackboardArtifact> artifacts = new ArrayList<>();
for (int idx = 0; idx < countToGenerate; idx++) {
BlackboardArtifact artifact = getRecentDocumentArtifact(dataSource,
1000 + idx, dateTimeRetriever.apply(idx), pathRetriever.apply(idx));
artifacts.add(artifact);
}
// run through method
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentFileDetails> results = summary.getRecentlyOpenedDocuments(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
Assert.assertEquals(dateTimeRetriever.apply(i), results.get(i).getDateAsLong());
Assert.assertEquals(pathRetriever.apply(i), results.get(i).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");
List<BlackboardArtifact> artifacts = Arrays.asList(nullTime, zeroTime, successItem);
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, results.get(0).getDateAsLong());
Assert.assertTrue("/a/path".equalsIgnoreCase(results.get(0).getPath()));
}
/**
* Creates a mock blackboard artifact for getRecentDownloads.
*
* @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.
*
* @return The mock artifact.
*/
private BlackboardArtifact getRecentDownloadArtifact(DataSource ds, long artifactId, Long dateTime, String domain, String path) {
return getArtifact(ds, artifactId, ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, Arrays.asList(
Pair.of(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, dateTime),
Pair.of(ATTRIBUTE_TYPE.TSK_DOMAIN, domain),
Pair.of(ATTRIBUTE_TYPE.TSK_PATH, path)
));
}
@Test
public void getRecentDownloads_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
Function<Integer, String> domainRetriever = (idx) -> String.format("www.domain%d.com", idx);
Function<Integer, String> pathRetriever = (idx) -> "/path/to/downloads/doc" + idx + ".pdf";
// run through method
DataSource dataSource = TskMockUtils.getDataSource(1);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// generate artifacts for each artifact
List<BlackboardArtifact> artifacts = new ArrayList<>();
for (int idx = 0; idx < countToGenerate; idx++) {
BlackboardArtifact artifact = getRecentDownloadArtifact(dataSource,
1000 + idx, dateTimeRetriever.apply(idx), domainRetriever.apply(idx),
pathRetriever.apply(idx));
artifacts.add(artifact);
}
// call method
Pair<SleuthkitCase, Blackboard> casePair = DataSourceSummaryMockUtils.getArtifactsTSKMock(RandomizationUtils.getMixedUp(artifacts));
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
List<RecentDownloadDetails> results = summary.getRecentDownloads(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
Assert.assertEquals(dateTimeRetriever.apply(i), results.get(i).getDateAsLong());
Assert.assertEquals(pathRetriever.apply(i), results.get(i).getPath());
Assert.assertEquals(domainRetriever.apply(i), results.get(i).getWebDomain());
}
}
}
@Test
public void getRecentDownloads_filtersMissingData() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
BlackboardArtifact successItem = getRecentDownloadArtifact(dataSource, 1001, DAY_SECONDS, "domain1.com", "/a/path1");
BlackboardArtifact nullTime = getRecentDownloadArtifact(dataSource, 1002, null, "domain2.com", "/a/path2");
BlackboardArtifact zeroTime = getRecentDownloadArtifact(dataSource, 10021, 0L, "domain2a.com", "/a/path2a");
List<BlackboardArtifact> artifacts = Arrays.asList(nullTime, zeroTime, successItem);
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, results.get(0).getDateAsLong());
Assert.assertTrue("/a/path1".equalsIgnoreCase(results.get(0).getPath()));
}
/**
* getRecentAttachments method has special setup conditions. This class
* encapsulates all the SleuthkitCase/BlackboardArtifact setup for on
* possible return item.
*/
private class AttachmentArtifactItem {
private final Integer messageArtifactTypeId;
private final boolean associatedAttrFormed;
private final String emailFrom;
private final Long messageTime;
private final boolean isParent;
private final String fileParentPath;
private final String fileName;
/**
* 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.
*/
AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime,
String fileParentPath, String fileName,
boolean associatedAttrFormed, boolean hasParent) {
this.messageArtifactTypeId = messageArtifactTypeId;
this.associatedAttrFormed = associatedAttrFormed;
this.emailFrom = emailFrom;
this.messageTime = messageTime;
this.isParent = hasParent;
this.fileParentPath = fileParentPath;
this.fileName = fileName;
}
/**
* Convenience constructor where defaults of required attributes and
* 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.
*/
AttachmentArtifactItem(Integer messageArtifactTypeId, String emailFrom, Long messageTime, String fileParentPath, String fileName) {
this(messageArtifactTypeId, emailFrom, messageTime, fileParentPath, fileName, true, true);
}
boolean isAssociatedAttrFormed() {
return associatedAttrFormed;
}
String getEmailFrom() {
return emailFrom;
}
Long getMessageTime() {
return messageTime;
}
boolean hasParent() {
return isParent;
}
String getFileParentPath() {
return fileParentPath;
}
String getFileName() {
return fileName;
}
Integer getMessageArtifactTypeId() {
return messageArtifactTypeId;
}
}
/**
* 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 associatedId The associated attribute id.
* @param artifactId The artifact id.
*
* @return The associated Artifact blackboard attribute.
*
* @throws TskCoreException
*/
private BlackboardAttribute setupAssociatedMessage(Map<Long, BlackboardArtifact> artifacts, AttachmentArtifactItem item,
DataSource dataSource, Long associatedId, Long artifactId) throws TskCoreException {
BlackboardAttribute associatedAttr = TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, associatedId);
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())
.findFirst()
.orElse(null);
// if there is a message type, create the artifact
if (messageType != null) {
List<BlackboardAttribute> attributes = new ArrayList<>();
if (item.getEmailFrom() != null) {
attributes.add(TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM, item.getEmailFrom()));
}
if (item.getMessageTime() != null) {
attributes.add(TskMockUtils.getAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT, item.getMessageTime()));
}
artifacts.put(associatedId, TskMockUtils.getArtifact(
new BlackboardArtifact.Type(messageType), artifactId, dataSource, attributes));
}
return associatedAttr;
}
/**
* Since getRecentAttachments does not simply query one type of artifact and
* return results, this method sets up a mock SleuthkitCase and Blackboard
* to return pertinent data.
*
* @param items Each attachment item where each item could represent a
* return result if fully formed.
*
* @return The mock SleuthkitCase and Blackboard.
*/
private Pair<SleuthkitCase, Blackboard> getRecentAttachmentArtifactCase(List<AttachmentArtifactItem> items) {
SleuthkitCase skCase = mock(SleuthkitCase.class);
Blackboard blackboard = mock(Blackboard.class);
when(skCase.getBlackboard()).thenReturn(blackboard);
DataSource dataSource = TskMockUtils.getDataSource(1);
long objIdCounter = 100;
Map<Long, BlackboardArtifact> artifacts = new HashMap<>();
try {
for (AttachmentArtifactItem item : items) {
BlackboardAttribute associatedAttr = null;
// if the associated attribute is fully formed,
// create the associated attribute and related artifact
if (item.isAssociatedAttrFormed()) {
associatedAttr = setupAssociatedMessage(artifacts, item, dataSource, ++objIdCounter, ++objIdCounter);
}
// create the content parent for the associated object if one should be present
Content parent = (item.hasParent())
? TskMockUtils.getAbstractFile(++objIdCounter, item.getFileParentPath(), item.getFileName())
: null;
Long associatedId = ++objIdCounter;
artifacts.put(associatedId, TskMockUtils.getArtifact(
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT),
parent, associatedId, dataSource, associatedAttr));
}
// set up the blackboard to return artifacts that match the type id.
when(blackboard.getArtifacts(anyInt(), anyLong())).thenAnswer((inv) -> {
Object[] args = inv.getArguments();
int artifactType = (Integer) args[0];
return artifacts.values().stream()
.filter(art -> art.getArtifactTypeID() == artifactType)
.collect(Collectors.toList());
});
// also set up the sleuthkitcase to return the artifact with the matching id or null.
when(skCase.getBlackboardArtifact(anyLong())).thenAnswer((inv2) -> {
Object[] args2 = inv2.getArguments();
long id = (Long) args2[0];
return artifacts.get(id);
});
return Pair.of(skCase, blackboard);
} catch (TskCoreException ex) {
fail("There was an error while creating SleuthkitCase for getRecentAttachments");
return null;
}
}
@Test
public void getRecentAttachments_sortedByDateTimeAndLimited() throws SleuthkitCaseProviderException, TskCoreException {
DataSource dataSource = TskMockUtils.getDataSource(1);
// a deterministic means of transforming an index into a particular attribute type so that they can be created
// and compared on return
Function<Integer, String> emailFromRetriever = (idx) -> String.format("person%d@basistech.com", idx);
Function<Integer, String> pathRetriever = (idx) -> "/path/to/attachment/" + idx;
Function<Integer, String> fileNameRetriever = (idx) -> String.format("%d-filename.png", idx);
int countRequest = 10;
for (int countToGenerate : new int[]{1, 9, 10, 11}) {
// set up the items in the sleuthkit case
List<AttachmentArtifactItem> items = IntStream.range(0, countToGenerate)
.mapToObj((idx) -> new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
emailFromRetriever.apply(idx), dateTimeRetriever.apply(idx),
pathRetriever.apply(idx), fileNameRetriever.apply(idx)))
.collect(Collectors.toList());
List<AttachmentArtifactItem> mixedUpItems = RandomizationUtils.getMixedUp(items);
Pair<SleuthkitCase, Blackboard> casePair = getRecentAttachmentArtifactCase(mixedUpItems);
RecentFilesSummary summary = new RecentFilesSummary(() -> casePair.getLeft());
// retrieve results
List<RecentAttachmentDetails> results = summary.getRecentAttachments(dataSource, countRequest);
// verify results
int expectedCount = Math.min(countRequest, countToGenerate);
Assert.assertNotNull(results);
Assert.assertEquals(expectedCount, results.size());
for (int i = 0; i < expectedCount; i++) {
RecentAttachmentDetails result = results.get(i);
Assert.assertEquals(dateTimeRetriever.apply(i), result.getDateAsLong());
Assert.assertTrue(emailFromRetriever.apply(i).equalsIgnoreCase(result.getSender()));
Assert.assertTrue(Paths.get(pathRetriever.apply(i), fileNameRetriever.apply(i)).toString()
.equalsIgnoreCase(result.getPath()));
}
}
}
@Test
public void getRecentAttachments_filterData() throws SleuthkitCaseProviderException, TskCoreException {
// setup data
DataSource dataSource = TskMockUtils.getDataSource(1);
AttachmentArtifactItem successItem = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person@sleuthkit.com", DAY_SECONDS, "/parent/path", "msg.pdf");
AttachmentArtifactItem successItem2 = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_MESSAGE.getTypeID(),
"person_on_skype", DAY_SECONDS + 1, "/parent/path/to/skype", "skype.png");
AttachmentArtifactItem wrongArtType = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_CALLLOG.getTypeID(),
"5555675309", DAY_SECONDS + 2, "/path/to/callog/info", "callog.dat");
AttachmentArtifactItem missingTimeStamp = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person2@sleuthkit.com", null, "/parent/path", "msg2.pdf");
AttachmentArtifactItem zeroTimeStamp = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person2a@sleuthkit.com", 0L, "/parent/path", "msg2a.png");
AttachmentArtifactItem noParentFile = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person4@sleuthkit.com", DAY_SECONDS + 4, "/parent/path", "msg4.jpg", true, false);
AttachmentArtifactItem noAssocAttr = new AttachmentArtifactItem(ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID(),
"person3@sleuthkit.com", DAY_SECONDS + 5, "/parent/path", "msg5.gif", false, true);
AttachmentArtifactItem missingAssocArt = new AttachmentArtifactItem(null,
"person3@sleuthkit.com", DAY_SECONDS + 6, "/parent/path", "msg6.pdf");
List<AttachmentArtifactItem> items = Arrays.asList(successItem, successItem2,
wrongArtType, missingTimeStamp, zeroTimeStamp,
noParentFile, noAssocAttr, missingAssocArt);
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(2, results.size());
RecentAttachmentDetails successItem2Details = results.get(0);
RecentAttachmentDetails successItemDetails = results.get(1);
Assert.assertEquals(successItemDetails.getDateAsLong(), (Long) DAY_SECONDS);
Assert.assertTrue(Paths.get(successItem.getFileParentPath(), successItem.getFileName())
.toString().equalsIgnoreCase(successItemDetails.getPath()));
Assert.assertTrue(successItem.getEmailFrom().equalsIgnoreCase(successItemDetails.getSender()));
Assert.assertEquals(successItem2Details.getDateAsLong(), (Long) (DAY_SECONDS + 1));
Assert.assertTrue(Paths.get(successItem2.getFileParentPath(), successItem2.getFileName())
.toString().equalsIgnoreCase(successItem2Details.getPath()));
Assert.assertTrue(successItem2.getEmailFrom().equalsIgnoreCase(successItem2Details.getSender()));
}
}
@@ -32,7 +32,7 @@ public class DomainSearchTestUtils {
public static ResultDomain mockDomainResult(String domain, long start, long end,
long totalVisits, long visits, long filesDownloaded, long dataSourceId) {
Content dataSource = TskMockUtils.mockDataSource(dataSourceId);
Content dataSource = TskMockUtils.getDataSource(dataSourceId);
return new ResultDomain(domain, start, end, totalVisits,
visits, filesDownloaded, dataSource);
}
@@ -0,0 +1,56 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.testutils;
import java.util.ArrayList;
import java.util.List;
/**
* Tools for pseudo-randomization.
*/
public final class RandomizationUtils {
/**
* Returns list in 0, n-1, 1, n-2 ... order. Deterministic so same results
* each time, but not in original order.
*
* @return Mixed up list.
*/
public static <T> List<T> getMixedUp(List<T> list) {
int forward = 0;
int backward = list.size() - 1;
List<T> newList = new ArrayList<>();
while (forward <= backward) {
newList.add(list.get(forward));
if (forward < backward) {
newList.add(list.get(backward));
}
forward++;
backward--;
}
return newList;
}
private RandomizationUtils() {
}
}
@@ -19,15 +19,26 @@
package org.sleuthkit.autopsy.testutils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
@@ -44,7 +55,7 @@ public class TskMockUtils {
*
* @return The mocked datasource.
*/
public static DataSource mockDataSource(long dataSourceId) {
public static DataSource getDataSource(long dataSourceId) {
DataSource dataSource = mock(DataSource.class);
when(dataSource.getName()).thenReturn("");
when(dataSource.getId()).thenReturn(dataSourceId);
@@ -65,14 +76,35 @@ public class TskMockUtils {
*
* @throws TskCoreException
*/
public static BlackboardArtifact mockArtifact(BlackboardArtifact.Type artifactType, long artifactId,
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, long artifactId,
DataSource dataSource, BlackboardAttribute... attributes) throws TskCoreException {
return getArtifact(artifactType, null, artifactId, dataSource, attributes);
}
/**
* Gets a mock Blackboard artifact.
*
* @param artifactType The artifact type for the artifact.
* @param parent The parent file of the artifact.
* @param artifactId The artifact id.
* @param dataSource The datasource.
* @param attributes The attributes for the artifact.
*
* @return The mocked artifact.
*
* @throws TskCoreException
*/
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, Content parent, long artifactId,
DataSource dataSource, BlackboardAttribute... attributes) throws TskCoreException {
BlackboardArtifact artifact = mock(BlackboardArtifact.class);
final Map<BlackboardAttribute.Type, BlackboardAttribute> attributeTypes = Stream.of(attributes)
.filter(attr -> attr != null)
.collect(Collectors.toMap((attr) -> attr.getAttributeType(), Function.identity()));
when(artifact.getParent()).thenReturn(parent);
when(artifact.getArtifactID()).thenReturn(artifactId);
when(artifact.getArtifactTypeID()).thenReturn(artifactType.getTypeID());
@@ -89,6 +121,146 @@ public class TskMockUtils {
return artifact;
}
public static BlackboardArtifact getArtifact(BlackboardArtifact.Type artifactType, long artifactId,
DataSource dataSource, List<BlackboardAttribute> attributes) throws TskCoreException {
return getArtifact(artifactType, artifactId, dataSource, attributes.toArray(new BlackboardAttribute[0]));
}
private static final String DEFAULT_ATTR_SOURCE = "TEST SOURCE";
public static BlackboardAttribute getAttribute(ATTRIBUTE_TYPE attrType, Object value) {
return getAttribute(new BlackboardAttribute.Type(attrType), DEFAULT_ATTR_SOURCE, value);
}
public static BlackboardAttribute getAttribute(BlackboardAttribute.Type attrType, String source, Object value) {
switch (attrType.getValueType()) {
case STRING:
case JSON:
if (value instanceof String) {
return new BlackboardAttribute(attrType, source, (String) value);
}
break;
case DATETIME:
case LONG:
if (value instanceof Long) {
return new BlackboardAttribute(attrType, source, (Long) value);
}
break;
case INTEGER:
if (value instanceof Integer) {
return new BlackboardAttribute(attrType, source, (Integer) value);
}
break;
case DOUBLE:
if (value instanceof Double) {
return new BlackboardAttribute(attrType, source, (Double) value);
}
break;
case BYTE:
if (value instanceof byte[]) {
return new BlackboardAttribute(attrType, source, (byte[]) value);
}
break;
default:
throw new IllegalArgumentException(String.format("Unknown attribute value type: %s", attrType.getValueType()));
}
throw new IllegalArgumentException(String.format("Attribute type expected type of %s but received argument of %s", attrType.getValueType(), value));
}
/**
* Returns a mock TextTranslationService.
*
* @param onTranslate A function that performs the translation. If null, a
* null result is always returned for .translate method.
* @param hasProvider What to return for the hasProvider method.
*
* @return The mocked text translation service.
*
* @throws NoServiceProviderException
* @throws TranslationException
*/
public static TextTranslationService getTextTranslationService(Function<String, String> onTranslate, boolean hasProvider)
throws NoServiceProviderException, TranslationException {
TextTranslationService translationService = mock(TextTranslationService.class);
when(translationService.hasProvider()).thenReturn(hasProvider);
when(translationService.translate(anyString())).thenAnswer((invocation) -> {
if (onTranslate == null) {
throw new NoServiceProviderException("No onTranslate function provided");
}
Object[] args = invocation.getArguments();
String input = (String) args[0];
return (input == null) ? null : onTranslate.apply(input);
});
return translationService;
}
/**
* Returns an AbstractFile mocking getPath and getName.
*
* @param objId The object id.
* @param path The path for the file.
* @param name The name
*
* @return
*/
public static AbstractFile getAbstractFile(long objId, String path, String name) {
AbstractFile mocked = mock(AbstractFile.class);
when(mocked.getId()).thenReturn(objId);
when(mocked.getName()).thenReturn(name);
when(mocked.getParentPath()).thenReturn(path);
return mocked;
}
private static void setConsoleHandler(Logger logger) {
// taken from https://stackoverflow.com/a/981230
// Handler for console (reuse it if it already exists)
Handler consoleHandler = null;
//see if there is already a console handler
for (Handler handler : logger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
//found the console handler
consoleHandler = handler;
break;
}
}
if (consoleHandler == null) {
//there was no console handler found, create a new one
consoleHandler = new ConsoleHandler();
logger.addHandler(consoleHandler);
}
//set the console handler to fine:
consoleHandler.setLevel(java.util.logging.Level.FINEST);
}
/**
* Retrieves an autopsy logger that does not write to disk.
*
* @param loggerName The name of the logger.
*
* @return The autopsy logger for the console
*
* @throws InstantiationException
* @throws IllegalStateException
*/
public static Logger getJavaLogger(String loggerName) {
// The logger doesn't appear to respond well to mocking with mockito.
// It appears that the issue may have to do with mocking methods in the java.* packages
// since the autopsy logger extends the java.util.logging.Logger class:
// https://javadoc.io/static/org.mockito/mockito-core/3.5.13/org/mockito/Mockito.html#39
Logger logger = Logger.getLogger(loggerName);
setConsoleHandler(logger);
return logger;
}
private TskMockUtils() {
}
}