mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge branch 'develop' of github.com:sleuthkit/autopsy into CloudClientLatest
This commit is contained in:
commit
08f17d844d
11
BUILDING.txt
11
BUILDING.txt
@ -37,16 +37,16 @@ to the root 64-bit JRE directory.
|
||||
2) Get Sleuth Kit Setup
|
||||
2a) Download and build a Release version of Sleuth Kit (TSK) 4.0. See
|
||||
win32\BUILDING.txt in the TSK package for more information. You need to
|
||||
build the tsk_jni project. Select the Release_PostgreSQL Win32 or x64 target,
|
||||
build the tsk_jni project. Select the Release Win32 or x64 target,
|
||||
depending upon your target build. You can use a released version or download
|
||||
the latest from github:
|
||||
- git://github.com/sleuthkit/sleuthkit.git
|
||||
|
||||
2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in
|
||||
2b) Build the TSK JAR file by typing 'ant dist' in
|
||||
bindings/java in the
|
||||
TSK source code folder from a command line. Note it is case
|
||||
sensitive. You can also add the code to a NetBeans project and build
|
||||
it from there, selecting the dist-PostgreSQL target.
|
||||
it from there, selecting the dist target.
|
||||
|
||||
2c) Set TSK_HOME environment variable to the root directory of TSK
|
||||
|
||||
@ -54,6 +54,9 @@ to the root 64-bit JRE directory.
|
||||
from the TSK root directory to install the libraries and such in
|
||||
the needed places (i.e. '/usr/local').
|
||||
|
||||
2e) Build the TSK CaseUco jar file by running 'ant' in
|
||||
the case-uco/java folder of the TSK source folder. You can also add the
|
||||
code to a NetBeans project and build using the regular 'build' action.
|
||||
|
||||
3) For Windows builds, GStreamer must be setup. GStreamer is used to view video
|
||||
files. You can either download it and install it, or you can copy it from the
|
||||
@ -100,7 +103,7 @@ the build process.
|
||||
|
||||
- The Sleuth Kit Java datamodel JAR file has native JNI libraries
|
||||
that are copied into it. These JNI libraries have dependencies on
|
||||
libewf, zlib, libpq, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows
|
||||
libewf, zlib, libintl-8, libeay32, and ssleay32 DLL files. On non-Windows
|
||||
platforms, the JNI library also has a dependency on libtsk (on Windows,
|
||||
it is compiled into libtsk_jni).
|
||||
|
||||
|
@ -110,7 +110,9 @@
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/c3p0-0.9.5.jar"
|
||||
tofile="${ext.dir}/c3p0-0.9.5.jar"/>
|
||||
<copy file="${env.TSK_HOME}/bindings/java/lib/SparseBitSet-1.1.jar"
|
||||
tofile="${ext.dir}/SparseBitSet-1.1.jar"/>
|
||||
tofile="${ext.dir}/SparseBitSet-1.1.jar"/>
|
||||
<copy file="${env.TSK_HOME}/case-uco/java/dist/sleuthkit-caseuco-${TSK_VERSION}.jar"
|
||||
tofile="${ext.dir}/sleuthkit-caseuco-${TSK_VERSION}.jar"/>
|
||||
</target>
|
||||
|
||||
<target name="download-binlist">
|
||||
|
@ -2,7 +2,7 @@ Manifest-Version: 1.0
|
||||
OpenIDE-Module: org.sleuthkit.autopsy.core/10
|
||||
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
|
||||
OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
|
||||
OpenIDE-Module-Implementation-Version: 31
|
||||
OpenIDE-Module-Implementation-Version: 32
|
||||
OpenIDE-Module-Requires: org.openide.windows.WindowManager
|
||||
AutoUpdate-Show-In-Client: true
|
||||
AutoUpdate-Essential-Module: true
|
||||
|
@ -83,6 +83,7 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
|
||||
file.reference.sis-metadata-0.8.jar=release\\modules\\ext\\sis-metadata-0.8.jar
|
||||
file.reference.sis-netcdf-0.8.jar=release\\modules\\ext\\sis-netcdf-0.8.jar
|
||||
file.reference.sis-utility-0.8.jar=release\\modules\\ext\\sis-utility-0.8.jar
|
||||
file.reference.sleuthkit-caseuco-4.9.0.jar=release\\modules\\ext\\sleuthkit-caseuco-4.9.0.jar
|
||||
file.reference.slf4j-api-1.7.25.jar=release\\modules\\ext\\slf4j-api-1.7.25.jar
|
||||
file.reference.sqlite-jdbc-3.25.2.jar=release/modules/ext/sqlite-jdbc-3.25.2.jar
|
||||
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
|
||||
@ -138,5 +139,5 @@ nbm.homepage=http://www.sleuthkit.org/
|
||||
nbm.module.author=Brian Carrier
|
||||
nbm.needs.restart=true
|
||||
source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar
|
||||
spec.version.base=10.19
|
||||
spec.version.base=10.20
|
||||
|
||||
|
@ -779,6 +779,10 @@
|
||||
<runtime-relative-path>ext/curator-client-2.8.0.jar</runtime-relative-path>
|
||||
<binary-origin>release/modules/ext/curator-client-2.8.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/sleuthkit-caseuco-4.9.0.jar</runtime-relative-path>
|
||||
<binary-origin>release\modules\ext\sleuthkit-caseuco-4.9.0.jar</binary-origin>
|
||||
</class-path-extension>
|
||||
<class-path-extension>
|
||||
<runtime-relative-path>ext/fontbox-2.0.13.jar</runtime-relative-path>
|
||||
<binary-origin>release\modules\ext\fontbox-2.0.13.jar</binary-origin>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Copyright 2013-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -139,7 +139,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
|
||||
if (!tagNamesMap.isEmpty()) {
|
||||
for (Map.Entry<String, TagName> entry : tagNamesMap.entrySet()) {
|
||||
TagName tagName = entry.getValue();
|
||||
TagSet tagSet = tagName.getTagSet();
|
||||
TagSet tagSet = tagsManager.getTagSet(tagName);
|
||||
|
||||
// Show custom tags before predefined tags in the menu
|
||||
if (tagSet != null) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,7 +23,6 @@ import java.awt.Window;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -36,7 +35,6 @@ import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.KeyStroke;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -150,7 +148,7 @@ public class GetTagNameAndCommentDialog extends JDialog {
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
try {
|
||||
TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager();
|
||||
List<String> standardTagNames = TagsManager.getStandardTagNames();
|
||||
Map<String, TagName> tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap());
|
||||
@ -161,7 +159,7 @@ public class GetTagNameAndCommentDialog extends JDialog {
|
||||
tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> {
|
||||
TagSet tagSet = null;
|
||||
try {
|
||||
tagSet = tagName.getTagSet();
|
||||
tagSet = tagsManager.getTagSet(tagName);
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(GetTagNameAndCommentDialog.class
|
||||
.getName()).log(Level.SEVERE, "Failed to get tag set", ex); //NON-NLS
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018 Basis Technology Corp.
|
||||
* Copyright 2018-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -131,7 +131,7 @@ abstract class ReplaceTagAction<T extends Tag> extends AbstractAction implements
|
||||
if (!tagNamesMap.isEmpty()) {
|
||||
for (Map.Entry<String, TagName> entry : tagNamesMap.entrySet()) {
|
||||
TagName tagName = entry.getValue();
|
||||
TagSet tagSet = tagName.getTagSet();
|
||||
TagSet tagSet = tagsManager.getTagSet(tagName);
|
||||
|
||||
// Show custom tags before predefined tags in the menu
|
||||
if (tagSet != null) {
|
||||
|
@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagewriter.ImageWriterService;
|
||||
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
|
||||
import org.sleuthkit.datamodel.AddDataSourceCallbacks;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||
@ -42,17 +43,10 @@ import org.sleuthkit.datamodel.TskDataException;
|
||||
class AddImageTask implements Runnable {
|
||||
|
||||
private final Logger logger = Logger.getLogger(AddImageTask.class.getName());
|
||||
private final String deviceId;
|
||||
private final String imagePath;
|
||||
private final int sectorSize;
|
||||
private final String timeZone;
|
||||
private final ImageWriterSettings imageWriterSettings;
|
||||
private final boolean ignoreFatOrphanFiles;
|
||||
private final String md5;
|
||||
private final String sha1;
|
||||
private final String sha256;
|
||||
private final ImageDetails imageDetails;
|
||||
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||
private final DataSourceProcessorCallback callback;
|
||||
private final AddDataSourceCallbacks addDataSourceCallbacks;
|
||||
private final AddImageTaskCallback addImageTaskCallback;
|
||||
private boolean criticalErrorOccurred;
|
||||
|
||||
/*
|
||||
@ -73,40 +67,18 @@ class AddImageTask implements Runnable {
|
||||
|
||||
/**
|
||||
* Constructs a runnable task that adds an image to the case database.
|
||||
*
|
||||
* @param deviceId An ASCII-printable identifier for the device
|
||||
* associated with the data source that is
|
||||
* intended to be unique across multiple cases
|
||||
* (e.g., a UUID).
|
||||
* @param imagePath Path to the image file.
|
||||
* @param sectorSize The sector size (use '0' for autodetect).
|
||||
* @param timeZone The time zone to use when processing dates
|
||||
* and times for the image, obtained from
|
||||
* java.util.TimeZone.getID.
|
||||
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
|
||||
* FAT filesystem.
|
||||
* @param md5 The MD5 hash of the image, may be null.
|
||||
* @param sha1 The SHA-1 hash of the image, may be null.
|
||||
* @param sha256 The SHA-256 hash of the image, may be null.
|
||||
* @param imageWriterPath Path that a copy of the image should be
|
||||
* written to. Use empty string to disable image
|
||||
* writing
|
||||
*
|
||||
* @param imageDetails Holds all data about the image.
|
||||
* @param progressMonitor Progress monitor to report progress during
|
||||
* processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
* @param addDataSourceCallbacks Callback for sending data to the ingest pipeline if an ingest stream is being used.
|
||||
* @param addImageTaskCallback Callback for dealing with add image task completion.
|
||||
*/
|
||||
AddImageTask(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings,
|
||||
DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
this.deviceId = deviceId;
|
||||
this.imagePath = imagePath;
|
||||
this.sectorSize = sectorSize;
|
||||
this.timeZone = timeZone;
|
||||
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
|
||||
this.md5 = md5;
|
||||
this.sha1 = sha1;
|
||||
this.sha256 = sha256;
|
||||
this.imageWriterSettings = imageWriterSettings;
|
||||
this.callback = callback;
|
||||
AddImageTask(ImageDetails imageDetails, DataSourceProcessorProgressMonitor progressMonitor, AddDataSourceCallbacks addDataSourceCallbacks,
|
||||
AddImageTaskCallback addImageTaskCallback) {
|
||||
this.imageDetails = imageDetails;
|
||||
this.addDataSourceCallbacks = addDataSourceCallbacks;
|
||||
this.addImageTaskCallback = addImageTaskCallback;
|
||||
this.progressMonitor = progressMonitor;
|
||||
tskAddImageProcessLock = new Object();
|
||||
}
|
||||
@ -120,21 +92,21 @@ class AddImageTask implements Runnable {
|
||||
try {
|
||||
currentCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to add image data source at %s, no current case", imagePath), ex);
|
||||
logger.log(Level.SEVERE, String.format("Failed to start AddImageTask for %s, no current case", imageDetails.getImagePath()), ex);
|
||||
return;
|
||||
}
|
||||
progressMonitor.setIndeterminate(true);
|
||||
progressMonitor.setProgress(0);
|
||||
String imageWriterPath = "";
|
||||
if (imageWriterSettings != null) {
|
||||
imageWriterPath = imageWriterSettings.getPath();
|
||||
if (imageDetails.imageWriterSettings != null) {
|
||||
imageWriterPath = imageDetails.imageWriterSettings.getPath();
|
||||
}
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
List<Content> newDataSources = new ArrayList<>();
|
||||
try {
|
||||
synchronized (tskAddImageProcessLock) {
|
||||
if (!tskAddImageProcessStopped) {
|
||||
tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles, imageWriterPath);
|
||||
tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(imageDetails.timeZone, true, imageDetails.ignoreFatOrphanFiles, imageWriterPath);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -143,7 +115,7 @@ class AddImageTask implements Runnable {
|
||||
progressUpdateThread.start();
|
||||
runAddImageProcess(errorMessages);
|
||||
progressUpdateThread.interrupt();
|
||||
commitOrRevertAddImageProcess(currentCase, errorMessages, newDataSources);
|
||||
finishAddImageProcess(errorMessages, newDataSources);
|
||||
progressMonitor.setProgress(100);
|
||||
} finally {
|
||||
DataSourceProcessorCallback.DataSourceProcessorResult result;
|
||||
@ -154,7 +126,7 @@ class AddImageTask implements Runnable {
|
||||
} else {
|
||||
result = DataSourceProcessorResult.NO_ERRORS;
|
||||
}
|
||||
callback.done(result, errorMessages, newDataSources);
|
||||
addImageTaskCallback.onCompleted(result, errorMessages, newDataSources);
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,7 +149,7 @@ class AddImageTask implements Runnable {
|
||||
tskAddImageProcess.stop();
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error cancelling adding image %s to the case database", imagePath), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Error cancelling adding image %s to the case database", imageDetails.getImagePath()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,23 +163,22 @@ class AddImageTask implements Runnable {
|
||||
*/
|
||||
private void runAddImageProcess(List<String> errorMessages) {
|
||||
try {
|
||||
tskAddImageProcess.run(deviceId, new String[]{imagePath}, sectorSize);
|
||||
tskAddImageProcess.run(imageDetails.deviceId, imageDetails.image, imageDetails.sectorSize, this.addDataSourceCallbacks);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Critical error occurred adding image %s", imagePath), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Critical error occurred adding image %s", imageDetails.getImagePath()), ex); //NON-NLS
|
||||
criticalErrorOccurred = true;
|
||||
errorMessages.add(ex.getMessage());
|
||||
} catch (TskDataException ex) {
|
||||
logger.log(Level.WARNING, String.format("Non-critical error occurred adding image %s", imagePath), ex); //NON-NLS
|
||||
logger.log(Level.WARNING, String.format("Non-critical error occurred adding image %s", imageDetails.getImagePath()), ex); //NON-NLS
|
||||
errorMessages.add(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits or reverts the results of the TSK add image process. If the
|
||||
* process was stopped before it completed or there was a critical error the
|
||||
* results are reverted, otherwise they are committed.
|
||||
* Handle the results of the TSK add image process.
|
||||
* The image will be in the database even if a critical error occurred or
|
||||
* the user canceled.
|
||||
*
|
||||
* @param currentCase The current case.
|
||||
* @param errorMessages Error messages, if any, are added to this list for
|
||||
* eventual return via the callback.
|
||||
* @param newDataSources If the new image is successfully committed, it is
|
||||
@ -216,84 +187,66 @@ class AddImageTask implements Runnable {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void commitOrRevertAddImageProcess(Case currentCase, List<String> errorMessages, List<Content> newDataSources) {
|
||||
private void finishAddImageProcess(List<String> errorMessages, List<Content> newDataSources) {
|
||||
synchronized (tskAddImageProcessLock) {
|
||||
if (tskAddImageProcessStopped || criticalErrorOccurred) {
|
||||
Image newImage = imageDetails.image;
|
||||
String verificationError = newImage.verifyImageSize();
|
||||
if (!verificationError.isEmpty()) {
|
||||
errorMessages.add(verificationError);
|
||||
}
|
||||
if (imageDetails.imageWriterSettings != null) {
|
||||
ImageWriterService.createImageWriter(newImage.getId(), imageDetails.imageWriterSettings);
|
||||
}
|
||||
newDataSources.add(newImage);
|
||||
|
||||
// If the add image process was cancelled don't do any further processing here
|
||||
if (tskAddImageProcessStopped) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StringUtils.isBlank(imageDetails.md5)) {
|
||||
try {
|
||||
tskAddImageProcess.revert();
|
||||
newImage.setMD5(imageDetails.md5);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error reverting after adding image %s to the case database", imagePath), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Failed to add MD5 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (!StringUtils.isBlank(imageDetails.sha1)) {
|
||||
try {
|
||||
long imageId = tskAddImageProcess.commit();
|
||||
if (imageId != 0) {
|
||||
Image newImage = currentCase.getSleuthkitCase().getImageById(imageId);
|
||||
String verificationError = newImage.verifyImageSize();
|
||||
if (!verificationError.isEmpty()) {
|
||||
errorMessages.add(verificationError);
|
||||
}
|
||||
if (imageWriterSettings != null) {
|
||||
ImageWriterService.createImageWriter(imageId, imageWriterSettings);
|
||||
}
|
||||
newDataSources.add(newImage);
|
||||
if (!StringUtils.isBlank(md5)) {
|
||||
try {
|
||||
newImage.setMD5(md5);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to add MD5 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
}
|
||||
if (!StringUtils.isBlank(sha1)) {
|
||||
try {
|
||||
newImage.setSha1(sha1);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to add SHA1 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
}
|
||||
if (!StringUtils.isBlank(sha256)) {
|
||||
try {
|
||||
newImage.setSha256(sha256);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to add SHA256 for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String errorMessage = String.format("Error commiting after adding image %s to the case database, no object id returned", imagePath); //NON-NLS
|
||||
logger.log(Level.SEVERE, errorMessage);
|
||||
errorMessages.add(errorMessage);
|
||||
criticalErrorOccurred = true;
|
||||
}
|
||||
newImage.setSha1(imageDetails.sha1);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error committing adding image %s to the case database", imagePath), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Failed to add SHA1 hash for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
}
|
||||
if (!StringUtils.isBlank(imageDetails.sha256)) {
|
||||
try {
|
||||
newImage.setSha256(imageDetails.sha256);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to add SHA256 for image data source %s (objId=%d)", newImage.getName(), newImage.getId()), ex);
|
||||
errorMessages.add(ex.getMessage());
|
||||
criticalErrorOccurred = true;
|
||||
} catch (TskDataException ignored) {
|
||||
/*
|
||||
* The only reasonable way for this to happen at
|
||||
* present is through C/C++ processing of an EWF
|
||||
* image, which is not an error.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,4 +305,37 @@ class AddImageTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to hold image data.
|
||||
*/
|
||||
static class ImageDetails {
|
||||
String deviceId;
|
||||
Image image;
|
||||
int sectorSize;
|
||||
String timeZone;
|
||||
boolean ignoreFatOrphanFiles;
|
||||
String md5;
|
||||
String sha1;
|
||||
String sha256;
|
||||
ImageWriterSettings imageWriterSettings;
|
||||
|
||||
ImageDetails(String deviceId, Image image, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings) {
|
||||
this.deviceId = deviceId;
|
||||
this.image = image;
|
||||
this.sectorSize = sectorSize;
|
||||
this.timeZone = timeZone;
|
||||
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
|
||||
this.md5 = md5;
|
||||
this.sha1 = sha1;
|
||||
this.sha256 = sha256;
|
||||
this.imageWriterSettings = imageWriterSettings;
|
||||
}
|
||||
|
||||
String getImagePath() {
|
||||
if (image.getPaths().length > 0) {
|
||||
return image.getPaths()[0];
|
||||
}
|
||||
return "Unknown data source path";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.casemodule;
|
||||
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Called on completion of the add image task.
|
||||
*/
|
||||
interface AddImageTaskCallback {
|
||||
|
||||
/**
|
||||
* Called when the add image task is completed.
|
||||
*
|
||||
* @param result The result from the data source processor.
|
||||
* @param errList The list of errors.
|
||||
* @param newDataSources The list of new data sources.
|
||||
*/
|
||||
void onCompleted(DataSourceProcessorResult result, List<String> errList, List<Content> newDataSources);
|
||||
}
|
@ -302,7 +302,9 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel {
|
||||
private void startIngest() {
|
||||
if (!newContents.isEmpty() && readyToIngest && !ingested) {
|
||||
ingested = true;
|
||||
IngestManager.getInstance().queueIngestJob(newContents, ingestJobSettings);
|
||||
if (dsProcessor != null && ! dsProcessor.supportsIngestStream()) {
|
||||
IngestManager.getInstance().queueIngestJob(newContents, ingestJobSettings);
|
||||
}
|
||||
setStateFinished();
|
||||
}
|
||||
}
|
||||
@ -360,8 +362,12 @@ class AddImageWizardAddingProgressPanel extends ShortcutWizardDescriptorPanel {
|
||||
|
||||
setStateStarted();
|
||||
|
||||
// Kick off the DSProcessor
|
||||
dsProcessor.run(getDSPProgressMonitorImpl(), cbObj);
|
||||
// Kick off the DSProcessor
|
||||
if (dsProcessor.supportsIngestStream()) {
|
||||
dsProcessor.runWithIngestStream(ingestJobSettings, getDSPProgressMonitorImpl(), cbObj);
|
||||
} else {
|
||||
dsProcessor.run(getDSPProgressMonitorImpl(), cbObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator<WizardDescript
|
||||
private final AddImageAction action;
|
||||
private int progressPanelIndex;
|
||||
private int dsPanelIndex;
|
||||
private int ingestPanelIndex;
|
||||
private final static String PROP_LASTPROFILE_NAME = "AIW_LASTPROFILE_NAME"; //NON-NLS
|
||||
|
||||
AddImageWizardIterator(AddImageAction action) {
|
||||
@ -69,6 +70,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator<WizardDescript
|
||||
panels.add(progressPanel);
|
||||
progressPanelIndex = panels.indexOf(progressPanel); //Doing programatically because number of panels is variable
|
||||
dsPanelIndex = panels.indexOf(dsPanel);
|
||||
ingestPanelIndex = panels.indexOf(ingestConfigPanel);
|
||||
String[] steps = new String[panels.size()];
|
||||
for (int i = 0; i < panels.size(); i++) {
|
||||
Component c = panels.get(i).getComponent();
|
||||
@ -177,7 +179,7 @@ class AddImageWizardIterator implements WizardDescriptor.Iterator<WizardDescript
|
||||
// Start processing the data source by handing it off to the selected DSP,
|
||||
// so it gets going in the background while the user is still picking the Ingest modules
|
||||
// This will occur when the next button is clicked on the panel where you have chosen your data to process
|
||||
if (index == dsPanelIndex) {
|
||||
if (index == ingestPanelIndex) {
|
||||
((AddImageWizardAddingProgressPanel) panels.get(progressPanelIndex)).
|
||||
startDataSourceProcessing(((AddImageWizardDataSourceSettingsPanel) panels.get(dsPanelIndex)).getComponent().getCurrentDSProcessor());
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.casemodule;
|
||||
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStream;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStreamClosedException;
|
||||
|
||||
/**
|
||||
* This is a default ingest stream to use with the data source processors when
|
||||
* an IngestStream is not supplied. Adding files/data sources are no-ops.
|
||||
*/
|
||||
class DefaultIngestStream implements IngestStream {
|
||||
|
||||
private boolean isClosed = false;
|
||||
private boolean isStopped = false;
|
||||
|
||||
@Override
|
||||
public void addFiles(List<Long> fileObjectIds) throws IngestStreamClosedException {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
isClosed = true;
|
||||
isStopped = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean wasStopped() {
|
||||
return isStopped;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import javax.swing.JPanel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.UUID;
|
||||
import javax.swing.filechooser.FileFilter;
|
||||
import org.openide.util.NbBundle;
|
||||
@ -33,7 +34,14 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||
import org.sleuthkit.autopsy.coreutils.DataSourceUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStream;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A image file data source processor that implements the DataSourceProcessor
|
||||
@ -49,6 +57,7 @@ import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
|
||||
public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSourceProcessor {
|
||||
|
||||
private final static String DATA_SOURCE_TYPE = NbBundle.getMessage(ImageDSProcessor.class, "ImageDSProcessor.dsType.text");
|
||||
private final Logger logger = Logger.getLogger(ImageDSProcessor.class.getName());
|
||||
private static final List<String> allExt = new ArrayList<>();
|
||||
private static final GeneralFilter rawFilter = new GeneralFilter(GeneralFilter.RAW_IMAGE_EXTS, GeneralFilter.RAW_IMAGE_DESC);
|
||||
private static final GeneralFilter encaseFilter = new GeneralFilter(GeneralFilter.ENCASE_IMAGE_EXTS, GeneralFilter.ENCASE_IMAGE_DESC);
|
||||
@ -58,6 +67,8 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
private static final List<FileFilter> filtersList = new ArrayList<>();
|
||||
private final ImageFilePanel configPanel;
|
||||
private AddImageTask addImageTask;
|
||||
private IngestStream ingestStream = null;
|
||||
private Image image = null;
|
||||
/*
|
||||
* TODO: Remove the setDataSourceOptionsCalled flag and the settings fields
|
||||
* when the deprecated method setDataSourceOptions is removed.
|
||||
@ -170,6 +181,77 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
*/
|
||||
@Override
|
||||
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
ingestStream = new DefaultIngestStream();
|
||||
readConfigSettings();
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Files found during ingest will be sent directly to the
|
||||
* IngestStream provided. Returns as soon as the background task is started.
|
||||
* The background task uses a callback object to signal task completion and
|
||||
* return results.
|
||||
*
|
||||
* This method should not be called unless isPanelValid returns true, and
|
||||
* should only be called for DSPs that support ingest streams.
|
||||
*
|
||||
* @param settings The ingest job settings.
|
||||
* @param progress Progress monitor that will be used by the
|
||||
* background task to report progress.
|
||||
* @param callBack Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
@Override
|
||||
public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
|
||||
DataSourceProcessorCallback callBack) {
|
||||
|
||||
// Read the settings from the wizard
|
||||
readConfigSettings();
|
||||
|
||||
// Set up the data source before creating the ingest stream
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
// Now initialize the ingest stream
|
||||
try {
|
||||
ingestStream = IngestManager.getInstance().openIngestStream(image, settings);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error starting ingest modules", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the options from the config panel.
|
||||
*/
|
||||
private void readConfigSettings() {
|
||||
if (!setDataSourceOptionsCalled) {
|
||||
configPanel.storeSettings();
|
||||
deviceId = UUID.randomUUID().toString();
|
||||
@ -190,8 +272,17 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
sha256 = null;
|
||||
}
|
||||
}
|
||||
run(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DSP supports ingest streams.
|
||||
*
|
||||
* @return True if this DSP supports an ingest stream, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsIngestStream() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a data source to the case database using a background task in a
|
||||
@ -215,7 +306,19 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
* @param callback Callback to call when processing is done.
|
||||
*/
|
||||
public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
run(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callback);
|
||||
ingestStream = new DefaultIngestStream();
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
doAddImageProcess(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -224,6 +327,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
* selection and configuration panel. Returns as soon as the background task
|
||||
* is started and uses the callback object to signal task completion and
|
||||
* return results.
|
||||
*
|
||||
* The image should be loaded in the database and stored in "image"
|
||||
* before calling this method. Additionally, an ingest stream should be initialized
|
||||
* and stored in "ingestStream".
|
||||
*
|
||||
* @param deviceId An ASCII-printable identifier for the device
|
||||
* associated with the data source that is
|
||||
@ -243,8 +350,31 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
* during processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
*/
|
||||
private void run(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback);
|
||||
private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
|
||||
// If the data source or ingest stream haven't been initialized, stop processing
|
||||
if (ingestStream == null) {
|
||||
String message = "Ingest stream was not initialized before running the add image process on " + imagePath;
|
||||
logger.log(Level.SEVERE, message);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(message);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
if (image == null) {
|
||||
String message = "Image was not added to database before running the add image process on " + imagePath;
|
||||
logger.log(Level.SEVERE, message);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(message);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null);
|
||||
addImageTask = new AddImageTask(imageDetails,
|
||||
progressMonitor,
|
||||
new StreamingAddDataSourceCallbacks(ingestStream),
|
||||
new StreamingAddImageTaskCallback(ingestStream, callback));
|
||||
new Thread(addImageTask).start();
|
||||
}
|
||||
|
||||
@ -260,6 +390,9 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
if (null != addImageTask) {
|
||||
addImageTask.cancelTask();
|
||||
}
|
||||
if (ingestStream != null) {
|
||||
ingestStream.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -316,7 +449,20 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
|
||||
this.timeZone = Calendar.getInstance().getTimeZone().getID();
|
||||
this.ignoreFatOrphanFiles = false;
|
||||
setDataSourceOptionsCalled = true;
|
||||
run(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
|
||||
|
||||
ingestStream = new DefaultIngestStream();
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,15 +18,22 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JPanel;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A local drive data source processor that implements the DataSourceProcessor
|
||||
@ -37,6 +44,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
|
||||
@ServiceProvider(service = DataSourceProcessor.class)
|
||||
public class LocalDiskDSProcessor implements DataSourceProcessor {
|
||||
|
||||
private final Logger logger = Logger.getLogger(LocalDiskDSProcessor.class.getName());
|
||||
private static final String DATA_SOURCE_TYPE = NbBundle.getMessage(LocalDiskDSProcessor.class, "LocalDiskDSProcessor.dsType.text");
|
||||
private final LocalDiskPanel configPanel;
|
||||
private AddImageTask addDiskTask;
|
||||
@ -139,7 +147,25 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
|
||||
imageWriterSettings = null;
|
||||
}
|
||||
}
|
||||
addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, progressMonitor, callback);
|
||||
|
||||
Image image;
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{drivePath}, sectorSize,
|
||||
timeZone, null, null, null, deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
addDiskTask = new AddImageTask(
|
||||
new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
|
||||
progressMonitor,
|
||||
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
|
||||
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
|
||||
new Thread(addDiskTask).start();
|
||||
}
|
||||
|
||||
@ -191,7 +217,23 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
|
||||
* @param callback Callback to call when processing is done.
|
||||
*/
|
||||
private void run(String deviceId, String drivePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, progressMonitor, callback);
|
||||
Image image;
|
||||
try {
|
||||
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
|
||||
new String[]{drivePath}, sectorSize,
|
||||
timeZone, null, null, null, deviceId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
|
||||
final List<String> errors = new ArrayList<>();
|
||||
errors.add(ex.getMessage());
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings),
|
||||
progressMonitor,
|
||||
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
|
||||
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
|
||||
new Thread(addDiskTask).start();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.casemodule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStream;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStreamClosedException;
|
||||
import org.sleuthkit.datamodel.AddDataSourceCallbacks;
|
||||
|
||||
/**
|
||||
* A set of callbacks to be called during the process of adding a data source to
|
||||
* the case database. This implementation of the interface is suitable for
|
||||
* streaming ingest use cases.
|
||||
*/
|
||||
class StreamingAddDataSourceCallbacks implements AddDataSourceCallbacks {
|
||||
|
||||
private final Logger logger = Logger.getLogger(StreamingAddDataSourceCallbacks.class.getName());
|
||||
private final IngestStream ingestStream;
|
||||
|
||||
/**
|
||||
* Constructs a set of callbacks to be called during the process of adding a
|
||||
* data source to the case database. This implementation of the interface is
|
||||
* suitable for streaming ingest use cases.
|
||||
*
|
||||
* @param stream The IngestStream to send data to
|
||||
*/
|
||||
StreamingAddDataSourceCallbacks(IngestStream stream) {
|
||||
ingestStream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFilesAdded(List<Long> fileObjectIds) {
|
||||
if (ingestStream.wasStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ingestStream.addFiles(fileObjectIds);
|
||||
} catch (IngestStreamClosedException ex) {
|
||||
if (!ingestStream.wasStopped()) {
|
||||
// If the ingest stream is closed but not stopped log the error.
|
||||
// This state should only happen once the data source is completely
|
||||
// added which means it's a severe error that files are still being added.
|
||||
logger.log(Level.SEVERE, "Error adding files to ingest stream - ingest stream is closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.casemodule;
|
||||
|
||||
import java.util.List;
|
||||
import org.sleuthkit.autopsy.ingest.IngestStream;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* A callback to be called on completion of an add image task. This
|
||||
* implementation of the interface is suitable for streaming ingest use cases.
|
||||
* It closes the ingest stream and then calls the data source processor done
|
||||
* callback.
|
||||
*/
|
||||
class StreamingAddImageTaskCallback implements AddImageTaskCallback {
|
||||
|
||||
private final IngestStream ingestStream;
|
||||
private final DataSourceProcessorCallback dspCallback;
|
||||
|
||||
/**
|
||||
* Constructs a callback to be called on completion of an add image task.
|
||||
* This implementation of the interface is suitable for streaming ingest use
|
||||
* cases. It closes the ingest stream and then calls the data source
|
||||
* processor done callback.
|
||||
*
|
||||
* @param ingestStream The ingest stream that data is being sent to.
|
||||
* @param dspCallback The callback for non-ingest stream related
|
||||
* processing.
|
||||
*/
|
||||
StreamingAddImageTaskCallback(IngestStream ingestStream, DataSourceProcessorCallback dspCallback) {
|
||||
this.ingestStream = ingestStream;
|
||||
this.dspCallback = dspCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the add image task is completed.
|
||||
*
|
||||
* @param result The result from the data source processor.
|
||||
* @param errList The list of errors.
|
||||
* @param newDataSources The list of new data sources.
|
||||
*/
|
||||
@Override
|
||||
public void onCompleted(DataSourceProcessorResult result, List<String> errList, List<Content> newDataSources) {
|
||||
ingestStream.close();
|
||||
dspCallback.done(result, errList, newDataSources);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2020 Basis Technology Corp.
|
||||
* Copyright 2013-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -56,7 +56,7 @@ public class TagsManager implements Closeable {
|
||||
private final SleuthkitCase caseDb;
|
||||
|
||||
private static String DEFAULT_TAG_SET_NAME = "Project VIC";
|
||||
|
||||
|
||||
private static final Object lock = new Object();
|
||||
|
||||
static {
|
||||
@ -235,16 +235,16 @@ public class TagsManager implements Closeable {
|
||||
public static String getNotableTagDisplayName() {
|
||||
return TagNameDefinition.getNotableTagDisplayName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new TagSetDefinition file.
|
||||
*
|
||||
*
|
||||
* @param tagSetDef The tag set definition.
|
||||
*
|
||||
* @throws IOException
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void addTagSetDefinition(TagSetDefinition tagSetDef) throws IOException {
|
||||
synchronized(lock) {
|
||||
synchronized (lock) {
|
||||
TagSetDefinition.writeTagSetDefinition(tagSetDef);
|
||||
}
|
||||
}
|
||||
@ -267,20 +267,20 @@ public class TagsManager implements Closeable {
|
||||
caseDb.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus());
|
||||
}
|
||||
//Assume new case and add tag sets
|
||||
for(TagSetDefinition setDef: TagSetDefinition.readTagSetDefinitions()) {
|
||||
for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) {
|
||||
List<TagName> tagNameList = new ArrayList<>();
|
||||
for(TagNameDefinition tagNameDef: setDef.getTagNameDefinitions()) {
|
||||
for (TagNameDefinition tagNameDef : setDef.getTagNameDefinitions()) {
|
||||
tagNameList.add(caseDb.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus()));
|
||||
}
|
||||
|
||||
if(!tagNameList.isEmpty()) {
|
||||
|
||||
if (!tagNameList.isEmpty()) {
|
||||
taggingMgr.addTagSet(setDef.getName(), tagNameList);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex);
|
||||
} catch(IOException ex) {
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error loading tag set JSON files", ex);
|
||||
}
|
||||
|
||||
@ -288,28 +288,41 @@ public class TagsManager implements Closeable {
|
||||
tagName.saveToCase(caseDb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of all tag sets currently in the case database.
|
||||
*
|
||||
*
|
||||
* @return A list, possibly empty, of TagSet objects.
|
||||
*
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public List<TagSet> getAllTagSets() throws TskCoreException {
|
||||
return caseDb.getTaggingManager().getTagSets();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the tag set a tag name (tag definition) belongs to, if any.
|
||||
*
|
||||
* @param tagName The tag name.
|
||||
*
|
||||
* @return A TagSet object or null.
|
||||
*
|
||||
* @throws TskCoreException If there is an error querying the case database.
|
||||
*/
|
||||
public TagSet getTagSet(TagName tagName) throws TskCoreException {
|
||||
return caseDb.getTaggingManager().getTagSet(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new TagSet to the case database. Tags will be ranked in the order
|
||||
* which they are passed to this method.
|
||||
*
|
||||
* @param name Tag set name.
|
||||
*
|
||||
* @param name Tag set name.
|
||||
* @param tagNameList List of TagName in rank order.
|
||||
*
|
||||
*
|
||||
* @return A new TagSet object.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public TagSet addTagSet(String name, List<TagName> tagNameList) throws TskCoreException {
|
||||
return caseDb.getTaggingManager().addTagSet(name, tagNameList);
|
||||
@ -501,7 +514,7 @@ public class TagsManager implements Closeable {
|
||||
* name to the case database.
|
||||
*/
|
||||
public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TagNameAlreadyExistsException, TskCoreException {
|
||||
synchronized(lock) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
TagName tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus);
|
||||
Set<TagNameDefinition> customTypes = TagNameDefinition.getTagNameDefinitions();
|
||||
|
@ -1,9 +1,9 @@
|
||||
OpenIDE-Module-Name=Central Repository
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Short-Description=Correlation Engine Ingest Module
|
||||
OpenIDE-Module-Short-Description=Central Repository Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Correlation Engine ingest module and central database. \n\n\
|
||||
The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Central Repository ingest module and central database. \n\n\
|
||||
The Central Repository ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
CentralRepoCommentDialog.commentLabel.text=Comment:
|
||||
CentralRepoCommentDialog.okButton.text=&OK
|
||||
|
@ -4,10 +4,10 @@ AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoCommentNoMD5=Add/
|
||||
CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Repository Comment
|
||||
OpenIDE-Module-Name=Central Repository
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Short-Description=Correlation Engine Ingest Module
|
||||
OpenIDE-Module-Short-Description=Central Repository Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Correlation Engine ingest module and central database. \n\n\
|
||||
The Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Central Repository ingest module and central database. \n\n\
|
||||
The Central Repository ingest module stores attributes of artifacts matching selected correlation types into a central database.\n\
|
||||
Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
|
||||
CentralRepoCommentDialog.commentLabel.text=Comment:
|
||||
CentralRepoCommentDialog.okButton.text=&OK
|
||||
|
@ -23,11 +23,11 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import static org.sleuthkit.datamodel.CommunicationsUtils.normalizeEmailAddress;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
|
||||
/**
|
||||
* This class abstracts an Account as stored in the CR database.
|
||||
@ -223,10 +223,13 @@ public final class CentralRepoAccount {
|
||||
public static Collection<CentralRepoAccount> getAccountsWithIdentifierLike(String accountIdentifierSubstring) throws CentralRepoException {
|
||||
|
||||
String queryClause = ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')";
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER(?)";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add("%" + accountIdentifierSubstring + "%");
|
||||
|
||||
AccountsQueryCallback queryCallback = new AccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
CentralRepository.getInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getAccountsList();
|
||||
}
|
||||
@ -242,21 +245,17 @@ public final class CentralRepoAccount {
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* accounts.
|
||||
*/
|
||||
public static Collection<CentralRepoAccount> getAccountsWithIdentifier(String accountIdentifier) throws CentralRepoException {
|
||||
|
||||
String normalizedAccountIdentifier;
|
||||
|
||||
try {
|
||||
normalizedAccountIdentifier = normalizeAccountIdentifier(accountIdentifier);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new CentralRepoException("Failed to normalize account identifier.", ex);
|
||||
}
|
||||
public static Collection<CentralRepoAccount> getAccountsWithIdentifier(String accountIdentifier) throws InvalidAccountIDException, CentralRepoException {
|
||||
|
||||
String normalizedAccountIdentifier = normalizeAccountIdentifier(accountIdentifier);
|
||||
String queryClause = ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) = LOWER('" + normalizedAccountIdentifier + "')";
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) = LOWER(?)";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(normalizedAccountIdentifier);
|
||||
|
||||
AccountsQueryCallback queryCallback = new AccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
CentralRepository.getInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getAccountsList();
|
||||
}
|
||||
@ -274,8 +273,10 @@ public final class CentralRepoAccount {
|
||||
|
||||
String queryClause = ACCOUNTS_QUERY_CLAUSE;
|
||||
|
||||
List<Object> params = new ArrayList<>(); // empty param list
|
||||
|
||||
AccountsQueryCallback queryCallback = new AccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
CentralRepository.getInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getAccountsList();
|
||||
}
|
||||
@ -287,14 +288,24 @@ public final class CentralRepoAccount {
|
||||
* @param accountIdentifier Account identifier to be normalized.
|
||||
* @return normalized identifier
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws InvalidAccountIDException If the account identifier is not valid.
|
||||
*/
|
||||
private static String normalizeAccountIdentifier(String accountIdentifier) throws TskCoreException {
|
||||
String normalizedAccountIdentifier = accountIdentifier;
|
||||
if (CommunicationsUtils.isValidPhoneNumber(accountIdentifier)) {
|
||||
normalizedAccountIdentifier = CommunicationsUtils.normalizePhoneNum(accountIdentifier);
|
||||
} else if (CommunicationsUtils.isValidEmailAddress(accountIdentifier)) {
|
||||
normalizedAccountIdentifier = normalizeEmailAddress(accountIdentifier);
|
||||
private static String normalizeAccountIdentifier(String accountIdentifier) throws InvalidAccountIDException {
|
||||
if (StringUtils.isEmpty(accountIdentifier)) {
|
||||
throw new InvalidAccountIDException("Account id is null or empty.");
|
||||
}
|
||||
|
||||
String normalizedAccountIdentifier;
|
||||
try {
|
||||
if (CorrelationAttributeNormalizer.isValidPhoneNumber(accountIdentifier)) {
|
||||
normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizePhone(accountIdentifier);
|
||||
} else if (CorrelationAttributeNormalizer.isValidEmailAddress(accountIdentifier)) {
|
||||
normalizedAccountIdentifier = CorrelationAttributeNormalizer.normalizeEmail(accountIdentifier);
|
||||
} else {
|
||||
normalizedAccountIdentifier = accountIdentifier.toLowerCase().trim();
|
||||
}
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
throw new InvalidAccountIDException("Failed to normalize the account idenitier.", ex);
|
||||
}
|
||||
return normalizedAccountIdentifier;
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
|
||||
import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION;
|
||||
@ -259,8 +262,23 @@ public class CentralRepoDbUtil {
|
||||
* used
|
||||
*/
|
||||
public static void setUseCentralRepo(boolean centralRepoCheckBoxIsSelected) {
|
||||
if (!centralRepoCheckBoxIsSelected) {
|
||||
closePersonasTopComponent();
|
||||
}
|
||||
ModuleSettings.setConfigSetting(CENTRAL_REPO_NAME, CENTRAL_REPO_USE_KEY, Boolean.toString(centralRepoCheckBoxIsSelected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes Personas top component if it exists.
|
||||
*/
|
||||
private static void closePersonasTopComponent() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TopComponent personasWindow = WindowManager.getDefault().findTopComponent("PersonasTopComponent");
|
||||
if (personasWindow != null && personasWindow.isOpened()) {
|
||||
personasWindow.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the current settings and the validation query to test the connection
|
||||
|
@ -53,7 +53,7 @@ public interface CentralRepository {
|
||||
case SQLITE:
|
||||
return SqliteCentralRepo.getInstance();
|
||||
default:
|
||||
throw new CentralRepoException("Failed to get CentralRepository instance, Central Repositiory is not enabled.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,8 @@ public interface CentralRepository {
|
||||
* It will not close active/in-use connections. Thus, it is vital that there
|
||||
* are no in-use connections when you call this method.
|
||||
*
|
||||
* @throws CentralRepoException if there is a problem closing the connection pool.
|
||||
* @throws CentralRepoException if there is a problem closing the connection
|
||||
* pool.
|
||||
*/
|
||||
void shutdownConnections() throws CentralRepoException;
|
||||
|
||||
@ -101,7 +102,7 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Add a new name/value pair in the db_info table.
|
||||
*
|
||||
* @param name Key to set
|
||||
* @param name Key to set
|
||||
* @param value Value to set
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
@ -112,9 +113,9 @@ public interface CentralRepository {
|
||||
* Set the data source object id for a specific entry in the data_sources
|
||||
* table
|
||||
*
|
||||
* @param rowId - the row id for the data_sources table entry
|
||||
* @param rowId - the row id for the data_sources table entry
|
||||
* @param dataSourceObjectId - the object id for the data source from the
|
||||
* caseDb
|
||||
* caseDb
|
||||
*/
|
||||
void addDataSourceObjectId(int rowId, long dataSourceObjectId) throws CentralRepoException;
|
||||
|
||||
@ -132,7 +133,7 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Update the value for a name in the name/value db_info table.
|
||||
*
|
||||
* @param name Name to find
|
||||
* @param name Name to find
|
||||
* @param value Value to assign to name.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
@ -161,10 +162,10 @@ public interface CentralRepository {
|
||||
* @param eamCase The case to update
|
||||
*/
|
||||
void updateCase(CorrelationCase eamCase) throws CentralRepoException;
|
||||
|
||||
|
||||
/**
|
||||
* Queries the examiner table for the given user name.
|
||||
* Adds a row if the user is not found in the examiner table.
|
||||
* Queries the examiner table for the given user name. Adds a row if the
|
||||
* user is not found in the examiner table.
|
||||
*
|
||||
* @param examinerLoginName user name to look for.
|
||||
* @return CentralRepoExaminer for the given user name.
|
||||
@ -215,7 +216,7 @@ public interface CentralRepository {
|
||||
* @param eamDataSource the data source to add
|
||||
*
|
||||
* @return - A CorrelationDataSource object with data source's central
|
||||
* repository id
|
||||
* repository id
|
||||
*/
|
||||
CorrelationDataSource newDataSource(CorrelationDataSource eamDataSource) throws CentralRepoException;
|
||||
|
||||
@ -244,8 +245,8 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Retrieves Data Source details based on data source device ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param caseDbDataSourceId the data source device ID number
|
||||
*
|
||||
* @return The data source
|
||||
@ -256,8 +257,8 @@ public interface CentralRepository {
|
||||
* Retrieves Data Source details based on data source ID
|
||||
*
|
||||
* @param correlationCase the current CorrelationCase used for ensuring
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
* uniqueness of DataSource
|
||||
* @param dataSourceId the data source ID number
|
||||
*
|
||||
* @return The data source
|
||||
*/
|
||||
@ -274,7 +275,7 @@ public interface CentralRepository {
|
||||
* Changes the name of a data source in the DB
|
||||
*
|
||||
* @param eamDataSource The data source
|
||||
* @param newName The new name
|
||||
* @param newName The new name
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -292,12 +293,12 @@ public interface CentralRepository {
|
||||
* Retrieves eamArtifact instances from the database that are associated
|
||||
* with the eamArtifactType and eamArtifactValues of the given eamArtifact.
|
||||
*
|
||||
* @param aType EamArtifact.Type to search for
|
||||
* @param aType EamArtifact.Type to search for
|
||||
* @param values The list of correlation values to get
|
||||
* CorrelationAttributeInstances for
|
||||
* CorrelationAttributeInstances for
|
||||
*
|
||||
* @return List of artifact instances for a given type with the specified
|
||||
* values
|
||||
* values
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException
|
||||
* @throws CentralRepoException
|
||||
@ -323,14 +324,14 @@ public interface CentralRepository {
|
||||
* with the eamArtifactType and eamArtifactValues of the given eamArtifact
|
||||
* for the specified cases.
|
||||
*
|
||||
* @param aType The type of the artifact
|
||||
* @param values The list of correlation values to get
|
||||
* CorrelationAttributeInstances for
|
||||
* @param aType The type of the artifact
|
||||
* @param values The list of correlation values to get
|
||||
* CorrelationAttributeInstances for
|
||||
* @param caseIds The list of central repository case ids to get
|
||||
* CorrelationAttributeInstances for
|
||||
* CorrelationAttributeInstances for
|
||||
*
|
||||
* @return List of artifact instances for a given type with the specified
|
||||
* values for the specified cases
|
||||
* values for the specified cases
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException
|
||||
* @throws CentralRepoException
|
||||
@ -345,7 +346,7 @@ public interface CentralRepository {
|
||||
* @param value Value to search for
|
||||
*
|
||||
* @return Number of artifact instances having ArtifactType and
|
||||
* ArtifactValue.
|
||||
* ArtifactValue.
|
||||
*/
|
||||
Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException, CorrelationAttributeNormalizationException;
|
||||
|
||||
@ -384,7 +385,7 @@ public interface CentralRepository {
|
||||
* @param correlationDataSource Data source to search for
|
||||
*
|
||||
* @return Number of artifact instances having caseDisplayName and
|
||||
* dataSource
|
||||
* dataSource
|
||||
*/
|
||||
Long getCountArtifactInstancesByCaseDataSource(CorrelationDataSource correlationDataSource) throws CentralRepoException;
|
||||
|
||||
@ -413,7 +414,7 @@ public interface CentralRepository {
|
||||
* in the associated CorrelationAttribute object.
|
||||
*
|
||||
* @param eamArtifact The correlation attribute whose database instance will
|
||||
* be updated.
|
||||
* be updated.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -426,11 +427,11 @@ public interface CentralRepository {
|
||||
* Method exists to support instances added using Central Repository version
|
||||
* 1,1 and older
|
||||
*
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param correlationDataSource The data source tied to the instance.
|
||||
* @param value The value tied to the instance.
|
||||
* @param filePath The file path tied to the instance.
|
||||
* @param value The value tied to the instance.
|
||||
* @param filePath The file path tied to the instance.
|
||||
*
|
||||
* @return The correlation attribute if it exists; otherwise null.
|
||||
*
|
||||
@ -443,11 +444,10 @@ public interface CentralRepository {
|
||||
* Find a correlation attribute in the Central Repository database given the
|
||||
* instance type, case, data source, object id.
|
||||
*
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param type The type of instance.
|
||||
* @param correlationCase The case tied to the instance.
|
||||
* @param correlationDataSource The data source tied to the instance.
|
||||
* @param objectID The object id of the file tied to the
|
||||
* instance.
|
||||
* @param objectID The object id of the file tied to the instance.
|
||||
*
|
||||
* @return The correlation attribute if it exists; otherwise null.
|
||||
*
|
||||
@ -483,7 +483,7 @@ public interface CentralRepository {
|
||||
* @param value Value to search for
|
||||
*
|
||||
* @return List of cases containing this artifact with instances marked as
|
||||
* bad
|
||||
* bad
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -497,7 +497,7 @@ public interface CentralRepository {
|
||||
* @param value Value to search for
|
||||
*
|
||||
* @return List of cases containing this artifact with instances marked as
|
||||
* bad
|
||||
* bad
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -554,13 +554,13 @@ public interface CentralRepository {
|
||||
*/
|
||||
public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException;
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the given file HashHitInfo if the given file hash is in this
|
||||
* Retrieves the given file HashHitInfo if the given file hash is in this
|
||||
* reference set. Only searches the reference_files table.
|
||||
*
|
||||
* @param hash The hash to find in a search.
|
||||
* @param referenceSetID The referenceSetID within which the file should exist.
|
||||
* @param referenceSetID The referenceSetID within which the file should
|
||||
* exist.
|
||||
*
|
||||
* @return The HashHitInfo if found or null if not found.
|
||||
*
|
||||
@ -569,7 +569,6 @@ public interface CentralRepository {
|
||||
*/
|
||||
HashHitInfo lookupHash(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException;
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given value is in a specific reference set
|
||||
*
|
||||
@ -637,7 +636,7 @@ public interface CentralRepository {
|
||||
* Update an existing organization.
|
||||
*
|
||||
* @param updatedOrganization the values the Organization with the same ID
|
||||
* will be updated to in the database.
|
||||
* will be updated to in the database.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -689,8 +688,7 @@ public interface CentralRepository {
|
||||
* Add a new reference instance
|
||||
*
|
||||
* @param eamGlobalFileInstance The reference instance to add
|
||||
* @param correlationType Correlation Type that this Reference
|
||||
* Instance is
|
||||
* @param correlationType Correlation Type that this Reference Instance is
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -700,8 +698,8 @@ public interface CentralRepository {
|
||||
* Insert the bulk collection of Global File Instances
|
||||
*
|
||||
* @param globalInstances a Set of EamGlobalFileInstances to insert into the
|
||||
* db.
|
||||
* @param contentType the Type of the global instances
|
||||
* db.
|
||||
* @param contentType the Type of the global instances
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -710,7 +708,7 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Get all reference entries having a given correlation type and value
|
||||
*
|
||||
* @param aType Type to use for matching
|
||||
* @param aType Type to use for matching
|
||||
* @param aValue Value to use for matching
|
||||
*
|
||||
* @return List of all global file instances with a type and value
|
||||
@ -735,7 +733,7 @@ public interface CentralRepository {
|
||||
* used to correlate artifacts.
|
||||
*
|
||||
* @return List of EamArtifact.Type's. If none are defined in the database,
|
||||
* the default list will be returned.
|
||||
* the default list will be returned.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -746,7 +744,7 @@ public interface CentralRepository {
|
||||
* artifacts.
|
||||
*
|
||||
* @return List of enabled EamArtifact.Type's. If none are defined in the
|
||||
* database, the default list will be returned.
|
||||
* database, the default list will be returned.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -757,7 +755,7 @@ public interface CentralRepository {
|
||||
* correlate artifacts.
|
||||
*
|
||||
* @return List of supported EamArtifact.Type's. If none are defined in the
|
||||
* database, the default list will be returned.
|
||||
* database, the default list will be returned.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -798,15 +796,15 @@ public interface CentralRepository {
|
||||
*
|
||||
* @return the lock, or null if locking is not supported
|
||||
*
|
||||
* @throws CentralRepoException if the coordination service is running but we fail
|
||||
* to get the lock
|
||||
* @throws CentralRepoException if the coordination service is running but
|
||||
* we fail to get the lock
|
||||
*/
|
||||
public CoordinationService.Lock getExclusiveMultiUserDbLock() throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
@ -816,9 +814,9 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Process the Artifact instance in the EamDb
|
||||
*
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param type EamArtifact.Type to search for
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
* @param whereClause query string to execute
|
||||
* @param whereClause query string to execute
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -827,78 +825,77 @@ public interface CentralRepository {
|
||||
/**
|
||||
* Process a SELECT query
|
||||
*
|
||||
* @param selectClause query string to execute
|
||||
* @param selectClause query string to execute
|
||||
* @param instanceTableCallback callback to process the instance
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
|
||||
|
||||
|
||||
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Executes an INSERT sql statement on the central repository database.
|
||||
* @param sql INSERT sql to execute.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
* Executes an INSERT/UPDATE/DELETE sql as a prepared statement, on the
|
||||
* central repository database.
|
||||
*
|
||||
* @param sql sql to execute.
|
||||
* @param params List of query params to use, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeInsertSQL(String sql) throws CentralRepoException;
|
||||
|
||||
void executeCommand(String sql, List<Object> params) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Executes a SELECT sql statement on the central repository database.
|
||||
*
|
||||
* @param sql SELECT sql to execute.
|
||||
* Executes a SELECT query sql as a prepared statement, on the central
|
||||
* repository database.
|
||||
*
|
||||
* @param sql sql to execute.
|
||||
* @param params List of query params to use, may be empty.
|
||||
* @param queryCallback Query callback to handle the result of the query.
|
||||
*
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeSelectSQL(String sql, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Executes an UPDATE sql statement on the central repository database.
|
||||
*
|
||||
* @param sql UPDATE sql to execute.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeUpdateSQL(String sql) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Executes a DELETE sql statement on the central repository database.
|
||||
*
|
||||
* @param sql DELETE sql to execute.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
void executeDeleteSQL(String sql) throws CentralRepoException;
|
||||
|
||||
void executeQuery(String sql, List<Object> params, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException;
|
||||
|
||||
/**
|
||||
* Get account type by type name.
|
||||
*
|
||||
*
|
||||
* @param accountTypeName account type name to look for
|
||||
* @return CR account type
|
||||
* @throws CentralRepoException
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException;
|
||||
|
||||
|
||||
/**
|
||||
* Gets all account types.
|
||||
*
|
||||
*
|
||||
* @return Collection of all CR account types in the database.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
Collection<CentralRepoAccountType> getAllAccountTypes() throws CentralRepoException;
|
||||
|
||||
|
||||
/**
|
||||
* Get an account from the accounts table matching the given type/ID.
|
||||
* Get an account from the accounts table matching the given type/ID.
|
||||
* Inserts a row if one doesn't exists.
|
||||
*
|
||||
*
|
||||
* @param crAccountType CR account type to look for or create
|
||||
* @param accountUniqueID type specific unique account id
|
||||
* @return CR account
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
|
||||
|
||||
|
||||
/**
|
||||
* Gets an account from the accounts table matching the given type/ID, if
|
||||
* one exists.
|
||||
*
|
||||
* @param crAccountType CR account type to look for or create
|
||||
* @param accountUniqueID type specific unique account id
|
||||
*
|
||||
* @return CR account, if found, null otherwise.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
CentralRepoAccount getAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
|
||||
|
||||
}
|
||||
|
@ -19,12 +19,14 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.centralrepository.datamodel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.validator.routines.DomainValidator;
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Provides functions for normalizing data by attribute type before insertion or
|
||||
@ -40,7 +42,7 @@ final public class CorrelationAttributeNormalizer {
|
||||
* data is a valid string of the format expected given the attributeType.
|
||||
*
|
||||
* @param attributeType correlation type of data
|
||||
* @param data data to normalize
|
||||
* @param data data to normalize
|
||||
*
|
||||
* @return normalized data
|
||||
*/
|
||||
@ -94,7 +96,7 @@ final public class CorrelationAttributeNormalizer {
|
||||
} catch (CentralRepoException ex) {
|
||||
throw new CorrelationAttributeNormalizationException("Failed to get default correlation types.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +104,7 @@ final public class CorrelationAttributeNormalizer {
|
||||
* is a valid string of the format expected given the attributeType.
|
||||
*
|
||||
* @param attributeTypeId correlation type of data
|
||||
* @param data data to normalize
|
||||
* @param data data to normalize
|
||||
*
|
||||
* @return normalized data
|
||||
*/
|
||||
@ -155,25 +157,43 @@ final public class CorrelationAttributeNormalizer {
|
||||
|
||||
/**
|
||||
* Verify and normalize email address.
|
||||
*
|
||||
* @param emailAddress Address to normalize.
|
||||
* @return Normalized email address.
|
||||
* @throws CorrelationAttributeNormalizationExceptions If the input is not a
|
||||
* valid email address.
|
||||
*
|
||||
*/
|
||||
private static String normalizeEmail(String data) throws CorrelationAttributeNormalizationException {
|
||||
try {
|
||||
return CommunicationsUtils.normalizeEmailAddress(data);
|
||||
}
|
||||
catch(TskCoreException ex) {
|
||||
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", data), ex);
|
||||
}
|
||||
static String normalizeEmail(String emailAddress) throws CorrelationAttributeNormalizationException {
|
||||
if (isValidEmailAddress(emailAddress)) {
|
||||
return emailAddress.toLowerCase().trim();
|
||||
} else {
|
||||
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", emailAddress));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify and normalize phone number.
|
||||
*
|
||||
* @param phoneNumber Phone number to normalize.
|
||||
* @return Normalized phone number.
|
||||
* @throws CorrelationAttributeNormalizationExceptions If the input is not a
|
||||
* valid phone number.
|
||||
*
|
||||
*/
|
||||
private static String normalizePhone(String data) throws CorrelationAttributeNormalizationException {
|
||||
try {
|
||||
return CommunicationsUtils.normalizePhoneNum(data);
|
||||
}
|
||||
catch(TskCoreException ex) {
|
||||
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", data));
|
||||
static String normalizePhone(String phoneNumber) throws CorrelationAttributeNormalizationException {
|
||||
if (isValidPhoneNumber(phoneNumber)) {
|
||||
String normalizedNumber = phoneNumber.replaceAll("\\s+", ""); // remove spaces.
|
||||
normalizedNumber = normalizedNumber.replaceAll("[\\-()]", ""); // remove parens & dashes.
|
||||
|
||||
// ensure a min length
|
||||
if (normalizedNumber.length() < MIN_PHONENUMBER_LEN) {
|
||||
throw new CorrelationAttributeNormalizationException(String.format("Phone number string %s is too short ", phoneNumber));
|
||||
}
|
||||
return normalizedNumber;
|
||||
|
||||
} else {
|
||||
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", phoneNumber));
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,7 +216,7 @@ final public class CorrelationAttributeNormalizer {
|
||||
* @return the unmodified data if the data was a valid length to be an SSID
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException if the data was not a
|
||||
* valid SSID
|
||||
* valid SSID
|
||||
*/
|
||||
private static String verifySsid(String data) throws CorrelationAttributeNormalizationException {
|
||||
if (data.length() <= 32) {
|
||||
@ -223,10 +243,10 @@ final public class CorrelationAttributeNormalizer {
|
||||
* @param data The string to normalize and validate
|
||||
*
|
||||
* @return the data with common number seperators removed and lower cased if
|
||||
* the data was determined to be a possible ICCID
|
||||
* the data was determined to be a possible ICCID
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException if the data was not a
|
||||
* valid ICCID
|
||||
* valid ICCID
|
||||
*/
|
||||
private static String normalizeIccid(String data) throws CorrelationAttributeNormalizationException {
|
||||
final String validIccidRegex = "^89[f0-9]{17,22}$";
|
||||
@ -250,10 +270,10 @@ final public class CorrelationAttributeNormalizer {
|
||||
* @param data The string to normalize and validate
|
||||
*
|
||||
* @return the data with common number seperators removed if the data was
|
||||
* determined to be a possible IMSI
|
||||
* determined to be a possible IMSI
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException if the data was not a
|
||||
* valid IMSI
|
||||
* valid IMSI
|
||||
*/
|
||||
private static String normalizeImsi(String data) throws CorrelationAttributeNormalizationException {
|
||||
final String validImsiRegex = "^[0-9]{14,15}$";
|
||||
@ -274,10 +294,10 @@ final public class CorrelationAttributeNormalizer {
|
||||
* @param data The string to normalize and validate
|
||||
*
|
||||
* @return the data with common number seperators removed and lowercased if
|
||||
* the data was determined to be a possible MAC
|
||||
* the data was determined to be a possible MAC
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException if the data was not a
|
||||
* valid MAC
|
||||
* valid MAC
|
||||
*/
|
||||
private static String normalizeMac(String data) throws CorrelationAttributeNormalizationException {
|
||||
final String validMacRegex = "^([a-f0-9]{12}|[a-f0-9]{16})$";
|
||||
@ -303,10 +323,10 @@ final public class CorrelationAttributeNormalizer {
|
||||
* @param data The string to normalize and validate
|
||||
*
|
||||
* @return the data with common number seperators removed if the data was
|
||||
* determined to be a possible IMEI
|
||||
* determined to be a possible IMEI
|
||||
*
|
||||
* @throws CorrelationAttributeNormalizationException if the data was not a
|
||||
* valid IMEI
|
||||
* valid IMEI
|
||||
*/
|
||||
private static String normalizeImei(String data) throws CorrelationAttributeNormalizationException {
|
||||
final String validImeiRegex = "^[0-9]{14,16}$";
|
||||
@ -318,6 +338,58 @@ final public class CorrelationAttributeNormalizer {
|
||||
}
|
||||
}
|
||||
|
||||
// These symbols are allowed in written form of phone numbers.
|
||||
// A '+' is allowed only as a leading digit and hence not inlcuded here.
|
||||
// While a dialed sequence may have additonal special characters, such as #, * or ',',
|
||||
// CR attributes represent accounts and hence those chatracter are not allowed.
|
||||
private static final Set<String> PHONENUMBER_CHARS = new HashSet<>(Arrays.asList(
|
||||
"-", "(", ")"
|
||||
));
|
||||
|
||||
private static final int MIN_PHONENUMBER_LEN = 5;
|
||||
|
||||
/**
|
||||
* Checks if the given string is a valid phone number.
|
||||
*
|
||||
* @param phoneNumber String to check.
|
||||
*
|
||||
* @return True if the given string is a valid phone number, false
|
||||
* otherwise.
|
||||
*/
|
||||
static boolean isValidPhoneNumber(String phoneNumber) {
|
||||
|
||||
// A phone number may have a leading '+', special telephony chars, or digits.
|
||||
// Anything else implies an invalid phone number.
|
||||
for (int i = 0; i < phoneNumber.length(); i++) {
|
||||
if ( !((i == 0 && phoneNumber.charAt(i) == '+')
|
||||
|| Character.isSpaceChar(phoneNumber.charAt(i))
|
||||
|| Character.isDigit(phoneNumber.charAt(i))
|
||||
|| PHONENUMBER_CHARS.contains(String.valueOf(phoneNumber.charAt(i))))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure a min length
|
||||
return phoneNumber.length() >= MIN_PHONENUMBER_LEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given string is a valid email address.
|
||||
*
|
||||
* @param emailAddress String to check.
|
||||
*
|
||||
* @return True if the given string is a valid email address, false
|
||||
* otherwise.
|
||||
*/
|
||||
static boolean isValidEmailAddress(String emailAddress) {
|
||||
if (!StringUtils.isEmpty(emailAddress)) {
|
||||
EmailValidator validator = EmailValidator.getInstance(true, true);
|
||||
return validator.isValid(emailAddress);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a utility class - no need for constructing or subclassing, etc...
|
||||
*/
|
||||
|
@ -34,7 +34,6 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import org.sleuthkit.datamodel.HashUtility;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
@ -184,7 +183,11 @@ public class CorrelationAttributeUtil {
|
||||
makeCorrAttrsFromCommunicationArtifacts(correlationAttrs, sourceArtifact);
|
||||
}
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
} catch (CorrelationAttributeNormalizationException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error normalizing correlation attribute (%s)", artifact), ex); // NON-NLS
|
||||
return correlationAttrs;
|
||||
}
|
||||
catch (CentralRepoException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS
|
||||
return correlationAttrs;
|
||||
} catch (TskCoreException ex) {
|
||||
@ -198,18 +201,19 @@ public class CorrelationAttributeUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a correlation attribute instance from a phone number attribute of an
|
||||
* artifact.
|
||||
* Makes a correlation attribute instance from a phone number attribute of
|
||||
* an artifact.
|
||||
*
|
||||
* @param corrAttrInstances Correlation attributes will be added to this.
|
||||
* @param artifact An artifact with a phone number attribute.
|
||||
*
|
||||
* @throws TskCoreException If there is an error querying the case
|
||||
* database.
|
||||
* @throws TskCoreException If there is an error querying the case database.
|
||||
* @throws CentralRepoException If there is an error querying the central
|
||||
* repository.
|
||||
* repository.
|
||||
* @throws CorrelationAttributeNormalizationException If there is an error
|
||||
* in normalizing the attribute.
|
||||
*/
|
||||
private static void makeCorrAttrsFromCommunicationArtifacts(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException {
|
||||
private static void makeCorrAttrsFromCommunicationArtifacts(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact) throws TskCoreException, CentralRepoException, CorrelationAttributeNormalizationException {
|
||||
CorrelationAttributeInstance corrAttr = null;
|
||||
|
||||
/*
|
||||
@ -227,13 +231,13 @@ public class CorrelationAttributeUtil {
|
||||
/*
|
||||
* Normalize the phone number.
|
||||
*/
|
||||
if (value != null) {
|
||||
if(CommunicationsUtils.isValidPhoneNumber(value)) {
|
||||
value = CommunicationsUtils.normalizePhoneNum(value);
|
||||
corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value);
|
||||
if(corrAttr != null) {
|
||||
corrAttrInstances.add(corrAttr);
|
||||
}
|
||||
if (value != null
|
||||
&& CorrelationAttributeNormalizer.isValidPhoneNumber(value)) {
|
||||
|
||||
value = CorrelationAttributeNormalizer.normalizePhone(value);
|
||||
corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value);
|
||||
if (corrAttr != null) {
|
||||
corrAttrInstances.add(corrAttr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import org.sleuthkit.datamodel.TskDataException;
|
||||
|
||||
/**
|
||||
*
|
||||
* Stores information about a Data Source in the correlation engine
|
||||
* Stores information about a Data Source in the Central Repository
|
||||
*
|
||||
*/
|
||||
public class CorrelationDataSource implements Serializable {
|
||||
|
@ -24,11 +24,11 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts a persona.
|
||||
@ -122,7 +122,7 @@ public class Persona {
|
||||
private final long modifiedDate;
|
||||
private final PersonaStatus status;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
|
||||
@NbBundle.Messages("Persona.defaultName=Unnamed")
|
||||
public static String getDefaultName() {
|
||||
return Bundle.Persona_defaultName();
|
||||
@ -200,16 +200,17 @@ public class Persona {
|
||||
/**
|
||||
* Creates a Persona and associates the specified account with it.
|
||||
*
|
||||
* @param personaName Persona name.
|
||||
* @param comment Comment to associate with persona, may be null.
|
||||
* @param status Persona status
|
||||
* @param account Account for which the persona is being created.
|
||||
* @param personaName Persona name.
|
||||
* @param comment Comment to associate with persona, may be null.
|
||||
* @param status Persona status
|
||||
* @param account Account for which the persona is being created.
|
||||
* @param justification Justification for why this account belongs to this
|
||||
* persona, may be null.
|
||||
* @param confidence Confidence level for this association of Persona &
|
||||
* account.
|
||||
* persona, may be null.
|
||||
* @param confidence Confidence level for this association of Persona &
|
||||
* account.
|
||||
*
|
||||
* @return Persona Persona created.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error creating the Persona.
|
||||
*/
|
||||
public static Persona createPersonaForAccount(String personaName, String comment, PersonaStatus status, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
@ -221,49 +222,55 @@ public class Persona {
|
||||
/**
|
||||
* Inserts a row in the Persona tables.
|
||||
*
|
||||
* @param name Persona name, may be null - default name is used in that
|
||||
* case.
|
||||
* @param name Persona name, may be null - default name is used in that
|
||||
* case.
|
||||
* @param comment Comment to associate with persona, may be null.
|
||||
* @param status Persona status.
|
||||
* @param status Persona status.
|
||||
*
|
||||
* @return Persona corresponding to the row inserted in the personas table.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in adding a row to
|
||||
* personas table.
|
||||
* personas table.
|
||||
*/
|
||||
private static Persona createPersona(String name, String comment, PersonaStatus status) throws CentralRepoException {
|
||||
// generate a UUID for the persona
|
||||
String uuidStr = UUID.randomUUID().toString();
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
CentralRepoExaminer examiner = getCRInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
String insertClause = " INTO personas (uuid, comment, name, created_date, modified_date, status_id, examiner_id ) "
|
||||
+ "VALUES ( '" + uuidStr + "', "
|
||||
+ "'" + ((StringUtils.isBlank(comment) ? "" : SleuthkitCase.escapeSingleQuotes(comment))) + "',"
|
||||
+ "'" + ((StringUtils.isBlank(name) ? getDefaultName() : SleuthkitCase.escapeSingleQuotes(name))) + "',"
|
||||
+ timeStampMillis.toString() + ","
|
||||
+ timeStampMillis.toString() + ","
|
||||
+ status.getStatusId() + ","
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
String insertPersonaSQL = "INSERT INTO personas (uuid, comment, name, created_date, modified_date, status_id, examiner_id ) " //NON-NLS
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(uuidStr);
|
||||
params.add(StringUtils.isBlank(comment) ? "" : comment);
|
||||
params.add(StringUtils.isBlank(name) ? getDefaultName() : name);
|
||||
params.add(timeStampMillis);
|
||||
params.add(timeStampMillis);
|
||||
params.add(status.getStatusId());
|
||||
params.add(examiner.getId());
|
||||
|
||||
getCRInstance().executeCommand(insertPersonaSQL, params);
|
||||
return getPersonaByUUID(uuidStr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the comment of this persona.
|
||||
*
|
||||
* @param name The new comment.
|
||||
*
|
||||
* @param comment The new comment.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
public void setComment(String comment) throws CentralRepoException {
|
||||
String updateClause = "UPDATE personas SET comment = \"" + comment + "\" WHERE id = " + id;
|
||||
String updateSQL = "UPDATE personas SET comment = ? WHERE id = ?";
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
if (cr != null) {
|
||||
cr.executeUpdateSQL(updateClause);
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(StringUtils.isBlank(comment) ? "" : comment);
|
||||
params.add(id);
|
||||
|
||||
getCRInstance().executeCommand(updateSQL, params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,14 +278,18 @@ public class Persona {
|
||||
* Sets the name of this persona
|
||||
*
|
||||
* @param name The new name.
|
||||
*
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
public void setName(String name) throws CentralRepoException {
|
||||
String updateClause = "UPDATE personas SET name = \"" + name + "\" WHERE id = " + id;
|
||||
String updateSQL = "UPDATE personas SET name = ? WHERE id = ?";
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
if (cr != null) {
|
||||
cr.executeUpdateSQL(updateClause);
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(StringUtils.isBlank(name) ? getDefaultName() : name);
|
||||
params.add(id);
|
||||
|
||||
getCRInstance().executeCommand(updateSQL, params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,50 +297,57 @@ public class Persona {
|
||||
* Associates an account with a persona by creating a row in the
|
||||
* PersonaAccounts table.
|
||||
*
|
||||
* @param account Account to add to persona.
|
||||
* @param account Account to add to persona.
|
||||
* @param justification Reason for adding the account to persona, may be
|
||||
* null.
|
||||
* @param confidence Confidence level.
|
||||
* null.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAccount
|
||||
*
|
||||
* @throws CentralRepoException If there is an error.
|
||||
*/
|
||||
public PersonaAccount addAccount(CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
return PersonaAccount.addPersonaAccount(this, account, justification, confidence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given PersonaAccount (persona/account association)
|
||||
*
|
||||
* @param account account to remove
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void removeAccount(PersonaAccount account) throws CentralRepoException {
|
||||
PersonaAccount.removePersonaAccount(account.getId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the confidence / justification of the given PersonaAccount
|
||||
*
|
||||
* @param account account to modify
|
||||
* @param account Account to modify.
|
||||
* @param confidence Level of confidence.
|
||||
* @param justification Justification.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void modifyAccount(PersonaAccount account, Confidence confidence, String justification) throws CentralRepoException {
|
||||
PersonaAccount.modifyPersonaAccount(account.getId(), confidence, justification);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks this persona as deleted
|
||||
*/
|
||||
public void delete() throws CentralRepoException {
|
||||
String deleteSQL = "UPDATE personas SET status_id = " + PersonaStatus.DELETED.status_id + " WHERE id = " + this.id;
|
||||
String deleteSQL = "UPDATE personas SET status_id = ? WHERE id = ?";
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
if (cr != null) {
|
||||
cr.executeUpdateSQL(deleteSQL);
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(PersonaStatus.DELETED.getStatusId());
|
||||
params.add(id);
|
||||
|
||||
getCRInstance().executeCommand(deleteSQL, params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +356,7 @@ public class Persona {
|
||||
*/
|
||||
private static class PersonaQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
|
||||
private final Collection<Persona> personaList = new ArrayList<>();
|
||||
private final Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) throws SQLException {
|
||||
@ -359,7 +377,7 @@ public class Persona {
|
||||
status,
|
||||
examiner
|
||||
);
|
||||
|
||||
|
||||
personaList.add(persona);
|
||||
}
|
||||
}
|
||||
@ -371,124 +389,161 @@ public class Persona {
|
||||
|
||||
// Partial query string to select from personas table,
|
||||
// just supply the where clause.
|
||||
private static final String PERSONA_QUERY =
|
||||
"SELECT p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM personas as p "
|
||||
+ "INNER JOIN examiners as e ON e.id = p.examiner_id ";
|
||||
|
||||
|
||||
private static final String PERSONA_QUERY
|
||||
= "SELECT p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM personas as p "
|
||||
+ "INNER JOIN examiners as e ON e.id = p.examiner_id ";
|
||||
|
||||
/**
|
||||
* Gets the row from the Personas table with the given UUID, creates and
|
||||
* returns the Persona from that data.
|
||||
*
|
||||
* @param uuid Persona UUID to match.
|
||||
*
|
||||
* @return Persona matching the given UUID, may be null if no match is
|
||||
* found.
|
||||
* found.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
private static Persona getPersonaByUUID(String uuid) throws CentralRepoException {
|
||||
|
||||
String queryClause =
|
||||
PERSONA_QUERY
|
||||
+ "WHERE p.uuid = '" + uuid + "'";
|
||||
String queryClause
|
||||
= PERSONA_QUERY
|
||||
+ "WHERE p.uuid = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(uuid);
|
||||
|
||||
PersonaQueryCallback queryCallback = new PersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
Collection<Persona> personas = queryCallback.getPersonas();
|
||||
|
||||
|
||||
return personas.isEmpty() ? null : personas.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rows from the Personas table with matching name.
|
||||
* Persona marked as DELETED are not returned.
|
||||
* Gets the rows from the Personas table with matching name. Persona marked
|
||||
* as DELETED are not returned.
|
||||
*
|
||||
* @param partialName Name substring to match.
|
||||
*
|
||||
* @return Collection of personas matching the given name substring, may be
|
||||
* empty if no match is found.
|
||||
* empty if no match is found.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public static Collection<Persona> getPersonaByName(String partialName) throws CentralRepoException {
|
||||
|
||||
String queryClause = PERSONA_QUERY
|
||||
+ "WHERE p.status_id != " + PersonaStatus.DELETED.status_id +
|
||||
" AND LOWER(p.name) LIKE " + "LOWER('%" + partialName + "%')" ;
|
||||
+ "WHERE p.status_id != ? "
|
||||
+ " AND LOWER(p.name) LIKE LOWER(?) ESCAPE '!'";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(PersonaStatus.DELETED.getStatusId());
|
||||
params.add("%" + getLikeEscaped(partialName) + "%"); // partial substring search
|
||||
|
||||
PersonaQueryCallback queryCallback = new PersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getPersonas();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escapes string for use with like statements removing '%', '_', '\'. This
|
||||
* uses '!' as the escape character and the sql should reflect this
|
||||
* accordingly. See
|
||||
* https://stackoverflow.com/questions/8247970/using-like-wildcard-in-prepared-statement,
|
||||
* https://www.postgresql.org/docs/8.3/functions-matching.html and
|
||||
* https://www.sqlite.org/lang_expr.html for more information.
|
||||
*
|
||||
* @param initial The initial string.
|
||||
*
|
||||
* @return The resulting string.
|
||||
*/
|
||||
private static String getLikeEscaped(String initial) {
|
||||
if (initial == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return initial
|
||||
.replace("!", "!!")
|
||||
.replace("%", "!%")
|
||||
.replace("_", "!_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rows from the Personas table where persona accounts' names are
|
||||
* similar to the given one. Persona marked as DELETED are not returned.
|
||||
*
|
||||
* @param partialName Name substring to match.
|
||||
*
|
||||
* @return Collection of personas matching the given name substring, may be
|
||||
* empty if no match is found.
|
||||
* empty if no match is found.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public static Collection<Persona> getPersonaByAccountIdentifierLike(String partialName) throws CentralRepoException {
|
||||
String queryClause = "SELECT DISTINCT accounts.id as a_id,"
|
||||
+ "p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name, e.display_name"
|
||||
+ " FROM accounts"
|
||||
+ " JOIN persona_accounts as pa ON pa.account_id = accounts.id"
|
||||
+ " JOIN personas as p ON p.id = pa.persona_id"
|
||||
+ " JOIN examiners as e ON e.id = p.examiner_id"
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + partialName + "%')"
|
||||
+ " AND p.status_id != " + Persona.PersonaStatus.DELETED.getStatusId()
|
||||
+ " GROUP BY p.id";
|
||||
String queryClause = "SELECT p.id, p.uuid, p.name, p.comment, p.created_date, p.modified_date, p.status_id, p.examiner_id, e.login_name\n"
|
||||
+ "FROM personas p\n"
|
||||
+ "LEFT JOIN examiners e ON e.id = p.examiner_id\n"
|
||||
+ "WHERE p.status_id <> ?\n"
|
||||
+ "AND p.id IN (\n"
|
||||
+ " SELECT pa.persona_id\n"
|
||||
+ " FROM persona_accounts pa\n"
|
||||
+ " INNER JOIN accounts a ON a.id = pa.account_id\n"
|
||||
+ " WHERE LOWER(a.account_unique_identifier) LIKE LOWER(?) ESCAPE '!'\n"
|
||||
+ ")";
|
||||
|
||||
PersonaQueryCallback queryCallback = new PersonaQueryCallback();
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
if (cr != null) {
|
||||
cr.executeSelectSQL(queryClause, queryCallback);
|
||||
}
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(PersonaStatus.DELETED.getStatusId());
|
||||
params.add("%" + getLikeEscaped(partialName) + "%"); // partial substring search
|
||||
|
||||
getCRInstance().executeQuery(queryClause, params, queryCallback);
|
||||
return queryCallback.getPersonas();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an alias for the Persona.
|
||||
*
|
||||
* @param alias Alias name.
|
||||
* @param alias Alias name.
|
||||
* @param justification Reason for assigning the alias, may be null.
|
||||
* @param confidence Confidence level.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAlias
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in creating the alias.
|
||||
*/
|
||||
public PersonaAlias addAlias(String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
return PersonaAlias.addPersonaAlias(this, alias, justification, confidence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given alias.
|
||||
*
|
||||
* @param alias alias to remove
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void removeAlias(PersonaAlias alias) throws CentralRepoException {
|
||||
PersonaAlias.removePersonaAlias(alias);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the given alias.
|
||||
*
|
||||
* @param alias alias to modify
|
||||
* @param key Key for the alias to modify.
|
||||
* @param confidence Level of confidence.
|
||||
* @param justification Justification.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void modifyAlias(PersonaAlias key, Confidence confidence, String justification) throws CentralRepoException {
|
||||
PersonaAlias.modifyPersonaAlias(key, confidence, justification);
|
||||
@ -508,37 +563,40 @@ public class Persona {
|
||||
/**
|
||||
* Adds specified metadata to the persona.
|
||||
*
|
||||
* @param name Metadata name.
|
||||
* @param value Metadata value.
|
||||
* @param name Metadata name.
|
||||
* @param value Metadata value.
|
||||
* @param justification Reason for adding the metadata, may be null.
|
||||
* @param confidence Confidence level.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaMetadata
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in adding metadata.
|
||||
*/
|
||||
public PersonaMetadata addMetadata(String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
return PersonaMetadata.addPersonaMetadata(this.getId(), name, value, justification, confidence);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given metadata from this persona.
|
||||
*
|
||||
* @param metadata metadata to remove
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void removeMetadata(PersonaMetadata metadata) throws CentralRepoException {
|
||||
PersonaMetadata.removePersonaMetadata(metadata);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the given metadata.
|
||||
*
|
||||
* @param metadata metadata to modify
|
||||
* @param key Key for the metadata to modify.
|
||||
* @param confidence Level of confidence.
|
||||
* @param justification Justification.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in querying the
|
||||
* Personas table.
|
||||
* Personas table.
|
||||
*/
|
||||
public void modifyMetadata(PersonaMetadata key, Confidence confidence, String justification) throws CentralRepoException {
|
||||
PersonaMetadata.modifyPersonaMetadata(key, confidence, justification);
|
||||
@ -561,12 +619,12 @@ public class Persona {
|
||||
* @return Collection of PersonaAccounts, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
* persona_account.
|
||||
*/
|
||||
public Collection<PersonaAccount> getPersonaAccounts() throws CentralRepoException {
|
||||
return PersonaAccount.getPersonaAccountsForPersona(this.getId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback to process a query that gets cases for account instances of an
|
||||
* account
|
||||
@ -580,7 +638,7 @@ public class Persona {
|
||||
|
||||
while (resultSet.next()) {
|
||||
// get Case for case_id
|
||||
CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
CorrelationCase correlationCase = getCRInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
correlationCases.add(correlationCase);
|
||||
}
|
||||
}
|
||||
@ -594,8 +652,9 @@ public class Persona {
|
||||
* Gets a list of cases that the persona appears in.
|
||||
*
|
||||
* @return Collection of cases that the persona appears in, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the cases
|
||||
* from the database.
|
||||
* from the database.
|
||||
*/
|
||||
public Collection<CorrelationCase> getCases() throws CentralRepoException {
|
||||
|
||||
@ -605,14 +664,17 @@ public class Persona {
|
||||
Collection<CentralRepoAccount> accounts = PersonaAccount.getAccountsForPersona(this.getId());
|
||||
for (CentralRepoAccount account : accounts) {
|
||||
int corrTypeId = account.getAccountType().getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
CorrelationAttributeInstance.Type correlationType = getCRInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
String querySql = "SELECT DISTINCT case_id FROM " + tableName
|
||||
+ " WHERE account_id = " + account.getId();
|
||||
+ " WHERE account_id = ?"; // param 1
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(account.getId());
|
||||
|
||||
CaseForAccountInstanceQueryCallback queryCallback = new CaseForAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
getCRInstance().executeQuery(querySql, params, queryCallback);
|
||||
|
||||
// Add any cases that aren't already on the list.
|
||||
for (CorrelationCase corrCase : queryCallback.getCases()) {
|
||||
@ -639,8 +701,8 @@ public class Persona {
|
||||
while (resultSet.next()) {
|
||||
// get Case for case_id
|
||||
|
||||
CorrelationCase correlationCase = CentralRepository.getInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
CorrelationDataSource correlationDatasource = CentralRepository.getInstance().getDataSourceById(correlationCase, resultSet.getInt("data_source_id"));
|
||||
CorrelationCase correlationCase = getCRInstance().getCaseById(resultSet.getInt("case_id"));
|
||||
CorrelationDataSource correlationDatasource = getCRInstance().getDataSourceById(correlationCase, resultSet.getInt("data_source_id"));
|
||||
|
||||
// Add data source to list if not already on it.
|
||||
if (!correlationDataSources.stream().anyMatch(p -> Objects.equals(p.getDataSourceObjectID(), correlationDatasource.getDataSourceObjectID()))) {
|
||||
@ -658,7 +720,7 @@ public class Persona {
|
||||
* Gets all data sources that the persona appears in.
|
||||
*
|
||||
* @return Collection of data sources that the persona appears in, may be
|
||||
* empty.
|
||||
* empty.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@ -668,14 +730,17 @@ public class Persona {
|
||||
Collection<CentralRepoAccount> accounts = PersonaAccount.getAccountsForPersona(this.getId());
|
||||
for (CentralRepoAccount account : accounts) {
|
||||
int corrTypeId = account.getAccountType().getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
CorrelationAttributeInstance.Type correlationType = getCRInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
String querySql = "SELECT case_id, data_source_id FROM " + tableName
|
||||
+ " WHERE account_id = " + account.getId();
|
||||
+ " WHERE account_id = ?"; // param 1
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(account.getId());
|
||||
|
||||
DatasourceForAccountInstanceQueryCallback queryCallback = new DatasourceForAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
getCRInstance().executeQuery(querySql, params, queryCallback);
|
||||
|
||||
// Add any data sources that aren't already on the list.
|
||||
for (CorrelationDataSource correlationDatasource : queryCallback.getDataSources()) {
|
||||
@ -732,13 +797,15 @@ public class Persona {
|
||||
* the X_instance table for the given account type.
|
||||
*
|
||||
* @param crAccountType Account type to generate the query string for.
|
||||
*
|
||||
* @return Query substring.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static String getPersonaFromInstanceTableQueryTemplate(CentralRepoAccount.CentralRepoAccountType crAccountType) throws CentralRepoException {
|
||||
|
||||
int corrTypeId = crAccountType.getCorrelationTypeId();
|
||||
CorrelationAttributeInstance.Type correlationType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
|
||||
CorrelationAttributeInstance.Type correlationType = getCRInstance().getCorrelationTypeById(corrTypeId);
|
||||
|
||||
String instanceTableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(correlationType);
|
||||
return "SELECT " + instanceTableName + ".account_id, case_id, data_source_id, "
|
||||
@ -757,20 +824,25 @@ public class Persona {
|
||||
* @param correlationCase Case to look the persona in.
|
||||
*
|
||||
* @return Collection of personas, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public static Collection<Persona> getPersonasForCase(CorrelationCase correlationCase) throws CentralRepoException {
|
||||
Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = CentralRepository.getInstance().getAllAccountTypes();
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = getCRInstance().getAllAccountTypes();
|
||||
for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
|
||||
|
||||
String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
|
||||
+ " WHERE case_id = " + correlationCase.getID()
|
||||
+ "AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId();
|
||||
+ " WHERE case_id = ?" // param 1
|
||||
+ " AND personas.status_id != ?"; // param 2
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(correlationCase.getID());
|
||||
params.add(Persona.PersonaStatus.DELETED.getStatusId());
|
||||
|
||||
PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
getCRInstance().executeQuery(querySql, params, queryCallback);
|
||||
|
||||
// Add persona that aren't already on the list.
|
||||
for (Persona persona : queryCallback.getPersonasList()) {
|
||||
@ -789,20 +861,25 @@ public class Persona {
|
||||
* @param dataSource Data source to look the persona in.
|
||||
*
|
||||
* @return Collection of personas, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public static Collection<Persona> getPersonasForDataSource(CorrelationDataSource dataSource) throws CentralRepoException {
|
||||
Collection<Persona> personaList = new ArrayList<>();
|
||||
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = CentralRepository.getInstance().getAllAccountTypes();
|
||||
Collection<CentralRepoAccount.CentralRepoAccountType> accountTypes = getCRInstance().getAllAccountTypes();
|
||||
for (CentralRepoAccount.CentralRepoAccountType crAccountType : accountTypes) {
|
||||
|
||||
String querySql = getPersonaFromInstanceTableQueryTemplate(crAccountType)
|
||||
+ " WHERE data_source_id = " + dataSource.getID()
|
||||
+ "AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId();
|
||||
+ " WHERE data_source_id = ?"
|
||||
+ " AND personas.status_id != ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(dataSource.getID());
|
||||
params.add(Persona.PersonaStatus.DELETED.getStatusId());
|
||||
|
||||
PersonaFromAccountInstanceQueryCallback queryCallback = new PersonaFromAccountInstanceQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(querySql, queryCallback);
|
||||
getCRInstance().executeQuery(querySql, params, queryCallback);
|
||||
|
||||
// Add persona that aren't already on the list.
|
||||
for (Persona persona : queryCallback.getPersonasList()) {
|
||||
@ -814,4 +891,22 @@ public class Persona {
|
||||
}
|
||||
return personaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the call to CentralRepository.getInstance() throwing an exception
|
||||
* if instance is null;
|
||||
*
|
||||
* @return Instance of CentralRepository
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static CentralRepository getCRInstance() throws CentralRepoException {
|
||||
CentralRepository instance = CentralRepository.getInstance();
|
||||
|
||||
if (instance == null) {
|
||||
throw new CentralRepoException("Failed to get instance of CentralRespository, CR was null");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
|
||||
/**
|
||||
@ -121,40 +121,47 @@ public class PersonaAccount {
|
||||
/**
|
||||
* Creates an account for the specified Persona.
|
||||
*
|
||||
* @param persona Persona for which the account is being added.
|
||||
* @param account Account.
|
||||
* @param persona Persona for which the account is being added.
|
||||
* @param account Account.
|
||||
* @param justification Reason for assigning the alias, may be null.
|
||||
* @param confidence Confidence level.
|
||||
* @param confidence Confidence level.
|
||||
*
|
||||
* @return PersonaAccount
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in creating the
|
||||
* account.
|
||||
* account.
|
||||
*/
|
||||
static PersonaAccount addPersonaAccount(Persona persona, CentralRepoAccount account, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
CentralRepoExaminer currentExaminer = cr.getOrInsertExaminer(System.getProperty("user.name"));
|
||||
CentralRepoExaminer currentExaminer = getCRInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
String insertClause = " INTO persona_accounts (persona_id, account_id, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ persona.getId() + ", "
|
||||
+ account.getId() + ", "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ currentExaminer.getId()
|
||||
+ ")";
|
||||
|
||||
cr.executeInsertSQL(insertClause);
|
||||
String insertSQL = "INSERT INTO persona_accounts (persona_id, account_id, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ " VALUES ( ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(persona.getId());
|
||||
params.add(account.getId());
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(timeStampMillis);
|
||||
params.add(currentExaminer.getId());
|
||||
|
||||
getCRInstance().executeCommand(insertSQL, params);
|
||||
|
||||
String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ "WHERE persona_id = ? "
|
||||
+ " AND account_type_id = ?"
|
||||
+ " AND account_unique_identifier = ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(persona.getId());
|
||||
queryParams.add(account.getAccountType().getAccountTypeId());
|
||||
queryParams.add(account.getIdentifier());
|
||||
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ "WHERE persona_id = " + persona.getId()
|
||||
+ " AND account_type_id = " + account.getAccountType().getAccountTypeId()
|
||||
+ " AND account_unique_identifier = \"" + account.getIdentifier() + "\"";
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(querySQL, queryParams, queryCallback);
|
||||
|
||||
Collection<PersonaAccount> accounts = queryCallback.getPersonaAccountsList();
|
||||
if (accounts.size() != 1) {
|
||||
@ -199,7 +206,7 @@ public class PersonaAccount {
|
||||
);
|
||||
|
||||
// create account
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount account = new CentralRepoAccount(
|
||||
rs.getInt("account_id"),
|
||||
crAccountType,
|
||||
@ -242,14 +249,17 @@ public class PersonaAccount {
|
||||
* @return Collection of PersonaAccounts, may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
* persona_account.
|
||||
*/
|
||||
static Collection<PersonaAccount> getPersonaAccountsForPersona(long personaId) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE persona_accounts.persona_id = " + personaId;
|
||||
String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE persona_accounts.persona_id = ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(personaId);
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(querySQL, queryParams, queryCallback);
|
||||
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
@ -262,16 +272,19 @@ public class PersonaAccount {
|
||||
* @return Collection of PersonaAccounts. may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
* persona_account.
|
||||
*/
|
||||
public static Collection<PersonaAccount> getPersonaAccountsForAccount(long accountId) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE persona_accounts.account_id = " + accountId
|
||||
+ " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId();
|
||||
String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE persona_accounts.account_id = ?"
|
||||
+ " AND personas.status_id != ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(accountId);
|
||||
queryParams.add(Persona.PersonaStatus.DELETED.getStatusId());
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
getCRInstance().executeQuery(querySQL, queryParams, queryCallback);
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
|
||||
@ -280,21 +293,24 @@ public class PersonaAccount {
|
||||
* account identifier substring.
|
||||
*
|
||||
* @param accountIdentifierSubstring Account identifier substring to search
|
||||
* for.
|
||||
* for.
|
||||
*
|
||||
* @return Collection of PersonaAccounts. may be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* persona_account.
|
||||
* persona_account.
|
||||
*/
|
||||
public static Collection<PersonaAccount> getPersonaAccountsForIdentifierLike(String accountIdentifierSubstring) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + accountIdentifierSubstring + "%')"
|
||||
+ " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId();
|
||||
String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER(?)"
|
||||
+ " AND personas.status_id != ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add("%" + accountIdentifierSubstring + "%"); // substring match
|
||||
queryParams.add(Persona.PersonaStatus.DELETED.getStatusId());
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
getCRInstance().executeQuery(querySQL, queryParams, queryCallback);
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
}
|
||||
|
||||
@ -304,20 +320,24 @@ public class PersonaAccount {
|
||||
* @param account Account to search for.
|
||||
*
|
||||
* @return Collection of PersonaAccounts, maybe empty if none were found or
|
||||
* CR is not enabled.
|
||||
* CR is not enabled.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
public static Collection<PersonaAccount> getPersonaAccountsForAccount(Account account) throws CentralRepoException {
|
||||
String queryClause = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER('%" + account.getTypeSpecificID() + "%')"
|
||||
+ " AND type_name = '" + account.getAccountType().getTypeName() + "' "
|
||||
+ " AND personas.status_id != " + Persona.PersonaStatus.DELETED.getStatusId();
|
||||
String querySQL = PERSONA_ACCOUNTS_QUERY_CLAUSE
|
||||
+ " WHERE LOWER(accounts.account_unique_identifier) LIKE LOWER(?)"
|
||||
+ " AND type_name = ?"
|
||||
+ " AND personas.status_id != ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add("%" + account.getTypeSpecificID() + "%"); // substring match
|
||||
queryParams.add(account.getAccountType().getTypeName());
|
||||
queryParams.add(Persona.PersonaStatus.DELETED.getStatusId());
|
||||
|
||||
PersonaAccountsQueryCallback queryCallback = new PersonaAccountsQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(querySQL, queryParams, queryCallback);
|
||||
return queryCallback.getPersonaAccountsList();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,11 +346,14 @@ public class PersonaAccount {
|
||||
* @param id row id for the account to be removed
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in removing the
|
||||
* account.
|
||||
* account.
|
||||
*/
|
||||
static void removePersonaAccount(long id) throws CentralRepoException {
|
||||
String deleteClause = " DELETE FROM persona_accounts WHERE id = " + id;
|
||||
CentralRepository.getInstance().executeDeleteSQL(deleteClause);
|
||||
String deleteSQL = " DELETE FROM persona_accounts WHERE id = ?";
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(id);
|
||||
|
||||
getCRInstance().executeCommand(deleteSQL, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,11 +362,17 @@ public class PersonaAccount {
|
||||
* @param id row id for the account to be removed
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in removing the
|
||||
* account.
|
||||
* account.
|
||||
*/
|
||||
static void modifyPersonaAccount(long id, Persona.Confidence confidence, String justification) throws CentralRepoException {
|
||||
String updateClause = "UPDATE persona_accounts SET confidence_id = " + confidence.getLevelId() + ", justification = \"" + justification + "\" WHERE id = " + id;
|
||||
CentralRepository.getInstance().executeUpdateSQL(updateClause);
|
||||
String updateSQL = "UPDATE persona_accounts SET confidence_id = ?, justification = ? WHERE id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(id);
|
||||
|
||||
getCRInstance().executeCommand(updateSQL, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -360,7 +389,7 @@ public class PersonaAccount {
|
||||
while (rs.next()) {
|
||||
|
||||
// create account
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name"));
|
||||
CentralRepoAccount account = new CentralRepoAccount(
|
||||
rs.getInt("account_id"),
|
||||
crAccountType,
|
||||
@ -381,25 +410,44 @@ public class PersonaAccount {
|
||||
* @param personaId Id of the persona to look for.
|
||||
*
|
||||
* @return Collection of all accounts associated with the given persona, may
|
||||
* be empty.
|
||||
* be empty.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in getting the
|
||||
* accounts.
|
||||
* accounts.
|
||||
*/
|
||||
static Collection<CentralRepoAccount> getAccountsForPersona(long personaId) throws CentralRepoException {
|
||||
|
||||
String queryClause = "SELECT account_id, "
|
||||
+ " accounts.account_type_id as account_type_id, accounts.account_unique_identifier as account_unique_identifier,"
|
||||
+ " account_types.type_name as type_name "
|
||||
+ " FROM persona_accounts "
|
||||
+ " JOIN accounts as accounts on persona_accounts.account_id = accounts.id "
|
||||
+ " JOIN account_types as account_types on accounts.account_type_id = account_types.id "
|
||||
+ " WHERE persona_accounts.persona_id = " + personaId;
|
||||
+ " WHERE persona_accounts.persona_id = ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(personaId);
|
||||
|
||||
AccountsForPersonaQueryCallback queryCallback = new AccountsForPersonaQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(queryClause, queryParams, queryCallback);
|
||||
|
||||
return queryCallback.getAccountsList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the call to CentralRepository.getInstance() throwing an exception
|
||||
* if instance is null;
|
||||
*
|
||||
* @return Instance of CentralRepository
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static CentralRepository getCRInstance() throws CentralRepoException {
|
||||
CentralRepository instance = CentralRepository.getInstance();
|
||||
|
||||
if (instance == null) {
|
||||
throw new CentralRepoException("Failed to get instance of CentralRespository, CR was null");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -24,20 +24,20 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts an alias assigned to a Persona.
|
||||
* A Persona may have multiple aliases.
|
||||
*
|
||||
* This class abstracts an alias assigned to a Persona. A Persona may have
|
||||
* multiple aliases.
|
||||
*
|
||||
*/
|
||||
public class PersonaAlias {
|
||||
|
||||
private static final String SELECT_QUERY_BASE =
|
||||
"SELECT pa.id, pa.persona_id, pa.alias, pa.justification, pa.confidence_id, pa.date_added, pa.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_alias as pa "
|
||||
+ "INNER JOIN examiners as e ON e.id = pa.examiner_id ";
|
||||
|
||||
private static final String SELECT_QUERY_BASE
|
||||
= "SELECT pa.id, pa.persona_id, pa.alias, pa.justification, pa.confidence_id, pa.date_added, pa.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_alias as pa "
|
||||
+ "INNER JOIN examiners as e ON e.id = pa.examiner_id ";
|
||||
|
||||
private final long id;
|
||||
private final long personaId;
|
||||
@ -46,7 +46,7 @@ public class PersonaAlias {
|
||||
private final Persona.Confidence confidence;
|
||||
private final long dateAdded;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
@ -74,7 +74,7 @@ public class PersonaAlias {
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
|
||||
public PersonaAlias(long id, long personaId, String alias, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
|
||||
this.id = id;
|
||||
this.personaId = personaId;
|
||||
@ -84,8 +84,8 @@ public class PersonaAlias {
|
||||
this.dateAdded = dateAdded;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Creates an alias for the specified Persona.
|
||||
*
|
||||
* @param persona Persona for which the alias is being added.
|
||||
@ -98,40 +98,47 @@ public class PersonaAlias {
|
||||
*/
|
||||
static PersonaAlias addPersonaAlias(Persona persona, String alias, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
CentralRepoExaminer examiner = getCRInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
|
||||
String insertClause = " INTO persona_alias (persona_id, alias, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ persona.getId() + ", "
|
||||
+ "'" + alias + "', "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
String insertSQL = "INSERT INTO persona_alias (persona_id, alias, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ " VALUES ( ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(persona.getId());
|
||||
params.add(alias);
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(timeStampMillis);
|
||||
params.add(examiner.getId());
|
||||
|
||||
getCRInstance().executeCommand(insertSQL, params);
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
|
||||
String queryClause = SELECT_QUERY_BASE
|
||||
+ "WHERE pa.persona_id = " + persona.getId()
|
||||
+ " AND pa.alias = \"" + alias + "\""
|
||||
+ " AND pa.date_added = " + timeStampMillis
|
||||
+ " AND pa.examiner_id = " + examiner.getId();
|
||||
|
||||
+ "WHERE pa.persona_id = ?"
|
||||
+ " AND pa.alias = ?"
|
||||
+ " AND pa.date_added = ?"
|
||||
+ " AND pa.examiner_id = ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(persona.getId());
|
||||
queryParams.add(alias);
|
||||
queryParams.add(timeStampMillis);
|
||||
queryParams.add(examiner.getId());
|
||||
|
||||
PersonaAliasesQueryCallback queryCallback = new PersonaAliasesQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
getCRInstance().executeQuery(queryClause, queryParams, queryCallback);
|
||||
|
||||
Collection<PersonaAlias> aliases = queryCallback.getAliases();
|
||||
if (aliases.size() != 1) {
|
||||
throw new CentralRepoException("Alias add query failed");
|
||||
}
|
||||
|
||||
|
||||
return aliases.iterator().next();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a PersonaAlias.
|
||||
*
|
||||
@ -140,10 +147,14 @@ public class PersonaAlias {
|
||||
* @throws CentralRepoException If there is an error in removing the alias.
|
||||
*/
|
||||
static void removePersonaAlias(PersonaAlias alias) throws CentralRepoException {
|
||||
String deleteClause = " DELETE FROM persona_alias WHERE id = " + alias.getId();
|
||||
CentralRepository.getInstance().executeDeleteSQL(deleteClause);
|
||||
String deleteSQL = " DELETE FROM persona_alias WHERE id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(alias.getId());
|
||||
|
||||
getCRInstance().executeCommand(deleteSQL, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies a PesronaAlias.
|
||||
*
|
||||
@ -153,17 +164,22 @@ public class PersonaAlias {
|
||||
*/
|
||||
static void modifyPersonaAlias(PersonaAlias alias, Persona.Confidence confidence, String justification) throws CentralRepoException {
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
|
||||
|
||||
if (cr == null) {
|
||||
throw new CentralRepoException("Failed to modify persona alias, Central Repo is not enabled");
|
||||
}
|
||||
|
||||
String updateClause = "UPDATE persona_alias SET confidence_id = " + confidence.getLevelId() + ", justification = \"" + justification + "\" WHERE id = " + alias.id;
|
||||
cr.executeUpdateSQL(updateClause);
|
||||
|
||||
String updateClause = "UPDATE persona_alias SET confidence_id = ?, justification = ? WHERE id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(alias.getId());
|
||||
|
||||
cr.executeCommand(updateClause, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Callback to process a Persona aliases query.
|
||||
*/
|
||||
static class PersonaAliasesQueryCallback implements CentralRepositoryDbQueryCallback {
|
||||
@ -195,7 +211,7 @@ public class PersonaAlias {
|
||||
return Collections.unmodifiableCollection(personaAliases);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets all aliases for the persona with specified id.
|
||||
*
|
||||
@ -205,12 +221,33 @@ public class PersonaAlias {
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
public static Collection<PersonaAlias> getPersonaAliases(long personaId) throws CentralRepoException {
|
||||
String queryClause = SELECT_QUERY_BASE + "WHERE pa.persona_id = " + personaId;
|
||||
String queryClause = SELECT_QUERY_BASE
|
||||
+ "WHERE pa.persona_id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(personaId);
|
||||
|
||||
PersonaAliasesQueryCallback queryCallback = new PersonaAliasesQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getAliases();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps the call to CentralRepository.getInstance() throwing an exception
|
||||
* if instance is null;
|
||||
*
|
||||
* @return Instance of CentralRepository
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static CentralRepository getCRInstance() throws CentralRepoException {
|
||||
CentralRepository instance = CentralRepository.getInstance();
|
||||
|
||||
if (instance == null) {
|
||||
throw new CentralRepoException("Failed to get instance of CentralRespository, CR was null");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -24,23 +24,23 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* This class abstracts metadata associated with a Persona.
|
||||
* Metadata is in the form of a name/value pair.
|
||||
*
|
||||
* This class abstracts metadata associated with a Persona. Metadata is in the
|
||||
* form of a name/value pair.
|
||||
*
|
||||
* A Persona may have zero or more metadata.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class PersonaMetadata {
|
||||
|
||||
private static final String SELECT_QUERY_BASE =
|
||||
"SELECT pmd.id, pmd.persona_id, pmd.name, pmd.value, pmd.justification, pmd.confidence_id, pmd.date_added, pmd.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_metadata as pmd "
|
||||
+ "INNER JOIN examiners as e ON e.id = pmd.examiner_id ";
|
||||
|
||||
|
||||
private static final String SELECT_QUERY_BASE
|
||||
= "SELECT pmd.id, pmd.persona_id, pmd.name, pmd.value, pmd.justification, pmd.confidence_id, pmd.date_added, pmd.examiner_id, e.login_name, e.display_name "
|
||||
+ "FROM persona_metadata as pmd "
|
||||
+ "INNER JOIN examiners as e ON e.id = pmd.examiner_id ";
|
||||
|
||||
private final long id;
|
||||
private final long personaId;
|
||||
private final String name;
|
||||
@ -49,7 +49,7 @@ public class PersonaMetadata {
|
||||
private final Persona.Confidence confidence;
|
||||
private final long dateAdded;
|
||||
private final CentralRepoExaminer examiner;
|
||||
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
@ -81,7 +81,7 @@ public class PersonaMetadata {
|
||||
public CentralRepoExaminer getExaminer() {
|
||||
return examiner;
|
||||
}
|
||||
|
||||
|
||||
public PersonaMetadata(long id, long personaId, String name, String value, String justification, Persona.Confidence confidence, long dateAdded, CentralRepoExaminer examiner) {
|
||||
this.id = id;
|
||||
this.personaId = personaId;
|
||||
@ -92,8 +92,8 @@ public class PersonaMetadata {
|
||||
this.dateAdded = dateAdded;
|
||||
this.examiner = examiner;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Adds specified metadata to the given persona.
|
||||
*
|
||||
* @param personaId Id of persona to add metadata for.
|
||||
@ -107,72 +107,92 @@ public class PersonaMetadata {
|
||||
*/
|
||||
static PersonaMetadata addPersonaMetadata(long personaId, String name, String value, String justification, Persona.Confidence confidence) throws CentralRepoException {
|
||||
|
||||
CentralRepoExaminer examiner = CentralRepository.getInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
CentralRepoExaminer examiner = getCRInstance().getOrInsertExaminer(System.getProperty("user.name"));
|
||||
|
||||
Instant instant = Instant.now();
|
||||
Long timeStampMillis = instant.toEpochMilli();
|
||||
|
||||
String insertClause = " INTO persona_metadata (persona_id, name, value, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( "
|
||||
+ personaId + ", "
|
||||
+ "'" + name + "', "
|
||||
+ "'" + value + "', "
|
||||
+ "'" + ((StringUtils.isBlank(justification) ? "" : SleuthkitCase.escapeSingleQuotes(justification))) + "', "
|
||||
+ confidence.getLevelId() + ", "
|
||||
+ timeStampMillis.toString() + ", "
|
||||
+ examiner.getId()
|
||||
+ ")";
|
||||
String insertSQL = "INSERT INTO persona_metadata (persona_id, name, value, justification, confidence_id, date_added, examiner_id ) "
|
||||
+ "VALUES ( ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(personaId);
|
||||
params.add(name);
|
||||
params.add(value);
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(timeStampMillis);
|
||||
params.add(examiner.getId());
|
||||
|
||||
getCRInstance().executeCommand(insertSQL, params);
|
||||
|
||||
CentralRepository.getInstance().executeInsertSQL(insertClause);
|
||||
|
||||
String queryClause = SELECT_QUERY_BASE
|
||||
+ "WHERE pmd.persona_id = " + personaId
|
||||
+ " AND pmd.name = \"" + name + "\""
|
||||
+ " AND pmd.value = \"" + value + "\""
|
||||
+ " AND pmd.date_added = " + timeStampMillis
|
||||
+ " AND pmd.examiner_id = " + examiner.getId();
|
||||
|
||||
+ "WHERE pmd.persona_id = ?"
|
||||
+ " AND pmd.name = ?"
|
||||
+ " AND pmd.value = ?"
|
||||
+ " AND pmd.date_added = ?"
|
||||
+ " AND pmd.examiner_id = ?";
|
||||
|
||||
List<Object> queryParams = new ArrayList<>();
|
||||
queryParams.add(personaId);
|
||||
queryParams.add(name);
|
||||
queryParams.add(value);
|
||||
queryParams.add(timeStampMillis);
|
||||
queryParams.add(examiner.getId());
|
||||
|
||||
PersonaMetadataQueryCallback queryCallback = new PersonaMetadataQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
|
||||
getCRInstance().executeQuery(queryClause, queryParams, queryCallback);
|
||||
|
||||
Collection<PersonaMetadata> metadata = queryCallback.getMetadataList();
|
||||
if (metadata.size() != 1) {
|
||||
throw new CentralRepoException("Metadata add query failed");
|
||||
}
|
||||
|
||||
|
||||
return metadata.iterator().next();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the given PersonaMetadata
|
||||
*
|
||||
* @param metadata Metadata to remove.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in removing the metadata.
|
||||
* @throws CentralRepoException If there is an error in removing the
|
||||
* metadata.
|
||||
*/
|
||||
static void removePersonaMetadata(PersonaMetadata metadata) throws CentralRepoException {
|
||||
String deleteClause = " DELETE FROM persona_metadata WHERE id = " + metadata.getId();
|
||||
CentralRepository.getInstance().executeDeleteSQL(deleteClause);
|
||||
String deleteSql = " DELETE FROM persona_metadata WHERE id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(metadata.getId());
|
||||
|
||||
getCRInstance().executeCommand(deleteSql, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the given PersonaMetadata
|
||||
*
|
||||
* @param metadata Metadata to modify.
|
||||
*
|
||||
* @throws CentralRepoException If there is an error in modifying the metadata.
|
||||
* @throws CentralRepoException If there is an error in modifying the
|
||||
* metadata.
|
||||
*/
|
||||
static void modifyPersonaMetadata(PersonaMetadata metadata, Persona.Confidence confidence, String justification) throws CentralRepoException {
|
||||
CentralRepository cr = CentralRepository.getInstance();
|
||||
|
||||
|
||||
if (cr == null) {
|
||||
throw new CentralRepoException("Failed to modify persona metadata, Central Repo is not enabled");
|
||||
}
|
||||
|
||||
String updateClause = "UPDATE persona_metadata SET confidence_id = " + confidence.getLevelId() + ", justification = \"" + justification + "\" WHERE id = " + metadata.id;
|
||||
cr.executeUpdateSQL(updateClause);
|
||||
|
||||
String updateSql = "UPDATE persona_metadata SET confidence_id = ?, justification = ? WHERE id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(confidence.getLevelId());
|
||||
params.add(StringUtils.isBlank(justification) ? "" : justification);
|
||||
params.add(metadata.id);
|
||||
|
||||
getCRInstance().executeCommand(updateSql, params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback to process a Persona metadata query.
|
||||
*/
|
||||
@ -206,8 +226,8 @@ public class PersonaMetadata {
|
||||
return Collections.unmodifiableCollection(personaMetadataList);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Gets all metadata for the persona with specified id.
|
||||
*
|
||||
* @param personaId Id of the persona for which to get the metadata.
|
||||
@ -216,13 +236,34 @@ public class PersonaMetadata {
|
||||
* @throws CentralRepoException If there is an error in retrieving aliases.
|
||||
*/
|
||||
static Collection<PersonaMetadata> getPersonaMetadata(long personaId) throws CentralRepoException {
|
||||
String queryClause = SELECT_QUERY_BASE + "WHERE pmd.persona_id = " + personaId;
|
||||
|
||||
String queryClause = SELECT_QUERY_BASE
|
||||
+ "WHERE pmd.persona_id = ?";
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(personaId);
|
||||
|
||||
PersonaMetadataQueryCallback queryCallback = new PersonaMetadataQueryCallback();
|
||||
CentralRepository.getInstance().executeSelectSQL(queryClause, queryCallback);
|
||||
getCRInstance().executeQuery(queryClause, params, queryCallback);
|
||||
|
||||
return queryCallback.getMetadataList();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps the call to CentralRepository.getInstance() throwing an exception
|
||||
* if instance is null;
|
||||
*
|
||||
* @return Instance of CentralRepository
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private static CentralRepository getCRInstance() throws CentralRepoException {
|
||||
CentralRepository instance = CentralRepository.getInstance();
|
||||
|
||||
if (instance == null) {
|
||||
throw new CentralRepoException("Failed to get instance of CentralRespository, CR was null");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -1082,28 +1081,23 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
*/
|
||||
@Override
|
||||
public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
|
||||
// Get the account fom the accounts table
|
||||
CentralRepoAccount account = getAccount(crAccountType, accountUniqueID);
|
||||
|
||||
// account not found in the table, create it
|
||||
if (null == account) {
|
||||
String insertSQL = "INSERT INTO accounts (account_type_id, account_unique_identifier) "
|
||||
+ "VALUES (?, ?) " + getConflictClause();
|
||||
|
||||
String query = "INSERT INTO accounts (account_type_id, account_unique_identifier) "
|
||||
+ "VALUES ( " + crAccountType.getAccountTypeId() + ", '"
|
||||
+ accountUniqueID + "' )";
|
||||
try (Connection connection = connect();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);) {
|
||||
|
||||
try (Connection connection = connect();
|
||||
Statement s = connection.createStatement();) {
|
||||
preparedStatement.setInt(1, crAccountType.getAccountTypeId());
|
||||
preparedStatement.setString(2, accountUniqueID); // TBD: fill in the normalized ID
|
||||
|
||||
s.execute(query);
|
||||
// get the account from the db - should exist now.
|
||||
account = getAccount(crAccountType, accountUniqueID);
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException("Error adding an account to CR database.", ex);
|
||||
}
|
||||
preparedStatement.executeUpdate();
|
||||
|
||||
// get the account from the db - should exist now.
|
||||
return getAccount(crAccountType, accountUniqueID);
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException("Error adding an account to CR database.", ex);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1185,7 +1179,8 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
|
||||
@Override
|
||||
public CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
|
||||
|
||||
CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID));
|
||||
if (crAccount == null) {
|
||||
@ -1679,7 +1674,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
bulkArtifacts.get(tableName).clear();
|
||||
}
|
||||
|
||||
TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Bulk insert");
|
||||
TimingMetric timingMetric = HealthMonitor.getTimingMetric("Central Repository: Bulk insert");
|
||||
HealthMonitor.submitTimingMetric(timingMetric);
|
||||
|
||||
// Reset state
|
||||
@ -2543,89 +2538,52 @@ abstract class RdbmsCentralRepo implements CentralRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeInsertSQL(String insertClause) throws CentralRepoException {
|
||||
|
||||
if (insertClause == null) {
|
||||
throw new CentralRepoException("Insert SQL is null");
|
||||
}
|
||||
|
||||
String sql = getPlatformSpecificInsertSQL(insertClause);
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
|
||||
public void executeCommand(String sql, List<Object> params) throws CentralRepoException {
|
||||
|
||||
try (Connection conn = connect();) {
|
||||
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sql);
|
||||
|
||||
// Fill in the params
|
||||
if (params != null) {
|
||||
int paramIndex = 1;
|
||||
for (Object param : params) {
|
||||
preparedStatement.setObject(paramIndex, param);
|
||||
paramIndex += 1;
|
||||
}
|
||||
}
|
||||
// execute the prepared statement
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", sql, ex.getMessage()), ex);
|
||||
throw new CentralRepoException(String.format("Error executing prepared statement for SQL %s", sql), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
public void executeQuery(String sql, List<Object> params, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
if (queryCallback == null) {
|
||||
throw new CentralRepoException("Query callback is null");
|
||||
}
|
||||
|
||||
if (selectSQL == null) {
|
||||
throw new CentralRepoException("Select SQL is null");
|
||||
}
|
||||
|
||||
StringBuilder sqlSb = new StringBuilder(QUERY_STR_MAX_LEN);
|
||||
if (selectSQL.trim().toUpperCase().startsWith("SELECT") == false) {
|
||||
sqlSb.append("SELECT ");
|
||||
}
|
||||
|
||||
sqlSb.append(selectSQL);
|
||||
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sqlSb.toString());
|
||||
ResultSet resultSet = preparedStatement.executeQuery();) {
|
||||
queryCallback.process(resultSet);
|
||||
|
||||
try ( Connection conn = connect();) {
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sql);
|
||||
|
||||
// fill in the params
|
||||
if (params != null) {
|
||||
int paramIndex = 1;
|
||||
for (Object param : params) {
|
||||
preparedStatement.setObject(paramIndex, param);
|
||||
paramIndex += 1;
|
||||
}
|
||||
}
|
||||
// execute query, and the callback to process result
|
||||
try (ResultSet resultSet = preparedStatement.executeQuery();) {
|
||||
queryCallback.process(resultSet);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", selectSQL, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeUpdateSQL(String updateSQL) throws CentralRepoException {
|
||||
|
||||
if (updateSQL == null) {
|
||||
throw new CentralRepoException("Update SQL is null");
|
||||
}
|
||||
|
||||
StringBuilder sqlSb = new StringBuilder(QUERY_STR_MAX_LEN);
|
||||
if (updateSQL.trim().toUpperCase().startsWith("UPDATE") == false) {
|
||||
sqlSb.append("UPDATE ");
|
||||
}
|
||||
|
||||
sqlSb.append(updateSQL);
|
||||
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sqlSb.toString());) {
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", updateSQL, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeDeleteSQL(String deleteSQL) throws CentralRepoException {
|
||||
|
||||
if (deleteSQL == null) {
|
||||
throw new CentralRepoException("Delete SQL is null");
|
||||
}
|
||||
|
||||
StringBuilder sqlSb = new StringBuilder(QUERY_STR_MAX_LEN);
|
||||
if (deleteSQL.trim().toUpperCase().startsWith("DELETE") == false) {
|
||||
sqlSb.append("DELETE ");
|
||||
}
|
||||
|
||||
sqlSb.append(deleteSQL);
|
||||
|
||||
try (Connection conn = connect();
|
||||
PreparedStatement preparedStatement = conn.prepareStatement(sqlSb.toString());) {
|
||||
preparedStatement.executeUpdate();
|
||||
} catch (SQLException ex) {
|
||||
throw new CentralRepoException(String.format("Error running SQL %s, exception = %s", deleteSQL, ex.getMessage()), ex);
|
||||
}
|
||||
throw new CentralRepoException(String.format("Error executing prepared statement for SQL query %s", sql), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -832,48 +832,27 @@ final class SqliteCentralRepo extends RdbmsCentralRepo {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void executeInsertSQL(String insertSQL) throws CentralRepoException {
|
||||
public void executeCommand(String sql, List<Object> params) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeInsertSQL(insertSQL);
|
||||
acquireExclusiveLock();
|
||||
super.executeCommand(sql, params);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
releaseExclusiveLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSelectSQL(String selectSQL, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
public void executeQuery(String sql, List<Object> params, CentralRepositoryDbQueryCallback queryCallback) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeSelectSQL(selectSQL, queryCallback);
|
||||
super.executeQuery(sql, params, queryCallback);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeUpdateSQL(String updateSQL) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeUpdateSQL(updateSQL);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeDeleteSQL(String deleteSQL) throws CentralRepoException {
|
||||
try {
|
||||
acquireSharedLock();
|
||||
super.executeDeleteSQL(deleteSQL);
|
||||
} finally {
|
||||
releaseSharedLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a reference set with the given name/version is in the
|
||||
* central repo. Used to check for name collisions when creating reference
|
||||
|
@ -1,5 +1,5 @@
|
||||
caseeventlistener.evidencetag=Evidence
|
||||
IngestEventsListener.ingestmodule.name=Correlation Engine
|
||||
IngestEventsListener.ingestmodule.name=Central Repository
|
||||
IngestEventsListener.prevCaseComment.text=Previous Case:
|
||||
# {0} - typeName
|
||||
# {1} - count
|
||||
|
@ -73,7 +73,7 @@ import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
* Listen for ingest events and update entries in the Central Repository
|
||||
* database accordingly
|
||||
*/
|
||||
@NbBundle.Messages({"IngestEventsListener.ingestmodule.name=Correlation Engine"})
|
||||
@NbBundle.Messages({"IngestEventsListener.ingestmodule.name=Central Repository"})
|
||||
public class IngestEventsListener {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CorrelationAttributeInstance.class.getName());
|
||||
@ -116,24 +116,24 @@ public class IngestEventsListener {
|
||||
|
||||
/**
|
||||
* Increase the number of IngestEventsListeners adding contents to the
|
||||
* Correlation Engine.
|
||||
* Central Repository.
|
||||
*/
|
||||
public synchronized static void incrementCorrelationEngineModuleCount() {
|
||||
correlationModuleInstanceCount++; //Should be called once in the Correlation Engine module's startup method.
|
||||
correlationModuleInstanceCount++; //Should be called once in the Central Repository module's startup method.
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrease the number of IngestEventsListeners adding contents to the
|
||||
* Correlation Engine.
|
||||
* Central Repository.
|
||||
*/
|
||||
public synchronized static void decrementCorrelationEngineModuleCount() {
|
||||
if (getCeModuleInstanceCount() > 0) { //prevent it ingestJobCounter from going negative
|
||||
correlationModuleInstanceCount--; //Should be called once in the Correlation Engine module's shutdown method.
|
||||
correlationModuleInstanceCount--; //Should be called once in the Central Repository module's shutdown method.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the counter which keeps track of if the Correlation Engine Module
|
||||
* Reset the counter which keeps track of if the Central Repository Module
|
||||
* is being run during injest to 0.
|
||||
*/
|
||||
synchronized static void resetCeModuleInstanceCount() {
|
||||
@ -141,10 +141,10 @@ public class IngestEventsListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the Correlation Engine Module is enabled for any of the
|
||||
* Whether or not the Central Repository Module is enabled for any of the
|
||||
* currently running ingest jobs.
|
||||
*
|
||||
* @return boolean True for Correlation Engine enabled, False for disabled
|
||||
* @return boolean True for Central Repository enabled, False for disabled
|
||||
*/
|
||||
public synchronized static int getCeModuleInstanceCount() {
|
||||
return correlationModuleInstanceCount;
|
||||
@ -282,7 +282,7 @@ public class IngestEventsListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
//if ingest is running we want there to check if there is a Correlation Engine module running
|
||||
//if ingest is running we want there to check if there is a Central Repository module running
|
||||
//sometimes artifacts are generated by DSPs or other sources while ingest is not running
|
||||
//in these cases we still want to create correlation attributesForNewArtifact for those artifacts when appropriate
|
||||
if (!IngestManager.getInstance().isIngestRunning() || getCeModuleInstanceCount() > 0) {
|
||||
@ -349,7 +349,7 @@ public class IngestEventsListener {
|
||||
if (getCeModuleInstanceCount() == 0) {
|
||||
recentlyAddedCeArtifacts.clear();
|
||||
}
|
||||
//else another instance of the Correlation Engine Module is still being run.
|
||||
//else another instance of the Central Repository Module is still being run.
|
||||
|
||||
/*
|
||||
* Ensure the data source in the Central Repository has hash values
|
||||
|
@ -1,6 +1,6 @@
|
||||
CentralRepoIngestModel_name_header=Name:<br>
|
||||
CentralRepoIngestModel_previous_case_header=<br>Previous Cases:<br>
|
||||
CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Correlation Engine ingest module.
|
||||
CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Central Repository ingest module.
|
||||
CentralRepoIngestModule.notfyBubble.title=Central Repository Not Initialized
|
||||
CentralRepoIngestModule.prevCaseComment.text=Previous Case:
|
||||
CentralRepoIngestModule.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)
|
||||
@ -8,7 +8,7 @@ CentralRepoIngestModule_notable_message_header=<html>A file in this data source
|
||||
# {0} - Name of file that is Notable
|
||||
CentralRepoIngestModule_postToBB_knownBadMsg=Notable: {0}
|
||||
CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation
|
||||
CentralRepoIngestModuleFactory.ingestmodule.name=Correlation Engine
|
||||
CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository
|
||||
IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings
|
||||
IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable
|
||||
IngestSettingsPanel.flagPreviouslySeenDevicesCheckbox.text=Flag devices previously seen in other cases
|
||||
|
@ -85,7 +85,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
|
||||
private final boolean createCorrelationProperties;
|
||||
|
||||
/**
|
||||
* Instantiate the Correlation Engine ingest module.
|
||||
* Instantiate the Central Repository ingest module.
|
||||
*
|
||||
* @param settings The ingest settings for the module instance.
|
||||
*/
|
||||
@ -147,7 +147,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
|
||||
*/
|
||||
if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) {
|
||||
try {
|
||||
TimingMetric timingMetric = HealthMonitor.getTimingMetric("Correlation Engine: Notable artifact query");
|
||||
TimingMetric timingMetric = HealthMonitor.getTimingMetric("Central Repository: Notable artifact query");
|
||||
List<String> caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5);
|
||||
HealthMonitor.submitTimingMetric(timingMetric);
|
||||
if (!caseDisplayNamesList.isEmpty()) {
|
||||
@ -220,7 +220,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
|
||||
// see ArtifactManagerTimeTester for details
|
||||
@Messages({
|
||||
"CentralRepoIngestModule.notfyBubble.title=Central Repository Not Initialized",
|
||||
"CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Correlation Engine ingest module."
|
||||
"CentralRepoIngestModule.errorMessage.isNotEnabled=Central repository settings are not initialized, cannot run Central Repository ingest module."
|
||||
})
|
||||
@Override
|
||||
public void startUp(IngestJobContext context) throws IngestModuleException {
|
||||
@ -235,7 +235,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
|
||||
* posited.
|
||||
*
|
||||
* Note: Flagging cannot be disabled if any other instances of the
|
||||
* Correlation Engine module are running. This restriction is to prevent
|
||||
* Central Repository module are running. This restriction is to prevent
|
||||
* missing results in the case where the first module is flagging
|
||||
* notable items, and the proceeding module (with flagging disabled)
|
||||
* causes the first to stop flagging.
|
||||
@ -276,7 +276,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
|
||||
// Don't allow sqlite central repo databases to be used for multi user cases
|
||||
if ((autopsyCase.getCaseType() == Case.CaseType.MULTI_USER_CASE)
|
||||
&& (CentralRepoDbManager.getSavedDbChoice().getDbPlatform() == CentralRepoPlatforms.SQLITE)) {
|
||||
logger.log(Level.SEVERE, "Cannot run correlation engine on a multi-user case with a SQLite central repository.");
|
||||
logger.log(Level.SEVERE, "Cannot run Central Repository ingest module on a multi-user case with a SQLite central repository.");
|
||||
throw new IngestModuleException("Cannot run on a multi-user case with a SQLite central repository."); // NON-NLS
|
||||
}
|
||||
jobId = context.getJobId();
|
||||
|
@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.ingest.NoIngestModuleIngestJobSettings;
|
||||
* Factory for Central Repository ingest modules
|
||||
*/
|
||||
@ServiceProvider(service = org.sleuthkit.autopsy.ingest.IngestModuleFactory.class)
|
||||
@NbBundle.Messages({"CentralRepoIngestModuleFactory.ingestmodule.name=Correlation Engine",
|
||||
@NbBundle.Messages({"CentralRepoIngestModuleFactory.ingestmodule.name=Central Repository",
|
||||
"CentralRepoIngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation"})
|
||||
public class CentralRepoIngestModuleFactory extends IngestModuleFactoryAdapter {
|
||||
|
||||
|
@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.ingestmodule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
|
||||
|
||||
/**
|
||||
* Ingest job settings for the Correlation Engine module.
|
||||
* Ingest job settings for the Central Repository module.
|
||||
*/
|
||||
final class IngestSettings implements IngestModuleIngestJobSettings {
|
||||
|
||||
|
@ -22,7 +22,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
|
||||
|
||||
/**
|
||||
* Ingest job settings panel for the Correlation Engine module.
|
||||
* Ingest job settings panel for the Central Repository module.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel {
|
||||
|
@ -70,7 +70,7 @@ PersonasTopComponent.deleteBtn.text=Delete Persona
|
||||
PersonasTopComponent.editBtn.text=Edit Persona
|
||||
PersonasTopComponent.createBtn.text=New Persona
|
||||
PersonasTopComponent.createAccountBtn.text=Create Account
|
||||
PersonasTopComponent.searchBtn.text=Search
|
||||
PersonasTopComponent.searchBtn.text=Show
|
||||
PersonasTopComponent.resultsTable.columnModel.title1=Name
|
||||
PersonasTopComponent.resultsTable.columnModel.title0=ID
|
||||
PersonasTopComponent.resultsTable.toolTipText=
|
||||
@ -79,3 +79,5 @@ CreatePersonaAccountDialog.typeLbl.text=Type:
|
||||
CreatePersonaAccountDialog.identifierTextField.text=
|
||||
CreatePersonaAccountDialog.identiferLbl.text=Identifier:
|
||||
CreatePersonaAccountDialog.okBtn.text=OK
|
||||
PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here.
|
||||
PersonasTopComponent.cbFilterByKeyword.text=Filter personas by keyword
|
||||
|
@ -4,8 +4,10 @@ AddMetadataDialog_dup_Title=Metadata add failure
|
||||
AddMetadataDialog_empty_name_msg=A metadata entry cannot have an empty name or value.
|
||||
AddMetadataDialog_empty_name_Title=Missing field(s)
|
||||
CreatePersonaAccountDialog.title.text=Create Account
|
||||
CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists.
|
||||
CreatePersonaAccountDialog_dup_Title=Account creation failure
|
||||
CreatePersonaAccountDialog_error_msg=Failed to create account.
|
||||
CreatePersonaAccountDialog_error_title=Account failure
|
||||
CreatePersonaAccountDialog_success_msg=Account added.
|
||||
CreatePersonaAccountDialog_success_title=Account added
|
||||
CTL_OpenPersonas=Personas
|
||||
CTL_PersonasTopComponentAction=Personas
|
||||
CTL_PersonaDetailsTopComponent=Persona Details
|
||||
@ -17,6 +19,8 @@ PersonaAccountDialog_get_types_exception_msg=Failed to access central repository
|
||||
PersonaAccountDialog_get_types_exception_Title=Central Repository failure
|
||||
PersonaAccountDialog_identifier_empty_msg=The identifier field cannot be empty.
|
||||
PersonaAccountDialog_identifier_empty_Title=Empty identifier
|
||||
PersonaAccountDialog_invalid_account_msg=Account identifier is not valid.
|
||||
PersonaAccountDialog_invalid_account_Title=Invalid account identifier
|
||||
PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type.
|
||||
PersonaAccountDialog_search_empty_Title=Account not found
|
||||
PersonaAccountDialog_search_failure_msg=Central Repository account search failed.
|
||||
@ -110,7 +114,7 @@ PersonasTopComponent.deleteBtn.text=Delete Persona
|
||||
PersonasTopComponent.editBtn.text=Edit Persona
|
||||
PersonasTopComponent.createBtn.text=New Persona
|
||||
PersonasTopComponent.createAccountBtn.text=Create Account
|
||||
PersonasTopComponent.searchBtn.text=Search
|
||||
PersonasTopComponent.searchBtn.text=Show
|
||||
PersonasTopComponent.resultsTable.columnModel.title1=Name
|
||||
PersonasTopComponent.resultsTable.columnModel.title0=ID
|
||||
PersonasTopComponent.resultsTable.toolTipText=
|
||||
@ -119,10 +123,13 @@ CreatePersonaAccountDialog.typeLbl.text=Type:
|
||||
CreatePersonaAccountDialog.identifierTextField.text=
|
||||
CreatePersonaAccountDialog.identiferLbl.text=Identifier:
|
||||
CreatePersonaAccountDialog.okBtn.text=OK
|
||||
PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here.
|
||||
PersonasTopComponent.cbFilterByKeyword.text=Filter personas by keyword
|
||||
PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona?
|
||||
PersonasTopComponent_delete_confirmation_Title=Are you sure?
|
||||
PersonasTopComponent_delete_exception_msg=Failed to delete persona.
|
||||
PersonasTopComponent_delete_exception_Title=Delete failure
|
||||
PersonasTopComponent_Name=Personas
|
||||
PersonasTopComponent_noCR_msg=Central Repository is not enabled.
|
||||
PersonasTopComponent_search_exception_msg=Failed to search personas.
|
||||
PersonasTopComponent_search_exception_Title=Search failure
|
||||
|
@ -28,6 +28,7 @@ import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
|
||||
@ -212,6 +213,10 @@ public class CreatePersonaAccountDialog extends JDialog {
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"CreatePersonaAccountDialog_error_title=Account failure",
|
||||
"CreatePersonaAccountDialog_error_msg=Failed to create account.",
|
||||
})
|
||||
private CentralRepoAccount createAccount(CentralRepoAccount.CentralRepoAccountType type, String identifier) {
|
||||
CentralRepoAccount ret = null;
|
||||
try {
|
||||
@ -220,20 +225,21 @@ public class CreatePersonaAccountDialog extends JDialog {
|
||||
ret = cr.getOrCreateAccount(type, identifier);
|
||||
}
|
||||
} catch (CentralRepoException e) {
|
||||
logger.log(Level.SEVERE, "Failed to access central repository", e);
|
||||
logger.log(Level.SEVERE, "Failed to create account", e);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAccountDialog_get_types_exception_Title(),
|
||||
Bundle.PersonaAccountDialog_get_types_exception_msg(),
|
||||
Bundle.CreatePersonaAccountDialog_error_title(),
|
||||
Bundle.CreatePersonaAccountDialog_error_msg(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"CreatePersonaAccountDialog_dup_Title=Account creation failure",
|
||||
"CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists.",})
|
||||
"CreatePersonaAccountDialog_success_title=Account added",
|
||||
"CreatePersonaAccountDialog_success_msg=Account added.",
|
||||
})
|
||||
private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed
|
||||
if (identifierTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(identifierTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAccountDialog_identifier_empty_msg(),
|
||||
Bundle.PersonaAccountDialog_identifier_empty_Title(),
|
||||
@ -246,6 +252,12 @@ public class CreatePersonaAccountDialog extends JDialog {
|
||||
String identifier = identifierTextField.getText();
|
||||
|
||||
if (createAccount(type, identifier) != null) {
|
||||
// show account created message
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.CreatePersonaAccountDialog_success_msg(),
|
||||
Bundle.CreatePersonaAccountDialog_success_title(),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
|
||||
dispose();
|
||||
}
|
||||
}//GEN-LAST:event_okBtnActionPerformed
|
||||
|
@ -28,6 +28,7 @@ import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
|
||||
@ -36,6 +37,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
|
||||
/**
|
||||
* Configuration dialog for adding an account to a persona.
|
||||
@ -276,16 +278,19 @@ public class PersonaAccountDialog extends JDialog {
|
||||
"PersonaAccountDialog_search_failure_Title=Account add failure",
|
||||
"PersonaAccountDialog_search_failure_msg=Central Repository account search failed.",
|
||||
"PersonaAccountDialog_search_empty_Title=Account not found",
|
||||
"PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type.",})
|
||||
"PersonaAccountDialog_search_empty_msg=Account not found for given identifier and type.",
|
||||
"PersonaAccountDialog_invalid_account_Title=Invalid account identifier",
|
||||
"PersonaAccountDialog_invalid_account_msg=Account identifier is not valid.",
|
||||
})
|
||||
private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed
|
||||
if (identifierTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(identifierTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAccountDialog_identifier_empty_msg(),
|
||||
Bundle.PersonaAccountDialog_identifier_empty_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (justificationTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(justificationTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaDetailsPanel_empty_justification_msg(),
|
||||
Bundle.PersonaDetailsPanel_empty_justification_Title(),
|
||||
@ -303,6 +308,14 @@ public class PersonaAccountDialog extends JDialog {
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
catch (InvalidAccountIDException e) {
|
||||
logger.log(Level.SEVERE, "Invalid account identifier", e);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAccountDialog_invalid_account_msg(),
|
||||
Bundle.PersonaAccountDialog_invalid_account_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAccountDialog_search_empty_msg(),
|
||||
|
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.persona;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
@ -200,14 +201,14 @@ public class PersonaAliasDialog extends JDialog {
|
||||
"PersonaAliasDialog_dup_Title=Alias add failure",
|
||||
"PersonaAliasDialog_dup_msg=This alias has already been added to this persona.",})
|
||||
private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed
|
||||
if (aliasTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(aliasTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaAliasDialog_empty_msg(),
|
||||
Bundle.PersonaAliasDialog_empty_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (justificationTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(justificationTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaDetailsPanel_empty_justification_msg(),
|
||||
Bundle.PersonaDetailsPanel_empty_justification_Title(),
|
||||
|
@ -34,6 +34,7 @@ import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.RetainLocation;
|
||||
@ -891,14 +892,14 @@ public final class PersonaDetailsPanel extends javax.swing.JPanel {
|
||||
return null;
|
||||
|
||||
}
|
||||
if (commentField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(commentField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaDetailsPanel_EmptyComment_msg(),
|
||||
Bundle.PersonaDetailsPanel_EmptyComment_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return null;
|
||||
}
|
||||
if (nameField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(nameField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaDetailsPanel_EmptyName_msg(),
|
||||
Bundle.PersonaDetailsPanel_EmptyName_Title(),
|
||||
|
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.persona;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
@ -216,14 +217,14 @@ public class PersonaMetadataDialog extends JDialog {
|
||||
"AddMetadataDialog_empty_name_Title=Missing field(s)",
|
||||
"AddMetadataDialog_empty_name_msg=A metadata entry cannot have an empty name or value.",})
|
||||
private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed
|
||||
if (nameTextField.getText().isEmpty() || valueTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(nameTextField.getText()) || StringUtils.isBlank(valueTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.AddMetadataDialog_empty_name_msg(),
|
||||
Bundle.AddMetadataDialog_empty_name_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (justificationTextField.getText().isEmpty()) {
|
||||
if (StringUtils.isBlank(justificationTextField.getText())) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonaDetailsPanel_empty_justification_msg(),
|
||||
Bundle.PersonaDetailsPanel_empty_justification_Title(),
|
||||
|
@ -1,26 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
|
||||
-->
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<NonVisualComponents>
|
||||
<Component class="javax.swing.ButtonGroup" name="searchButtonGroup">
|
||||
@ -44,17 +23,52 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jSplitPane1" alignment="0" pref="794" max="32767" attributes="0"/>
|
||||
<Component id="introTextScrollPane" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="mainSplitPane" alignment="0" pref="724" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jSplitPane1" alignment="0" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="introTextScrollPane" min="-2" pref="49" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="mainSplitPane" pref="489" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JSplitPane" name="jSplitPane1">
|
||||
<Container class="javax.swing.JScrollPane" name="introTextScrollPane">
|
||||
<Properties>
|
||||
<Property name="verticalScrollBarPolicy" type="int" value="21"/>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextArea" name="introText">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection component="Form" name="getBackground" type="method"/>
|
||||
</Property>
|
||||
<Property name="columns" type="int" value="20"/>
|
||||
<Property name="lineWrap" type="boolean" value="true"/>
|
||||
<Property name="rows" type="int" value="5"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.introText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="wrapStyleWord" type="boolean" value="true"/>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JSplitPane" name="mainSplitPane">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="400"/>
|
||||
</Properties>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
@ -68,17 +82,10 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jSeparator1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="createBtn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="editBtn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="deleteBtn" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="createButtonSeparator" max="32767" attributes="0"/>
|
||||
<Component id="resultsPane" pref="0" max="32767" attributes="0"/>
|
||||
<Component id="searchField" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
@ -88,9 +95,19 @@
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="searchBtn" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="createAccountBtn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="createAccountBtn" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="createBtn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="editBtn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deleteBtn" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="cbFilterByKeyword" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="50" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
@ -100,7 +117,8 @@
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="cbFilterByKeyword" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
|
||||
<Component id="searchField" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
@ -109,7 +127,7 @@
|
||||
<Component id="searchBtn" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="resultsPane" pref="457" max="32767" attributes="0"/>
|
||||
<Component id="resultsPane" min="-2" pref="302" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="editBtn" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
@ -117,7 +135,7 @@
|
||||
<Component id="deleteBtn" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jSeparator1" min="-2" pref="4" max="-2" attributes="0"/>
|
||||
<Component id="createButtonSeparator" min="-2" pref="4" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="createAccountBtn" min="-2" pref="32" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
@ -154,6 +172,13 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="searchBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.searchBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="resultsPane">
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
@ -191,13 +216,6 @@
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="searchBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.searchBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="createAccountBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
@ -208,16 +226,6 @@
|
||||
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="createBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.createBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="editBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
@ -234,17 +242,43 @@
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSeparator" name="jSeparator1">
|
||||
<Component class="javax.swing.JSeparator" name="createButtonSeparator">
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="createBtn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.createBtn.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="cbFilterByKeyword">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties" key="PersonasTopComponent.cbFilterByKeyword.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cbFilterByKeywordActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel" name="detailsPanel">
|
||||
<Container class="javax.swing.JScrollPane" name="detailsScrollPane">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="right"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel" name="detailsPanel">
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -27,6 +27,8 @@ import java.util.logging.Level;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
@ -37,6 +39,7 @@ import org.openide.windows.RetainLocation;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
@ -49,7 +52,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@RetainLocation("personas")
|
||||
@SuppressWarnings("PMD.SingularField")
|
||||
public final class PersonasTopComponent extends TopComponent {
|
||||
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PersonasTopComponent.class.getName());
|
||||
@ -57,17 +60,37 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
private List<Persona> currentResults = null;
|
||||
private Persona selectedPersona = null;
|
||||
|
||||
/**
|
||||
* Listens for when this component will be rendered and executes a search to
|
||||
* update gui when it is displayed.
|
||||
*/
|
||||
private final AncestorListener onAddListener = new AncestorListener() {
|
||||
@Override
|
||||
public void ancestorAdded(AncestorEvent event) {
|
||||
resetSearchControls();
|
||||
setKeywordSearchEnabled(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent event) {
|
||||
//Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) {
|
||||
//Empty
|
||||
}
|
||||
};
|
||||
|
||||
@Messages({
|
||||
"PersonasTopComponent_Name=Personas",
|
||||
"PersonasTopComponent_delete_exception_Title=Delete failure",
|
||||
"PersonasTopComponent_delete_exception_msg=Failed to delete persona.",
|
||||
"PersonasTopComponent_delete_confirmation_Title=Are you sure?",
|
||||
"PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona?",
|
||||
})
|
||||
"PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona?",})
|
||||
public PersonasTopComponent() {
|
||||
initComponents();
|
||||
setName(Bundle.PersonasTopComponent_Name());
|
||||
executeSearch();
|
||||
|
||||
searchBtn.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
@ -91,14 +114,14 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
PersonaDetailsMode.CREATE, selectedPersona, new CreateEditCallbackImpl());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
deleteBtn.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
NotifyDescriptor confirm = new NotifyDescriptor.Confirmation(
|
||||
Bundle.PersonasTopComponent_delete_confirmation_msg(),
|
||||
Bundle.PersonasTopComponent_delete_confirmation_Title(),
|
||||
NotifyDescriptor.YES_NO_OPTION);
|
||||
Bundle.PersonasTopComponent_delete_confirmation_msg(),
|
||||
Bundle.PersonasTopComponent_delete_confirmation_Title(),
|
||||
NotifyDescriptor.YES_NO_OPTION);
|
||||
DialogDisplayer.getDefault().notify(confirm);
|
||||
if (confirm.getValue().equals(NotifyDescriptor.YES_OPTION)) {
|
||||
try {
|
||||
@ -108,9 +131,9 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to delete persona: " + selectedPersona.getName(), ex);
|
||||
JOptionPane.showMessageDialog(PersonasTopComponent.this,
|
||||
Bundle.PersonasTopComponent_delete_exception_msg(),
|
||||
Bundle.PersonasTopComponent_delete_exception_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
Bundle.PersonasTopComponent_delete_exception_msg(),
|
||||
Bundle.PersonasTopComponent_delete_exception_Title(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
executeSearch();
|
||||
@ -126,21 +149,23 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
handleSelectionChange(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
searchNameRadio.addActionListener((ActionEvent e) -> {
|
||||
searchField.setText("");
|
||||
});
|
||||
|
||||
|
||||
searchAccountRadio.addActionListener((ActionEvent e) -> {
|
||||
searchField.setText("");
|
||||
});
|
||||
|
||||
|
||||
createAccountBtn.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new CreatePersonaAccountDialog(detailsPanel);
|
||||
}
|
||||
});
|
||||
|
||||
addAncestorListener(onAddListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -161,6 +186,34 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets search controls to default state.
|
||||
*/
|
||||
private void resetSearchControls() {
|
||||
searchField.setText("");
|
||||
searchNameRadio.setSelected(true);
|
||||
searchAccountRadio.setSelected(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the GUI for appropriate state for keyword search enabled state.
|
||||
*
|
||||
* @param selected Whether or not keyword search is enabled.
|
||||
* @param setFilterCb Whether or not the filter checkbox should be
|
||||
* manipulated as a part of this change.
|
||||
*/
|
||||
private void setKeywordSearchEnabled(boolean selected, boolean setFilterCb) {
|
||||
if (setFilterCb && cbFilterByKeyword.isSelected() != selected) {
|
||||
cbFilterByKeyword.setSelected(selected);
|
||||
}
|
||||
|
||||
searchField.setEnabled(selected);
|
||||
searchNameRadio.setEnabled(selected);
|
||||
searchAccountRadio.setEnabled(selected);
|
||||
|
||||
executeSearch();
|
||||
}
|
||||
|
||||
void setPersona(int index) {
|
||||
Persona persona = currentResults.get(index);
|
||||
selectedPersona = persona;
|
||||
@ -224,14 +277,29 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
|
||||
@Messages({
|
||||
"PersonasTopComponent_search_exception_Title=Search failure",
|
||||
"PersonasTopComponent_search_exception_msg=Failed to search personas.",})
|
||||
"PersonasTopComponent_search_exception_msg=Failed to search personas.",
|
||||
"PersonasTopComponent_noCR_msg=Central Repository is not enabled.",})
|
||||
private void executeSearch() {
|
||||
// To prevent downstream failures, only execute search if central repository is enabled
|
||||
if (!CentralRepository.isEnabled()) {
|
||||
logger.log(Level.SEVERE, "Central Repository is not enabled, but execute search was called.");
|
||||
JOptionPane.showMessageDialog(this,
|
||||
Bundle.PersonasTopComponent_search_exception_Title(),
|
||||
Bundle.PersonasTopComponent_noCR_msg(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<Persona> results;
|
||||
try {
|
||||
if (searchNameRadio.isSelected()) {
|
||||
results = Persona.getPersonaByName(searchField.getText());
|
||||
if (cbFilterByKeyword.isSelected()) {
|
||||
if (searchNameRadio.isSelected()) {
|
||||
results = Persona.getPersonaByName(searchField.getText());
|
||||
} else {
|
||||
results = Persona.getPersonaByAccountIdentifierLike(searchField.getText());
|
||||
}
|
||||
} else {
|
||||
results = Persona.getPersonaByAccountIdentifierLike(searchField.getText());
|
||||
results = Persona.getPersonaByName("");
|
||||
}
|
||||
} catch (CentralRepoException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to search personas", ex);
|
||||
@ -263,23 +331,40 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
private void initComponents() {
|
||||
|
||||
searchButtonGroup = new javax.swing.ButtonGroup();
|
||||
jSplitPane1 = new javax.swing.JSplitPane();
|
||||
introTextScrollPane = new javax.swing.JScrollPane();
|
||||
introText = new javax.swing.JTextArea();
|
||||
mainSplitPane = new javax.swing.JSplitPane();
|
||||
searchPanel = new javax.swing.JPanel();
|
||||
searchField = new javax.swing.JTextField();
|
||||
searchNameRadio = new javax.swing.JRadioButton();
|
||||
searchAccountRadio = new javax.swing.JRadioButton();
|
||||
searchBtn = new javax.swing.JButton();
|
||||
resultsPane = new javax.swing.JScrollPane();
|
||||
resultsTable = new javax.swing.JTable();
|
||||
searchBtn = new javax.swing.JButton();
|
||||
createAccountBtn = new javax.swing.JButton();
|
||||
createBtn = new javax.swing.JButton();
|
||||
editBtn = new javax.swing.JButton();
|
||||
deleteBtn = new javax.swing.JButton();
|
||||
jSeparator1 = new javax.swing.JSeparator();
|
||||
createButtonSeparator = new javax.swing.JSeparator();
|
||||
createBtn = new javax.swing.JButton();
|
||||
cbFilterByKeyword = new javax.swing.JCheckBox();
|
||||
detailsScrollPane = new javax.swing.JScrollPane();
|
||||
detailsPanel = new org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel();
|
||||
|
||||
setName(""); // NOI18N
|
||||
|
||||
introTextScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
|
||||
|
||||
introText.setBackground(getBackground());
|
||||
introText.setColumns(20);
|
||||
introText.setLineWrap(true);
|
||||
introText.setRows(5);
|
||||
introText.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.introText.text")); // NOI18N
|
||||
introText.setWrapStyleWord(true);
|
||||
introText.setFocusable(false);
|
||||
introTextScrollPane.setViewportView(introText);
|
||||
|
||||
mainSplitPane.setDividerLocation(400);
|
||||
|
||||
searchField.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchField.text")); // NOI18N
|
||||
|
||||
searchButtonGroup.add(searchNameRadio);
|
||||
@ -289,6 +374,8 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
searchButtonGroup.add(searchAccountRadio);
|
||||
org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchAccountRadio.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N
|
||||
|
||||
resultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.toolTipText")); // NOI18N
|
||||
resultsTable.getTableHeader().setReorderingAllowed(false);
|
||||
resultsPane.setViewportView(resultsTable);
|
||||
@ -298,18 +385,23 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
resultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.columnModel.title1")); // NOI18N
|
||||
}
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(createAccountBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createAccountBtn.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createBtn.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(editBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.editBtn.text")); // NOI18N
|
||||
editBtn.setEnabled(false);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deleteBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.deleteBtn.text")); // NOI18N
|
||||
deleteBtn.setEnabled(false);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createBtn.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(cbFilterByKeyword, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.cbFilterByKeyword.text")); // NOI18N
|
||||
cbFilterByKeyword.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
cbFilterByKeywordActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel);
|
||||
searchPanel.setLayout(searchPanelLayout);
|
||||
searchPanelLayout.setHorizontalGroup(
|
||||
@ -317,13 +409,7 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jSeparator1)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(createBtn)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(editBtn)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(deleteBtn))
|
||||
.addComponent(createButtonSeparator)
|
||||
.addComponent(resultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
|
||||
.addComponent(searchField)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
@ -333,14 +419,23 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(searchBtn))
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(createAccountBtn)
|
||||
.addGap(0, 0, Short.MAX_VALUE)))
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(createAccountBtn)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addComponent(createBtn)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(editBtn)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deleteBtn))
|
||||
.addComponent(cbFilterByKeyword))
|
||||
.addGap(0, 50, Short.MAX_VALUE)))
|
||||
.addContainerGap())
|
||||
);
|
||||
searchPanelLayout.setVerticalGroup(
|
||||
searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(searchPanelLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(cbFilterByKeyword)
|
||||
.addGap(1, 1, 1)
|
||||
.addComponent(searchField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
@ -348,42 +443,57 @@ public final class PersonasTopComponent extends TopComponent {
|
||||
.addComponent(searchAccountRadio)
|
||||
.addComponent(searchBtn))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(resultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 457, Short.MAX_VALUE)
|
||||
.addComponent(resultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 302, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(editBtn)
|
||||
.addComponent(createBtn)
|
||||
.addComponent(deleteBtn))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(createButtonSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(createAccountBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
jSplitPane1.setLeftComponent(searchPanel);
|
||||
jSplitPane1.setRightComponent(detailsPanel);
|
||||
mainSplitPane.setLeftComponent(searchPanel);
|
||||
|
||||
detailsScrollPane.setViewportView(detailsPanel);
|
||||
|
||||
mainSplitPane.setRightComponent(detailsScrollPane);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 794, Short.MAX_VALUE)
|
||||
.addComponent(introTextScrollPane)
|
||||
.addComponent(mainSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 724, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jSplitPane1)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(introTextScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 49, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(mainSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 489, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void cbFilterByKeywordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbFilterByKeywordActionPerformed
|
||||
setKeywordSearchEnabled(cbFilterByKeyword.isSelected(), false);
|
||||
}//GEN-LAST:event_cbFilterByKeywordActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JCheckBox cbFilterByKeyword;
|
||||
private javax.swing.JButton createAccountBtn;
|
||||
private javax.swing.JButton createBtn;
|
||||
private javax.swing.JSeparator createButtonSeparator;
|
||||
private javax.swing.JButton deleteBtn;
|
||||
private org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel detailsPanel;
|
||||
private javax.swing.JScrollPane detailsScrollPane;
|
||||
private javax.swing.JButton editBtn;
|
||||
private javax.swing.JSeparator jSeparator1;
|
||||
private javax.swing.JSplitPane jSplitPane1;
|
||||
private javax.swing.JTextArea introText;
|
||||
private javax.swing.JScrollPane introTextScrollPane;
|
||||
private javax.swing.JSplitPane mainSplitPane;
|
||||
private javax.swing.JScrollPane resultsPane;
|
||||
private javax.swing.JTable resultsTable;
|
||||
private javax.swing.JRadioButton searchAccountRadio;
|
||||
|
@ -862,7 +862,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
|
||||
/**
|
||||
* If the settings reflect that a inter-case search is being performed,
|
||||
* checks that the data sources in the current case have been processed with
|
||||
* Correlation Engine enabled and exist in the central repository. Prompting
|
||||
* Central Repository enabled and exist in the central repository. Prompting
|
||||
* the user as to whether they still want to perform the search in the case
|
||||
* any data sources are unprocessed. If the settings reflect that a
|
||||
* intra-case search is being performed, it just performs the search.
|
||||
@ -870,7 +870,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
|
||||
* Notes: - Does not check that the data sources were processed into the
|
||||
* current central repository instead of another. - Does not check that the
|
||||
* appropriate modules to make all correlation types available were run. -
|
||||
* Does not check if the correlation engine was run with any of the
|
||||
* Does not check if the Central Repository was run with any of the
|
||||
* correlation properties properties disabled.
|
||||
*/
|
||||
@Messages({"CommonAttributePanel.incompleteResults.introText=Results may be incomplete. Not all data sources in the current case were ingested into the current Central Repository. The following data sources have not been processed:",
|
||||
@ -902,14 +902,14 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
|
||||
//if the datasource was previously processed we do not need to perform this check
|
||||
for (CorrelationDataSource correlatedDataSource : correlatedDataSources) {
|
||||
if (deviceID.equals(correlatedDataSource.getDeviceID())) {
|
||||
//if the datasource exists in the central repository it may of been processed with the correlation engine
|
||||
//if the datasource exists in the central repository it may of been processed with the Central Repository
|
||||
dataSourceCorrelationMap.put(dataSource, CorrelatedStatus.IN_CENTRAL_REPO);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataSourceCorrelationMap.get(dataSource) == CorrelatedStatus.IN_CENTRAL_REPO) {
|
||||
//if the data source was in the central repository check if any of the modules run on it were the correlation engine
|
||||
//if the data source was in the central repository check if any of the modules run on it were the Central Repository
|
||||
for (IngestModuleInfo ingestModuleInfo : jobInfo.getIngestModuleInfo()) {
|
||||
if (correlationEngineModuleName.equals(ingestModuleInfo.getDisplayName())) {
|
||||
dataSourceCorrelationMap.put(dataSource, CorrelatedStatus.CORRELATED);
|
||||
|
@ -14,7 +14,7 @@ FiltersPanel.endCheckBox.text=End:
|
||||
FiltersPanel.refreshButton.text=Refresh
|
||||
FiltersPanel.deviceRequiredLabel.text=Select at least one.
|
||||
FiltersPanel.accountTypeRequiredLabel.text=Select at least one.
|
||||
FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh.
|
||||
FiltersPanel.needsRefreshLabel.text=Displayed data may be out of date. Press Refresh to update.
|
||||
VisualizationPanel.jButton1.text=Fast Organic
|
||||
CVTTopComponent.vizPanel.TabConstraints.tabTitle=Visualize
|
||||
CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1=Browse
|
||||
|
@ -26,7 +26,7 @@ FiltersPanel.endCheckBox.text=End:
|
||||
FiltersPanel.refreshButton.text=Refresh
|
||||
FiltersPanel.deviceRequiredLabel.text=Select at least one.
|
||||
FiltersPanel.accountTypeRequiredLabel.text=Select at least one.
|
||||
FiltersPanel.needsRefreshLabel.text=Displayed data is out of date. Press Refresh.
|
||||
FiltersPanel.needsRefreshLabel.text=Displayed data may be out of date. Press Refresh to update.
|
||||
OpenCVTAction.displayName=Communications
|
||||
PinAccountsAction.pluralText=Add Selected Accounts to Visualization
|
||||
PinAccountsAction.singularText=Add Selected Account to Visualization
|
||||
|
161
Core/src/org/sleuthkit/autopsy/communications/CVTFilterRefresher.java
Executable file
161
Core/src/org/sleuthkit/autopsy/communications/CVTFilterRefresher.java
Executable file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
|
||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Refreshes the CVTFilterPanel.
|
||||
*/
|
||||
abstract class CVTFilterRefresher implements RefreshThrottler.Refresher {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CVTFilterRefresher.class.getName());
|
||||
/**
|
||||
* contains all of the gui control specific update code. Refresh will call
|
||||
* this method with an involkLater so that the updating of the swing
|
||||
* controls can happen on the EDT.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
abstract void updateFilterPanel(FilterPanelData data);
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
try {
|
||||
Integer startTime;
|
||||
Integer endTime;
|
||||
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
|
||||
// Fetch Min/Max start times
|
||||
try (SleuthkitCase.CaseDbQuery dbQuery = skCase.executeQuery("SELECT MAX(date_time) as end, MIN(date_time) as start from account_relationships")) {
|
||||
// ResultSet is closed by CasDBQuery
|
||||
ResultSet rs = dbQuery.getResultSet();
|
||||
startTime = rs.getInt("start"); // NON-NLS
|
||||
endTime = rs.getInt("end"); // NON-NLS
|
||||
}
|
||||
// Get the devices with CVT artifacts
|
||||
List<Integer> deviceObjIds = new ArrayList<>();
|
||||
try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery("SELECT DISTINCT data_source_obj_id FROM account_relationships")) {
|
||||
// ResultSet is closed by CasDBQuery
|
||||
ResultSet rs = queryResult.getResultSet();
|
||||
while (rs.next()) {
|
||||
deviceObjIds.add(rs.getInt(1));
|
||||
}
|
||||
}
|
||||
|
||||
// The map key is the Content name instead of the data source name
|
||||
// to match how the CVT filters work.
|
||||
Map<String, DataSource> dataSourceMap = new HashMap<>();
|
||||
for (DataSource dataSource : skCase.getDataSources()) {
|
||||
if (deviceObjIds.contains((int) dataSource.getId())) {
|
||||
String dsName = skCase.getContentById(dataSource.getId()).getName();
|
||||
dataSourceMap.put(dsName, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
List<Account.Type> accountTypesInUse = skCase.getCommunicationsManager().getAccountTypesInUse();
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateFilterPanel(new FilterPanelData(dataSourceMap, accountTypesInUse, startTime, endTime));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (SQLException | TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Unable to update CVT filter panel.", ex);
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(DATA_ADDED.toString())) {
|
||||
// Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
|
||||
return (null != eventData
|
||||
&& (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to hold the data for setting up the filter panel gui controls.
|
||||
*/
|
||||
class FilterPanelData {
|
||||
|
||||
private final Map<String, DataSource> dataSourceMap;
|
||||
private final Integer startTime;
|
||||
private final Integer endTime;
|
||||
private final List<Account.Type> accountTypesInUse;
|
||||
|
||||
FilterPanelData(Map<String, DataSource> dataSourceMap, List<Account.Type> accountTypesInUse, Integer startTime, Integer endTime) {
|
||||
this.dataSourceMap = dataSourceMap;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.accountTypesInUse = accountTypesInUse;
|
||||
}
|
||||
|
||||
Map<String, DataSource> getDataSourceMap() {
|
||||
return dataSourceMap;
|
||||
}
|
||||
|
||||
Integer getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
Integer getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
List<Account.Type> getAccountTypesInUse() {
|
||||
return accountTypesInUse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -189,7 +189,7 @@ public final class CVTTopComponent extends TopComponent {
|
||||
*
|
||||
* Re-applying the filters means we will lose the selection...
|
||||
*/
|
||||
filtersPane.updateAndApplyFilters(true);
|
||||
filtersPane.initalizeFilters();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -18,11 +18,11 @@
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="scrollPane">
|
||||
<Properties>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="autoscrolls" type="boolean" value="true"/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="null"/>
|
||||
</Property>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="autoscrolls" type="boolean" value="true"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
@ -222,9 +222,14 @@
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="devicesPane">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[300, 300]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="100" insetsTop="15" insetsLeft="0" insetsBottom="0" insetsRight="25" anchor="18" weightX="1.0" weightY="0.0"/>
|
||||
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="0" insetsBottom="0" insetsRight="25" anchor="18" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
@ -277,7 +282,6 @@
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="devicesScrollPane">
|
||||
<Properties>
|
||||
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[32767, 30]"/>
|
||||
</Property>
|
||||
|
@ -18,25 +18,24 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.communications;
|
||||
|
||||
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
@ -45,11 +44,9 @@ import javax.swing.ImageIcon;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
@ -59,7 +56,6 @@ import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
|
||||
@ -69,8 +65,6 @@ import org.sleuthkit.datamodel.DataSource;
|
||||
import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG;
|
||||
import static org.sleuthkit.datamodel.Relationship.Type.CONTACT;
|
||||
import static org.sleuthkit.datamodel.Relationship.Type.MESSAGE;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Panel that holds the Filter control widgets and triggers queries against the
|
||||
@ -114,6 +108,8 @@ final public class FiltersPanel extends JPanel {
|
||||
*/
|
||||
private final ItemListener validationListener;
|
||||
|
||||
private final RefreshThrottler refreshThrottler;
|
||||
|
||||
/**
|
||||
* Is the device account type filter enabled or not. It should be enabled
|
||||
* when the Table/Brows mode is active and disabled when the visualization
|
||||
@ -129,6 +125,7 @@ final public class FiltersPanel extends JPanel {
|
||||
initComponents();
|
||||
|
||||
initalizeDeviceAccountType();
|
||||
setDateTimeFiltersToDefault();
|
||||
|
||||
deviceRequiredLabel.setVisible(false);
|
||||
accountTypeRequiredLabel.setVisible(false);
|
||||
@ -160,25 +157,27 @@ final public class FiltersPanel extends JPanel {
|
||||
if (eventType.equals(DATA_ADDED.toString())) {
|
||||
// Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits
|
||||
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
|
||||
if (null != eventData
|
||||
if (!needsRefresh
|
||||
&& null != eventData
|
||||
&& (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()
|
||||
|| eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID())) {
|
||||
updateFilters(true);
|
||||
needsRefresh = true;
|
||||
validateFilters();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
refreshThrottler = new RefreshThrottler(new FilterPanelRefresher(false, false));
|
||||
|
||||
this.ingestJobListener = pce -> {
|
||||
String eventType = pce.getPropertyName();
|
||||
if (eventType.equals(COMPLETED.toString())
|
||||
&& updateFilters(true)) {
|
||||
if (eventType.equals(COMPLETED.toString()) && !needsRefresh) {
|
||||
|
||||
needsRefresh = true;
|
||||
validateFilters();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -220,39 +219,24 @@ final public class FiltersPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the filter widgets, and apply them.
|
||||
*/
|
||||
void updateAndApplyFilters(boolean initialState) {
|
||||
updateFilters(initialState);
|
||||
applyFilters();
|
||||
initalizeDateTimeFilters();
|
||||
void initalizeFilters() {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new FilterPanelRefresher(true, true).refresh();
|
||||
}
|
||||
};
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
private void updateTimeZone() {
|
||||
dateRangeLabel.setText("Date Range (" + Utils.getUserPreferredZoneId().toString() + "):");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the filter widgets to reflect he data sources/types in the case.
|
||||
*/
|
||||
private boolean updateFilters(boolean initialState) {
|
||||
final SleuthkitCase sleuthkitCase;
|
||||
try {
|
||||
sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "Unable to perform filter update, update has been cancelled. Case is closed.", ex);
|
||||
return false;
|
||||
}
|
||||
boolean newAccountType = updateAccountTypeFilter(initialState, sleuthkitCase);
|
||||
boolean newDeviceFilter = updateDeviceFilter(initialState, sleuthkitCase);
|
||||
// both or either are true, return true;
|
||||
return newAccountType || newDeviceFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
refreshThrottler.registerForIngestModuleEvents();
|
||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, ingestListener);
|
||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, ingestJobListener);
|
||||
Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> {
|
||||
@ -270,6 +254,7 @@ final public class FiltersPanel extends JPanel {
|
||||
@Override
|
||||
public void removeNotify() {
|
||||
super.removeNotify();
|
||||
refreshThrottler.unregisterEventListener();
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestListener);
|
||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||
}
|
||||
@ -283,33 +268,25 @@ final public class FiltersPanel extends JPanel {
|
||||
/**
|
||||
* Populate the Account Types filter widgets.
|
||||
*
|
||||
* @param selected The initial value for the account type checkbox.
|
||||
* @param sleuthkitCase The sleuthkit case for containing the account
|
||||
* information.
|
||||
* @param accountTypesInUse List of accountTypes currently in use
|
||||
*
|
||||
* @return True, if a new accountType was found
|
||||
*/
|
||||
private boolean updateAccountTypeFilter(boolean selected, SleuthkitCase sleuthkitCase) {
|
||||
private boolean updateAccountTypeFilter(List<Account.Type> accountTypesInUse, boolean checkNewOnes) {
|
||||
boolean newOneFound = false;
|
||||
try {
|
||||
List<Account.Type> accountTypesInUse = sleuthkitCase.getCommunicationsManager().getAccountTypesInUse();
|
||||
|
||||
for (Account.Type type : accountTypesInUse) {
|
||||
for (Account.Type type : accountTypesInUse) {
|
||||
if (!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)) {
|
||||
CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(type, checkNewOnes);
|
||||
accountTypeMap.put(type, panel.getCheckBox());
|
||||
accountTypeListPane.add(panel);
|
||||
|
||||
if (!accountTypeMap.containsKey(type) && !type.equals(Account.Type.CREDIT_CARD)) {
|
||||
CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(type, selected);
|
||||
accountTypeMap.put(type, panel.getCheckBox());
|
||||
accountTypeListPane.add(panel);
|
||||
|
||||
newOneFound = true;
|
||||
}
|
||||
newOneFound = true;
|
||||
}
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Unable to update to update Account Types Filter", ex);
|
||||
}
|
||||
|
||||
if (newOneFound) {
|
||||
accountTypeListPane.revalidate();
|
||||
accountTypeListPane.validate();
|
||||
}
|
||||
|
||||
return newOneFound;
|
||||
@ -343,32 +320,43 @@ final public class FiltersPanel extends JPanel {
|
||||
*
|
||||
* @return true if a new device was found
|
||||
*/
|
||||
private boolean updateDeviceFilter(boolean selected, SleuthkitCase sleuthkitCase) {
|
||||
private void updateDeviceFilterPanel(Map<String, DataSource> dataSourceMap, boolean checkNewOnes) {
|
||||
boolean newOneFound = false;
|
||||
try {
|
||||
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
|
||||
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
|
||||
if (devicesMap.containsKey(dataSource.getDeviceId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final JCheckBox jCheckBox = new JCheckBox(dsName, selected);
|
||||
jCheckBox.addItemListener(validationListener);
|
||||
devicesListPane.add(jCheckBox);
|
||||
devicesMap.put(dataSource.getDeviceId(), jCheckBox);
|
||||
|
||||
newOneFound = true;
|
||||
|
||||
for (Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
|
||||
if (devicesMap.containsKey(entry.getValue().getDeviceId())) {
|
||||
continue;
|
||||
}
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "There was a error loading the datasources for the case.", tskCoreException);
|
||||
|
||||
final JCheckBox jCheckBox = new JCheckBox(entry.getKey(), checkNewOnes);
|
||||
jCheckBox.addItemListener(validationListener);
|
||||
jCheckBox.setToolTipText(entry.getKey());
|
||||
devicesListPane.add(jCheckBox);
|
||||
devicesMap.put(entry.getValue().getDeviceId(), jCheckBox);
|
||||
|
||||
newOneFound = true;
|
||||
}
|
||||
|
||||
if (newOneFound) {
|
||||
devicesListPane.removeAll();
|
||||
List<JCheckBox> checkList = new ArrayList<>(devicesMap.values());
|
||||
checkList.sort(new DeviceCheckBoxComparator());
|
||||
|
||||
for (JCheckBox cb : checkList) {
|
||||
devicesListPane.add(cb);
|
||||
}
|
||||
|
||||
devicesListPane.revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
return newOneFound;
|
||||
private void updateDateTimePicker(Integer start, Integer end) {
|
||||
if (start != null && start != 0) {
|
||||
startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(start), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
|
||||
if (end != null && end != 0) {
|
||||
endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(end), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -477,9 +465,9 @@ final public class FiltersPanel extends JPanel {
|
||||
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.setAutoscrolls(true);
|
||||
scrollPane.setBorder(null);
|
||||
|
||||
mainPanel.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
@ -613,6 +601,7 @@ final public class FiltersPanel extends JPanel {
|
||||
gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 25);
|
||||
mainPanel.add(dateRangePane, gridBagConstraints);
|
||||
|
||||
devicesPane.setPreferredSize(new java.awt.Dimension(300, 300));
|
||||
devicesPane.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
unCheckAllDevicesButton.setText(org.openide.util.NbBundle.getMessage(FiltersPanel.class, "FiltersPanel.unCheckAllDevicesButton.text")); // NOI18N
|
||||
@ -652,7 +641,6 @@ final public class FiltersPanel extends JPanel {
|
||||
gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0);
|
||||
devicesPane.add(checkAllDevicesButton, gridBagConstraints);
|
||||
|
||||
devicesScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
devicesScrollPane.setMaximumSize(new java.awt.Dimension(32767, 30));
|
||||
devicesScrollPane.setMinimumSize(new java.awt.Dimension(27, 30));
|
||||
devicesScrollPane.setPreferredSize(new java.awt.Dimension(3, 30));
|
||||
@ -686,10 +674,10 @@ final public class FiltersPanel extends JPanel {
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 2;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.ipady = 100;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(15, 0, 0, 25);
|
||||
mainPanel.add(devicesPane, gridBagConstraints);
|
||||
|
||||
@ -836,10 +824,11 @@ final public class FiltersPanel extends JPanel {
|
||||
/**
|
||||
* Post an event with the new filters.
|
||||
*/
|
||||
private void applyFilters() {
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(getFilter(), getStartControlState(), getEndControlState()));
|
||||
void applyFilters() {
|
||||
needsRefresh = false;
|
||||
validateFilters();
|
||||
CVTEvents.getCVTEventBus().post(new CVTEvents.FilterChangeEvent(getFilter(), getStartControlState(), getEndControlState()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -958,31 +947,6 @@ final public class FiltersPanel extends JPanel {
|
||||
map.values().forEach(box -> box.setSelected(selected));
|
||||
}
|
||||
|
||||
/**
|
||||
* initalize the DateTimePickers by grabbing the earliest and latest time
|
||||
* from the autopsy db.
|
||||
*/
|
||||
private void initalizeDateTimeFilters() {
|
||||
Case currentCase = null;
|
||||
try {
|
||||
currentCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.INFO, "Tried to intialize communication filters date range filters without an open case, using default values");
|
||||
}
|
||||
|
||||
if (currentCase == null) {
|
||||
setDateTimeFiltersToDefault();
|
||||
openCase = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentCase.equals(openCase)) {
|
||||
setDateTimeFiltersToDefault();
|
||||
openCase = currentCase;
|
||||
(new DatePickerWorker()).execute();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDateTimeFiltersToDefault() {
|
||||
startDatePicker.setDate(LocalDate.now().minusWeeks(3));
|
||||
endDatePicker.setDate(LocalDate.now());
|
||||
@ -1159,69 +1123,51 @@ final public class FiltersPanel extends JPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class that implements CaseDbAccessQueryCallback. Can be used as
|
||||
* an anonymous innerclass with the CaseDbAccessManager select function.
|
||||
* Extends the CVTFilterRefresher abstract class to add the calls to update
|
||||
* the ui controls with the data found. Note that updateFilterPanel is run
|
||||
* in the EDT.
|
||||
*/
|
||||
class FilterPanelQueryCallback implements CaseDbAccessQueryCallback {
|
||||
final class FilterPanelRefresher extends CVTFilterRefresher {
|
||||
|
||||
private final boolean selectNewOption;
|
||||
private final boolean refreshAfterUpdate;
|
||||
|
||||
FilterPanelRefresher(boolean selectNewOptions, boolean refreshAfterUpdate) {
|
||||
this.selectNewOption = selectNewOptions;
|
||||
this.refreshAfterUpdate = refreshAfterUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
// Subclasses can implement their own process function.
|
||||
void updateFilterPanel(CVTFilterRefresher.FilterPanelData data) {
|
||||
updateDateTimePicker(data.getStartTime(), data.getEndTime());
|
||||
updateDeviceFilterPanel(data.getDataSourceMap(), selectNewOption);
|
||||
updateAccountTypeFilter(data.getAccountTypesInUse(), selectNewOption);
|
||||
|
||||
FiltersPanel.this.repaint();
|
||||
|
||||
if (refreshAfterUpdate) {
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
if (!isEnabled()) {
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
validateFilters();
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
final class DatePickerWorker extends SwingWorker<Map<String, Integer>, Void> {
|
||||
/**
|
||||
* Sorts a list of JCheckBoxes in alphabetical order of the text field
|
||||
* value.
|
||||
*/
|
||||
class DeviceCheckBoxComparator implements Comparator<JCheckBox> {
|
||||
|
||||
@Override
|
||||
protected Map<String, Integer> doInBackground() throws Exception {
|
||||
if (openCase == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Integer> resultMap = new HashMap<>();
|
||||
String queryString = "max(date_time) as end, min(date_time) as start from account_relationships"; // NON-NLS
|
||||
|
||||
openCase.getSleuthkitCase().getCaseDbAccessManager().select(queryString, new FilterPanelQueryCallback() {
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
try {
|
||||
if (rs.next()) {
|
||||
int startDate = rs.getInt("start"); // NON-NLS
|
||||
int endDate = rs.getInt("end"); // NON-NLS
|
||||
|
||||
resultMap.put("start", startDate); // NON-NLS
|
||||
resultMap.put("end", endDate); // NON-NLS
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
// Not the end of the world if this fails.
|
||||
logger.log(Level.WARNING, String.format("SQL Exception thrown from Query: %s", queryString), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
Map<String, Integer> resultMap = get();
|
||||
if (resultMap != null) {
|
||||
Integer start = resultMap.get("start");
|
||||
Integer end = resultMap.get("end");
|
||||
|
||||
if (start != null && start != 0) {
|
||||
startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(start), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
|
||||
if (end != null && end != 0) {
|
||||
endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(end), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
logger.log(Level.WARNING, "Exception occured after date time sql query", ex);
|
||||
}
|
||||
public int compare(JCheckBox e1, JCheckBox e2) {
|
||||
return e1.getText().toLowerCase().compareTo(e2.getText().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
|
||||
/**
|
||||
@ -113,7 +114,7 @@ class AccountSummary {
|
||||
isReference = true;
|
||||
break;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
logger.log(Level.WARNING, String.format("Exception thrown "
|
||||
+ "in trying to normalize attribute value: %s",
|
||||
attributeValue), ex); //NON-NLS
|
||||
|
@ -17,7 +17,7 @@ ContactsViewer_columnHeader_Phone=Phone
|
||||
ContactsViewer_noContacts_message=<No contacts found for selected account>
|
||||
ContactsViewer_tabTitle=Contacts
|
||||
MediaViewer_Name=Media Attachments
|
||||
MessageNode_Node_Property_Attms=Attachments
|
||||
MessageNode_Node_Property_Attms=Attachment Count
|
||||
MessageNode_Node_Property_Date=Date
|
||||
MessageNode_Node_Property_From=From
|
||||
MessageNode_Node_Property_Subject=Subject
|
||||
|
@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.communications.relationships;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.contentviewers.CallLogArtifactViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.artifactviewers.CallLogArtifactViewer;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
|
@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.communications.relationships;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.contentviewers.ContactArtifactViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.artifactviewers.ContactArtifactViewer;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
|
@ -25,7 +25,7 @@ import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.contentviewers.MessageArtifactViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.artifactviewers.MessageArtifactViewer;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
@ -48,22 +48,22 @@ import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
|
||||
class MessageNode extends BlackboardArtifactNode {
|
||||
|
||||
public static final String UNTHREADED_ID = "<UNTHREADED>";
|
||||
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MessageNode.class.getName());
|
||||
|
||||
|
||||
private final String threadID;
|
||||
|
||||
|
||||
private final Action preferredAction;
|
||||
|
||||
MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) {
|
||||
MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) {
|
||||
super(artifact);
|
||||
|
||||
|
||||
this.preferredAction = preferredAction;
|
||||
|
||||
final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); // NON-NLS
|
||||
String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); // NON-NLS
|
||||
setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase);
|
||||
|
||||
|
||||
this.threadID = threadID;
|
||||
}
|
||||
|
||||
@ -73,12 +73,12 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
"MessageNode_Node_Property_To=To",
|
||||
"MessageNode_Node_Property_Date=Date",
|
||||
"MessageNode_Node_Property_Subject=Subject",
|
||||
"MessageNode_Node_Property_Attms=Attachments"
|
||||
"MessageNode_Node_Property_Attms=Attachment Count"
|
||||
})
|
||||
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet sheet = super.createSheet();
|
||||
Sheet sheet = Sheet.createDefault();
|
||||
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
|
||||
if (sheetSet == null) {
|
||||
sheetSet = Sheet.createPropertiesSet();
|
||||
@ -89,42 +89,45 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
|
||||
final BlackboardArtifact artifact = getArtifact();
|
||||
BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID());
|
||||
|
||||
if(fromID == null ||
|
||||
(fromID != TSK_EMAIL_MSG &&
|
||||
fromID != TSK_MESSAGE)) {
|
||||
|
||||
if (fromID == null
|
||||
|| (fromID != TSK_EMAIL_MSG
|
||||
&& fromID != TSK_MESSAGE)) {
|
||||
return sheet;
|
||||
}
|
||||
|
||||
sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? UNTHREADED_ID : threadID)); //NON-NLS
|
||||
if (threadID != null) {
|
||||
sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID", "", threadID)); //NON-NLS
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "",
|
||||
getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS
|
||||
getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS
|
||||
try {
|
||||
sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", getAttachmentsCount())); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS
|
||||
}
|
||||
|
||||
switch (fromID) {
|
||||
case TSK_EMAIL_MSG:
|
||||
sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "",
|
||||
StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "",
|
||||
StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "",
|
||||
getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS
|
||||
break;
|
||||
case TSK_MESSAGE:
|
||||
sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "",
|
||||
getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "",
|
||||
getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "",
|
||||
getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
String msg_from = getAttributeDisplayString(artifact, TSK_EMAIL_FROM);
|
||||
String msg_to = getAttributeDisplayString(artifact, TSK_EMAIL_TO);
|
||||
String date = getAttributeDisplayString(artifact, TSK_DATETIME_SENT);
|
||||
|
||||
if (msg_from.isEmpty()) {
|
||||
msg_from = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM);
|
||||
|
||||
}
|
||||
if (msg_to.isEmpty()) {
|
||||
msg_to = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO);
|
||||
}
|
||||
if (date.isEmpty()) {
|
||||
date = getAttributeDisplayString(artifact, TSK_DATETIME);
|
||||
}
|
||||
|
||||
sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "",
|
||||
msg_from)); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "",
|
||||
msg_to)); //NON-NLS
|
||||
sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "",
|
||||
date)); //NON-NLS
|
||||
|
||||
return sheet;
|
||||
}
|
||||
|
||||
@ -138,16 +141,16 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
public String getSourceName() {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
|
||||
String getThreadID() {
|
||||
return threadID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Action getPreferredAction() {
|
||||
return preferredAction;
|
||||
}
|
||||
|
||||
|
||||
private int getAttachmentsCount() throws TskCoreException {
|
||||
final BlackboardArtifact artifact = getArtifact();
|
||||
int attachmentsCount;
|
||||
@ -158,8 +161,7 @@ class MessageNode extends BlackboardArtifactNode {
|
||||
try {
|
||||
MessageAttachments msgAttachments = BlackboardJsonAttrUtil.fromAttribute(attachmentsAttr, MessageAttachments.class);
|
||||
return msgAttachments.getAttachmentsCount();
|
||||
}
|
||||
catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
|
||||
} catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to parse json for MessageAttachments object in artifact: %s", artifact.getName()), ex);
|
||||
return 0;
|
||||
}
|
||||
|
@ -939,45 +939,4 @@ manager.properties.lafError =\
|
||||
manager.properties.brokenProperty = Broken default property {0} value: {1}
|
||||
|
||||
manager.properties.missingProperty = Missing default property {0} value: {1}
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
ContactArtifactViewer.contactNameLabel.text=Joanna Doe
|
||||
ContactArtifactViewer.phonesLabel.text=Phone
|
||||
ContactArtifactViewer.emailsLabel.text=Email
|
||||
ContactArtifactViewer.othersLabel.text=Other
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
CallLogArtifactViewer.dataSourceNameLabel.text=data source name
|
||||
CallLogArtifactViewer.jLabel2.text=Device Id
|
||||
CallLogArtifactViewer.deviceIdLabel.text=device id
|
||||
CallLogArtifactViewer.localAccountIdLabel.text=local account
|
||||
CallLogArtifactViewer.localAccountLabel.text=Local Account
|
||||
CallLogArtifactViewer.jLabel4.text=Data Source Name
|
||||
CallLogArtifactViewer.otherInfoLabel.text=Other Information
|
||||
CallLogArtifactViewer.sourceSectionLabel.text=Source
|
||||
CallLogArtifactViewer.callDetailsLabel.text=Call Details
|
||||
CallLogArtifactViewer.durationLabel.text=Duration....
|
||||
CallLogArtifactViewer.dateTimeLabel.text=Date/Time.....
|
||||
CallLogArtifactViewer.directionLabel.text=Direction
|
||||
CallLogArtifactViewer.onLabel.text=On
|
||||
CallLogArtifactViewer.callLabel.text=call
|
||||
MessageArtifactViewer.fromText.text=from address goes here
|
||||
MessageArtifactViewer.datetimeText.text=date goes here
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=Headers
|
||||
MessageArtifactViewer.fromLabel.text=From:
|
||||
MessageArtifactViewer.directionText.text=direction
|
||||
MessageArtifactViewer.subjectText.text=subject goes here
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=View in New Window
|
||||
MessageArtifactViewer.subjectLabel.text=Subject:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments
|
||||
MessageArtifactViewer.ccText.text=cc list goes here
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=to list goes here
|
||||
MessageArtifactViewer.toLabel.text=To:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
CallLogArtifactViewer.localAccountPersonaLabel.text=Persona
|
||||
CallLogArtifactViewer.localAccountPersonaNameLabel.text=jLabel1
|
||||
CallLogArtifactViewer.localAccountPersonaButton.text=jButton1
|
||||
ContactArtifactViewer.personasLabel.text=Personas
|
||||
MessageArtifactViewer.accountsTab.TabConstraints.tabTitle=Accounts
|
||||
|
||||
|
79
Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
Executable file → Normal file
79
Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
Executable file → Normal file
@ -35,36 +35,6 @@ AnnotationsContentViewer.title=Annotations
|
||||
AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.
|
||||
ApplicationContentViewer.title=Application
|
||||
ApplicationContentViewer.toolTip=Displays file contents.
|
||||
CallLogArtifactViewer_heading_metadata=Metadata
|
||||
CallLogArtifactViewer_heading_parties=Parties
|
||||
CallLogArtifactViewer_heading_Source=Source
|
||||
CallLogArtifactViewer_label_datasource=Data Source
|
||||
CallLogArtifactViewer_label_date=Date
|
||||
CallLogArtifactViewer_label_direction=Direction
|
||||
CallLogArtifactViewer_label_duration=Duration
|
||||
CallLogArtifactViewer_label_from=From
|
||||
CallLogArtifactViewer_label_to=To
|
||||
CallLogArtifactViewer_suffix_local=(Local)
|
||||
CallLogArtifactViewer_value_unknown=Unknown
|
||||
CommunicationArtifactViewerHelper_menuitem_copy=Copy
|
||||
CommunicationArtifactViewerHelper_persona_button_create=Create
|
||||
CommunicationArtifactViewerHelper_persona_button_view=View
|
||||
CommunicationArtifactViewerHelper_persona_label=Persona:
|
||||
CommunicationArtifactViewerHelper_persona_searching=Searching...
|
||||
CommunicationArtifactViewerHelper_persona_unknown=Unknown
|
||||
ContactArtifactViewer_missing_account_label=Missing Account:
|
||||
ContactArtifactViewer_persona_account_justification=Account found in Contact artifact
|
||||
ContactArtifactViewer_persona_button_new=Create
|
||||
ContactArtifactViewer_persona_button_view=View
|
||||
ContactArtifactViewer_persona_label=Persona
|
||||
ContactArtifactViewer_persona_searching=\ Searching...
|
||||
ContactArtifactViewer_persona_text_none=None found
|
||||
ContactArtifactViewer_persona_unknown=Unknown
|
||||
DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database
|
||||
DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database
|
||||
DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)
|
||||
DefaultArtifactContentViewer.attrsTableHeader.type=Type
|
||||
DefaultArtifactContentViewer.attrsTableHeader.value=Value
|
||||
FXVideoPanel.pauseButton.infoLabel.playbackErr=Unable to play video.
|
||||
FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
|
||||
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
|
||||
@ -115,11 +85,6 @@ MediaViewVideoPanel.progressLabel.text=00:00
|
||||
MediaViewVideoPanel.infoLabel.text=info
|
||||
MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0}
|
||||
|
||||
MessageAccountPanel_button_create_label=Create
|
||||
MessageAccountPanel_button_view_label=View
|
||||
MessageAccountPanel_persona_label=Persona:
|
||||
MessageAccountPanel_unknown_label=Unknown
|
||||
MessageArtifactViewer.AttachmentPanel.title=Attachments
|
||||
Metadata.nodeText.none=None
|
||||
Metadata.nodeText.truncated=(results truncated)
|
||||
Metadata.nodeText.unknown=Unknown
|
||||
@ -1062,46 +1027,4 @@ manager.properties.lafError =\
|
||||
manager.properties.brokenProperty = Broken default property {0} value: {1}
|
||||
|
||||
manager.properties.missingProperty = Missing default property {0} value: {1}
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
ContactArtifactViewer.contactNameLabel.text=Joanna Doe
|
||||
ContactArtifactViewer.phonesLabel.text=Phone
|
||||
ContactArtifactViewer.emailsLabel.text=Email
|
||||
ContactArtifactViewer.othersLabel.text=Other
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
CallLogArtifactViewer.dataSourceNameLabel.text=data source name
|
||||
CallLogArtifactViewer.jLabel2.text=Device Id
|
||||
CallLogArtifactViewer.deviceIdLabel.text=device id
|
||||
CallLogArtifactViewer.localAccountIdLabel.text=local account
|
||||
CallLogArtifactViewer.localAccountLabel.text=Local Account
|
||||
CallLogArtifactViewer.jLabel4.text=Data Source Name
|
||||
CallLogArtifactViewer.otherInfoLabel.text=Other Information
|
||||
CallLogArtifactViewer.sourceSectionLabel.text=Source
|
||||
CallLogArtifactViewer.callDetailsLabel.text=Call Details
|
||||
CallLogArtifactViewer.durationLabel.text=Duration....
|
||||
CallLogArtifactViewer.dateTimeLabel.text=Date/Time.....
|
||||
CallLogArtifactViewer.directionLabel.text=Direction
|
||||
CallLogArtifactViewer.onLabel.text=On
|
||||
CallLogArtifactViewer.callLabel.text=call
|
||||
MessageArtifactViewer.fromText.text=from address goes here
|
||||
MessageArtifactViewer.datetimeText.text=date goes here
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=Headers
|
||||
MessageArtifactViewer.fromLabel.text=From:
|
||||
MessageArtifactViewer.directionText.text=direction
|
||||
MessageArtifactViewer.subjectText.text=subject goes here
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=View in New Window
|
||||
MessageArtifactViewer.subjectLabel.text=Subject:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments
|
||||
MessageArtifactViewer.ccText.text=cc list goes here
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=to list goes here
|
||||
MessageArtifactViewer.toLabel.text=To:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
CallLogArtifactViewer.localAccountPersonaLabel.text=Persona
|
||||
CallLogArtifactViewer.localAccountPersonaNameLabel.text=jLabel1
|
||||
CallLogArtifactViewer.localAccountPersonaButton.text=jButton1
|
||||
ContactArtifactViewer.personasLabel.text=Personas
|
||||
MessageArtifactViewer.accountsTab.TabConstraints.tabTitle=Accounts
|
||||
ContactArtifactViewer.contactImage.text=
|
||||
|
||||
|
@ -149,20 +149,3 @@ MediaViewImagePanel.tagsMenu.text_1=\u30bf\u30b0\u30e1\u30cb\u30e5\u30fc
|
||||
SQLiteViewer.readTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
|
||||
# {0} - tableName
|
||||
SQLiteViewer.selectTable.errorText=\u6b21\u306e\u30c6\u30fc\u30d6\u30eb\u306e\u884c\u30ab\u30a6\u30f3\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc
|
||||
MessageArtifactViewer.fromText.text=\u9001\u4fe1\u5143\u30a2\u30c9\u30ec\u30b9\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.datetimeText.text=\u65e5\u4ed8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=\u30d8\u30c3\u30c0\u30fc
|
||||
MessageArtifactViewer.fromLabel.text=\u5dee\u51fa\u4eba:
|
||||
MessageArtifactViewer.directionText.text=\u9001\u53d7\u4fe1\u306e\u7a2e\u5225
|
||||
MessageArtifactViewer.subjectText.text=\u4ef6\u540d\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=\u65b0\u3057\u3044\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u8868\u793a
|
||||
MessageArtifactViewer.subjectLabel.text=\u4ef6\u540d:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb
|
||||
MessageArtifactViewer.ccText.text=\u5b9b\u5148\u306eCC\u30ea\u30b9\u30c8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=\u5b9b\u5148\u306eTO\u30ea\u30b9\u30c8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.toLabel.text=\u5b9b\u5148:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
|
@ -42,7 +42,7 @@ import org.w3c.dom.events.EventTarget;
|
||||
* A file content viewer for HTML files.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
final class HtmlPanel extends javax.swing.JPanel {
|
||||
public final class HtmlPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(HtmlPanel.class.getName());
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -54,7 +54,7 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
/**
|
||||
* Creates new form HtmlViewerPanel
|
||||
*/
|
||||
HtmlPanel() {
|
||||
public HtmlPanel() {
|
||||
initComponents();
|
||||
Platform.runLater(() -> {
|
||||
webView = new WebView();
|
||||
@ -83,7 +83,7 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
*
|
||||
* @param htmlText The HTML text to be applied to the text pane.
|
||||
*/
|
||||
void setHtmlText(String htmlText) {
|
||||
public void setHtmlText(String htmlText) {
|
||||
this.htmlText = htmlText;
|
||||
refresh();
|
||||
}
|
||||
@ -91,7 +91,7 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
/**
|
||||
* Clear the HTML in the text pane and disable the show/hide button.
|
||||
*/
|
||||
void reset() {
|
||||
public void reset() {
|
||||
Platform.runLater(() -> {
|
||||
webView.getEngine().loadContent("", TEXT_TYPE);
|
||||
});
|
||||
|
@ -131,6 +131,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
private double zoomRatio;
|
||||
private double rotation; // Can be 0, 90, 180, and 270.
|
||||
|
||||
private boolean autoResize = true; // Auto resize when the user changes the size
|
||||
// of the content viewer unless the user has used the zoom buttons.
|
||||
private static final double[] ZOOM_STEPS = {
|
||||
0.0625, 0.125, 0.25, 0.375, 0.5, 0.75,
|
||||
1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10};
|
||||
@ -197,7 +199,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
imageTaggingOptions.add(exportTagsMenuItem);
|
||||
|
||||
imageTaggingOptions.setPopupSize(300, 150);
|
||||
|
||||
|
||||
//Disable image tagging for non-windows users or upon failure to load OpenCV.
|
||||
if (!PlatformUtil.isWindowsOS() || !OpenCvLoader.openCvIsLoaded()) {
|
||||
tagsMenu.setEnabled(false);
|
||||
@ -266,8 +268,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
|
||||
/**
|
||||
* Handle tags menu item enabling and disabling given the state of the
|
||||
* content viewer. For example, when the tags group is empty (no tags on image),
|
||||
* disable delete menu item, hide menu item, and export menu item.
|
||||
* content viewer. For example, when the tags group is empty (no tags on
|
||||
* image), disable delete menu item, hide menu item, and export menu item.
|
||||
*/
|
||||
private void subscribeTagMenuItemsToStateChanges() {
|
||||
pcs.addPropertyChangeListener((event) -> {
|
||||
@ -394,6 +396,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
}
|
||||
|
||||
try {
|
||||
autoResize = true;
|
||||
Image fxImage = readImageTask.get();
|
||||
masterGroup.getChildren().clear();
|
||||
tagsGroup.getChildren().clear();
|
||||
@ -464,7 +467,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* current file.
|
||||
*
|
||||
* @param contentTags
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
@ -488,7 +493,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* appropriate type.
|
||||
*
|
||||
* @param contentTags
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws NoCurrentCaseException
|
||||
*/
|
||||
@ -685,16 +692,21 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void rotateLeftButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rotateLeftButtonActionPerformed
|
||||
autoResize = false;
|
||||
|
||||
rotation = (rotation + 270) % 360;
|
||||
updateView();
|
||||
}//GEN-LAST:event_rotateLeftButtonActionPerformed
|
||||
|
||||
private void rotateRightButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rotateRightButtonActionPerformed
|
||||
autoResize = false;
|
||||
|
||||
rotation = (rotation + 90) % 360;
|
||||
updateView();
|
||||
}//GEN-LAST:event_rotateRightButtonActionPerformed
|
||||
|
||||
private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed
|
||||
autoResize = false;
|
||||
// Find the next zoom step.
|
||||
for (int i = 0; i < ZOOM_STEPS.length; i++) {
|
||||
if (zoomRatio < ZOOM_STEPS[i]) {
|
||||
@ -706,6 +718,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
}//GEN-LAST:event_zoomInButtonActionPerformed
|
||||
|
||||
private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed
|
||||
autoResize = false;
|
||||
// Find the next zoom step.
|
||||
for (int i = ZOOM_STEPS.length - 1; i >= 0; i--) {
|
||||
if (zoomRatio > ZOOM_STEPS[i]) {
|
||||
@ -717,11 +730,16 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
}//GEN-LAST:event_zoomOutButtonActionPerformed
|
||||
|
||||
private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomResetButtonActionPerformed
|
||||
autoResize = true;
|
||||
resetView();
|
||||
}//GEN-LAST:event_zoomResetButtonActionPerformed
|
||||
|
||||
private void formComponentResized(java.awt.event.ComponentEvent evt) {//GEN-FIRST:event_formComponentResized
|
||||
updateView();
|
||||
if (autoResize) {
|
||||
resetView();
|
||||
} else {
|
||||
updateView();
|
||||
}
|
||||
}//GEN-LAST:event_formComponentResized
|
||||
|
||||
/**
|
||||
@ -801,6 +819,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
* Creates an ImageTag instance from the ContentViewerTag.
|
||||
*
|
||||
* @param contentViewerTag
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ImageTag buildImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag) {
|
||||
@ -883,17 +902,17 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
|
||||
List<ContentTag> tags = Case.getCurrentCase().getServices()
|
||||
.getTagsManager().getContentTagsByContent(file);
|
||||
List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
|
||||
|
||||
|
||||
//Pull out image tag regions
|
||||
Collection<ImageTagRegion> regions = contentViewerTags.stream()
|
||||
.map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
|
||||
|
||||
|
||||
//Apply tags to image and write to file
|
||||
BufferedImage taggedImage = ImageTagsUtil.getImageWithTags(file, regions);
|
||||
Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
|
||||
FilenameUtils.getBaseName(file.getName()) + "-with_tags.png"); //NON-NLS
|
||||
ImageIO.write(taggedImage, "png", output.toFile());
|
||||
|
||||
|
||||
JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
|
||||
} catch (Exception ex) { //Runtime exceptions may spill out of ImageTagsUtil from JavaFX.
|
||||
//This ensures we (devs and users) have something when it doesn't work.
|
||||
|
@ -37,14 +37,14 @@ import org.sleuthkit.autopsy.texttranslation.ui.TranslateTextTask;
|
||||
/**
|
||||
* This is a panel for translation with a subcomponent that allows for translation.
|
||||
*/
|
||||
class TranslatablePanel extends JPanel {
|
||||
public class TranslatablePanel extends JPanel {
|
||||
|
||||
/**
|
||||
* This is an exception that can occur during the normal operation of the translatable
|
||||
* panel. For instance, this exception can be thrown if it is not possible to set the child
|
||||
* content to the provided content string.
|
||||
*/
|
||||
class TranslatablePanelException extends Exception {
|
||||
public class TranslatablePanelException extends Exception {
|
||||
public static final long serialVersionUID = 1L;
|
||||
|
||||
TranslatablePanelException(String message) {
|
||||
@ -61,7 +61,7 @@ class TranslatablePanel extends JPanel {
|
||||
* This describes a child component to be placed as a child of this panel. The child received
|
||||
* from {@link #getRootComponent() getRootComponent() } will listen for content updates from setContent().
|
||||
*/
|
||||
interface ContentComponent {
|
||||
public interface ContentComponent {
|
||||
/**
|
||||
* This method gets root component of the translation panel.
|
||||
* @return the root component to insert into the translatable panel
|
||||
@ -191,7 +191,7 @@ class TranslatablePanel extends JPanel {
|
||||
|
||||
@Messages({"TranslatablePanel.comboBoxOption.originalText=Original Text",
|
||||
"TranslatablePanel.comboBoxOption.translatedText=Translated Text"})
|
||||
TranslatablePanel(ContentComponent contentComponent) {
|
||||
public TranslatablePanel(ContentComponent contentComponent) {
|
||||
this(
|
||||
contentComponent,
|
||||
Bundle.TranslatablePanel_comboBoxOption_originalText(),
|
||||
@ -263,7 +263,7 @@ class TranslatablePanel extends JPanel {
|
||||
* This resets the component to an empty state and sets the translation bar visibility
|
||||
* based on whether there is a provider.
|
||||
*/
|
||||
final void reset() {
|
||||
public final void reset() {
|
||||
setContent(null, null);
|
||||
}
|
||||
|
||||
@ -272,7 +272,7 @@ class TranslatablePanel extends JPanel {
|
||||
* @param content the content for the panel
|
||||
* @param contentDescriptor the content descriptor to be used in error messages
|
||||
*/
|
||||
void setContent(String content, String contentDescriptor) {
|
||||
public void setContent(String content, String contentDescriptor) {
|
||||
cancelPendingTranslation();
|
||||
setTranslationEnabled();
|
||||
this.translateComboBox.setSelectedIndex(0);
|
||||
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
@ -0,0 +1,34 @@
|
||||
# Copyright 2020 Basis Technology Corp.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=to list goes here
|
||||
MessageArtifactViewer.toLabel.text=To:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
MessageArtifactViewer.fromText.text=from address goes here
|
||||
MessageArtifactViewer.datetimeText.text=date goes here
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=Headers
|
||||
MessageArtifactViewer.fromLabel.text=From:
|
||||
MessageArtifactViewer.accountsTab.TabConstraints.tabTitle=Accounts
|
||||
MessageArtifactViewer.directionText.text=direction
|
||||
MessageArtifactViewer.subjectText.text=subject goes here
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=View in New Window
|
||||
MessageArtifactViewer.subjectLabel.text=Subject:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments
|
||||
MessageArtifactViewer.ccText.text=cc list goes here
|
||||
MessageArtifactViewer.textbodyScrollPane.TabConstraints.tabTitle=Text
|
@ -0,0 +1,86 @@
|
||||
# Copyright 2020 Basis Technology Corp.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
CalllogArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas.
|
||||
CallLogArtifactViewer_heading_metadata=Metadata
|
||||
CallLogArtifactViewer_heading_others=Other Attributes
|
||||
CallLogArtifactViewer_heading_parties=Parties
|
||||
CallLogArtifactViewer_heading_Source=Source
|
||||
CallLogArtifactViewer_label_datasource=Data Source
|
||||
CallLogArtifactViewer_label_date=Date
|
||||
CallLogArtifactViewer_label_direction=Direction
|
||||
CallLogArtifactViewer_label_duration=Duration
|
||||
CallLogArtifactViewer_label_from=From
|
||||
CallLogArtifactViewer_label_to=To
|
||||
CallLogArtifactViewer_suffix_local=(Local)
|
||||
CallLogArtifactViewer_value_unknown=Unknown
|
||||
CommunicationArtifactViewerHelper_menuitem_copy=Copy
|
||||
CommunicationArtifactViewerHelper_persona_button_create=Create
|
||||
CommunicationArtifactViewerHelper_persona_button_view=View
|
||||
CommunicationArtifactViewerHelper_persona_label=Persona:
|
||||
CommunicationArtifactViewerHelper_persona_searching=Searching...
|
||||
CommunicationArtifactViewerHelper_persona_unknown=Unknown
|
||||
ContactArtifactViewer.contactImage.text=
|
||||
ContactArtifactViewer_contactname_unknown=Unknown
|
||||
ContactArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas.
|
||||
ContactArtifactViewer_emails_header=Email
|
||||
ContactArtifactViewer_found_all_accounts_label=All accounts found.
|
||||
ContactArtifactViewer_heading_Source=Source
|
||||
ContactArtifactViewer_label_datasource=Data Source
|
||||
ContactArtifactViewer_missing_account_label=Missing contact account
|
||||
ContactArtifactViewer_others_header=Other
|
||||
ContactArtifactViewer_persona_account_justification=Account found in Contact artifact
|
||||
ContactArtifactViewer_persona_button_new=Create
|
||||
ContactArtifactViewer_persona_button_view=View
|
||||
ContactArtifactViewer_persona_header=Persona
|
||||
ContactArtifactViewer_persona_label=Persona
|
||||
ContactArtifactViewer_persona_match_num=Match
|
||||
ContactArtifactViewer_persona_no_match=No matches found
|
||||
ContactArtifactViewer_persona_searching=Searching...
|
||||
ContactArtifactViewer_persona_unknown=Unknown
|
||||
ContactArtifactViewer_phones_header=Phone
|
||||
ContactArtifactViewer_plural_suffix=s
|
||||
DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or all attributes from case database
|
||||
DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database
|
||||
DefaultArtifactContentViewer.attrsTableHeader.sources=Source(s)
|
||||
DefaultArtifactContentViewer.attrsTableHeader.type=Type
|
||||
DefaultArtifactContentViewer.attrsTableHeader.value=Value
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=Copy
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
|
||||
MessageAccountPanel_button_create_label=Create
|
||||
MessageAccountPanel_button_view_label=View
|
||||
MessageAccountPanel_no_matches=No matches found.
|
||||
MessageAccountPanel_persona_label=Persona:
|
||||
MessageAccountPanel_unknown_label=Unknown
|
||||
MessageArtifactViewer.AttachmentPanel.title=Attachments
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=to list goes here
|
||||
MessageArtifactViewer.toLabel.text=To:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
MessageArtifactViewer.fromText.text=from address goes here
|
||||
MessageArtifactViewer.datetimeText.text=date goes here
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=Headers
|
||||
MessageArtifactViewer.fromLabel.text=From:
|
||||
MessageArtifactViewer.accountsTab.TabConstraints.tabTitle=Accounts
|
||||
MessageArtifactViewer.directionText.text=direction
|
||||
MessageArtifactViewer.subjectText.text=subject goes here
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=View in New Window
|
||||
MessageArtifactViewer.subjectLabel.text=Subject:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments
|
||||
MessageArtifactViewer.ccText.text=cc list goes here
|
||||
MessageArtifactViewer.textbodyScrollPane.TabConstraints.tabTitle=Text
|
||||
# {0} - Persona count
|
||||
PersonaDisplayTask_persona_count_suffix=(1 of {0})
|
@ -0,0 +1,19 @@
|
||||
|
||||
DefaultArtifactContentViewer.selectAllMenuItem.text=\u3059\u3079\u3066\u3092\u9078\u629e
|
||||
|
||||
DefaultArtifactContentViewer.copyMenuItem.text=\u30b3\u30d4\u30fc
|
||||
MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle=RTF
|
||||
MessageArtifactViewer.toText.text=\u5b9b\u5148\u306eTO\u30ea\u30b9\u30c8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.toLabel.text=\u5b9b\u5148:
|
||||
MessageArtifactViewer.htmlPane.TabConstraints.tabTitle=HTML
|
||||
MessageArtifactViewer.fromText.text=\u9001\u4fe1\u5143\u30a2\u30c9\u30ec\u30b9\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.datetimeText.text=\u65e5\u4ed8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle=\u30d8\u30c3\u30c0\u30fc
|
||||
MessageArtifactViewer.fromLabel.text=\u5dee\u51fa\u4eba:
|
||||
MessageArtifactViewer.directionText.text=\u9001\u53d7\u4fe1\u306e\u7a2e\u5225
|
||||
MessageArtifactViewer.subjectText.text=\u4ef6\u540d\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.viewInNewWindowButton.text=\u65b0\u3057\u3044\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u8868\u793a
|
||||
MessageArtifactViewer.subjectLabel.text=\u4ef6\u540d:
|
||||
MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle=\u6dfb\u4ed8\u30d5\u30a1\u30a4\u30eb
|
||||
MessageArtifactViewer.ccText.text=\u5b9b\u5148\u306eCC\u30ea\u30b9\u30c8\u3092\u3053\u3053\u306b\u8868\u793a
|
||||
MessageArtifactViewer.ccLabel.text=CC:
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.GridBagConstraints;
|
||||
@ -27,7 +27,6 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JScrollPane;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.GridBagConstraints;
|
||||
@ -45,6 +45,7 @@ import javax.swing.SwingWorker;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
@ -56,8 +57,10 @@ import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
|
||||
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.CommunicationsManager;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@ -285,7 +288,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* section panel.
|
||||
*
|
||||
* @param sectionAttributesList List of attributes to display.
|
||||
* @param sectionLabel Section name label.
|
||||
* @param sectionHeader Section name label.
|
||||
* @param contactPanelLayout Panel layout.
|
||||
* @param contactPanelConstraints Layout constraints.
|
||||
*
|
||||
@ -323,11 +326,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off a search for personas, based in the given list of attributes.
|
||||
* Initiates a search for Personas for the accounts associated with the
|
||||
* Contact.
|
||||
*
|
||||
* @param accountAttributesList a list of account identifying attributes.
|
||||
*
|
||||
* @throws CentralRepoException
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"ContactArtifactViewer_persona_header=Persona",
|
||||
@ -335,12 +336,6 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
"ContactArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas.",
|
||||
"ContactArtifactViewer_persona_unknown=Unknown"
|
||||
})
|
||||
|
||||
/**
|
||||
* Initiates a search for Personas for the accounts associated with the
|
||||
* Contact.
|
||||
*
|
||||
*/
|
||||
private void initiatePersonasSearch() {
|
||||
|
||||
// add a section header
|
||||
@ -362,7 +357,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
|
||||
if (CentralRepository.isEnabled()) {
|
||||
// Kick off a background task to serach for personas for the contact
|
||||
personaSearchTask = new ContactPersonaSearcherTask(accountAttributesList);
|
||||
personaSearchTask = new ContactPersonaSearcherTask(contactArtifact);
|
||||
personaSearchTask.execute();
|
||||
} else {
|
||||
personaHeader.setEnabled(false);
|
||||
@ -422,6 +417,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* Displays the given persona in the persona panel.
|
||||
*
|
||||
* @param persona Persona to display.
|
||||
* @param matchNumber Number of matches.
|
||||
* @param missingAccountsList List of contact accounts this persona may be
|
||||
* missing.
|
||||
* @param gridBagLayout Layout to use.
|
||||
@ -611,7 +607,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
*/
|
||||
private class ContactPersonaSearcherTask extends SwingWorker<Map<Persona, ArrayList<CentralRepoAccount>>, Void> {
|
||||
|
||||
private final List<BlackboardAttribute> accountAttributesList;
|
||||
private final BlackboardArtifact artifact;
|
||||
private final List<CentralRepoAccount> uniqueAccountsList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
@ -620,8 +616,8 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
* @param accountAttributesList List of attributes that may map to
|
||||
* accounts.
|
||||
*/
|
||||
ContactPersonaSearcherTask(List<BlackboardAttribute> accountAttributesList) {
|
||||
this.accountAttributesList = accountAttributesList;
|
||||
ContactPersonaSearcherTask(BlackboardArtifact artifact) {
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -629,26 +625,32 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac
|
||||
|
||||
Map<Persona, ArrayList<CentralRepoAccount>> uniquePersonas = new HashMap<>();
|
||||
|
||||
// TBD: this search needs to change to use the new method CommunicationsManager.getAccountsRelatedToArtifact
|
||||
for (BlackboardAttribute bba : accountAttributesList) {
|
||||
CommunicationsManager commManager = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager();
|
||||
List<Account> contactAccountsList = commManager.getAccountsRelatedToArtifact(artifact);
|
||||
|
||||
// Get account, add to accounts list
|
||||
Collection<Persona> personas;
|
||||
for (Account account : contactAccountsList) {
|
||||
if (isCancelled()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Collection<CentralRepoAccount> accountCandidates
|
||||
= CentralRepoAccount.getAccountsWithIdentifier(bba.getValueString());
|
||||
// make a list of all unique accounts for this contact
|
||||
if (!account.getAccountType().equals(Account.Type.DEVICE)) {
|
||||
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName());
|
||||
CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID());
|
||||
|
||||
if (accountCandidates.isEmpty() == false) {
|
||||
CentralRepoAccount account = accountCandidates.iterator().next();
|
||||
if (uniqueAccountsList.contains(account) == false) {
|
||||
uniqueAccountsList.add(account);
|
||||
if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) {
|
||||
uniqueAccountsList.add(crAccount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Collection<PersonaAccount> personaAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
|
||||
if (personaAccounts != null && !personaAccounts.isEmpty()) {
|
||||
// get personas for the account
|
||||
personas = PersonaAccount.getPersonaAccountsForAccount(account.getId())
|
||||
.stream()
|
||||
.map(PersonaAccount::getPersona)
|
||||
.collect(Collectors.toList());
|
||||
Collection<Persona> personas
|
||||
= personaAccounts
|
||||
.stream()
|
||||
.map(PersonaAccount::getPersona)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// make a list of unique personas, along with all their accounts
|
||||
for (Persona persona : personas) {
|
@ -11,14 +11,14 @@
|
||||
<MenuItem class="javax.swing.JMenuItem" name="copyMenuItem">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="DefaultArtifactContentViewer.copyMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="DefaultArtifactContentViewer.copyMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</MenuItem>
|
||||
<MenuItem class="javax.swing.JMenuItem" name="selectAllMenuItem">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="DefaultArtifactContentViewer.selectAllMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="DefaultArtifactContentViewer.selectAllMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</MenuItem>
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
@ -54,6 +54,7 @@ import com.google.gson.JsonArray;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import javax.swing.SwingUtilities;
|
||||
//import org.sleuthkit.autopsy.contentviewers.Bundle;
|
||||
|
||||
/**
|
||||
* This class displays a Blackboard artifact as a table listing all it's
|
@ -17,7 +17,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -126,26 +126,47 @@ final class MessageAccountPanel extends JPanel {
|
||||
return dataList;
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"MessageAccountPanel_no_matches=No matches found.",
|
||||
})
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
List<AccountContainer> dataList = get();
|
||||
|
||||
dataList.forEach(container -> {
|
||||
container.initalizeSwingControls();
|
||||
});
|
||||
if (!dataList.isEmpty()) {
|
||||
dataList.forEach(container -> {
|
||||
container.initalizeSwingControls();
|
||||
});
|
||||
|
||||
GroupLayout layout = new GroupLayout(MessageAccountPanel.this);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(getMainHorizontalGroup(layout, dataList))
|
||||
.addContainerGap(158, Short.MAX_VALUE)));
|
||||
GroupLayout layout = new GroupLayout(MessageAccountPanel.this);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(getMainHorizontalGroup(layout, dataList))
|
||||
.addContainerGap(158, Short.MAX_VALUE)));
|
||||
|
||||
layout.setVerticalGroup(getMainVerticalGroup(layout, dataList));
|
||||
setLayout(layout);
|
||||
repaint();
|
||||
layout.setVerticalGroup(getMainVerticalGroup(layout, dataList));
|
||||
setLayout(layout);
|
||||
repaint();
|
||||
} else {
|
||||
// No match found, display a message.
|
||||
JPanel messagePanel = new javax.swing.JPanel();
|
||||
JLabel messageLabel = new javax.swing.JLabel();
|
||||
|
||||
messagePanel.setLayout(new java.awt.BorderLayout());
|
||||
|
||||
messageLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
|
||||
messageLabel.setText(Bundle.MessageAccountPanel_no_matches());
|
||||
messageLabel.setEnabled(false);
|
||||
messagePanel.add(messageLabel, java.awt.BorderLayout.CENTER);
|
||||
|
||||
setLayout(new javax.swing.OverlayLayout(MessageAccountPanel.this));
|
||||
|
||||
add(messagePanel);
|
||||
repaint();
|
||||
}
|
||||
} catch (CancellationException ex) {
|
||||
logger.log(Level.INFO, "MessageAccoutPanel thread cancelled", ex);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
@ -120,7 +120,7 @@
|
||||
<Component class="javax.swing.JLabel" name="fromLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.fromLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.fromLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
@ -128,28 +128,28 @@
|
||||
<Properties>
|
||||
<Property name="horizontalAlignment" type="int" value="4"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.datetimeText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.datetimeText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="fromText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.fromText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.fromText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="toLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.toLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.toLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="toText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.toText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.toText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="autoscrolls" type="boolean" value="true"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
@ -160,14 +160,14 @@
|
||||
<Component class="javax.swing.JLabel" name="ccLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.ccLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.ccLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="ccText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.ccText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.ccText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[27, 14]"/>
|
||||
@ -177,14 +177,14 @@
|
||||
<Component class="javax.swing.JLabel" name="subjectLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.subjectLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.subjectLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="subjectText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.subjectText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.subjectText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[26, 14]"/>
|
||||
@ -195,7 +195,7 @@
|
||||
<Properties>
|
||||
<Property name="horizontalAlignment" type="int" value="4"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.directionText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.directionText.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
@ -214,7 +214,7 @@
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="Headers">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.headersScrollPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
@ -238,7 +238,7 @@
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="HTML">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.htmlPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.htmlPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
@ -254,7 +254,7 @@
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="RTF">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.rtfbodyScrollPane.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
@ -274,7 +274,7 @@
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="Attachments">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.attachmentsPanel.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
@ -311,7 +311,7 @@
|
||||
<Component class="javax.swing.JButton" name="viewInNewWindowButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.viewInNewWindowButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.viewInNewWindowButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
@ -329,7 +329,7 @@
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="Accounts">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MessageArtifactViewer.accountsTab.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/artifactviewers/Bundle.properties" key="MessageArtifactViewer.accountsTab.TabConstraints.tabTitle" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import org.sleuthkit.autopsy.datamodel.AttachmentNode;
|
||||
import java.awt.Color;
|
||||
@ -41,7 +41,9 @@ import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||
import org.sleuthkit.autopsy.contentviewers.TranslatablePanel;
|
||||
import org.sleuthkit.autopsy.contentviewers.TranslatablePanel.TranslatablePanelException;
|
||||
import org.sleuthkit.autopsy.contentviewers.Utilities;
|
||||
import org.sleuthkit.autopsy.corecomponents.AutoWrappingJTextPane;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
package org.sleuthkit.autopsy.contentviewers.artifactviewers;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -30,6 +30,7 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.SwingWorker;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
|
||||
@ -115,6 +116,11 @@ class PersonaAccountFetcher extends SwingWorker<Map<String, Collection<Persona>>
|
||||
|
||||
parentComponent.repaint();
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"# {0} - Persona count",
|
||||
"PersonaDisplayTask_persona_count_suffix=(1 of {0})"
|
||||
})
|
||||
|
||||
/**
|
||||
* Update the Persona gui controls.
|
@ -33,6 +33,8 @@ import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import net.sf.sevenzipjbinding.SevenZip;
|
||||
import net.sf.sevenzipjbinding.SevenZipNativeInitializationException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.modules.InstalledFileLocator;
|
||||
@ -336,8 +338,8 @@ public class Installer extends ModuleInstall {
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a folder in the config directory for object detection classifiers if one does not
|
||||
* exist.
|
||||
* Make a folder in the config directory for object detection classifiers if
|
||||
* one does not exist.
|
||||
*/
|
||||
private static void ensureClassifierFolderExists() {
|
||||
File objectDetectionClassifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath());
|
||||
@ -381,6 +383,7 @@ public class Installer extends ModuleInstall {
|
||||
ensureClassifierFolderExists();
|
||||
ensureOcrLanguagePacksFolderExists();
|
||||
initJavaFx();
|
||||
initializeSevenZip();
|
||||
for (ModuleInstall mi : packageInstallers) {
|
||||
try {
|
||||
mi.restored();
|
||||
@ -393,8 +396,21 @@ public class Installer extends ModuleInstall {
|
||||
logger.log(Level.INFO, "Autopsy Core restore completed"); //NON-NLS
|
||||
preloadJython();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Initializes 7zip-java bindings. We are performing initialization once
|
||||
* because we encountered issues related to file locking when initialization
|
||||
* was performed closer to where the bindings are used. See JIRA-6528.
|
||||
*/
|
||||
private void initializeSevenZip() {
|
||||
try {
|
||||
SevenZip.initSevenZipFromPlatformJAR();
|
||||
logger.log(Level.INFO, "7zip-java bindings loaded"); //NON-NLS
|
||||
} catch (SevenZipNativeInitializationException e) {
|
||||
logger.log(Level.SEVERE, "Error loading 7zip-java bindings", e); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an initial load of the Jython modules to speed up subsequent loads.
|
||||
*/
|
||||
@ -403,13 +419,12 @@ public class Installer extends ModuleInstall {
|
||||
try {
|
||||
JythonModuleLoader.getIngestModuleFactories();
|
||||
JythonModuleLoader.getGeneralReportModules();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
} catch (Exception ex) {
|
||||
// This is a firewall exception to ensure that any possible exception caused
|
||||
// by this initial load of the Jython modules are caught and logged.
|
||||
logger.log(Level.SEVERE, "There was an error while doing an initial load of python plugins.", ex);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
new Thread(loader).start();
|
||||
}
|
||||
|
@ -42,65 +42,29 @@ import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* This class periodically checks availability of collaboration resources -
|
||||
* remote database, remote keyword search server, messaging service - and
|
||||
* reports status updates to the user in case of a gap in service.
|
||||
* Monitors the status of services and publishes events and user notifications
|
||||
* when the status of a service changes. The database server, keyword search
|
||||
* server, and messaging service are considered to be core services in a
|
||||
* collaborative, multi-user case environment. Additional services can provide
|
||||
* current status by calling the setServiceStatus() method.
|
||||
*/
|
||||
public class ServicesMonitor {
|
||||
|
||||
private AutopsyEventPublisher eventPublisher;
|
||||
private static final Logger logger = Logger.getLogger(ServicesMonitor.class.getName());
|
||||
private final ScheduledThreadPoolExecutor periodicTasksExecutor;
|
||||
|
||||
private static final String PERIODIC_TASK_THREAD_NAME = "services-monitor-periodic-task-%d"; //NON-NLS
|
||||
private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 1;
|
||||
private static final long CRASH_DETECTION_INTERVAL_MINUTES = 15;
|
||||
|
||||
private static final Set<String> servicesList = Stream.of(ServicesMonitor.Service.values())
|
||||
.map(Service::toString)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
/**
|
||||
* The service monitor maintains a mapping of each service to it's last
|
||||
* status update.
|
||||
*/
|
||||
private final ConcurrentHashMap<String, String> statusByService;
|
||||
|
||||
/**
|
||||
* Call constructor on start-up so that the first check of services is done
|
||||
* as soon as possible.
|
||||
*/
|
||||
private static ServicesMonitor instance = new ServicesMonitor();
|
||||
|
||||
/**
|
||||
* List of services that are being monitored. The service names should be
|
||||
* representative of the service functionality and readable as they get
|
||||
* logged when service outage occurs.
|
||||
* An enumeration of the core services in a collaborative, multi-user case
|
||||
* environment. The display names provided here can be used to identify the
|
||||
* service status events published for these services and to directly query
|
||||
* the ServicesMonitor for the current status of these services.
|
||||
*/
|
||||
public enum Service {
|
||||
|
||||
/**
|
||||
* Property change event fired when remote case database service status
|
||||
* changes. New value is set to updated ServiceStatus, old value is
|
||||
* null.
|
||||
*/
|
||||
REMOTE_CASE_DATABASE(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.remoteCaseDatabase.displayName.text")),
|
||||
/**
|
||||
* Property change event fired when remote keyword search service status
|
||||
* changes. New value is set to updated ServiceStatus, old value is
|
||||
* null.
|
||||
*/
|
||||
REMOTE_KEYWORD_SEARCH(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.remoteKeywordSearch.displayName.text")),
|
||||
/**
|
||||
* Property change event fired when messaging service status changes.
|
||||
* New value is set to updated ServiceStatus, old value is null.
|
||||
*/
|
||||
MESSAGING(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.messaging.displayName.text"));
|
||||
|
||||
private final String displayName;
|
||||
|
||||
private Service(String name) {
|
||||
this.displayName = name;
|
||||
private Service(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
@ -109,121 +73,249 @@ public class ServicesMonitor {
|
||||
};
|
||||
|
||||
/**
|
||||
* List of possible service statuses.
|
||||
* An enumeration of the standard service statuses.
|
||||
*/
|
||||
public enum ServiceStatus {
|
||||
|
||||
/**
|
||||
* Service is currently up.
|
||||
*/
|
||||
UP,
|
||||
/**
|
||||
* Service is currently down.
|
||||
*/
|
||||
DOWN
|
||||
};
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ServicesMonitor.class.getName());
|
||||
private static final String PERIODIC_TASK_THREAD_NAME = "services-monitor-periodic-task-%d"; //NON-NLS
|
||||
private static final int NUMBER_OF_PERIODIC_TASK_THREADS = 1;
|
||||
private static final long CRASH_DETECTION_INTERVAL_MINUTES = 15;
|
||||
private static final Set<String> coreServices = Stream.of(ServicesMonitor.Service.values()).map(Service::toString).collect(Collectors.toSet());
|
||||
private static ServicesMonitor servicesMonitor = new ServicesMonitor();
|
||||
private final ScheduledThreadPoolExecutor periodicTasksExecutor;
|
||||
private final ConcurrentHashMap<String, String> statusByService;
|
||||
private final AutopsyEventPublisher eventPublisher;
|
||||
|
||||
/**
|
||||
* Gets the services monitor that monitors the status of services and
|
||||
* publishes events and user notifications when the status of a service
|
||||
* changes.
|
||||
*
|
||||
* @return The services monitor singleton.
|
||||
*/
|
||||
public synchronized static ServicesMonitor getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ServicesMonitor();
|
||||
if (servicesMonitor == null) {
|
||||
servicesMonitor = new ServicesMonitor();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ServicesMonitor() {
|
||||
|
||||
this.eventPublisher = new AutopsyEventPublisher();
|
||||
this.statusByService = new ConcurrentHashMap<>();
|
||||
|
||||
// First check is triggered immediately on current thread.
|
||||
checkAllServices();
|
||||
|
||||
/**
|
||||
* Start periodic task that check the availability of key collaboration
|
||||
* services.
|
||||
*/
|
||||
periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
|
||||
periodicTasksExecutor.scheduleWithFixedDelay(new CrashDetectionTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
|
||||
return servicesMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates service status and publishes the service status update if it is
|
||||
* different from previous status. Event is published locally. Logs status
|
||||
* Constructs a services monitor that monitors the status of services and
|
||||
* publishes events and user notifications when the status of a service
|
||||
* changes.
|
||||
*/
|
||||
private ServicesMonitor() {
|
||||
eventPublisher = new AutopsyEventPublisher();
|
||||
statusByService = new ConcurrentHashMap<>();
|
||||
|
||||
/*
|
||||
* The first service statuses check is performed immediately in the
|
||||
* current thread.
|
||||
*/
|
||||
checkAllServices();
|
||||
|
||||
/**
|
||||
* Start a periodic task to do ongoing service status checks.
|
||||
*/
|
||||
periodicTasksExecutor = new ScheduledThreadPoolExecutor(NUMBER_OF_PERIODIC_TASK_THREADS, new ThreadFactoryBuilder().setNameFormat(PERIODIC_TASK_THREAD_NAME).build());
|
||||
periodicTasksExecutor.scheduleWithFixedDelay(new ServicesMonitoringTask(), CRASH_DETECTION_INTERVAL_MINUTES, CRASH_DETECTION_INTERVAL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the status of a service and publishes a service status event if
|
||||
* the current status is different from the previously reported status.
|
||||
*
|
||||
* @param service Name of the service.
|
||||
* @param status Updated status for the service.
|
||||
* @param details Details of the event.
|
||||
* @param status Current status of the service.
|
||||
* @param details Additional status details.
|
||||
*
|
||||
*/
|
||||
public void setServiceStatus(String service, String status, String details) {
|
||||
// if the status update is for an existing service who's status hasn't changed - do nothing.
|
||||
if (statusByService.containsKey(service) && status.equals(statusByService.get(service))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// new service or status has changed - identify service's display name
|
||||
statusByService.put(service, status);
|
||||
|
||||
String serviceDisplayName;
|
||||
try {
|
||||
serviceDisplayName = ServicesMonitor.Service.valueOf(service).getDisplayName();
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
// custom service that is not listed in ServicesMonitor.Service enum. Use service name as display name.
|
||||
serviceDisplayName = service;
|
||||
}
|
||||
|
||||
if (status.equals(ServiceStatus.UP.toString())) {
|
||||
logger.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS
|
||||
MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"),
|
||||
MessageNotifyUtil.Notify.info(
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.title"),
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.restoredService.notify.msg", serviceDisplayName));
|
||||
} else if (status.equals(ServiceStatus.DOWN.toString())) {
|
||||
logger.log(Level.SEVERE, "Failed to connect to {0}. Reason: {1}", new Object[]{serviceDisplayName, details}); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"),
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.title"),
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.failedService.notify.msg", serviceDisplayName));
|
||||
} else {
|
||||
logger.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS
|
||||
MessageNotifyUtil.Notify.info(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.title"),
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.msg", new Object[]{serviceDisplayName, status}));
|
||||
logger.log(Level.INFO, "Status for {0} is {1} ({2})", new Object[]{serviceDisplayName, status}); //NON-NLS
|
||||
MessageNotifyUtil.Notify.info(
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.title"),
|
||||
NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.statusChange.notify.msg", new Object[]{serviceDisplayName, status, details}));
|
||||
}
|
||||
|
||||
// update and publish new status
|
||||
statusByService.put(service, status);
|
||||
eventPublisher.publishLocally(new ServiceEvent(service, status, details));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last status update for a service.
|
||||
* Get last recorded status for a service.
|
||||
*
|
||||
* @param service Name of the service.
|
||||
*
|
||||
* @return ServiceStatus Status for the service.
|
||||
*
|
||||
* @throws ServicesMonitorException If service name is null or service
|
||||
* doesn't exist.
|
||||
* @throws ServicesMonitorException If the service name is unknown to the
|
||||
* services monitor.
|
||||
*/
|
||||
public String getServiceStatus(String service) throws ServicesMonitorException {
|
||||
|
||||
if (service == null) {
|
||||
if (service == null || service.isEmpty()) {
|
||||
throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.nullServiceName.excepton.txt"));
|
||||
}
|
||||
|
||||
// if request is for one of our "core" services - perform an on demand check
|
||||
// to make sure we have the latest status.
|
||||
if (servicesList.contains(service)) {
|
||||
/*
|
||||
* If the request is for a core service, perform an "on demand" check to
|
||||
* get the current status.
|
||||
*/
|
||||
if (coreServices.contains(service)) {
|
||||
checkServiceStatus(service);
|
||||
}
|
||||
|
||||
String status = statusByService.get(service);
|
||||
if (status == null) {
|
||||
// no such service
|
||||
throw new ServicesMonitorException(NbBundle.getMessage(ServicesMonitor.class, "ServicesMonitor.unknownServiceName.excepton.txt", service));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs service availability status check.
|
||||
* Adds a subscriber to service status events for the core services.
|
||||
*
|
||||
* @param service Name of the service.
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(coreServices, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscriber to service status events for a subset of the services
|
||||
* known to the services monitor.
|
||||
*
|
||||
* @param services The services the subscriber is interested in.
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(Set<String> services, PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(services, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscriber to service status events for a specific service known
|
||||
* to the services monitor.
|
||||
*
|
||||
* @param service The service the subscriber is interested in.
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(String service, PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(service, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a subscriber to service status events for the core services.
|
||||
*
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(coreServices, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a subscriber to service status events for a subset of the
|
||||
* services known to the services monitor.
|
||||
*
|
||||
* @param services The services the subscriber is no longer interested in.
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(Set<String> services, PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(services, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscriber to service status events for a specific service known
|
||||
* to the services monitor.
|
||||
*
|
||||
* @param service The service the subscriber is no longer interested in.
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(String service, PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(service, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the status of the core services in a collaborative, multi-user
|
||||
* case environment: the database server, the keyword search server and the
|
||||
* messaging service. Publishes a service event and user notification if the
|
||||
* status of a service has changed since the last check.
|
||||
*/
|
||||
private void checkAllServices() {
|
||||
if (!UserPreferences.getIsMultiUserModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String service : coreServices) {
|
||||
checkServiceStatus(service);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that checks the status of the core services in a collaborative,
|
||||
* multi-user case environment: the database server, the keyword search
|
||||
* server and the messaging service. Publishes a service event and user
|
||||
* notification if the status of a service has changed since the last check.
|
||||
*/
|
||||
private final class ServicesMonitoringTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
checkAllServices();
|
||||
} catch (Exception ex) { // Exception firewall
|
||||
logger.log(Level.SEVERE, "An error occurred during services monitoring", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown if an error occurs during a service status query.
|
||||
*/
|
||||
public class ServicesMonitorException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ServicesMonitorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServicesMonitorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a core service availability status check.
|
||||
*
|
||||
* @param service Name of the service to check.
|
||||
*/
|
||||
private void checkServiceStatus(String service) {
|
||||
if (service.equals(Service.REMOTE_CASE_DATABASE.toString())) {
|
||||
@ -236,7 +328,7 @@ public class ServicesMonitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs case database service availability status check.
|
||||
* Performs a database server availability status check.
|
||||
*/
|
||||
private void checkDatabaseConnectionStatus() {
|
||||
CaseDbConnectionInfo info;
|
||||
@ -256,7 +348,7 @@ public class ServicesMonitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs keyword search service availability status check.
|
||||
* Performs a keyword search service availability status check.
|
||||
*/
|
||||
private void checkKeywordSearchServerConnectionStatus() {
|
||||
KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
|
||||
@ -316,113 +408,4 @@ public class ServicesMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event subscriber to this publisher. Subscriber will be subscribed
|
||||
* to all events from this publisher.
|
||||
*
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(servicesList, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event subscriber to this publisher.
|
||||
*
|
||||
* @param eventNames The events the subscriber is interested in.
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(eventNames, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event subscriber to this publisher.
|
||||
*
|
||||
* @param eventName The event the subscriber is interested in.
|
||||
* @param subscriber The subscriber to add.
|
||||
*/
|
||||
public void addSubscriber(String eventName, PropertyChangeListener subscriber) {
|
||||
eventPublisher.addSubscriber(eventName, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event subscriber from this publisher.
|
||||
*
|
||||
* @param eventNames The events the subscriber is no longer interested in.
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(eventNames, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event subscriber from this publisher.
|
||||
*
|
||||
* @param eventName The event the subscriber is no longer interested in.
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(String eventName, PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(eventName, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event subscriber to this publisher. Subscriber will be removed
|
||||
* from all event notifications from this publisher.
|
||||
*
|
||||
* @param subscriber The subscriber to remove.
|
||||
*/
|
||||
public void removeSubscriber(PropertyChangeListener subscriber) {
|
||||
eventPublisher.removeSubscriber(servicesList, subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies connectivity to all services.
|
||||
*/
|
||||
private void checkAllServices() {
|
||||
if (!UserPreferences.getIsMultiUserModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (String service : servicesList) {
|
||||
checkServiceStatus(service);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Runnable task that periodically checks the availability of
|
||||
* collaboration resources (remote database, remote keyword search service,
|
||||
* message broker) and reports status to the user in case of a gap in
|
||||
* service.
|
||||
*/
|
||||
private final class CrashDetectionTask implements Runnable {
|
||||
|
||||
/**
|
||||
* Monitor the availability of collaboration resources
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
checkAllServices();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Unexpected exception in CrashDetectionTask", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when service status query results in an error.
|
||||
*/
|
||||
public class ServicesMonitorException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ServicesMonitorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServicesMonitorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -450,6 +450,7 @@
|
||||
<file name="cvt.wsmode" url="cvtWsmode.xml"/>
|
||||
<file name="discovery.wsmode" url="discoveryWsmode.xml"/>
|
||||
<file name="geolocation.wsmode" url="geolocationWsmode.xml"/>
|
||||
<file name="personas.wsmode" url="personasWsmode.xml"/>
|
||||
</folder>
|
||||
</folder>
|
||||
|
||||
|
11
Core/src/org/sleuthkit/autopsy/core/personasWsmode.xml
Normal file
11
Core/src/org/sleuthkit/autopsy/core/personasWsmode.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mode version="2.4">
|
||||
<name unique="personas"/>
|
||||
<kind type="editor"/>
|
||||
<state type="separated"/>
|
||||
<bounds x="76" y="68" width="1200" height="800"/>
|
||||
<frame state="0"/>
|
||||
|
||||
<empty-behavior permanent="false"/>
|
||||
</mode>
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.corecomponentinterfaces;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that add data sources of a particular type
|
||||
@ -36,10 +37,6 @@ import javax.swing.JPanel;
|
||||
*
|
||||
* Data source processors should perform all processing in a background task in
|
||||
* a separate thread, reporting results using a callback object.
|
||||
*
|
||||
* It is recommended that implementers provide an overload of the run method
|
||||
* that allows the data source processor to be run independently of the
|
||||
* selection and configuration panel.
|
||||
*/
|
||||
public interface DataSourceProcessor {
|
||||
|
||||
@ -111,6 +108,38 @@ public interface DataSourceProcessor {
|
||||
* to return results.
|
||||
*/
|
||||
void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback);
|
||||
|
||||
/**
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Files found during ingest will be sent directly to
|
||||
* the IngestStream provided. Returns as soon as the background task is
|
||||
* started. The background task uses a callback object to signal task
|
||||
* completion and return results.
|
||||
*
|
||||
* This method should not be called unless isPanelValid returns true, and
|
||||
* should only be called for DSPs that support ingest streams. The ingest
|
||||
* settings must be complete before calling this method.
|
||||
*
|
||||
* @param settings The ingest job settings.
|
||||
* @param progress Progress monitor that will be used by the background task
|
||||
* to report progress.
|
||||
* @param callBack Callback that will be used by the background task to
|
||||
* return results.
|
||||
*/
|
||||
default void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
|
||||
DataSourceProcessorCallback callBack) {
|
||||
throw new UnsupportedOperationException("Streaming ingest not supported for this data source processor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this DSP supports ingest streams.
|
||||
*
|
||||
* @return True if this DSP supports an ingest stream, false otherwise.
|
||||
*/
|
||||
default boolean supportsIngestStream() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests cancellation of the background task that adds a data source to
|
||||
|
@ -40,8 +40,8 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import org.sleuthkit.autopsy.contentviewers.ArtifactContentViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.DefaultArtifactContentViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.artifactviewers.ArtifactContentViewer;
|
||||
import org.sleuthkit.autopsy.contentviewers.artifactviewers.DefaultArtifactContentViewer;
|
||||
|
||||
/**
|
||||
* Instances of this class display the BlackboardArtifacts associated with the
|
||||
|
@ -55,6 +55,7 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWO
|
||||
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
|
||||
|
||||
/**
|
||||
* Parent of the "extracted content" artifacts to be displayed in the tree.
|
||||
@ -63,7 +64,6 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
public class ExtractedContent implements AutopsyVisitableItem {
|
||||
|
||||
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
|
||||
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
|
||||
public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text");
|
||||
private final long filteringDSObjId; // 0 if not filtering/grouping by data source
|
||||
private SleuthkitCase skCase; // set to null after case has been closed
|
||||
@ -145,12 +145,19 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
* This area has all of the blackboard artifacts that are not displayed in a
|
||||
* more specific form elsewhere in the tree.
|
||||
*/
|
||||
private class TypeFactory extends ChildFactory.Detachable<BlackboardArtifact.Type> {
|
||||
private class TypeFactory extends ChildFactory.Detachable<BlackboardArtifact.Type> implements RefreshThrottler.Refresher {
|
||||
|
||||
private final ArrayList<BlackboardArtifact.Type> doNotShow = new ArrayList<>();
|
||||
// maps the artifact type to its child node
|
||||
private final HashMap<BlackboardArtifact.Type, TypeNode> typeNodeList = new HashMap<>();
|
||||
|
||||
/**
|
||||
* RefreshThrottler is used to limit the number of refreshes performed
|
||||
* when CONTENT_CHANGED and DATA_ADDED ingest module events are
|
||||
* received.
|
||||
*/
|
||||
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
TypeFactory() {
|
||||
super();
|
||||
@ -173,27 +180,11 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
|
||||
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
/**
|
||||
* This is a stop gap measure until a different way of handling
|
||||
* the closing of cases is worked out. Currently, remote events
|
||||
* may be received for a case that is already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* Due to some unresolved issues with how cases are closed,
|
||||
* it is possible for the event to have a null oldValue if
|
||||
* the event is a remote event.
|
||||
*/
|
||||
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
|
||||
if (null != event && !(this.doNotShow.contains(event.getBlackboardArtifactType()))) {
|
||||
refresh(true);
|
||||
}
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
// case was closed. Remove listeners so that we don't get called with a stale case handle
|
||||
if (evt.getNewValue() == null) {
|
||||
removeNotify();
|
||||
skCase = null;
|
||||
}
|
||||
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
|
||||
@ -204,32 +195,26 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
refresh(true);
|
||||
refresh(false);
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
// case was closed. Remove listeners so that we don't get called with a stale case handle
|
||||
if (evt.getNewValue() == null) {
|
||||
removeNotify();
|
||||
skCase = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
refreshThrottler.registerForIngestModuleEvents();
|
||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
|
||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl);
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
refreshThrottler.unregisterEventListener();
|
||||
IngestManager.getInstance().removeIngestJobEventListener(pcl);
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
|
||||
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl);
|
||||
typeNodeList.clear();
|
||||
}
|
||||
@ -273,6 +258,40 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
typeNodeList.put(key, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
/**
|
||||
* This is a stop gap measure until a different way of handling
|
||||
* the closing of cases is worked out. Currently, remote events
|
||||
* may be received for a case that is already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* Due to some unresolved issues with how cases are closed,
|
||||
* it is possible for the event to have a null oldValue if
|
||||
* the event is a remote event.
|
||||
*/
|
||||
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
|
||||
if (null != event && !(this.doNotShow.contains(event.getBlackboardArtifactType()))) {
|
||||
return true;
|
||||
}
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -355,73 +374,53 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Creates children for a given artifact type
|
||||
*/
|
||||
private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> {
|
||||
private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> implements RefreshThrottler.Refresher {
|
||||
|
||||
private BlackboardArtifact.Type type;
|
||||
private final BlackboardArtifact.Type type;
|
||||
|
||||
/**
|
||||
* RefreshThrottler is used to limit the number of refreshes performed
|
||||
* when CONTENT_CHANGED and DATA_ADDED ingest module events are
|
||||
* received.
|
||||
*/
|
||||
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
|
||||
|
||||
ArtifactFactory(BlackboardArtifact.Type type) {
|
||||
super(type.getTypeName());
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private final PropertyChangeListener pcl = new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked out.
|
||||
* Currently, remote events may be received for a case that is
|
||||
* already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
refresh(false);
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked
|
||||
* out. Currently, remote events may be received for a case
|
||||
* that is already closed.
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* Even with the check above, it is still possible that
|
||||
* the case will be closed in a different thread before
|
||||
* this code executes. If that happens, it is possible
|
||||
* for the event to have a null oldValue.
|
||||
*/
|
||||
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
|
||||
if (null != event && event.getBlackboardArtifactType().equals(type)) {
|
||||
refresh(true);
|
||||
}
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked
|
||||
* out. Currently, remote events may be received for a case
|
||||
* that is already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
refresh(true);
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onAdd() {
|
||||
refreshThrottler.registerForIngestModuleEvents();
|
||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
|
||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
refreshThrottler.unregisterEventListener();
|
||||
IngestManager.getInstance().removeIngestJobEventListener(pcl);
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -451,5 +450,43 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
refresh(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
|
||||
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked out.
|
||||
* Currently, remote events may be received for a case that is
|
||||
* already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* Even with the check above, it is still possible that the
|
||||
* case will be closed in a different thread before this
|
||||
* code executes. If that happens, it is possible for the
|
||||
* event to have a null oldValue.
|
||||
*/
|
||||
final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue();
|
||||
if (null != event && event.getBlackboardArtifactType().equals(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
|
||||
|
||||
/**
|
||||
* Filters database results by file extension.
|
||||
@ -82,39 +83,26 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
* Listens for case and ingest invest. Updates observers when events are
|
||||
* fired. FileType and FileTypes nodes are all listening to this.
|
||||
*/
|
||||
private class FileTypesByExtObservable extends Observable {
|
||||
private class FileTypesByExtObservable extends Observable implements RefreshThrottler.Refresher {
|
||||
|
||||
private final PropertyChangeListener pcl;
|
||||
private final Set<Case.Events> CASE_EVENTS_OF_INTEREST;
|
||||
/**
|
||||
* RefreshThrottler is used to limit the number of refreshes performed
|
||||
* when CONTENT_CHANGED and DATA_ADDED ingest module events are
|
||||
* received.
|
||||
*/
|
||||
private final RefreshThrottler refreshThrottler = new RefreshThrottler(this);
|
||||
|
||||
private FileTypesByExtObservable() {
|
||||
super();
|
||||
this.CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE);
|
||||
this.pcl = (PropertyChangeEvent evt) -> {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())
|
||||
|| eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
|
||||
|
||||
/**
|
||||
* If a new file has been added but does not have an extension
|
||||
* there is nothing to do.
|
||||
*/
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
|
||||
if ((evt.getOldValue() instanceof ModuleContentEvent) == false) {
|
||||
return;
|
||||
}
|
||||
ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue();
|
||||
if ((moduleContentEvent.getSource() instanceof AbstractFile) == false) {
|
||||
return;
|
||||
}
|
||||
AbstractFile abstractFile = (AbstractFile) moduleContentEvent.getSource();
|
||||
if (abstractFile.getNameExtension().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked
|
||||
@ -139,14 +127,14 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
};
|
||||
|
||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
|
||||
IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl);
|
||||
refreshThrottler.registerForIngestModuleEvents();
|
||||
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
|
||||
}
|
||||
|
||||
private void removeListeners() {
|
||||
deleteObservers();
|
||||
IngestManager.getInstance().removeIngestJobEventListener(pcl);
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
|
||||
refreshThrottler.unregisterEventListener();
|
||||
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
|
||||
}
|
||||
|
||||
@ -154,7 +142,52 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
setChanged();
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
typesRoot.updateShowCounts();
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
|
||||
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked out.
|
||||
* Currently, remote events may be received for a case that is
|
||||
* already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
/**
|
||||
* If a new file has been added but does not have an
|
||||
* extension there is nothing to do.
|
||||
*/
|
||||
if ((evt.getOldValue() instanceof ModuleContentEvent) == false) {
|
||||
return false;
|
||||
}
|
||||
ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue();
|
||||
if ((moduleContentEvent.getSource() instanceof AbstractFile) == false) {
|
||||
return false;
|
||||
}
|
||||
AbstractFile abstractFile = (AbstractFile) moduleContentEvent.getSource();
|
||||
if (!abstractFile.getNameExtension().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
/**
|
||||
* Case is closed, no refresh needed.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String FNAME = NbBundle.getMessage(FileTypesByExtNode.class, "FileTypesByExtNode.fname.text");
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.autopsy.guiutils.RefreshThrottler;
|
||||
|
||||
/**
|
||||
* Class which contains the Nodes for the 'By Mime Type' view located in the
|
||||
@ -83,13 +84,19 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
|
||||
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE);
|
||||
|
||||
/**
|
||||
* RefreshThrottler is used to limit the number of refreshes performed when
|
||||
* CONTENT_CHANGED and DATA_ADDED ingest module events are received.
|
||||
*/
|
||||
private final RefreshThrottler refreshThrottler;
|
||||
|
||||
/**
|
||||
* Create the base expression used as the where clause in the queries for
|
||||
* files by mime type. Filters out certain kinds of files and directories,
|
||||
* and known/slack files based on user preferences.
|
||||
*
|
||||
* @return The base expression to be used in the where clause of queries for
|
||||
* files by mime type.
|
||||
* files by mime type.
|
||||
*/
|
||||
private String createBaseWhereExpr() {
|
||||
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
|
||||
@ -108,6 +115,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
private void removeListeners() {
|
||||
deleteObservers();
|
||||
IngestManager.getInstance().removeIngestJobEventListener(pcl);
|
||||
refreshThrottler.unregisterEventListener();
|
||||
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
|
||||
}
|
||||
|
||||
@ -155,32 +163,20 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
this.typesRoot = typesRoot;
|
||||
this.pcl = (PropertyChangeEvent evt) -> {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|
||||
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())
|
||||
|| eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a
|
||||
* different way of handling the closing of cases is worked out.
|
||||
* Currently, remote events may be received for a case that is
|
||||
* already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
typesRoot.updateShowCounts();
|
||||
populateHashMap();
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
|
||||
refreshMimeTypes();
|
||||
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
if (evt.getNewValue() == null) {
|
||||
removeListeners();
|
||||
}
|
||||
}
|
||||
};
|
||||
refreshThrottler = new RefreshThrottler(new FileTypesByMimeTypeRefresher());
|
||||
IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl);
|
||||
refreshThrottler.registerForIngestModuleEvents();
|
||||
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl);
|
||||
populateHashMap();
|
||||
}
|
||||
@ -201,7 +197,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
* @param node the Node which you wish to check.
|
||||
*
|
||||
* @return True if originNode is an instance of ByMimeTypeNode and is empty,
|
||||
* false otherwise.
|
||||
* false otherwise.
|
||||
*/
|
||||
public static boolean isEmptyMimeTypeNode(Node node) {
|
||||
boolean isEmptyMimeNode = false;
|
||||
@ -212,6 +208,41 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
|
||||
}
|
||||
|
||||
private void refreshMimeTypes() {
|
||||
/**
|
||||
* Checking for a current case is a stop gap measure until a different
|
||||
* way of handling the closing of cases is worked out. Currently, remote
|
||||
* events may be received for a case that is already closed.
|
||||
*/
|
||||
try {
|
||||
Case.getCurrentCaseThrows();
|
||||
typesRoot.updateShowCounts();
|
||||
populateHashMap();
|
||||
} catch (NoCurrentCaseException notUsed) {
|
||||
/**
|
||||
* Case is closed, do nothing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for updating the 'By Mime Type' view in the UI. See
|
||||
* RefreshThrottler for more details.
|
||||
*/
|
||||
private class FileTypesByMimeTypeRefresher implements RefreshThrottler.Refresher {
|
||||
|
||||
@Override
|
||||
public void refresh() {
|
||||
refreshMimeTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(PropertyChangeEvent evt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class which represents the root node of the "By MIME Type" tree, will
|
||||
* have children of each media type present in the database or no children
|
||||
|
@ -29,6 +29,7 @@ import org.sleuthkit.datamodel.Blackboard.BlackboardException;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
|
||||
@ -285,8 +286,12 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser {
|
||||
// If both callerId and calleeList were non-null/non-empty, then
|
||||
// it would have been a valid combination.
|
||||
if (callerId != null) {
|
||||
try {
|
||||
currentCase.getCommunicationsManager().createAccountFileInstance(
|
||||
Account.Type.PHONE, callerId, PARSER_NAME, parent);
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
logger.log(Level.WARNING, String.format("Invalid account identifier %s", callerId), ex);
|
||||
}
|
||||
|
||||
otherAttributes.add(new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
|
||||
@ -294,8 +299,13 @@ final class XRYCallsFileParser extends AbstractSingleEntityParser {
|
||||
}
|
||||
|
||||
for (String phone : calleeList) {
|
||||
try {
|
||||
currentCase.getCommunicationsManager().createAccountFileInstance(
|
||||
Account.Type.PHONE, phone, PARSER_NAME, parent);
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
logger.log(Level.WARNING, String.format("Invalid account identifier %s", phone), ex);
|
||||
}
|
||||
|
||||
|
||||
otherAttributes.add(new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
|
||||
|
@ -34,6 +34,7 @@ import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.Blackboard.BlackboardException;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
|
||||
@ -307,8 +308,13 @@ final class XRYMessagesFileParser implements XRYFileParser {
|
||||
} else if(namespace == XryNamespace.TO || direction == CommunicationDirection.OUTGOING) {
|
||||
recipientIdsList.add(pair.getValue());
|
||||
} else {
|
||||
currentCase.getCommunicationsManager().createAccountFileInstance(
|
||||
Account.Type.PHONE, pair.getValue(), PARSER_NAME, parent);
|
||||
try {
|
||||
currentCase.getCommunicationsManager().createAccountFileInstance(
|
||||
Account.Type.PHONE, pair.getValue(), PARSER_NAME, parent);
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
logger.log(Level.WARNING, String.format("Invalid account identifier %s", pair.getValue()), ex);
|
||||
}
|
||||
|
||||
otherAttributes.add(new BlackboardAttribute(
|
||||
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
|
||||
PARSER_NAME, pair.getValue()));
|
||||
|
@ -27,7 +27,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalQueries;
|
||||
import org.sleuthkit.datamodel.CommunicationsUtils;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.InvalidAccountIDException;
|
||||
|
||||
/**
|
||||
* Common utility methods shared among all XRY parser implementations.
|
||||
@ -46,7 +46,7 @@ final class XRYUtils {
|
||||
try {
|
||||
CommunicationsUtils.normalizePhoneNum(phoneNumber);
|
||||
return true;
|
||||
} catch (TskCoreException ex) {
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -55,7 +55,7 @@ final class XRYUtils {
|
||||
try {
|
||||
CommunicationsUtils.normalizeEmailAddress(email);
|
||||
return true;
|
||||
} catch (TskCoreException ex) {
|
||||
} catch (InvalidAccountIDException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li
|
||||
constraints.weightx = LABEL_WEIGHT;
|
||||
constraints.weighty = LABEL_WEIGHT;
|
||||
constraints.gridwidth = LABEL_WIDTH;
|
||||
addToGridBagLayout(filterPanel.getCheckbox(), null, column);
|
||||
addToGridBagLayout(filterPanel.getCheckbox(), filterPanel.getAdditionalLabel(), column);
|
||||
if (filterPanel.hasPanel()) {
|
||||
constraints.gridx += constraints.gridwidth;
|
||||
constraints.fill = GridBagConstraints.BOTH;
|
||||
|
@ -101,3 +101,6 @@ DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which
|
||||
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
|
||||
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
|
||||
DiscoveryDialog.step1Label.text=Step 1: Choose result type
|
||||
ResultsSplitPaneDivider.hideButton.text=
|
||||
ResultsSplitPaneDivider.showButton.text=
|
||||
ResultsSplitPaneDivider.detailsLabel.text=Details Area
|
||||
|
@ -11,7 +11,7 @@ DiscoveryTopComponent.name=\ Discovery
|
||||
DiscoveryTopComponent.newSearch.text=New Search
|
||||
DiscoveryTopComponent.searchCancelled.text=Search has been cancelled.
|
||||
# {0} - search
|
||||
DiscoveryTopComponent.searchComplete.text=Results for {0}
|
||||
DiscoveryTopComponent.searchComplete.text=Results with {0}
|
||||
# {0} - searchType
|
||||
DiscoveryTopComponent.searchInProgress.text=Performing search for results of type {0}. Please wait.
|
||||
DiscoveryUiUtility.bytes.text=bytes
|
||||
@ -57,18 +57,24 @@ FileSearch.InterestingItemGroupKey.noSets=None
|
||||
FileSearch.KeywordListGroupKey.noKeywords=None
|
||||
FileSearch.NoGroupingGroupKey.allFiles=All Files
|
||||
FileSearch.ObjectDetectedGroupKey.noSets=None
|
||||
FileSearchData.FileSize.LARGE_IMAGE.displayName=Large: 1-50MB
|
||||
FileSearchData.FileSize.LARGE_VIDEO.displayName=Large: 1-5GB
|
||||
FileSearchData.FileSize.MEDIUM_IMAGE.displayName=Medium: 100KB-1MB
|
||||
FileSearchData.FileSize.MEDIUM_VIDEO.displayName=Medium: 100MB-1GB
|
||||
FileSearchData.FileSize.SMALL_IMAGE.displayName=Small: 16-100KB
|
||||
FileSearchData.FileSize.SMALL_VIDEO.displayName=Small: 500KB-100MB
|
||||
FileSearchData.FileSize.XLARGE_IMAGE.displayName=XLarge: 50-200MB
|
||||
FileSearchData.FileSize.XLARGE_VIDEO.displayName=XLarge: 5-10GB
|
||||
FileSearchData.FileSize.XSMALL_IMAGE.displayName=XSmall: 0-16KB
|
||||
FileSearchData.FileSize.XSMALL_VIDEO.displayName=XSmall: 0-500KB
|
||||
FileSearchData.FileSize.XXLARGE_IMAGE.displayName=XXLarge: 200MB+
|
||||
FileSearchData.FileSize.XXLARGE_VIDEO.displayName=XXLarge: 10GB+
|
||||
FileSearchData.FileSize.100kbto1mb=: 100KB-1MB
|
||||
FileSearchData.FileSize.100mbto1gb=: 100MB-1GB
|
||||
FileSearchData.FileSize.10PlusGb=: 10GB+
|
||||
FileSearchData.FileSize.16kbto100kb=: 16-100KB
|
||||
FileSearchData.FileSize.1gbto5gb=: 1-5GB
|
||||
FileSearchData.FileSize.1mbto50mb=: 1-50MB
|
||||
FileSearchData.FileSize.200PlusMb=: 200MB+
|
||||
FileSearchData.FileSize.500kbto100mb=: 500KB-100MB
|
||||
FileSearchData.FileSize.50mbto200mb=: 50-200MB
|
||||
FileSearchData.FileSize.5gbto10gb=: 5-10GB
|
||||
FileSearchData.FileSize.LARGE.displayName=Large
|
||||
FileSearchData.FileSize.MEDIUM.displayName=Medium
|
||||
FileSearchData.FileSize.SMALL.displayName=Small
|
||||
FileSearchData.FileSize.upTo16kb=: 0-16KB
|
||||
FileSearchData.FileSize.upTo500kb=: 0-500KB
|
||||
FileSearchData.FileSize.XLARGE.displayName=XLarge
|
||||
FileSearchData.FileSize.XSMALL.displayName=XSmall
|
||||
FileSearchData.FileSize.XXLARGE.displayName=XXLarge
|
||||
FileSearchData.FileType.Audio.displayName=Audio
|
||||
FileSearchData.FileType.Documents.displayName=Documents
|
||||
FileSearchData.FileType.Executables.displayName=Executables
|
||||
@ -106,45 +112,44 @@ FileSearchFiltering.concatenateSetNamesForDisplay.comma=,
|
||||
# {1} - Data source ID
|
||||
FileSearchFiltering.DataSourceFilter.datasource={0}({1})
|
||||
# {0} - filters
|
||||
FileSearchFiltering.DataSourceFilter.desc=Files in data source(s): {0}
|
||||
FileSearchFiltering.DataSourceFilter.or=\ or
|
||||
FileSearchFiltering.DataSourceFilter.desc=Data source(s): {0}
|
||||
FileSearchFiltering.DataSourceFilter.or=,
|
||||
# {0} - filters
|
||||
FileSearchFiltering.FileTypeFilter.desc=Files with type: {0}
|
||||
FileSearchFiltering.FileTypeFilter.or=\ or
|
||||
FileSearchFiltering.FileTypeFilter.desc=Type: {0}
|
||||
FileSearchFiltering.FileTypeFilter.or=,
|
||||
# {0} - filters
|
||||
FileSearchFiltering.FrequencyFilter.desc=Files with frequency: {0}
|
||||
FileSearchFiltering.FrequencyFilter.or=\ or
|
||||
FileSearchFiltering.FrequencyFilter.desc=Past occurrences: {0}
|
||||
FileSearchFiltering.FrequencyFilter.or=,
|
||||
# {0} - filters
|
||||
FileSearchFiltering.HashSetFilter.desc=Files with hash set hits in set(s): {0}
|
||||
FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}
|
||||
# {0} - filters
|
||||
FileSearchFiltering.InterestingItemSetFilter.desc=Files with interesting item hits in set(s): {0}
|
||||
FileSearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}
|
||||
# {0} - filters
|
||||
FileSearchFiltering.KeywordListFilter.desc=Files with keywords in list(s): {0}
|
||||
FileSearchFiltering.KnownFilter.desc=Files which are not known
|
||||
FileSearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}
|
||||
FileSearchFiltering.KnownFilter.desc=which are not known
|
||||
# {0} - filters
|
||||
FileSearchFiltering.ObjectDetectionFilter.desc=Files with objects detected in set(s): {0}
|
||||
FileSearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}
|
||||
# {0} - filters
|
||||
FileSearchFiltering.ParentFilter.desc=Files with paths matching: {0}
|
||||
FileSearchFiltering.ParentFilter.desc=Paths matching: {0}
|
||||
FileSearchFiltering.ParentFilter.exact=(exact match)
|
||||
FileSearchFiltering.ParentFilter.or=\ or
|
||||
FileSearchFiltering.ParentFilter.excluded=(excluded)
|
||||
FileSearchFiltering.ParentFilter.included=(included)
|
||||
FileSearchFiltering.ParentFilter.or=,
|
||||
FileSearchFiltering.ParentFilter.substring=(substring)
|
||||
FileSearchFiltering.ParentSearchTerm.excludeString=\ (exclude)
|
||||
FileSearchFiltering.ParentSearchTerm.fullString=\ (exact)
|
||||
FileSearchFiltering.ParentSearchTerm.includeString=\ (include)
|
||||
FileSearchFiltering.ParentSearchTerm.subString=\ (substring)
|
||||
FileSearchFiltering.PreviouslyNotableFilter.desc=Files that were previously marked as notable
|
||||
FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable
|
||||
# {0} - filters
|
||||
FileSearchFiltering.ScoreFilter.desc=Files with score(s) of : {0}
|
||||
FileSearchFiltering.ScoreFilter.desc=Score(s) of : {0}
|
||||
# {0} - filters
|
||||
FileSearchFiltering.SizeFilter.desc=Files with size in range(s): {0}
|
||||
FileSearchFiltering.SizeFilter.or=\ or
|
||||
# {0} - Minimum bytes
|
||||
# {1} - Maximum bytes
|
||||
FileSearchFiltering.SizeFilter.range=({0} to {1})
|
||||
FileSearchFiltering.SizeFilter.desc=Size(s): {0}
|
||||
FileSearchFiltering.SizeFilter.or=,
|
||||
# {0} - tag names
|
||||
FileSearchFiltering.TagsFilter.desc=Files that have been tagged {0}
|
||||
FileSearchFiltering.TagsFilter.or=\ or
|
||||
FileSearchFiltering.UserCreatedFilter.desc=Files that contain EXIF data
|
||||
FileSearchFiltering.TagsFilter.desc=Tagged {0}
|
||||
FileSearchFiltering.TagsFilter.or=,
|
||||
FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data
|
||||
FileSearchPanel.sortingPanel.border.title=Grouping
|
||||
FileSearchPanel.addButton.text=Add
|
||||
FileSearchPanel.substringRadioButton.text=Substring
|
||||
@ -186,7 +191,7 @@ GroupsListPanel.noResults.title.text=No results found
|
||||
ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
|
||||
# {0} - otherInstanceCount
|
||||
ImageThumbnailPanel.nameLabel.more.text=\ and {0} more
|
||||
OpenDiscoveryAction.resultsIncomplete.text=Results may be incomplete
|
||||
OpenDiscoveryAction.resultsIncomplete.text=Discovery results may be incomplete
|
||||
ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
|
||||
ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
|
||||
ResultFile.score.notableTaggedFile.description=At least one instance of the file is tagged with a notable tag.
|
||||
@ -259,6 +264,9 @@ DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which
|
||||
ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show
|
||||
VideoFilterPanel.videoFiltersSplitPane.border.title=Step 2: Filter which videos to show
|
||||
DiscoveryDialog.step1Label.text=Step 1: Choose result type
|
||||
ResultsSplitPaneDivider.hideButton.text=
|
||||
ResultsSplitPaneDivider.showButton.text=
|
||||
ResultsSplitPaneDivider.detailsLabel.text=Details Area
|
||||
VideoThumbnailPanel.bytes.text=bytes
|
||||
VideoThumbnailPanel.deleted.text=All instances of file are deleted.
|
||||
VideoThumbnailPanel.gigaBytes.text=GB
|
||||
|
@ -43,23 +43,25 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace pref="196" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="filler1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="step1Label" min="-2" pref="243" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="10" pref="10" max="-2" attributes="0"/>
|
||||
<Component id="imagesButton" min="-2" pref="110" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="videosButton" min="-2" pref="110" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="documentsButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="370" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="step1Label" min="-2" pref="243" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="filler1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="391" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace pref="196" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user