mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge pull request #6079 from dannysmyda/6474-Update-Case-Report-Module
6474 update case report module
This commit is contained in:
commit
1cd48f4480
@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.report.modules.caseuco;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Writes Autopsy DataModel objects to Case UCO format.
|
||||
*
|
||||
* Clients are expected to add the Case first. Then they should add each data
|
||||
* source before adding any files for that data source.
|
||||
*
|
||||
* Here is an example, where we add everything:
|
||||
*
|
||||
* Path directory = Paths.get("C:", "Reports");
|
||||
* CaseUcoReportGenerator caseUco = new CaseUcoReportGenerator(directory, "my-report");
|
||||
*
|
||||
* Case caseObj = Case.getCurrentCase();
|
||||
* caseUco.addCase(caseObj);
|
||||
* List<Content> dataSources = caseObj.getDataSources();
|
||||
* for(Content dataSource : dataSources) {
|
||||
* caseUco.addDataSource(dataSource, caseObj);
|
||||
* List<AbstractFile> files = getAllFilesInDataSource(dataSource);
|
||||
* for(AbstractFile file : files) {
|
||||
* caseUco.addFile(file, dataSource);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Path reportOutput = caseUco.generateReport();
|
||||
* //Done. Report at - "C:\Reports\my-report.json-ld"
|
||||
*
|
||||
* Please note that the life cycle for this class ends with generateReport().
|
||||
* The underlying file handle to 'my-report.json-ld' will be closed. Any further
|
||||
* calls to addX() will result in an IOException.
|
||||
*/
|
||||
public final class CaseUcoReportGenerator {
|
||||
|
||||
private static final String EXTENSION = "json-ld";
|
||||
|
||||
private final TimeZone timeZone;
|
||||
private final Path reportPath;
|
||||
private final JsonGenerator reportGenerator;
|
||||
|
||||
/**
|
||||
* Creates a CaseUCO Report Generator that writes a report in the specified
|
||||
* directory.
|
||||
*
|
||||
* TimeZone is assumed to be GMT+0 for formatting file creation time,
|
||||
* accessed time and modified time.
|
||||
*
|
||||
* @param directory Directory to write the CaseUCO report file. Assumes the
|
||||
* calling thread has write access to the directory and that the directory
|
||||
* exists.
|
||||
* @param reportName Name of the CaseUCO report file.
|
||||
* @throws IOException If an I/O error occurs
|
||||
*/
|
||||
public CaseUcoReportGenerator(Path directory, String reportName) throws IOException {
|
||||
this.reportPath = directory.resolve(reportName + "." + EXTENSION);
|
||||
|
||||
JsonFactory jsonGeneratorFactory = new JsonFactory();
|
||||
reportGenerator = jsonGeneratorFactory.createGenerator(reportPath.toFile(), JsonEncoding.UTF8);
|
||||
// Puts a newline between each Key, Value pair for readability.
|
||||
reportGenerator.setPrettyPrinter(new DefaultPrettyPrinter()
|
||||
.withObjectIndenter(new DefaultIndenter(" ", "\n")));
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
|
||||
|
||||
reportGenerator.setCodec(mapper);
|
||||
|
||||
reportGenerator.writeStartObject();
|
||||
reportGenerator.writeFieldName("@graph");
|
||||
reportGenerator.writeStartArray();
|
||||
|
||||
//Assume GMT+0
|
||||
this.timeZone = new SimpleTimeZone(0, "GMT");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an AbstractFile instance to the Case UCO report.
|
||||
*
|
||||
* @param file AbstractFile instance to write
|
||||
* @param parentDataSource The parent data source for this abstract file. It
|
||||
* is assumed that this parent has been written to the report (via
|
||||
* addDataSource) prior to this call. Otherwise, the report may be invalid.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public void addFile(AbstractFile file, Content parentDataSource) throws IOException, TskCoreException {
|
||||
addFile(file, parentDataSource, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an AbstractFile instance to the Case UCO report.
|
||||
*
|
||||
* @param file AbstractFile instance to write
|
||||
* @param parentDataSource The parent data source for this abstract file. It
|
||||
* is assumed that this parent has been written to the report (via
|
||||
* addDataSource) prior to this call. Otherwise, the report may be invalid.
|
||||
* @param localPath The location of the file on secondary storage, somewhere
|
||||
* other than the case. Example: local disk. This value will be ignored if
|
||||
* it is null.
|
||||
* @throws IOException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public void addFile(AbstractFile file, Content parentDataSource, Path localPath) throws IOException, TskCoreException {
|
||||
String fileTraceId = getFileTraceId(file);
|
||||
|
||||
//Create the Trace CASE node, which will contain attributes about some evidence.
|
||||
//Trace is the standard term for evidence. For us, this means file system files.
|
||||
CASENode fileTrace = new CASENode(fileTraceId, "Trace");
|
||||
|
||||
//The bits of evidence for each Trace node are contained within Property
|
||||
//Bundles. There are a number of Property Bundles available in the CASE ontology.
|
||||
|
||||
//Build up the File Property Bundle, as the name implies - properties of
|
||||
//the file itself.
|
||||
CASEPropertyBundle filePropertyBundle = createFileBundle(file);
|
||||
fileTrace.addBundle(filePropertyBundle);
|
||||
|
||||
//Build up the ContentData Property Bundle, as the name implies - properties of
|
||||
//the File data itself.
|
||||
CASEPropertyBundle contentDataPropertyBundle = createContentDataBundle(file);
|
||||
fileTrace.addBundle(contentDataPropertyBundle);
|
||||
|
||||
if(localPath != null) {
|
||||
String urlTraceId = getURLTraceId(file);
|
||||
CASENode urlTrace = new CASENode(urlTraceId, "Trace");
|
||||
CASEPropertyBundle urlPropertyBundle = new CASEPropertyBundle("URL");
|
||||
urlPropertyBundle.addProperty("fullValue", localPath.toString());
|
||||
urlTrace.addBundle(urlPropertyBundle);
|
||||
|
||||
contentDataPropertyBundle.addProperty("dataPayloadReferenceUrl", urlTraceId);
|
||||
reportGenerator.writeObject(urlTrace);
|
||||
}
|
||||
|
||||
//Create the Relationship CASE node. This defines how the Trace CASE node described above
|
||||
//is related to another CASE node (in this case, the parent data source).
|
||||
String relationshipID = getRelationshipId(file);
|
||||
CASENode relationship = createRelationshipNode(relationshipID,
|
||||
fileTraceId, getDataSourceTraceId(parentDataSource));
|
||||
|
||||
//Build up the PathRelation bundle for the relationship node,
|
||||
//as the name implies - the Path of the Trace in the data source.
|
||||
CASEPropertyBundle pathRelationPropertyBundle = new CASEPropertyBundle("PathRelation");
|
||||
pathRelationPropertyBundle.addProperty("path", file.getUniquePath());
|
||||
relationship.addBundle(pathRelationPropertyBundle);
|
||||
|
||||
//This completes the triage, write them to JSON.
|
||||
reportGenerator.writeObject(fileTrace);
|
||||
reportGenerator.writeObject(relationship);
|
||||
}
|
||||
|
||||
private String getURLTraceId(Content content) {
|
||||
return "url-" + content.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* All relationship nodes will be the same within our context. Namely, contained-within
|
||||
* and isDirectional as true.
|
||||
*/
|
||||
private CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID) {
|
||||
CASENode relationship = new CASENode(relationshipID, "Relationship");
|
||||
relationship.addProperty("source", sourceID);
|
||||
relationship.addProperty("target", targetID);
|
||||
relationship.addProperty("kindOfRelationship", "contained-within");
|
||||
relationship.addProperty("isDirectional", true);
|
||||
return relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a File Property Bundle with a selection of file attributes.
|
||||
*/
|
||||
private CASEPropertyBundle createFileBundle(AbstractFile file) throws TskCoreException {
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
String createdTime = ContentUtils.getStringTimeISO8601(file.getCrtime(), timeZone);
|
||||
String accessedTime = ContentUtils.getStringTimeISO8601(file.getAtime(), timeZone);
|
||||
String modifiedTime = ContentUtils.getStringTimeISO8601(file.getMtime(), timeZone);
|
||||
filePropertyBundle.addProperty("createdTime", createdTime);
|
||||
filePropertyBundle.addProperty("accessedTime", accessedTime);
|
||||
filePropertyBundle.addProperty("modifiedTime", modifiedTime);
|
||||
if (!Strings.isNullOrEmpty(file.getNameExtension())) {
|
||||
filePropertyBundle.addProperty("extension", file.getNameExtension());
|
||||
}
|
||||
filePropertyBundle.addProperty("fileName", file.getName());
|
||||
filePropertyBundle.addProperty("filePath", file.getUniquePath());
|
||||
filePropertyBundle.addProperty("isDirectory", file.isDir());
|
||||
filePropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
|
||||
return filePropertyBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Content Data Property Bundle with a selection of file attributes.
|
||||
*/
|
||||
private CASEPropertyBundle createContentDataBundle(AbstractFile file) {
|
||||
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
|
||||
if (!Strings.isNullOrEmpty(file.getMIMEType())) {
|
||||
contentDataPropertyBundle.addProperty("mimeType", file.getMIMEType());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(file.getMd5Hash())) {
|
||||
List<CASEPropertyBundle> hashPropertyBundles = new ArrayList<>();
|
||||
CASEPropertyBundle md5HashPropertyBundle = new CASEPropertyBundle("Hash");
|
||||
md5HashPropertyBundle.addProperty("hashMethod", "MD5");
|
||||
md5HashPropertyBundle.addProperty("hashValue", file.getMd5Hash());
|
||||
hashPropertyBundles.add(md5HashPropertyBundle);
|
||||
contentDataPropertyBundle.addProperty("hash", hashPropertyBundles);
|
||||
}
|
||||
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
|
||||
return contentDataPropertyBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique CASE Node file trace id.
|
||||
*/
|
||||
private String getFileTraceId(AbstractFile file) {
|
||||
return "file-" + file.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique CASE Node relationship id value.
|
||||
*/
|
||||
private String getRelationshipId(Content content) {
|
||||
return "relationship-" + content.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Content instance (which is known to be a DataSource) to the CASE
|
||||
* report. This means writing a selection of attributes to a CASE or UCO
|
||||
* object.
|
||||
*
|
||||
* @param dataSource Datasource content to write
|
||||
* @param parentCase The parent case that this data source belongs in. It is
|
||||
* assumed that this parent has been written to the report (via addCase)
|
||||
* prior to this call. Otherwise, the report may be invalid.
|
||||
*/
|
||||
public void addDataSource(Content dataSource, Case parentCase) throws IOException, TskCoreException {
|
||||
String dataSourceTraceId = this.getDataSourceTraceId(dataSource);
|
||||
|
||||
CASENode dataSourceTrace = new CASENode(dataSourceTraceId, "Trace");
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
|
||||
String dataSourcePath = getDataSourcePath(dataSource);
|
||||
|
||||
filePropertyBundle.addProperty("filePath", dataSourcePath);
|
||||
dataSourceTrace.addBundle(filePropertyBundle);
|
||||
|
||||
if (dataSource.getSize() > 0) {
|
||||
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
|
||||
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(dataSource.getSize()));
|
||||
dataSourceTrace.addBundle(contentDataPropertyBundle);
|
||||
}
|
||||
|
||||
// create a "relationship" entry between the case and the data source
|
||||
String caseTraceId = getCaseTraceId(parentCase);
|
||||
String relationshipTraceId = getRelationshipId(dataSource);
|
||||
CASENode relationship = createRelationshipNode(relationshipTraceId,
|
||||
dataSourceTraceId, caseTraceId);
|
||||
|
||||
CASEPropertyBundle pathRelationBundle = new CASEPropertyBundle("PathRelation");
|
||||
pathRelationBundle.addProperty("path", dataSourcePath);
|
||||
relationship.addBundle(pathRelationBundle);
|
||||
|
||||
//This completes the triage, write them to JSON.
|
||||
reportGenerator.writeObject(dataSourceTrace);
|
||||
reportGenerator.writeObject(relationship);
|
||||
}
|
||||
|
||||
private String getDataSourcePath(Content dataSource) {
|
||||
String dataSourcePath = "";
|
||||
if (dataSource instanceof Image) {
|
||||
String[] paths = ((Image) dataSource).getPaths();
|
||||
if (paths.length > 0) {
|
||||
//Get the first data source in the path, as this will
|
||||
//be reflected in each file's uniquePath.
|
||||
dataSourcePath = paths[0];
|
||||
}
|
||||
} else {
|
||||
dataSourcePath = dataSource.getName();
|
||||
}
|
||||
dataSourcePath = dataSourcePath.replaceAll("\\\\", "/");
|
||||
return dataSourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique Case UCO trace id for a data source.
|
||||
*
|
||||
* @param dataSource
|
||||
* @return
|
||||
*/
|
||||
private String getDataSourceTraceId(Content dataSource) {
|
||||
return "data-source-" + dataSource.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Case instance to the Case UCO report. This means writing a
|
||||
* selection of Case attributes to a CASE/UCO object.
|
||||
*
|
||||
* @param caseObj Case instance to include in the report.
|
||||
* @throws IOException If an I/O error is encountered.
|
||||
*/
|
||||
public void addCase(Case caseObj) throws IOException {
|
||||
SleuthkitCase skCase = caseObj.getSleuthkitCase();
|
||||
|
||||
String caseDirPath = skCase.getDbDirPath();
|
||||
String caseTraceId = getCaseTraceId(caseObj);
|
||||
CASENode caseTrace = new CASENode(caseTraceId, "Trace");
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
|
||||
// replace double slashes with single ones
|
||||
caseDirPath = caseDirPath.replaceAll("\\\\", "/");
|
||||
|
||||
Case.CaseType caseType = caseObj.getCaseType();
|
||||
if (caseType.equals(CaseType.SINGLE_USER_CASE)) {
|
||||
filePropertyBundle.addProperty("filePath", caseDirPath + "/" + skCase.getDatabaseName());
|
||||
filePropertyBundle.addProperty("isDirectory", false);
|
||||
} else {
|
||||
filePropertyBundle.addProperty("filePath", caseDirPath);
|
||||
filePropertyBundle.addProperty("isDirectory", true);
|
||||
}
|
||||
|
||||
caseTrace.addBundle(filePropertyBundle);
|
||||
reportGenerator.writeObject(caseTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique Case UCO trace id for a Case.
|
||||
*
|
||||
* @param caseObj
|
||||
* @return
|
||||
*/
|
||||
private String getCaseTraceId(Case caseObj) {
|
||||
return "case-" + caseObj.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Path to the completed Case UCO report file.
|
||||
*
|
||||
* This marks the end of the CaseUcoReportGenerator's life cycle. This
|
||||
* function will close an underlying file handles, meaning any subsequent
|
||||
* calls to addX() will result in an IOException.
|
||||
*
|
||||
* @return The Path to the finalized report.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
public Path generateReport() throws IOException {
|
||||
//Finalize the report.
|
||||
reportGenerator.writeEndArray();
|
||||
reportGenerator.writeEndObject();
|
||||
reportGenerator.close();
|
||||
|
||||
return reportPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A CASE or UCO object. CASE objects can have properties and
|
||||
* property bundles.
|
||||
*/
|
||||
private final class CASENode {
|
||||
|
||||
private final String id;
|
||||
private final String type;
|
||||
|
||||
//Dynamic properties added to this CASENode.
|
||||
private final Map<String, Object> properties;
|
||||
private final List<CASEPropertyBundle> propertyBundle;
|
||||
|
||||
public CASENode(String id, String type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
properties = new LinkedHashMap<>();
|
||||
propertyBundle = new ArrayList<>();
|
||||
}
|
||||
|
||||
@JsonProperty("@id")
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@JsonProperty("@type")
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@JsonProperty("propertyBundle")
|
||||
public List<CASEPropertyBundle> getPropertyBundle() {
|
||||
return propertyBundle;
|
||||
}
|
||||
|
||||
public void addProperty(String key, Object val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
|
||||
public void addBundle(CASEPropertyBundle bundle) {
|
||||
propertyBundle.add(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains CASE or UCO properties.
|
||||
*/
|
||||
private final class CASEPropertyBundle {
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
public CASEPropertyBundle(String type) {
|
||||
properties = new LinkedHashMap<>();
|
||||
addProperty("@type", type);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void addProperty(String key, Object val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.report.modules.caseuco;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -39,14 +47,20 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.report.GeneralReportModule;
|
||||
import org.sleuthkit.autopsy.report.GeneralReportSettings;
|
||||
import org.sleuthkit.autopsy.report.ReportProgressPanel;
|
||||
import org.sleuthkit.caseuco.CaseUcoExporter;
|
||||
import org.sleuthkit.caseuco.ContentNotExportableException;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
|
||||
/**
|
||||
* CaseUcoReportModule generates a report in CASE-UCO format. This module will
|
||||
* write all files and data sources to the report.
|
||||
* Exports an Autopsy case to a CASE-UCO report file. This module will write all
|
||||
* files and artifacts from the selected data sources.
|
||||
*/
|
||||
public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
|
||||
@ -54,14 +68,16 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
private static final CaseUcoReportModule SINGLE_INSTANCE = new CaseUcoReportModule();
|
||||
|
||||
//Supported types of TSK_FS_FILES
|
||||
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {{
|
||||
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {
|
||||
{
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
|
||||
}};
|
||||
}
|
||||
};
|
||||
|
||||
private static final String REPORT_FILE_NAME = "CASE_UCO_output";
|
||||
private static final String EXTENSION = "json-ld";
|
||||
private static final String EXTENSION = "jsonld";
|
||||
|
||||
// Hidden constructor for the report
|
||||
private CaseUcoReportModule() {
|
||||
@ -140,42 +156,76 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
return;
|
||||
}
|
||||
|
||||
CaseUcoReportGenerator generator =
|
||||
new CaseUcoReportGenerator(reportDirectory, REPORT_FILE_NAME);
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
|
||||
//First write the Case to the report file.
|
||||
Case caseObj = Case.getCurrentCaseThrows();
|
||||
generator.addCase(caseObj);
|
||||
Path caseJsonReportFile = reportDirectory.resolve(REPORT_FILE_NAME + "." + EXTENSION);
|
||||
|
||||
List<Content> dataSources = caseObj.getDataSources().stream()
|
||||
.filter((dataSource) -> {
|
||||
if(settings.getSelectedDataSources() == null) {
|
||||
// Assume all data sources if list is null.
|
||||
return true;
|
||||
try (OutputStream stream = new FileOutputStream(caseJsonReportFile.toFile());
|
||||
JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
reportWriter.setIndent(" ");
|
||||
reportWriter.beginObject();
|
||||
reportWriter.name("@graph");
|
||||
reportWriter.beginArray();
|
||||
|
||||
CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
|
||||
for (JsonElement element : exporter.exportSleuthkitCase()) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
return settings.getSelectedDataSources().contains(dataSource.getId());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Get a list of selected data sources to process.
|
||||
List<DataSource> dataSources = getSelectedDataSources(currentCase, settings);
|
||||
|
||||
progressPanel.setIndeterminate(false);
|
||||
progressPanel.setMaximumProgress(dataSources.size());
|
||||
progressPanel.start();
|
||||
|
||||
//Then search each data source for file content.
|
||||
for(int i = 0; i < dataSources.size(); i++) {
|
||||
Content dataSource = dataSources.get(i);
|
||||
// First stage of reporting is for files and data sources.
|
||||
// Iterate through each data source and dump all files contained
|
||||
// in that data source.
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
DataSource dataSource = dataSources.get(i);
|
||||
progressPanel.updateStatusLabel(String.format(
|
||||
Bundle.CaseUcoReportModule_processingDataSource(),
|
||||
dataSource.getName()));
|
||||
//Add the data source and then all children.
|
||||
generator.addDataSource(dataSource, caseObj);
|
||||
performDepthFirstSearch(dataSource, generator);
|
||||
progressPanel.setProgress(i+1);
|
||||
// Add the data source export.
|
||||
for (JsonElement element : exporter.exportDataSource(dataSource)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
// Search all children of the data source.
|
||||
performDepthFirstSearch(dataSource, gson, exporter, reportWriter);
|
||||
progressPanel.setProgress(i + 1);
|
||||
}
|
||||
|
||||
//Complete the report.
|
||||
Path reportPath = generator.generateReport();
|
||||
caseObj.addReport(reportPath.toString(),
|
||||
// Second stage of reporting handles artifacts.
|
||||
Set<Long> dataSourceIds = dataSources.stream()
|
||||
.map((datasource) -> datasource.getId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Write all standard artifacts that are contained within the
|
||||
// selected data sources.
|
||||
for (ARTIFACT_TYPE artType : currentCase.getSleuthkitCase().getBlackboardArtifactTypesInUse()) {
|
||||
for (BlackboardArtifact artifact : currentCase.getSleuthkitCase().getBlackboardArtifacts(artType)) {
|
||||
if (dataSourceIds.contains(artifact.getDataSource().getId())) {
|
||||
|
||||
try {
|
||||
for (JsonElement element : exporter.exportBlackboardArtifact(artifact)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
} catch (ContentNotExportableException | BlackboardJsonAttrUtil.InvalidJsonException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to export blackboard artifact (id: %d) to CASE/UCO. "
|
||||
+ "The artifact type is either not supported or the artifact instance does not have any "
|
||||
+ "exportable attributes.", artifact.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reportWriter.endArray();
|
||||
reportWriter.endObject();
|
||||
}
|
||||
|
||||
currentCase.addReport(caseJsonReportFile.toString(),
|
||||
Bundle.CaseUcoReportModule_srcModuleName(),
|
||||
REPORT_FILE_NAME);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
|
||||
@ -196,6 +246,21 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected data sources from the settings instance.
|
||||
*/
|
||||
private List<DataSource> getSelectedDataSources(Case currentCase, GeneralReportSettings settings) throws TskCoreException {
|
||||
return currentCase.getSleuthkitCase().getDataSources().stream()
|
||||
.filter((dataSource) -> {
|
||||
if (settings.getSelectedDataSources() == null) {
|
||||
// Assume all data sources if list is null.
|
||||
return true;
|
||||
}
|
||||
return settings.getSelectedDataSources().contains(dataSource.getId());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn the user if ingest is still ongoing.
|
||||
*/
|
||||
@ -207,25 +272,27 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
|
||||
/**
|
||||
* Perform DFS on the data sources tree, which will search it in entirety.
|
||||
* This traversal is more memory efficient than BFS (Breadth first search).
|
||||
*/
|
||||
private void performDepthFirstSearch(Content dataSource,
|
||||
CaseUcoReportGenerator generator) throws IOException, TskCoreException {
|
||||
private void performDepthFirstSearch(DataSource dataSource,
|
||||
Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter) throws IOException, TskCoreException {
|
||||
|
||||
Deque<Content> stack = new ArrayDeque<>();
|
||||
stack.addAll(dataSource.getChildren());
|
||||
|
||||
//Depth First Search the data source tree.
|
||||
while(!stack.isEmpty()) {
|
||||
while (!stack.isEmpty()) {
|
||||
Content current = stack.pop();
|
||||
if(current instanceof AbstractFile) {
|
||||
AbstractFile f = (AbstractFile) (current);
|
||||
if(SUPPORTED_TYPES.contains(f.getMetaType().getValue())) {
|
||||
generator.addFile(f, dataSource);
|
||||
if (current instanceof AbstractFile) {
|
||||
AbstractFile file = (AbstractFile) (current);
|
||||
if (SUPPORTED_TYPES.contains(file.getMetaType().getValue())) {
|
||||
|
||||
for (JsonElement element : exporter.exportAbstractFile(file)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Content child : current.getChildren()) {
|
||||
for (Content child : current.getChildren()) {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.report.modules.portablecase;
|
||||
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.sleuthkit.autopsy.report.ReportModule;
|
||||
import java.util.logging.Level;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -49,7 +56,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory;
|
||||
import org.sleuthkit.autopsy.report.ReportProgressPanel;
|
||||
import org.sleuthkit.autopsy.report.modules.caseuco.CaseUcoReportGenerator;
|
||||
import org.sleuthkit.caseuco.CaseUcoExporter;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
@ -76,6 +83,7 @@ import org.sleuthkit.datamodel.VolumeSystem;
|
||||
* Creates a portable case from tagged files
|
||||
*/
|
||||
public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
|
||||
private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
|
||||
private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
|
||||
@ -155,9 +163,9 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to avoid code duplication.
|
||||
* Assumes that if an exception is supplied then the error is SEVERE. Otherwise
|
||||
* it is logged as a WARNING.
|
||||
* Convenience method to avoid code duplication. Assumes that if an
|
||||
* exception is supplied then the error is SEVERE. Otherwise it is logged as
|
||||
* a WARNING.
|
||||
*
|
||||
* @param logWarning Warning to write to the log
|
||||
* @param dialogWarning Warning to write to a pop-up window
|
||||
@ -214,13 +222,13 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Validate the input parameters
|
||||
File outputDir = new File(reportPath);
|
||||
if (! outputDir.exists()) {
|
||||
if (!outputDir.exists()) {
|
||||
handleError("Output folder " + outputDir.toString() + " does not exist",
|
||||
Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
if (! outputDir.isDirectory()) {
|
||||
if (!outputDir.isDirectory()) {
|
||||
handleError("Output folder " + outputDir.toString() + " is not a folder",
|
||||
Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
|
||||
return;
|
||||
@ -295,7 +303,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
// Copy the selected tags
|
||||
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
for (TagName tagName : tagNames) {
|
||||
TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
|
||||
oldTagNameToNewTagName.put(tagName, newTagName);
|
||||
}
|
||||
@ -305,10 +313,10 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
// Set up tracking to support any custom artifact or attribute types
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
||||
for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) {
|
||||
oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
|
||||
}
|
||||
for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
||||
for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
|
||||
try {
|
||||
oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
|
||||
} catch (TskCoreException ex) {
|
||||
@ -320,7 +328,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Copy the tagged files
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
for (TagName tagName : tagNames) {
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
handleCancellation(progressPanel);
|
||||
@ -342,7 +350,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Copy the tagged artifacts and associated files
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
for (TagName tagName : tagNames) {
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
handleCancellation(progressPanel);
|
||||
@ -363,10 +371,10 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
// Copy interesting files and results
|
||||
if (! setNames.isEmpty()) {
|
||||
if (!setNames.isEmpty()) {
|
||||
try {
|
||||
List<BlackboardArtifact> interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
for (BlackboardArtifact art:interestingFiles) {
|
||||
for (BlackboardArtifact art : interestingFiles) {
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
handleCancellation(progressPanel);
|
||||
@ -385,7 +393,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
try {
|
||||
List<BlackboardArtifact> interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
|
||||
for (BlackboardArtifact art:interestingResults) {
|
||||
for (BlackboardArtifact art : interestingResults) {
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
handleCancellation(progressPanel);
|
||||
@ -423,7 +431,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
// Errors have been handled already
|
||||
return;
|
||||
}
|
||||
@ -456,17 +464,23 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
private void generateCaseUcoReport(List<TagName> tagNames, List<String> setNames, ReportProgressPanel progressPanel) {
|
||||
//Create the 'Reports' directory to include a CASE-UCO report.
|
||||
Path reportsDirectory = Paths.get(caseFolder.toString(), "Reports");
|
||||
if(!reportsDirectory.toFile().mkdir()) {
|
||||
if (!reportsDirectory.toFile().mkdir()) {
|
||||
logger.log(Level.SEVERE, "Could not make the report folder... skipping "
|
||||
+ "CASE-UCO report generation for the portable case");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//Try to generate case uco output.
|
||||
Path reportFile = reportsDirectory.resolve(CASE_UCO_FILE_NAME);
|
||||
|
||||
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration());
|
||||
CaseUcoReportGenerator reportGenerator = new CaseUcoReportGenerator(reportsDirectory, CASE_UCO_FILE_NAME);
|
||||
//Acquire references for file discovery
|
||||
try (OutputStream stream = new FileOutputStream(reportFile.toFile());
|
||||
JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
reportWriter.setIndent(" ");
|
||||
reportWriter.beginObject();
|
||||
reportWriter.name("@graph");
|
||||
reportWriter.beginArray();
|
||||
|
||||
String caseTempDirectory = currentCase.getTempDirectory();
|
||||
SleuthkitCase skCase = currentCase.getSleuthkitCase();
|
||||
TagsManager tagsManager = currentCase.getServices().getTagsManager();
|
||||
@ -477,7 +491,10 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
FileUtils.deleteDirectory(tmpDir.toFile());
|
||||
Files.createDirectory(tmpDir);
|
||||
|
||||
reportGenerator.addCase(currentCase);
|
||||
CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
|
||||
for (JsonElement element : exporter.exportSleuthkitCase()) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
|
||||
//Load all interesting BlackboardArtifacts that belong to the selected SET_NAMEs
|
||||
//binned by data source id.
|
||||
@ -485,35 +502,33 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
//Search each data source looking for content tags and interesting
|
||||
//items that match the selected tag names and set names.
|
||||
for (Content dataSource : currentCase.getDataSources()) {
|
||||
/**
|
||||
* It is currently believed that DataSources in a CASE-UCO report
|
||||
* should precede all file entities. Therefore, before
|
||||
* writing a file, add the data source if it
|
||||
* has yet to be included.
|
||||
*/
|
||||
for (DataSource dataSource : currentCase.getSleuthkitCase().getDataSources()) {
|
||||
// Helper flag to ensure each data source is only written once in
|
||||
// a report.
|
||||
boolean dataSourceHasBeenIncluded = false;
|
||||
|
||||
//Search content tags and artifact tags that match
|
||||
for (TagName tagName : tagNames) {
|
||||
for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) {
|
||||
dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(),
|
||||
dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded);
|
||||
dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
|
||||
}
|
||||
for (BlackboardArtifactTag bat : tagsManager.getBlackboardArtifactTagsByTagName(tagName, dataSource.getId())) {
|
||||
dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(),
|
||||
dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded);
|
||||
dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
|
||||
}
|
||||
}
|
||||
//Search artifacts that this data source contains
|
||||
for(BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
|
||||
for (BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
|
||||
Content sourceContent = bArt.getParent();
|
||||
dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource,
|
||||
tmpDir, reportGenerator, dataSourceHasBeenIncluded);
|
||||
tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
|
||||
}
|
||||
}
|
||||
|
||||
//Create the report.
|
||||
reportGenerator.generateReport();
|
||||
// Finish the report.
|
||||
reportWriter.endArray();
|
||||
reportWriter.endObject();
|
||||
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_successCaseUcoReportGeneration());
|
||||
} catch (IOException | TskCoreException ex) {
|
||||
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_errorGeneratingCaseUcoReport());
|
||||
@ -530,13 +545,13 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
*/
|
||||
private Multimap<Long, BlackboardArtifact> getInterestingArtifactsBySetName(SleuthkitCase skCase, List<String> setNames) throws TskCoreException {
|
||||
Multimap<Long, BlackboardArtifact> artifactsWithSetName = ArrayListMultimap.create();
|
||||
if(!setNames.isEmpty()) {
|
||||
if (!setNames.isEmpty()) {
|
||||
List<BlackboardArtifact> allArtifacts = skCase.getBlackboardArtifacts(
|
||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
allArtifacts.addAll(skCase.getBlackboardArtifacts(
|
||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT));
|
||||
|
||||
for(BlackboardArtifact bArt : allArtifacts) {
|
||||
for (BlackboardArtifact bArt : allArtifacts) {
|
||||
BlackboardAttribute setAttr = bArt.getAttribute(
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
if (setNames.contains(setAttr.getValueString())) {
|
||||
@ -555,25 +570,29 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
* @param tmpDir Path to the tmpDir to enforce uniqueness
|
||||
* @param reportGenerator Report generator instance to add the content to
|
||||
* @param dataSourceHasBeenIncluded Flag determining if the data source
|
||||
* should be written before the file. False will cause the data source to be written.
|
||||
* should be written to the report (false indicates that it should be written).
|
||||
* @throws IOException If an I/O error occurs.
|
||||
* @throws TskCoreException If an internal database error occurs.
|
||||
*
|
||||
* return True if the data source was written during this operation.
|
||||
* return True if the file was written during this operation.
|
||||
*/
|
||||
private boolean addUniqueFile(Content content, Content dataSource,
|
||||
Path tmpDir, CaseUcoReportGenerator reportGenerator,
|
||||
private boolean addUniqueFile(Content content, DataSource dataSource,
|
||||
Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter,
|
||||
boolean dataSourceHasBeenIncluded) throws IOException, TskCoreException {
|
||||
if (content instanceof AbstractFile && !(content instanceof DataSource)) {
|
||||
AbstractFile absFile = (AbstractFile) content;
|
||||
Path filePath = tmpDir.resolve(Long.toString(absFile.getId()));
|
||||
if (!absFile.isDir() && !Files.exists(filePath)) {
|
||||
if(!dataSourceHasBeenIncluded) {
|
||||
reportGenerator.addDataSource(dataSource, currentCase);
|
||||
if (!dataSourceHasBeenIncluded) {
|
||||
for (JsonElement element : exporter.exportDataSource(dataSource)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
}
|
||||
String subFolder = getExportSubfolder(absFile);
|
||||
String fileName = absFile.getId() + "-" + FileUtil.escapeFileName(absFile.getName());
|
||||
reportGenerator.addFile(absFile, dataSource, Paths.get(FILE_FOLDER_NAME, subFolder, fileName));
|
||||
for (JsonElement element : exporter.exportAbstractFile(absFile, Paths.get(FILE_FOLDER_NAME, subFolder, fileName).toString())) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
Files.createFile(filePath);
|
||||
return true;
|
||||
}
|
||||
@ -604,10 +623,9 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
return setNames;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the case directory and case database.
|
||||
* portableSkCase will be set if this completes without error.
|
||||
* Create the case directory and case database. portableSkCase will be set
|
||||
* if this completes without error.
|
||||
*
|
||||
* @param outputDir The parent for the case folder
|
||||
* @param progressPanel
|
||||
@ -618,8 +636,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
"PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
|
||||
"# {0} - folder",
|
||||
"PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
|
||||
"PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
|
||||
})
|
||||
"PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",})
|
||||
private void createCase(File outputDir, ReportProgressPanel progressPanel) {
|
||||
|
||||
// Create the case folder
|
||||
@ -651,23 +668,23 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Create the base folder for the copied files
|
||||
copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
|
||||
if (! copiedFilesFolder.mkdir()) {
|
||||
if (!copiedFilesFolder.mkdir()) {
|
||||
handleError("Error creating folder " + copiedFilesFolder.toString(),
|
||||
Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
// Create subfolders for the copied files
|
||||
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
|
||||
for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
|
||||
File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
|
||||
if (! subFolder.mkdir()) {
|
||||
if (!subFolder.mkdir()) {
|
||||
handleError("Error creating folder " + subFolder.toString(),
|
||||
Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
|
||||
return;
|
||||
}
|
||||
}
|
||||
File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
|
||||
if (! unknownTypeFolder.mkdir()) {
|
||||
if (!unknownTypeFolder.mkdir()) {
|
||||
handleError("Error creating folder " + unknownTypeFolder.toString(),
|
||||
Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
|
||||
return;
|
||||
@ -706,7 +723,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Create the image tags table in the portable case
|
||||
CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
|
||||
if (! portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
|
||||
if (!portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
|
||||
portableDbAccessManager.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE);
|
||||
}
|
||||
}
|
||||
@ -738,7 +755,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
long newFileId = copyContentToPortableCase(content, progressPanel);
|
||||
|
||||
// Tag the file
|
||||
if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
|
||||
if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
|
||||
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
|
||||
}
|
||||
ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
|
||||
@ -746,7 +763,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
// Get the image tag data associated with this tag (empty string if there is none)
|
||||
// and save it if present
|
||||
String appData = getImageTagDataForContentTag(tag);
|
||||
if (! appData.isEmpty()) {
|
||||
if (!appData.isEmpty()) {
|
||||
addImageTagToPortableCase(newContentTag.getAddedTag(), appData);
|
||||
}
|
||||
}
|
||||
@ -758,7 +775,8 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
*
|
||||
* @param tag The ContentTag in the current case
|
||||
*
|
||||
* @return The app_data string for this content tag or an empty string if there was none
|
||||
* @return The app_data string for this content tag or an empty string if
|
||||
* there was none
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@ -816,7 +834,6 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add all artifacts with a given tag to the portable case.
|
||||
*
|
||||
@ -845,7 +862,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
|
||||
|
||||
// Tag the artfiact
|
||||
if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
|
||||
if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
|
||||
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
|
||||
}
|
||||
portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
|
||||
@ -853,9 +870,11 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an artifact into the new case. Will also copy any associated artifacts
|
||||
* Copy an artifact into the new case. Will also copy any associated
|
||||
* artifacts
|
||||
*
|
||||
* @param newContentId The content ID (in the portable case) of the source content
|
||||
* @param newContentId The content ID (in the portable case) of the source
|
||||
* content
|
||||
* @param artifactToCopy The artifact to copy
|
||||
*
|
||||
* @return The new artifact in the portable case
|
||||
@ -884,7 +903,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
|
||||
|
||||
// Copy over each attribute, making sure the type is in the new case.
|
||||
for (BlackboardAttribute oldAttr:oldAttrs) {
|
||||
for (BlackboardAttribute oldAttr : oldAttrs) {
|
||||
|
||||
// The associated artifact has already been handled
|
||||
if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
|
||||
@ -927,8 +946,9 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the artifact type ID in the portable case and create new artifact type if needed.
|
||||
* For built-in artifacts this will be the same as the original.
|
||||
* Get the artifact type ID in the portable case and create new artifact
|
||||
* type if needed. For built-in artifacts this will be the same as the
|
||||
* original.
|
||||
*
|
||||
* @param oldArtifact The artifact in the current case
|
||||
*
|
||||
@ -950,8 +970,9 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute type ID in the portable case and create new attribute type if needed.
|
||||
* For built-in attributes this will be the same as the original.
|
||||
* Get the attribute type ID in the portable case and create new attribute
|
||||
* type if needed. For built-in attributes this will be the same as the
|
||||
* original.
|
||||
*
|
||||
* @param oldAttribute The attribute in the current case
|
||||
*
|
||||
@ -985,8 +1006,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - File name",
|
||||
"PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",
|
||||
})
|
||||
"PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",})
|
||||
private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
|
||||
progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
|
||||
return copyContent(content);
|
||||
@ -1018,38 +1038,38 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
Content newContent;
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
|
||||
newContent = copyArtifact(parentId, artifactToCopy);
|
||||
} else {
|
||||
CaseDbTransaction trans = portableSkCase.beginTransaction();
|
||||
try {
|
||||
if (content instanceof Image) {
|
||||
Image image = (Image)content;
|
||||
Image image = (Image) content;
|
||||
newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
||||
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
|
||||
} else if (content instanceof VolumeSystem) {
|
||||
VolumeSystem vs = (VolumeSystem)content;
|
||||
VolumeSystem vs = (VolumeSystem) content;
|
||||
newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
|
||||
} else if (content instanceof Volume) {
|
||||
Volume vs = (Volume)content;
|
||||
Volume vs = (Volume) content;
|
||||
newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
|
||||
vs.getDescription(), vs.getFlags(), trans);
|
||||
} else if (content instanceof Pool) {
|
||||
Pool pool = (Pool)content;
|
||||
Pool pool = (Pool) content;
|
||||
newContent = portableSkCase.addPool(parentId, pool.getType(), trans);
|
||||
} else if (content instanceof FileSystem) {
|
||||
FileSystem fs = (FileSystem)content;
|
||||
FileSystem fs = (FileSystem) content;
|
||||
newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
||||
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
||||
fs.getName(), trans);
|
||||
} else if (content instanceof BlackboardArtifact) {
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
|
||||
BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
|
||||
newContent = copyArtifact(parentId, artifactToCopy);
|
||||
} else if (content instanceof AbstractFile) {
|
||||
AbstractFile abstractFile = (AbstractFile)content;
|
||||
AbstractFile abstractFile = (AbstractFile) content;
|
||||
|
||||
if (abstractFile instanceof LocalFilesDataSource) {
|
||||
LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
|
||||
LocalFilesDataSource localFilesDS = (LocalFilesDataSource) abstractFile;
|
||||
newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
|
||||
} else {
|
||||
if (abstractFile.isDir()) {
|
||||
@ -1065,7 +1085,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Get the new parent object in the portable case database
|
||||
Content oldParent = abstractFile.getParent();
|
||||
if (! oldIdToNewContent.containsKey(oldParent.getId())) {
|
||||
if (!oldIdToNewContent.containsKey(oldParent.getId())) {
|
||||
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
|
||||
}
|
||||
Content newParent = oldIdToNewContent.get(oldParent.getId());
|
||||
@ -1090,7 +1110,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
trans.commit();
|
||||
} catch (TskCoreException ex) {
|
||||
trans.rollback();
|
||||
throw(ex);
|
||||
throw (ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1112,7 +1132,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
return UNKNOWN_FILE_TYPE_FOLDER;
|
||||
}
|
||||
|
||||
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
|
||||
for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
|
||||
if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
|
||||
return cat.getDisplayName();
|
||||
}
|
||||
@ -1153,7 +1173,6 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
configPanel = new CreatePortableCasePanel();
|
||||
return configPanel;
|
||||
} */
|
||||
|
||||
private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
|
||||
|
||||
private final String tableName;
|
||||
@ -1190,8 +1209,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
"# {0} - Temp folder path",
|
||||
"PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
|
||||
"PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
|
||||
"PortableCaseReportModule.compressCase.canceled=Compression canceled by user",
|
||||
})
|
||||
"PortableCaseReportModule.compressCase.canceled=Compression canceled by user",})
|
||||
private boolean compressCase(ReportProgressPanel progressPanel) {
|
||||
|
||||
// Close the portable case database (we still need some of the variables that would be cleared by cleanup())
|
||||
@ -1199,7 +1217,7 @@ public class PortableCaseReportModule implements ReportModule {
|
||||
|
||||
// Make a temporary folder for the compressed case
|
||||
File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS
|
||||
if (! tempZipFolder.mkdir()) {
|
||||
if (!tempZipFolder.mkdir()) {
|
||||
handleError("Error creating temporary folder " + tempZipFolder.toString(),
|
||||
Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
|
||||
return false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user