Merge pull request #5413 from sleuthkit/data-src-deletion

Merge data-src-deletion-branch into develop branch
This commit is contained in:
Richard Cordovano 2019-11-13 12:30:51 -05:00 committed by GitHub
commit be7107d3db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1146 additions and 598 deletions

View File

@ -50,7 +50,9 @@ public interface AutopsyService {
* @param context The case context which includes things such as the case, a * @param context The case context which includes things such as the case, a
* progress indicator for the operation, a cancellation * progress indicator for the operation, a cancellation
* request flag, etc. * request flag, etc.
* @throws org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException *
* @throws
* org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
*/ */
default void openCaseResources(CaseContext context) throws AutopsyServiceException { default void openCaseResources(CaseContext context) throws AutopsyServiceException {
/* /*
@ -64,7 +66,9 @@ public interface AutopsyService {
* @param context The case context which includes things such as the case, a * @param context The case context which includes things such as the case, a
* progress indicator for the operation, a cancellation * progress indicator for the operation, a cancellation
* request flag, etc. * request flag, etc.
* @throws org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException *
* @throws
* org.sleuthkit.autopsy.framework.AutopsyService.AutopsyServiceException
*/ */
default void closeCaseResources(CaseContext context) throws AutopsyServiceException { default void closeCaseResources(CaseContext context) throws AutopsyServiceException {
/* /*
@ -113,7 +117,9 @@ public interface AutopsyService {
/** /**
* Gets the progress indicator for the creation/opening/upgrading of * Gets the progress indicator for the creation/opening/upgrading of
* case-level resources by a service. * case-level resources by a service. IMPORTANT: The service should only
* call progress() on the progress indicator. Calling start() and
* finish() are the responsibility of the case providing the context.
* *
* @return The progress indicator. * @return The progress indicator.
*/ */

View File

@ -1,49 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.io.File;
import java.nio.file.Paths;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/**
* Class for methods to check if access should be limited to a feature
*
*/
final class AccessLimiterUtils {
private final static String MULTI_USER_ACCESS_FILE_NAME = "mualimit"; // NON-NLS
private final static String MULTI_USER_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), MULTI_USER_ACCESS_FILE_NAME).toString();
/**
* Check if privileges regarding multi-user cases should be restricted.
*
* @return True if privileges should be restricted, false otherwise.
*/
static boolean limitMultiUserAccess() {
return new File(MULTI_USER_ACCESS_FILE_PATH).exists();
}
/**
* Private constructor for a utility class
*/
private AccessLimiterUtils() {
//private constructer left empty intentionally
}
}

View File

@ -2,14 +2,13 @@ AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules
AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode
# {0} - exception message # {0} - exception message
Case.closeException.couldNotCloseCase=Error closing case: {0} Case.closeException.couldNotCloseCase=Error closing case: {0}
Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory
Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources
Case.deleteCaseConfirmationDialog.message=Are you sure you want to close and delete the current case? Case.deleteCaseConfirmationDialog.message=Are you sure you want to close and delete the current case?
Case.deleteCaseConfirmationDialog.title=Delete Current Case? Case.deleteCaseConfirmationDialog.title=Delete Current Case?
# {0} - exception message # {0} - exception message
Case.deleteCaseFailureMessageBox.message=Error deleting case: {0} Case.deleteCaseFailureMessageBox.message=Error deleting case: {0}
Case.deleteCaseFailureMessageBox.title=Failed to Delete Case Case.deleteCaseFailureMessageBox.title=Failed to Delete Case
Case.exceptionMessage.cancelledByUser=Cancelled by user. Case.exceptionMessage.cancelled=Cancelled.
Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first. Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first.
Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host. Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.
Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window
@ -32,10 +31,13 @@ Case.exceptionMessage.couldNotSaveCaseMetadata=Failed to save case metadata:\n{0
Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}. Case.exceptionMessage.couldNotSaveDbNameToMetadataFile=Failed to save case database name to case metadata file:\n{0}.
# {0} - exception message # {0} - exception message
Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}. Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination service node data:\n{0}.
Case.exceptionMessage.dataSourceNotFound=The data source was not found.
# {0} - case display name # {0} - case display name
Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled. Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled.
Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path.
Case.exceptionMessage.emptyCaseName=Must specify a case name. Case.exceptionMessage.emptyCaseName=Must specify a case name.
Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.
Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.
Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details. Case.exceptionMessage.errorsDeletingCase=Errors occured while deleting the case. See the application log for details.
# {0} - exception message # {0} - exception message
Case.exceptionMessage.execExceptionWrapperMessage={0} Case.exceptionMessage.execExceptionWrapperMessage={0}
@ -50,11 +52,14 @@ Case.exceptionMessage.failedToReadMetadata=Failed to read case metadata:\n{0}.
Case.exceptionMessage.metadataUpdateError=Failed to update case metadata Case.exceptionMessage.metadataUpdateError=Failed to update case metadata
# {0} - exception message # {0} - exception message
Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}. Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.
Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case.
Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.
Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User. Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User.
Case.progressIndicatorCancelButton.label=Cancel Case.progressIndicatorCancelButton.label=Cancel
Case.progressIndicatorTitle.closingCase=Closing Case Case.progressIndicatorTitle.closingCase=Closing Case
Case.progressIndicatorTitle.creatingCase=Creating Case Case.progressIndicatorTitle.creatingCase=Creating Case
Case.progressIndicatorTitle.deletingCase=Deleting Case Case.progressIndicatorTitle.deletingCase=Deleting Case
Case.progressIndicatorTitle.deletingDataSource=Removing Data Source
Case.progressIndicatorTitle.openingCase=Opening Case Case.progressIndicatorTitle.openingCase=Opening Case
Case.progressMessage.cancelling=Cancelling... Case.progressMessage.cancelling=Cancelling...
Case.progressMessage.clearingTempDirectory=Clearing case temp directory... Case.progressMessage.clearingTempDirectory=Clearing case temp directory...
@ -67,6 +72,7 @@ Case.progressMessage.creatingCaseNodeData=Creating coordination service node dat
Case.progressMessage.deletingCaseDatabase=Deleting case database... Case.progressMessage.deletingCaseDatabase=Deleting case database...
Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node... Case.progressMessage.deletingCaseDirCoordSvcNode=Deleting case directory coordination service node...
Case.progressMessage.deletingCaseDirectory=Deleting case directory... Case.progressMessage.deletingCaseDirectory=Deleting case directory...
Case.progressMessage.deletingDataSource=Removing the data source from the case...
Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node... Case.progressMessage.deletingResourcesCoordSvcNode=Deleting case resources coordination service node...
Case.progressMessage.deletingTextIndex=Deleting text index... Case.progressMessage.deletingTextIndex=Deleting text index...
Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case... Case.progressMessage.fetchingCoordSvcNodeData=Fetching coordination service node data for the case...
@ -74,7 +80,6 @@ Case.progressMessage.openingApplicationServiceResources=Opening application serv
Case.progressMessage.openingCaseDatabase=Opening case database... Case.progressMessage.openingCaseDatabase=Opening case database...
Case.progressMessage.openingCaseLevelServices=Opening case-level services... Case.progressMessage.openingCaseLevelServices=Opening case-level services...
Case.progressMessage.preparing=Preparing... Case.progressMessage.preparing=Preparing...
Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>
Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu... Case.progressMessage.removingCaseFromRecentCases=Removing case from Recent Cases menu...
Case.progressMessage.savingCaseMetadata=Saving case metadata to file... Case.progressMessage.savingCaseMetadata=Saving case metadata to file...
Case.progressMessage.settingUpNetworkCommunications=Setting up network communications... Case.progressMessage.settingUpNetworkCommunications=Setting up network communications...
@ -118,6 +123,13 @@ CTL_CaseDetailsAction=Case Details
CTL_CaseDeleteAction=Delete Case CTL_CaseDeleteAction=Delete Case
CTL_CaseOpenAction=Open Case CTL_CaseOpenAction=Open Case
CTL_UnpackagePortableCaseAction=Unpack and Open Portable Case CTL_UnpackagePortableCaseAction=Unpack and Open Portable Case
DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal.
# {0} - exception message
DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to re-open the case:\n{0}\nPlease see the application log for details.
# {0} - exception message
DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while removing the data source:\n{0}\nPlease see the application log for details.
DeleteDataSourceAction.ingestRunningWarningDialog.message=Data sources cannot be removed from a case when ingest is running.
DeleteDataSourceAction.name.text=Remove Data Source
EditOptionalCasePropertiesPanel.cancelButton.text=Cancel EditOptionalCasePropertiesPanel.cancelButton.text=Cancel
EditOptionalCasePropertiesPanel.saveButton.text=Save EditOptionalCasePropertiesPanel.saveButton.text=Save
GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) GeneralFilter.encaseImageDesc.text=Encase Images (*.e01)

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.casemodule;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import com.google.common.annotations.Beta; import com.google.common.annotations.Beta;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData;
@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent; import org.sleuthkit.autopsy.casemodule.events.DataSourceNameChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException;
@ -121,11 +123,14 @@ import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.CaseDbConnectionInfo;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.TimelineManager;
import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskDataException;
import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
/** /**
@ -133,8 +138,8 @@ import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException;
*/ */
public class Case { public class Case {
private static final int DIR_LOCK_TIMOUT_HOURS = 12; private static final int CASE_LOCK_TIMEOUT_MINS = 1;
private static final int RESOURCES_LOCK_TIMOUT_HOURS = 12; private static final int CASE_RESOURCES_LOCK_TIMEOUT_HOURS = 1;
private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db"; private static final String SINGLE_USER_CASE_DB_NAME = "autopsy.db";
private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
private static final String CACHE_FOLDER = "Cache"; //NON-NLS private static final String CACHE_FOLDER = "Cache"; //NON-NLS
@ -153,13 +158,12 @@ public class Case {
private static volatile Frame mainFrame; private static volatile Frame mainFrame;
private static volatile Case currentCase; private static volatile Case currentCase;
private final CaseMetadata metadata; private final CaseMetadata metadata;
private volatile ExecutorService caseLockingExecutor; private volatile ExecutorService caseActionExecutor;
private CoordinationService.Lock caseDirLock; private CoordinationService.Lock caseLock;
private SleuthkitCase caseDb; private SleuthkitCase caseDb;
private final SleuthkitEventListener sleuthkitEventListener;
private CollaborationMonitor collaborationMonitor; private CollaborationMonitor collaborationMonitor;
private Services caseServices; private Services caseServices;
private boolean hasDataSources;
private final TSKCaseRepublisher tskEventForwarder = new TSKCaseRepublisher();
/* /*
* Get a reference to the main window of the desktop application to use to * Get a reference to the main window of the desktop application to use to
@ -167,11 +171,8 @@ public class Case {
* changing the main window title. * changing the main window title.
*/ */
static { static {
WindowManager.getDefault().invokeWhenUIReady(new Runnable() { WindowManager.getDefault().invokeWhenUIReady(() -> {
@Override mainFrame = WindowManager.getDefault().getMainWindow();
public void run() {
mainFrame = WindowManager.getDefault().getMainWindow();
}
}); });
} }
@ -398,30 +399,37 @@ public class Case {
* TimelineEvent that was added. * TimelineEvent that was added.
*/ */
TIMELINE_EVENT_ADDED, TIMELINE_EVENT_ADDED,
/* An item in the central repository has had its comment /*
* modified. The old value is null, the new value is string for current * An item in the central repository has had its comment modified. The
* comment. * old value is null, the new value is string for current comment.
*/ */
CR_COMMENT_CHANGED; CR_COMMENT_CHANGED;
}; };
private final class TSKCaseRepublisher { /**
* An instance of this class is registered as a listener on the event bus
* associated with the case database so that selected SleuthKit layer
* application events can be published as Autopsy application events.
*/
private final class SleuthkitEventListener {
@Subscribe @Subscribe
public void rebroadcastTimelineEventCreated(TimelineManager.TimelineEventAddedEvent event) { public void publishTimelineEventAddedEvent(TimelineManager.TimelineEventAddedEvent event) {
eventPublisher.publish(new TimelineEventAddedEvent(event)); eventPublisher.publish(new TimelineEventAddedEvent(event));
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Subscribe @Subscribe
public void rebroadcastArtifactsPosted(Blackboard.ArtifactsPostedEvent event) { public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) {
for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) { for (BlackboardArtifact.Type artifactType : event.getArtifactTypes()) {
/* /*
* fireModuleDataEvent is deprecated so module writers don't use * IngestServices.fireModuleDataEvent is deprecated to
* it (they should use Blackboard.postArtifact(s) instead), but * discourage ingest module writers from using it (they should
* we still need a way to rebroadcast the ArtifactsPostedEvent * use org.sleuthkit.datamodel.Blackboard.postArtifact(s)
* as a ModuleDataEvent. * instead), but a way to publish
* Blackboard.ArtifactsPostedEvents from the SleuthKit layer as
* Autopsy ModuleDataEvents is still needed.
*/ */
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent( IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
event.getModuleName(), event.getModuleName(),
@ -535,8 +543,8 @@ public class Case {
*/ */
public static boolean isValidName(String caseName) { public static boolean isValidName(String caseName) {
return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":") return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
|| caseName.contains("*") || caseName.contains("?") || caseName.contains("\"") || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
|| caseName.contains("<") || caseName.contains(">") || caseName.contains("|")); || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
} }
/** /**
@ -668,14 +676,15 @@ public class Case {
* method should put all operations in an exception firewall with a try and * method should put all operations in an exception firewall with a try and
* catch-all block to handle the possibility of bad timing. * catch-all block to handle the possibility of bad timing.
* *
* TODO (JIRA-3825): Introduce a reference counting scheme for this get case
* method.
*
* @return The current case. * @return The current case.
* *
* @throws NoCurrentCaseException if there is no current case. * @throws NoCurrentCaseException if there is no current case.
*/ */
public static Case getCurrentCaseThrows() throws NoCurrentCaseException { public static Case getCurrentCaseThrows() throws NoCurrentCaseException {
/*
* TODO (JIRA-3825): Introduce a reference counting scheme for this get
* case method.
*/
Case openCase = currentCase; Case openCase = currentCase;
if (openCase == null) { if (openCase == null) {
throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen")); throw new NoCurrentCaseException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
@ -706,7 +715,7 @@ public class Case {
eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null)); eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), closedCase, null));
logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS logger.log(Level.INFO, "Closing current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
currentCase = null; currentCase = null;
closedCase.close(); closedCase.doCloseCaseAction();
logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS logger.log(Level.INFO, "Closed current case {0} ({1}) in {2}", new Object[]{closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()}); //NON-NLS
} catch (CaseActionException ex) { } catch (CaseActionException ex) {
logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS logger.log(Level.SEVERE, String.format("Error closing current case %s (%s) in %s", closedCase.getDisplayName(), closedCase.getName(), closedCase.getCaseDirectory()), ex); //NON-NLS
@ -738,6 +747,46 @@ public class Case {
} }
} }
/**
* Deletes a data source from the current case.
*
* @param dataSourceObjectID The object ID of the data source to delete.
*
* @throws CaseActionException If there is a problem deleting the case. The
* exception will have a user-friendly message
* and may be a wrapper for a lower-level
* exception.
*/
@Messages({
"Case.progressIndicatorTitle.deletingDataSource=Removing Data Source"
})
public static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
synchronized (caseActionSerializationLock) {
if (null == currentCase) {
return;
}
/*
* Close the current case to release the shared case lock.
*/
CaseMetadata caseMetadata = currentCase.getMetadata();
closeCurrentCase();
/*
* Re-open the case with an exclusive case lock, delete the data
* source, and close the case again, releasing the exclusive case
* lock.
*/
Case theCase = new Case(caseMetadata);
theCase.doOpenCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), theCase::deleteDataSource, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
/*
* Re-open the case with a shared case lock.
*/
openAsCurrentCase(new Case(caseMetadata), false);
}
}
/** /**
* Deletes a case. The case to be deleted must not be the "current case." * Deletes a case. The case to be deleted must not be the "current case."
* Deleting the current case must be done by calling Case.deleteCurrentCase. * Deleting the current case must be done by calling Case.deleteCurrentCase.
@ -799,6 +848,8 @@ public class Case {
* @throws CaseActionCancelledException If creating the case is cancelled. * @throws CaseActionCancelledException If creating the case is cancelled.
*/ */
@Messages({ @Messages({
"Case.progressIndicatorTitle.creatingCase=Creating Case",
"Case.progressIndicatorTitle.openingCase=Opening Case",
"Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window" "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window"
}) })
private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException { private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException {
@ -816,7 +867,16 @@ public class Case {
} }
try { try {
logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS logger.log(Level.INFO, "Opening {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
newCurrentCase.open(isNewCase); String progressIndicatorTitle;
CaseAction<ProgressIndicator, Object, Void> openCaseAction;
if (isNewCase) {
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
openCaseAction = newCurrentCase::create;
} else {
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
openCaseAction = newCurrentCase::open;
}
newCurrentCase.doOpenCaseAction(progressIndicatorTitle, openCaseAction, CaseLockType.SHARED, true, null);
currentCase = newCurrentCase; currentCase = newCurrentCase;
logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS logger.log(Level.INFO, "Opened {0} ({1}) in {2} as the current case", new Object[]{newCurrentCase.getDisplayName(), newCurrentCase.getName(), newCurrentCase.getCaseDirectory()}); //NON-NLS
if (RuntimeProperties.runningWithGUI()) { if (RuntimeProperties.runningWithGUI()) {
@ -978,14 +1038,14 @@ public class Case {
@Messages({ @Messages({
"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources" "Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"
}) })
private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseDir) throws CaseActionException { private static CoordinationService.Lock acquireCaseResourcesLock(String caseDir) throws CaseActionException {
try { try {
Path caseDirPath = Paths.get(caseDir); Path caseDirPath = Paths.get(caseDir);
String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath); String resourcesNodeName = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirPath);
Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, RESOURCES_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, resourcesNodeName, CASE_RESOURCES_LOCK_TIMEOUT_HOURS, TimeUnit.HOURS);
return lock; return lock;
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
} catch (CoordinationServiceException ex) { } catch (CoordinationServiceException ex) {
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex); throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock(), ex);
} }
@ -1049,7 +1109,7 @@ public class Case {
/* /*
* Enable the case-specific actions. * Enable the case-specific actions.
*/ */
CallableSystemAction.get(AddImageAction.class).setEnabled(Case.getCurrentCase().getMetadata().getCaseType() == CaseType.SINGLE_USER_CASE || !AccessLimiterUtils.limitMultiUserAccess()); CallableSystemAction.get(AddImageAction.class).setEnabled(FeatureAccessUtils.canAddDataSources());
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
@ -1366,16 +1426,14 @@ public class Case {
/** /**
* Gets the data sources for the case. * Gets the data sources for the case.
* *
* @return A list of data sources. * @return A list of data sources, possibly empty.
* *
* @throws org.sleuthkit.datamodel.TskCoreException if there is a problem * @throws org.sleuthkit.datamodel.TskCoreException if there is a problem
* querying the case * querying the case
* database. * database.
*/ */
public List<Content> getDataSources() throws TskCoreException { public List<Content> getDataSources() throws TskCoreException {
List<Content> list = caseDb.getRootObjects(); return caseDb.getRootObjects();
hasDataSources = (list.size() > 0);
return list;
} }
/** /**
@ -1416,12 +1474,11 @@ public class Case {
* @return True or false. * @return True or false.
*/ */
public boolean hasData() { public boolean hasData() {
if (!hasDataSources) { boolean hasDataSources = false;
try { try {
hasDataSources = (getDataSources().size() > 0); hasDataSources = (getDataSources().size() > 0);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS logger.log(Level.SEVERE, "Error accessing case database", ex); //NON-NLS
}
} }
return hasDataSources; return hasDataSources;
} }
@ -1699,7 +1756,7 @@ public class Case {
* *
*/ */
private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) { private Case(CaseType caseType, String caseDir, CaseDetails caseDetails) {
metadata = new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails); this(new CaseMetadata(caseType, caseDir, displayNameToUniqueName(caseDetails.getCaseDisplayName()), caseDetails));
} }
/** /**
@ -1709,91 +1766,91 @@ public class Case {
*/ */
private Case(CaseMetadata caseMetaData) { private Case(CaseMetadata caseMetaData) {
metadata = caseMetaData; metadata = caseMetaData;
sleuthkitEventListener = new SleuthkitEventListener();
} }
/** /**
* Opens this case by creating a task running in the same non-UI thread that * Performs a case action that involves creating or opening a case. If the
* will be used to close the case. If the case is a single-user case, this * case is a multi-user case, the action is done after acquiring a
* supports cancelling creation of the case by cancelling the task. If the * coordination service case lock. This case lock must be released in the
* case is a multi-user case, this ensures ensures that case directory lock * same thread in which it was acquired, as required by the coordination
* held as long as the case is open is released in the same thread in which * service. A single-threaded executor is therefore created to do the case
* it was acquired, as is required by the coordination service. * opening action, and is saved for an eventual case closing action.
* *
* @param isNewCase True for a new case, false otherwise. * IMPORTANT: If an open case action for a multi-user case is terminated
* because an exception is thrown or the action is cancelled, the action is
* responsible for releasing the case lock while still running in the case
* action executor's thread. This method assumes this has been done and
* performs an orderly shut down of the case action executor.
* *
* @throws CaseActionException If there is a problem creating the case. The * @param progressIndicatorTitle A title for the progress indicator for the
* exception will have a user-friendly message * case action.
* and may be a wrapper for a lower-level * @param caseAction The case action method.
* exception. * @param caseLockType The type of case lock required for the case
* action.
* @param allowCancellation Whether or not to allow the action to be
* cancelled.
* @param additionalParams An Object that holds any additional
* parameters for a case action.
*
* @throws CaseActionException If there is a problem completing the action.
* The exception will have a user-friendly
* message and may be a wrapper for a
* lower-level exception.
*/ */
@Messages({ @Messages({
"Case.progressIndicatorTitle.creatingCase=Creating Case",
"Case.progressIndicatorTitle.openingCase=Opening Case",
"Case.progressIndicatorCancelButton.label=Cancel", "Case.progressIndicatorCancelButton.label=Cancel",
"Case.progressMessage.preparing=Preparing...", "Case.progressMessage.preparing=Preparing...",
"Case.progressMessage.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
"Case.progressMessage.cancelling=Cancelling...", "Case.progressMessage.cancelling=Cancelling...",
"Case.exceptionMessage.cancelledByUser=Cancelled by user.", "Case.exceptionMessage.cancelled=Cancelled.",
"# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}" "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
}) })
private void open(boolean isNewCase) throws CaseActionException { private void doOpenCaseAction(String progressIndicatorTitle, CaseAction<ProgressIndicator, Object, Void> caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException {
/* /*
* Create and start either a GUI progress indicator with a Cancel button * Create and start either a GUI progress indicator (with or without a
* or a logging progress indicator. * cancel button) or a logging progress indicator.
*/ */
CancelButtonListener cancelButtonListener = null; CancelButtonListener cancelButtonListener = null;
ProgressIndicator progressIndicator; ProgressIndicator progressIndicator;
if (RuntimeProperties.runningWithGUI()) { if (RuntimeProperties.runningWithGUI()) {
cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling()); if (allowCancellation) {
String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase(); cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
progressIndicator = new ModalDialogProgressIndicator( progressIndicator = new ModalDialogProgressIndicator(
mainFrame, mainFrame,
progressIndicatorTitle, progressIndicatorTitle,
new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, new String[]{Bundle.Case_progressIndicatorCancelButton_label()},
Bundle.Case_progressIndicatorCancelButton_label(), Bundle.Case_progressIndicatorCancelButton_label(),
cancelButtonListener); cancelButtonListener);
} else {
progressIndicator = new ModalDialogProgressIndicator(
mainFrame,
progressIndicatorTitle);
}
} else { } else {
progressIndicator = new LoggingProgressIndicator(); progressIndicator = new LoggingProgressIndicator();
} }
progressIndicator.start(Bundle.Case_progressMessage_preparing()); progressIndicator.start(Bundle.Case_progressMessage_preparing());
/* /*
* Creating/opening a case is always done by creating a task running in * Do the case action in the single thread in the case action executor.
* the same non-UI thread that will be used to close the case, so a * If the case is a multi-user case, a case lock is acquired and held
* single-threaded executor service is created here and saved as case * until explictly released and an exclusive case resources lock is
* state (must be volatile for cancellation to work). * aquired and held for the duration of the action.
*
* --- If the case is a single-user case, this supports cancelling
* opening of the case by cancelling the task.
*
* --- If the case is a multi-user case, this still supports
* cancellation, but it also makes it possible for the shared case
* directory lock held as long as the case is open to be released in the
* same thread in which it was acquired, as is required by the
* coordination service.
*/ */
TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName())); TaskThreadFactory threadFactory = new TaskThreadFactory(String.format(CASE_ACTION_THREAD_NAME, metadata.getCaseName()));
caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory); caseActionExecutor = Executors.newSingleThreadExecutor(threadFactory);
Future<Void> future = caseLockingExecutor.submit(() -> { Future<Void> future = caseActionExecutor.submit(() -> {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
open(isNewCase, progressIndicator); caseAction.execute(progressIndicator, additionalParams);
} else { } else {
/* acquireCaseLock(caseLockType);
* First, acquire a shared case directory lock that will be held try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
* as long as this node has this case open. This will prevent
* deletion of the case by another node. Next, acquire an
* exclusive case resources lock to ensure only one node at a
* time can create/open/upgrade/close the case resources.
*/
progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
acquireSharedCaseDirLock(metadata.getCaseDirectory());
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) {
if (null == resourcesLock) { if (null == resourcesLock) {
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
} }
open(isNewCase, progressIndicator); caseAction.execute(progressIndicator, additionalParams);
} catch (CaseActionException ex) { } catch (CaseActionException ex) {
releaseSharedCaseDirLock(getMetadata().getCaseDirectory()); releaseCaseLock();
throw ex; throw ex;
} }
} }
@ -1804,44 +1861,31 @@ public class Case {
} }
/* /*
* Wait for the case creation/opening task to finish. * Wait for the case action task to finish.
*/ */
try { try {
future.get(); future.get();
} catch (InterruptedException discarded) { } catch (InterruptedException discarded) {
/* /*
* The thread this method is running in has been interrupted. Cancel * The thread this method is running in has been interrupted.
* the create/open task, wait for it to finish, and shut down the
* executor. This can be done safely because if the task is
* completed with a cancellation condition, the case will have been
* closed and the case directory lock released will have been
* released.
*/ */
if (null != cancelButtonListener) { if (null != cancelButtonListener) {
cancelButtonListener.actionPerformed(null); cancelButtonListener.actionPerformed(null);
} else { } else {
future.cancel(true); future.cancel(true);
} }
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
} catch (CancellationException discarded) { } catch (CancellationException discarded) {
/* /*
* The create/open task has been cancelled. Wait for it to finish, * The case action has been cancelled.
* and shut down the executor. This can be done safely because if
* the task is completed with a cancellation condition, the case
* will have been closed and the case directory lock released will
* have been released.
*/ */
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
/* /*
* The create/open task has thrown an exception. Wait for it to * The case action has thrown an exception.
* finish, and shut down the executor. This can be done safely
* because if the task is completed with an execution condition, the
* case will have been closed and the case directory lock released
* will have been released.
*/ */
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
} finally { } finally {
progressIndicator.finish(); progressIndicator.finish();
@ -1849,55 +1893,48 @@ public class Case {
} }
/** /**
* Opens the case database and services for this case. * A case action (interface CaseAction<T, V, R>) that creates the case
* directory and case database and opens the application services for this
* case.
* *
* @param isNewCase True for a new case, false otherwise.
* @param progressIndicator A progress indicator. * @param progressIndicator A progress indicator.
* @param additionalParams An Object that holds any additional parameters
* for a case action. For this action, this is
* null.
* *
* @throws CaseActionException If there is a problem creating the case. The * @throws CaseActionException If there is a problem completing the action.
* exception will have a user-friendly message * The exception will have a user-friendly
* and may be a wrapper for a lower-level * message and may be a wrapper for a
* exception. * lower-level exception.
*/ */
private void open(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException { private Void create(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams == null);
try { try {
checkForUserCancellation(); checkForCancellation();
createCaseDirectoryIfDoesNotExist(progressIndicator); createCaseDirectoryIfDoesNotExist(progressIndicator);
checkForUserCancellation(); checkForCancellation();
switchLoggingToCaseLogsDirectory(progressIndicator); switchLoggingToCaseLogsDirectory(progressIndicator);
checkForUserCancellation(); checkForCancellation();
if (isNewCase) { saveCaseMetadataToFile(progressIndicator);
saveCaseMetadataToFile(progressIndicator); checkForCancellation();
} createCaseNodeData(progressIndicator);
checkForUserCancellation(); checkForCancellation();
if (isNewCase) { checkForCancellation();
createCaseNodeData(progressIndicator); createCaseDatabase(progressIndicator);
} else { checkForCancellation();
updateCaseNodeData(progressIndicator);
}
checkForUserCancellation();
if (!isNewCase) {
deleteTempfilesFromCaseDirectory(progressIndicator);
}
checkForUserCancellation();
if (isNewCase) {
createCaseDatabase(progressIndicator);
} else {
openCaseDataBase(progressIndicator);
}
checkForUserCancellation();
openCaseLevelServices(progressIndicator); openCaseLevelServices(progressIndicator);
checkForUserCancellation(); checkForCancellation();
openAppServiceCaseResources(progressIndicator); openAppServiceCaseResources(progressIndicator);
checkForUserCancellation(); checkForCancellation();
openCommunicationChannels(progressIndicator); openCommunicationChannels(progressIndicator);
return null;
} catch (CaseActionException ex) { } catch (CaseActionException ex) {
/* /*
* Cancellation or failure. Clean up by calling the close method. * Cancellation or failure. The sleep is a little hack to clear the
* The sleep is a little hack to clear the interrupted flag for this * interrupted flag for this thread if this is a cancellation
* thread if this is a cancellation scenario, so that the clean up * scenario, so that the clean up can run to completion in the
* can run to completion in the current thread. * current thread.
*/ */
try { try {
Thread.sleep(1); Thread.sleep(1);
@ -1908,6 +1945,102 @@ public class Case {
} }
} }
/**
* A case action (interface CaseAction<T, V, R>) that opens the case
* database and application services for this case.
*
* @param progressIndicator A progress indicator.
* @param additionalParams An Object that holds any additional parameters
* for a case action. For this action, this is
* null.
*
* @throws CaseActionException If there is a problem completing the action.
* The exception will have a user-friendly
* message and may be a wrapper for a
* lower-level exception.
*/
private Void open(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams == null);
try {
checkForCancellation();
switchLoggingToCaseLogsDirectory(progressIndicator);
checkForCancellation();
updateCaseNodeData(progressIndicator);
checkForCancellation();
deleteTempfilesFromCaseDirectory(progressIndicator);
checkForCancellation();
openCaseDataBase(progressIndicator);
checkForCancellation();
openCaseLevelServices(progressIndicator);
checkForCancellation();
openAppServiceCaseResources(progressIndicator);
checkForCancellation();
openCommunicationChannels(progressIndicator);
return null;
} catch (CaseActionException ex) {
/*
* Cancellation or failure. The sleep is a little hack to clear the
* interrupted flag for this thread if this is a cancellation
* scenario, so that the clean up can run to completion in the
* current thread.
*/
try {
Thread.sleep(1);
} catch (InterruptedException discarded) {
}
close(progressIndicator);
throw ex;
}
}
/**
* A case action (interface CaseAction<T, V, R>) that opens a case, deletes
* a data source from the case, and closes the case.
*
* @param progressIndicator A progress indicator.
* @param additionalParams An Object that holds any additional parameters
* for a case action. For this action, this the
* object ID of the data source to be deleted.
*
* @throws CaseActionException If there is a problem completing the action.
* The exception will have a user-friendly
* message and may be a wrapper for a
* lower-level exception.
*/
@Messages({
"Case.progressMessage.deletingDataSource=Removing the data source from the case...",
"Case.exceptionMessage.dataSourceNotFound=The data source was not found.",
"Case.exceptionMessage.errorDeletingDataSourceFromCaseDb=An error occurred while removing the data source from the case database.",
"Case.exceptionMessage.errorDeletingDataSourceFromTextIndex=An error occurred while removing the data source from the text index.",})
Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams instanceof Long);
open(progressIndicator, null);
try {
progressIndicator.progress(Bundle.Case_progressMessage_deletingDataSource());
Long dataSourceObjectID = (Long) additionalParams;
try {
DataSource dataSource = this.caseDb.getDataSource(dataSourceObjectID);
if (dataSource == null) {
throw new CaseActionException(Bundle.Case_exceptionMessage_dataSourceNotFound());
}
SleuthkitCaseAdminUtil.deleteDataSource(this.caseDb, dataSourceObjectID);
} catch (TskDataException | TskCoreException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromCaseDb(), ex);
}
try {
this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
} catch (KeywordSearchServiceException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSourceFromTextIndex(), ex);
}
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
return null;
} finally {
close(progressIndicator);
releaseCaseLock();
}
}
/** /**
* Create an empty portable case from the current case * Create an empty portable case from the current case
* *
@ -1954,18 +2087,15 @@ public class Case {
* interrupted, assumes interrupt was * interrupted, assumes interrupt was
* due to a user action. * due to a user action.
*/ */
private static void checkForUserCancellation() throws CaseActionCancelledException { private static void checkForCancellation() throws CaseActionCancelledException {
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelled());
} }
} }
/** /**
* Creates the case directory, if it does not already exist. * Creates the case directory, if it does not already exist.
* *
* TODO (JIRA-2180): Always create the case directory as part of the case
* creation process.
*
* @param progressIndicator A progress indicator. * @param progressIndicator A progress indicator.
* *
* @throws CaseActionException If there is a problem completing the * @throws CaseActionException If there is a problem completing the
@ -1977,6 +2107,10 @@ public class Case {
"Case.progressMessage.creatingCaseDirectory=Creating case directory..." "Case.progressMessage.creatingCaseDirectory=Creating case directory..."
}) })
private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException { private void createCaseDirectoryIfDoesNotExist(ProgressIndicator progressIndicator) throws CaseActionException {
/*
* TODO (JIRA-2180): Always create the case directory as part of the
* case creation process.
*/
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
if (new File(metadata.getCaseDirectory()).exists() == false) { if (new File(metadata.getCaseDirectory()).exists() == false) {
progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory()); progressIndicator.progress(Bundle.Case_progressMessage_creatingCaseDirectory());
@ -2166,6 +2300,7 @@ public class Case {
} else { } else {
throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled()); throw new CaseActionException(Bundle.Case_open_exception_multiUserCaseNotEnabled());
} }
caseDb.registerForEvents(sleuthkitEventListener);
} catch (TskUnsupportedSchemaVersionException ex) { } catch (TskUnsupportedSchemaVersionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_unsupportedSchemaVersionMessage(ex.getLocalizedMessage()), ex);
} catch (UserPreferencesException ex) { } catch (UserPreferencesException ex) {
@ -2186,8 +2321,6 @@ public class Case {
private void openCaseLevelServices(ProgressIndicator progressIndicator) { private void openCaseLevelServices(ProgressIndicator progressIndicator) {
progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices()); progressIndicator.progress(Bundle.Case_progressMessage_openingCaseLevelServices());
this.caseServices = new Services(caseDb); this.caseServices = new Services(caseDb);
caseDb.registerForEvents(tskEventForwarder);
} }
/** /**
@ -2217,7 +2350,9 @@ public class Case {
* one starts by awaiting termination of the executor service. * one starts by awaiting termination of the executor service.
*/ */
progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources()); progressIndicator.progress(Bundle.Case_progressMessage_openingApplicationServiceResources());
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) {
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
)) {
/* /*
* Create a progress indicator for the task and start the task. If * Create a progress indicator for the task and start the task. If
* running with a GUI, the progress indicator will be a dialog box * running with a GUI, the progress indicator will be a dialog box
@ -2295,7 +2430,7 @@ public class Case {
ThreadUtils.shutDownTaskExecutor(executor); ThreadUtils.shutDownTaskExecutor(executor);
appServiceProgressIndicator.finish(); appServiceProgressIndicator.finish();
} }
checkForUserCancellation(); checkForCancellation();
} }
} }
@ -2320,7 +2455,7 @@ public class Case {
progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications()); progressIndicator.progress(Bundle.Case_progressMessage_settingUpNetworkCommunications());
try { try {
eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName())); eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, metadata.getCaseName()));
checkForUserCancellation(); checkForCancellation();
collaborationMonitor = new CollaborationMonitor(metadata.getCaseName()); collaborationMonitor = new CollaborationMonitor(metadata.getCaseName());
} catch (AutopsyEventException ex) { } catch (AutopsyEventException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_couldNotOpenRemoteEventChannel(ex.getLocalizedMessage()), ex);
@ -2331,14 +2466,20 @@ public class Case {
} }
/** /**
* Closes the case. * Performs a case action that involves closing a case opened by calling
* doOpenCaseAction. If the case is a multi-user case, the coordination
* service case lock acquired by the call to doOpenCaseAction is released.
* This case lock must be released in the same thread in which it was
* acquired, as required by the coordination service. The single-threaded
* executor saved during the case opening action is therefore used to do the
* case closing action.
* *
* @throws CaseActionException If there is a problem completing the * @throws CaseActionException If there is a problem completing the action.
* operation. The exception will have a * The exception will have a user-friendly
* user-friendly message and may be a wrapper * message and may be a wrapper for a
* for a lower-level exception. * lower-level exception.
*/ */
private void close() throws CaseActionException { private void doCloseCaseAction() throws CaseActionException {
/* /*
* Set up either a GUI progress indicator without a Cancel button or a * Set up either a GUI progress indicator without a Cancel button or a
* logging progress indicator. * logging progress indicator.
@ -2356,11 +2497,11 @@ public class Case {
/* /*
* Closing a case is always done in the same non-UI thread that * Closing a case is always done in the same non-UI thread that
* opened/created the case. If the case is a multi-user case, this * opened/created the case. If the case is a multi-user case, this
* ensures that case directory lock that is held as long as the case is * ensures that case lock that is held as long as the case is open is
* open is released in the same thread in which it was acquired, as is * released in the same thread in which it was acquired, as is required
* required by the coordination service. * by the coordination service.
*/ */
Future<Void> future = caseLockingExecutor.submit(() -> { Future<Void> future = caseActionExecutor.submit(() -> {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
close(progressIndicator); close(progressIndicator);
} else { } else {
@ -2370,7 +2511,7 @@ public class Case {
* resources. * resources.
*/ */
progressIndicator.progress(Bundle.Case_progressMessage_preparing()); progressIndicator.progress(Bundle.Case_progressMessage_preparing());
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) { try (CoordinationService.Lock resourcesLock = acquireCaseResourcesLock(metadata.getCaseDirectory())) {
if (null == resourcesLock) { if (null == resourcesLock) {
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock());
} }
@ -2380,7 +2521,7 @@ public class Case {
* Always release the case directory lock that was acquired * Always release the case directory lock that was acquired
* when the case was opened. * when the case was opened.
*/ */
releaseSharedCaseDirLock(metadata.getCaseName()); releaseCaseLock();
} }
} }
return null; return null;
@ -2392,13 +2533,13 @@ public class Case {
/* /*
* The wait has been interrupted by interrupting the thread running * The wait has been interrupted by interrupting the thread running
* this method. Not allowing cancellation of case closing, so ignore * this method. Not allowing cancellation of case closing, so ignore
* the interrupt. Likewsie, cancellation of the case closing task is * the interrupt. Likewise, cancellation of the case closing task is
* not supported. * not supported.
*/ */
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getMessage()), ex);
} finally { } finally {
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseActionExecutor);
progressIndicator.finish(); progressIndicator.finish();
} }
} }
@ -2440,7 +2581,7 @@ public class Case {
*/ */
if (null != caseDb) { if (null != caseDb) {
progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase()); progressIndicator.progress(Bundle.Case_progressMessage_closingCaseDatabase());
caseDb.unregisterForEvents(tskEventForwarder); caseDb.unregisterForEvents(sleuthkitEventListener);
caseDb.close(); caseDb.close();
} }
@ -2464,7 +2605,8 @@ public class Case {
* Each service gets its own independently cancellable task, and thus * Each service gets its own independently cancellable task, and thus
* its own task progress indicator. * its own task progress indicator.
*/ */
for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class)) { for (AutopsyService service : Lookup.getDefault().lookupAll(AutopsyService.class
)) {
ProgressIndicator progressIndicator; ProgressIndicator progressIndicator;
if (RuntimeProperties.runningWithGUI()) { if (RuntimeProperties.runningWithGUI()) {
progressIndicator = new ModalDialogProgressIndicator( progressIndicator = new ModalDialogProgressIndicator(
@ -2504,37 +2646,47 @@ public class Case {
} }
/** /**
* Acquires a shared case directory lock for the current case. * Acquires a case (case directory) lock for the current case.
* *
* @param caseDir The full path of the case directory. * @throws CaseActionException If the lock cannot be acquired.
*
* @throws CaseActionException with a user-friendly message if the lock
* cannot be acquired.
*/ */
@Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"}) @Messages({
private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { "Case.lockingException.couldNotAcquireSharedLock=Failed to get an shared lock on the case.",
"Case.lockingException.couldNotAcquireExclusiveLock=Failed to get a exclusive lock on the case."
})
private void acquireCaseLock(CaseLockType lockType) throws CaseActionException {
String caseDir = metadata.getCaseDirectory();
try { try {
caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); CoordinationService coordinationService = CoordinationService.getInstance();
if (null == caseDirLock) { caseLock = lockType == CaseLockType.SHARED
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); ? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES)
: coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, CASE_LOCK_TIMEOUT_MINS, TimeUnit.MINUTES);
if (caseLock == null) {
if (lockType == CaseLockType.SHARED) {
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock());
} else {
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock());
}
} }
} catch (InterruptedException | CoordinationServiceException ex) { } catch (InterruptedException | CoordinationServiceException ex) {
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock(), ex); if (lockType == CaseLockType.SHARED) {
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireSharedLock(), ex);
} else {
throw new CaseActionException(Bundle.Case_lockingException_couldNotAcquireExclusiveLock(), ex);
}
} }
} }
/** /**
* Releases a shared case directory lock for the current case. * Releases a case (case directory) lock for the current case.
*
* @param caseDir The full path of the case directory.
*/ */
private void releaseSharedCaseDirLock(String caseDir) { private void releaseCaseLock() {
if (caseDirLock != null) { if (caseLock != null) {
try { try {
caseDirLock.release(); caseLock.release();
caseDirLock = null; caseLock = null;
} catch (CoordinationService.CoordinationServiceException ex) { } catch (CoordinationService.CoordinationServiceException ex) {
logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", caseDir), ex); logger.log(Level.SEVERE, String.format("Failed to release shared case directory lock for %s", getMetadata().getCaseDirectory()), ex);
} }
} }
} }
@ -2807,7 +2959,9 @@ public class Case {
}) })
private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException { private static void deleteTextIndex(CaseMetadata metadata, ProgressIndicator progressIndicator) throws KeywordSearchServiceException {
progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex()); progressIndicator.progress(Bundle.Case_progressMessage_deletingTextIndex());
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class)) {
for (KeywordSearchService searchService : Lookup.getDefault().lookupAll(KeywordSearchService.class
)) {
searchService.deleteTextIndex(metadata); searchService.deleteTextIndex(metadata);
} }
} }
@ -2908,9 +3062,41 @@ public class Case {
CaseNodeData.writeCaseNodeData(caseNodeData); CaseNodeData.writeCaseNodeData(caseNodeData);
} catch (CaseNodeDataException ex) { } catch (CaseNodeDataException ex) {
logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex); logger.log(Level.SEVERE, String.format("Error updating deleted item flag %s for %s (%s) in %s", flag.name(), caseNodeData.getDisplayName(), caseNodeData.getName(), caseNodeData.getDirectory()), ex);
} }
} }
/**
* Defines the signature for case action methods that can be passed as
* arguments to the doCaseAction method.
*
* @param <T> A ProgressIndicator
* @param <V> The optional parameters stored in an Object.
* @param <R> The return type of Void.
*/
private interface CaseAction<T, V, R> {
/**
* The signature for a case action method.
*
* @param progressIndicator A ProgressIndicator.
* @param additionalParams The optional parameters stored in an Object.
*
* @return A Void object (null).
*
* @throws CaseActionException
*/
R execute(T progressIndicator, V additionalParams) throws CaseActionException;
}
/**
* The choices for the case (case directory) coordination service lock used
* for multi-user cases.
*/
private enum CaseLockType {
SHARED, EXCLUSIVE;
}
/** /**
* A case operation Cancel button listener for use with a * A case operation Cancel button listener for use with a
* ModalDialogProgressIndicator when running with a GUI. * ModalDialogProgressIndicator when running with a GUI.

View File

@ -0,0 +1,152 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.awt.event.ActionEvent;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import org.sleuthkit.autopsy.ingest.IngestManager;
/**
* An Action that allows a user to remove a data source from the current case.
*/
public final class DeleteDataSourceAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DeleteDataSourceAction.class.getName());
private long dataSourceObjectID;
private Path caseMetadataFilePath;
/**
* Constructs an Action that allows a user to remove a data source from a
* case.
*
* @param dataSourceObjectID The object ID of the data source to be removed.
*/
@NbBundle.Messages({
"DeleteDataSourceAction.name.text=Remove Data Source"
})
public DeleteDataSourceAction(Long dataSourceObjectID) {
super(Bundle.DeleteDataSourceAction_name_text());
this.dataSourceObjectID = dataSourceObjectID;
this.setEnabled(FeatureAccessUtils.canDeleteDataSources());
}
@NbBundle.Messages({
"DeleteDataSourceAction.ingestRunningWarningDialog.message=Data sources cannot be removed from a case when ingest is running.",
"DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to remove the selected data source from the case?\nNote that the case will be closed and re-opened during the removal.",
"# {0} - exception message", "DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while removing the data source:\n{0}\nPlease see the application log for details.",
"# {0} - exception message", "DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to re-open the case:\n{0}\nPlease see the application log for details."
})
@Override
public void actionPerformed(ActionEvent event) {
if (IngestManager.getInstance().isIngestRunning()) {
MessageNotifyUtil.Message.warn(Bundle.DeleteDataSourceAction_ingestRunningWarningDialog_message());
return;
}
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteDataSourceAction_confirmationDialog_message())) {
new DataSourceDeletionWorker().execute();
}
}
/**
* A SwingWorker to do the data source deletion.
*/
private class DataSourceDeletionWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
/*
* Save the case metadata file path so the case can be reopened if
* something goes wrong and the case ends up closed.
*/
caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath();
Case.deleteDataSourceFromCurrentCase(dataSourceObjectID);
return null;
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_dataSourceDeletionError(ex.getLocalizedMessage()), MessageNotifyUtil.MessageType.ERROR);
if (!Case.isCaseOpen()) {
new CaseReopeningWorker().execute();
}
}
}
}
/**
* A SwingWorker to attempt to re-open the case after a data source deletion
* exception.
*/
private class CaseReopeningWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
Case.openAsCurrentCase(caseMetadataFilePath.toString());
return null;
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException ex) {
logger.log(Level.WARNING, String.format("Interrupted reopening case after error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Error reopening case after error deleting data source (obj_id=%d)", dataSourceObjectID), ex);
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_dataSourceDeletionError(ex.getCause().getLocalizedMessage()), MessageNotifyUtil.MessageType.ERROR);
StartupWindowProvider.getInstance().open();
}
}
}
@Override
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
clonedObject.setDataSourceID(this.dataSourceObjectID);
return clonedObject;
}
/**
* Allows the setting of the data source object ID field of a clone of this
* action.
*
* @param dataSourceObjectID The data source object ID.
*/
private void setDataSourceID(long dataSourceObjectID) {
this.dataSourceObjectID = dataSourceObjectID;
}
}

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.casemodule;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import java.awt.Component; import java.awt.Component;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
@ -61,7 +62,7 @@ final class NewCaseVisualPanel1 extends JPanel implements DocumentListener {
*/ */
void readSettings() { void readSettings() {
caseNameTextField.setText(""); caseNameTextField.setText("");
if (UserPreferences.getIsMultiUserModeEnabled() && !AccessLimiterUtils.limitMultiUserAccess()) { if (FeatureAccessUtils.canCreateMultiUserCases()) {
multiUserCaseRadioButton.setEnabled(true); multiUserCaseRadioButton.setEnabled(true);
multiUserCaseRadioButton.setSelected(true); multiUserCaseRadioButton.setSelected(true);
} else { } else {

View File

@ -0,0 +1,53 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.events;
import java.io.Serializable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.events.AutopsyEvent;
/**
* An application event that is published when a data source has been deleted.
*/
public class DataSourceDeletedEvent extends AutopsyEvent implements Serializable {
private static final long serialVersionUID = 1L;
private final long dataSourceID;
/**
* Constructs an application event that is published when a data source has
* been deleted.
*
* @param dataSourceId The object ID of the data source that was deleted.
*/
public DataSourceDeletedEvent(Long dataSourceId) {
super(Case.Events.DATA_SOURCE_DELETED.toString(), dataSourceId, null);
this.dataSourceID = dataSourceId;
}
/**
* Gets the object ID of the data source that was deleted.
*
* @return The data source id.
*/
public long getDataSourceId() {
return dataSourceID;
}
}

View File

@ -103,8 +103,8 @@ public class Services implements Closeable {
/** /**
* Closes the services for the current case. * Closes the services for the current case.
* *
* @throws IOException if there is a problem closing the services. * @throws IOException if there is a problem closing the services.
* @deprecated Do not use. * @deprecated Do not use.
*/ */
@Deprecated @Deprecated
@Override @Override

View File

@ -5,10 +5,7 @@ CentralRepoCommentDialog.title.addEditCentralRepoComment=Add/Edit Central Reposi
OpenIDE-Module-Name=Central Repository OpenIDE-Module-Name=Central Repository
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Short-Description=Correlation Engine Ingest Module OpenIDE-Module-Short-Description=Correlation Engine Ingest Module
OpenIDE-Module-Long-Description=\ OpenIDE-Module-Long-Description=Correlation Engine ingest module and central database. \n\nThe Correlation Engine ingest module stores attributes of artifacts matching selected correlation types into a central database.\nStored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
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\
Stored attributes are used in future cases to correlate and analyzes files and artifacts during ingest.
CentralRepoCommentDialog.commentLabel.text=Comment: CentralRepoCommentDialog.commentLabel.text=Comment:
CentralRepoCommentDialog.okButton.text=&OK CentralRepoCommentDialog.okButton.text=&OK
CentralRepoCommentDialog.cancelButton.text=C&ancel CentralRepoCommentDialog.cancelButton.text=C&ancel

View File

@ -3,13 +3,7 @@ Installer.closing.confirmationDialog.title=Ingest is Running
# {0} - exception message # {0} - exception message
Installer.closing.messageBox.caseCloseExceptionMessage=Error closing case: {0} Installer.closing.messageBox.caseCloseExceptionMessage=Error closing case: {0}
OpenIDE-Module-Display-Category=Infrastructure OpenIDE-Module-Display-Category=Infrastructure
OpenIDE-Module-Long-Description=\ OpenIDE-Module-Long-Description=This is the core Autopsy module.\n\nThe module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\nThe framework included in the module contains APIs for developing modules for ingest, viewers and reporting. The modules can be deployed as Plugins using the Autopsy plugin installer.\nThis module should not be uninstalled - without it, Autopsy will not run.\n\nFor more information, see http://www.sleuthkit.org/autopsy/
This is the core Autopsy module.\n\n\
The module contains the core components needed for the bare application to run; the RCP platform, windowing GUI, sleuthkit bindings, datamodel / storage, explorer, result viewers, content viewers, ingest framework, reporting, and core tools, such as the file search.\n\n\
The framework included in the module contains APIs for developing modules for ingest, viewers and reporting. \
The modules can be deployed as Plugins using the Autopsy plugin installer.\n\
This module should not be uninstalled - without it, Autopsy will not run.\n\n\
For more information, see http://www.sleuthkit.org/autopsy/
OpenIDE-Module-Name=Autopsy-Core OpenIDE-Module-Name=Autopsy-Core
OpenIDE-Module-Short-Description=Autopsy Core Module OpenIDE-Module-Short-Description=Autopsy Core Module
org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml

View File

@ -63,9 +63,9 @@ DataContentViewerHex.totalPageLabel.text_1=100
DataContentViewerHex.pageLabel2.text=Page DataContentViewerHex.pageLabel2.text=Page
# Product Information panel # Product Information panel
LBL_Description=<div style=\"font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;\">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div> LBL_Description=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div>
Format_OperatingSystem_Value={0} version {1} running on {2} Format_OperatingSystem_Value={0} version {1} running on {2}
LBL_Copyright=<div style\="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy&trade; is a digital forensics platform based on The Sleuth Kit&trade; and other tools. <br><ul><li>General Information: <a style\="color: \#1E2A60;" href\="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style\="color: \#1E2A60;" href\="http://www.basistech.com/autopsy-training">http://www.basistech.com/autopsy-training</a></li><li>Commercial Support: <a style\="color: \#1E2A60;" href\="http://www.basistech.com/digital-forensics/autopsy/support/">http://www.basistech.com/digital-forensics/autopsy/support/</a></li></ul>Copyright &copy; 2003-2018. </div> LBL_Copyright=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy&trade; is a digital forensics platform based on The Sleuth Kit&trade; and other tools. <br><ul><li>General Information: <a style="color: #1E2A60;" href="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style="color: #1E2A60;" href="http://www.basistech.com/autopsy-training">http://www.basistech.com/autopsy-training</a></li><li>Commercial Support: <a style="color: #1E2A60;" href="http://www.basistech.com/digital-forensics/autopsy/support/">http://www.basistech.com/digital-forensics/autopsy/support/</a></li></ul>Copyright &copy; 2003-2018. </div>
SortChooser.dialogTitle=Choose Sort Criteria SortChooser.dialogTitle=Choose Sort Criteria
ThumbnailViewChildren.progress.cancelling=(Cancelling) ThumbnailViewChildren.progress.cancelling=(Cancelling)
# {0} - file name # {0} - file name
@ -95,7 +95,7 @@ DataResultViewerThumbnail.pageNextButton.text=
DataResultViewerThumbnail.imagesLabel.text=Images: DataResultViewerThumbnail.imagesLabel.text=Images:
DataResultViewerThumbnail.imagesRangeLabel.text=- DataResultViewerThumbnail.imagesRangeLabel.text=-
DataResultViewerThumbnail.pageNumLabel.text=- DataResultViewerThumbnail.pageNumLabel.text=-
DataResultViewerThumbnail.filePathLabel.text=\ \ \ DataResultViewerThumbnail.filePathLabel.text=\
DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageLabel.text=Go to Page:
DataResultViewerThumbnail.goToPageField.text= DataResultViewerThumbnail.goToPageField.text=
AdvancedConfigurationDialog.cancelButton.text=Cancel AdvancedConfigurationDialog.cancelButton.text=Cancel

View File

@ -30,9 +30,7 @@ PlatformUtil.getProcVmUsed.sigarNotInit.msg=Cannot get virt mem used, sigar not
PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0} PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0}
PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1} PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1}
PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2} PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2}
PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ PlatformUtil.getAllMemUsageInfo.usageText={0}\n{1}\nProcess Virtual Memory: {2}
{1}\n\
Process Virtual Memory: {2}
# {0} - file name # {0} - file name
ReadImageTask.mesageText=Reading image: {0} ReadImageTask.mesageText=Reading image: {0}
StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract

View File

@ -44,7 +44,7 @@ final public class ThreadUtils {
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
/* /*
* Ignore interrupts. The policy implemented by this method is * Ignore interrupts. The policy implemented by this method is
* an unconditional wait.: * an unconditional wait.
*/ */
} }
} }

View File

@ -43,7 +43,6 @@ ArtifactStringContent.attrsTableHeader.type=Type
ArtifactStringContent.attrsTableHeader.value=Value ArtifactStringContent.attrsTableHeader.value=Value
ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database
ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database
# {0} - node name
BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0} BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}
BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details
BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details
@ -264,10 +263,10 @@ ImageNode.getActions.viewInNewWin.text=View in New Window
ImageNode.createSheet.name.name=Name ImageNode.createSheet.name.name=Name
ImageNode.createSheet.name.displayName=Name ImageNode.createSheet.name.displayName=Name
ImageNode.createSheet.name.desc=no description ImageNode.createSheet.name.desc=no description
Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null\! Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null!
Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""\! Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""!
Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n\nDetails: {0} Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed!\n\nDetails: {0}
Installer.tskLibErr.err=Fatal Error\! Installer.tskLibErr.err=Fatal Error!
InterestingHits.interestingItems.text=INTERESTING ITEMS InterestingHits.interestingItems.text=INTERESTING ITEMS
InterestingHits.displayName.text=Interesting Items InterestingHits.displayName.text=Interesting Items
InterestingHits.createSheet.name.name=Name InterestingHits.createSheet.name.name=Name

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2019 Basis Technology Corp. * Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -34,6 +34,7 @@ import org.openide.nodes.Sheet;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
@ -105,7 +106,7 @@ public class ImageNode extends AbstractContentNode<Image> {
*/ */
@Override @Override
@Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules", @Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules",
"ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes",}) "ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes"})
public Action[] getActions(boolean context) { public Action[] getActions(boolean context) {
List<Action> actionsList = new ArrayList<>(); List<Action> actionsList = new ArrayList<>();
@ -113,13 +114,12 @@ public class ImageNode extends AbstractContentNode<Image> {
actionsList.add(a); actionsList.add(a);
} }
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
actionsList.add(new FileSearchAction( actionsList.add(new FileSearchAction(Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
actionsList.add(new ViewSummaryInformationAction(content.getId())); actionsList.add(new ViewSummaryInformationAction(content.getId()));
actionsList.add(new RunIngestModulesAction(Collections.<Content>singletonList(content))); actionsList.add(new RunIngestModulesAction(Collections.<Content>singletonList(content)));
actionsList.add(new NewWindowViewAction( actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this));
NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this)); actionsList.add(new DeleteDataSourceAction(content.getId()));
return actionsList.toArray(new Action[actionsList.size()]); return actionsList.toArray(new Action[0]);
} }
@Override @Override

View File

@ -0,0 +1,102 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.featureaccess;
import java.io.File;
import java.nio.file.Paths;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
/**
* Check if access to various features is permitted for the current user and the
* current case, if any.
*
* IMPORTANT: These utilities are not concerned with transitory restrictions on
* access to a feature, e.g., whether or not ingest is running.
*/
final public class FeatureAccessUtils {
private final static String MULTIUSER_CASE_RESTRICTED_FILE_NAME = "mualimit"; // NON-NLS
private final static String MULTIUSER_CASE_RESTRICTED_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), MULTIUSER_CASE_RESTRICTED_FILE_NAME).toString();
private static final int DATA_SRC_DEL_MIN_DB_MAJOR_VER = 8;
private static final int DATA_SRC_DEL_MIN_DB_MINOR_VER = 4;
/**
* Indicates whether or not a user can create multi-user cases.
*
* @return True or false.
*/
public static boolean canCreateMultiUserCases() {
return UserPreferences.getIsMultiUserModeEnabled() && multiUserCaseRestrictionsFileAbsent();
}
/**
* Indicates whether or not a user can add data sources to a case.
*
* @return True or false.
*/
public static boolean canAddDataSources() {
return currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent();
}
/**
* Indicates whether or not a user can delete data sources from a case.
*
* @return True or false.
*/
public static boolean canDeleteDataSources() {
boolean dataSourceDeletionAllowed = false;
if (Case.isCaseOpen()) {
CaseDbSchemaVersionNumber version = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
dataSourceDeletionAllowed
= ((version.getMajor() > DATA_SRC_DEL_MIN_DB_MAJOR_VER) || (version.getMajor() == DATA_SRC_DEL_MIN_DB_MAJOR_VER && version.getMinor() >= DATA_SRC_DEL_MIN_DB_MINOR_VER))
&& (currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent());
}
return dataSourceDeletionAllowed;
}
/**
* Indicates whether or not the current case is a single-user case.
*
* @return True or false.
*/
private static boolean currentCaseIsSingleUserCase() {
return Case.isCaseOpen() && Case.getCurrentCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE;
}
/**
* Indicates whether or not the current user is allowed to create or modify
* (add or delete data sources) multi-user cases.
*
* @return True or false.
*/
public static boolean multiUserCaseRestrictionsFileAbsent() {
File accessLimitingFile = new File(MULTIUSER_CASE_RESTRICTED_FILE_PATH);
return !accessLimitingFile.exists();
}
/**
* Private constructor to prevent instantiation of this utility class.
*/
private FeatureAccessUtils() {
}
}

View File

@ -14,7 +14,7 @@ KnownStatusSearchPanel.knownCheckBox.text=Known Status:
KnownStatusSearchPanel.knownBadOptionCheckBox.text=Notable KnownStatusSearchPanel.knownBadOptionCheckBox.text=Notable
KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL or other) KnownStatusSearchPanel.knownOptionCheckBox.text=Known (NSRL or other)
KnownStatusSearchPanel.unknownOptionCheckBox.text=Unknown KnownStatusSearchPanel.unknownOptionCheckBox.text=Unknown
DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected\! DateSearchFilter.noneSelectedMsg.text=At least one date type must be selected!
DateSearchPanel.dateCheckBox.text=Date: DateSearchPanel.dateCheckBox.text=Date:
DateSearchPanel.jLabel4.text=Timezone: DateSearchPanel.jLabel4.text=Timezone:
DateSearchPanel.jLabel3.text=*The date format is mm/dd/yyyy DateSearchPanel.jLabel3.text=*The date format is mm/dd/yyyy
@ -56,7 +56,7 @@ FileSearchPanel.search.results.details=Large number of matches may impact perfor
FileSearchPanel.search.exception.noFilterSelected.msg=At least one filter must be selected. FileSearchPanel.search.exception.noFilterSelected.msg=At least one filter must be selected.
FileSearchPanel.search.validationErr.msg=Validation Error: {0} FileSearchPanel.search.validationErr.msg=Validation Error: {0}
FileSearchPanel.emptyWhereClause.text=Invalid options, nothing to show. FileSearchPanel.emptyWhereClause.text=Invalid options, nothing to show.
KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected\! KnownStatusSearchFilter.noneSelectedMsg.text=At least one known status must be selected!
NameSearchFilter.emptyNameMsg.text=Must enter something for name search. NameSearchFilter.emptyNameMsg.text=Must enter something for name search.
SearchNode.getName.text=Search Result SearchNode.getName.text=Search Result
SizeSearchPanel.sizeCompareComboBox.equalTo=equal to SizeSearchPanel.sizeCompareComboBox.equalTo=equal to

View File

@ -140,7 +140,7 @@ IngestJob.cancelReason.outOfDiskSpace.text=Out of disk space
IngestJob.cancelReason.servicesDown.text=Services Down IngestJob.cancelReason.servicesDown.text=Services Down
IngestJob.cancelReason.caseClosed.text=Case closed IngestJob.cancelReason.caseClosed.text=Case closed
IngestJobSettingsPanel.globalSettingsButton.text=Global Settings IngestJobSettingsPanel.globalSettingsButton.text=Global Settings
gest gest=
IngestJobSettingsPanel.globalSettingsButton.actionCommand=Advanced IngestJobSettingsPanel.globalSettingsButton.actionCommand=Advanced
IngestJobSettingsPanel.globalSettingsButton.text=Global Settings IngestJobSettingsPanel.globalSettingsButton.text=Global Settings
IngestJobSettingsPanel.pastJobsButton.text=History IngestJobSettingsPanel.pastJobsButton.text=History

View File

@ -110,7 +110,6 @@ public final class IngestServices {
@Deprecated @Deprecated
public void fireModuleDataEvent(ModuleDataEvent moduleDataEvent) { public void fireModuleDataEvent(ModuleDataEvent moduleDataEvent) {
IngestManager.getInstance().fireIngestModuleDataEvent(moduleDataEvent); IngestManager.getInstance().fireIngestModuleDataEvent(moduleDataEvent);
} }
/** /**

View File

@ -28,11 +28,11 @@ import org.sleuthkit.datamodel.TskCoreException;
/** /**
* An interface for implementations of a keyword search service. You can find * An interface for implementations of a keyword search service. You can find
* the implementations by using Lookup, such as: * the implementations by using Lookup, such as:
* *
* Lookup.getDefault().lookup(KeywordSearchService.class) * Lookup.getDefault().lookup(KeywordSearchService.class)
* *
* although most clients should obtain a keyword search service by calling: * although most clients should obtain a keyword search service by calling:
* *
* Case.getCurrentCase().getServices().getKeywordSearchService() * Case.getCurrentCase().getServices().getKeywordSearchService()
* *
* TODO (AUT-2158): This interface should not extend Closeable. * TODO (AUT-2158): This interface should not extend Closeable.
@ -82,7 +82,7 @@ public interface KeywordSearchService extends Closeable {
* @throws KeywordSearchServiceException if unable to delete. * @throws KeywordSearchServiceException if unable to delete.
*/ */
public void deleteTextIndex(CaseMetadata metadata) throws KeywordSearchServiceException; public void deleteTextIndex(CaseMetadata metadata) throws KeywordSearchServiceException;
/** /**
* Closes the keyword search service. * Closes the keyword search service.
* *
@ -95,6 +95,15 @@ public interface KeywordSearchService extends Closeable {
* No-op maintained for backwards compatibility. Clients should not * No-op maintained for backwards compatibility. Clients should not
* attempt to close case services. * attempt to close case services.
*/ */
} }
/**
* Deletes the keyword search text for a specific data source.
*
* @param dataSourceId The data source id to be deleted.
*
* @throws KeywordSearchServiceException if unable to delete.
*/
void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException;
} }

View File

@ -11,12 +11,7 @@ ExtractArchiveWithPasswordAction.progress.text=Unpacking contents of archive: {0
ExtractArchiveWithPasswordAction.prompt.text=Enter Password ExtractArchiveWithPasswordAction.prompt.text=Enter Password
ExtractArchiveWithPasswordAction.prompt.title=Enter Password ExtractArchiveWithPasswordAction.prompt.title=Enter Password
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=\ OpenIDE-Module-Long-Description=Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\nContents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\nIf the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\nThe extracted files are navigable in the directory tree.\n\nThe module is supported on Windows, Linux and Mac operating systems.
Embedded File Extraction Ingest Module\n\nThe Embedded File Extraction Ingest Module processes document files (such as doc, docx, ppt, pptx, xls, xlsx) and archive files (such as zip and others archive types supported by the 7zip extractor).\n\
Contents of these files are extracted and the derived files are added back to the current ingest to be processed by the configured ingest modules.\n\
If the derived file happens to be an archive file, it will be re-processed by the 7zip extractor - the extractor will process archive files N-levels deep.\n\n\
The extracted files are navigable in the directory tree.\n\n\
The module is supported on Windows, Linux and Mac operating systems.
OpenIDE-Module-Name=Embedded File Extraction OpenIDE-Module-Name=Embedded File Extraction
OpenIDE-Module-Short-Description=Embedded File Extraction Ingest Module OpenIDE-Module-Short-Description=Embedded File Extraction Ingest Module
EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin: {0} EmbeddedFileExtractorIngestModule.SevenZipContentReadStream.seek.exception.invalidOrigin=Invalid seek origin: {0}

View File

@ -1,9 +1,7 @@
CannotRunFileTypeDetection=Cannot run file type detection. CannotRunFileTypeDetection=Cannot run file type detection.
ExifParserFileIngestModule.indexError.message=Failed to post EXIF Metadata artifact(s). ExifParserFileIngestModule.indexError.message=Failed to post EXIF Metadata artifact(s).
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=\ OpenIDE-Module-Long-Description=Exif metadata ingest module. \n\nThe ingest module analyzes image files, extracts Exif information and posts the Exif data as results.
Exif metadata ingest module. \n\n\
The ingest module analyzes image files, extracts Exif information and posts the Exif data as results.
OpenIDE-Module-Name=ExifParser OpenIDE-Module-Name=ExifParser
OpenIDE-Module-Short-Description=Exif metadata ingest module OpenIDE-Module-Short-Description=Exif metadata ingest module
ExifParserFileIngestModule.moduleName.text=Exif Parser ExifParserFileIngestModule.moduleName.text=Exif Parser

View File

@ -36,27 +36,27 @@ FileExtMismatchSettingsPanel.jLabel1.text=File Types:
FileExtMismatchSettingsPanel.newExtButton.text=New Extension FileExtMismatchSettingsPanel.newExtButton.text=New Extension
FileExtMismatchSettingsPanel.newMimePrompt.message=Add a new MIME file type: FileExtMismatchSettingsPanel.newMimePrompt.message=Add a new MIME file type:
FileExtMismatchSettingsPanel.newMimePrompt.title=New MIME FileExtMismatchSettingsPanel.newMimePrompt.title=New MIME
FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty\! FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.message=MIME type text is empty!
FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.title=Empty type FileExtMismatchSettingsPanel.newMimePrompt.emptyMime.title=Empty type
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported\! FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.message=MIME type not supported!
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.title=Type not supported FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotSupported.title=Type not supported
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists\! FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.message=MIME type already exists!
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.title=Type already exists FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeExists.title=Type already exists
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.message=MIME type is not detectable by this module. FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.message=MIME type is not detectable by this module.
FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.title=Type not detectable FileExtMismatchSettingsPanel.newMimePrompt.mimeTypeNotDetectable.title=Type not detectable
FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected\! FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.message=No MIME type selected!
FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.title=No type selected FileExtMismatchSettingsPanel.removeTypeButton.noneSelected.title=No type selected
FileExtMismatchSettingsPanel.newExtPrompt.message=Add an allowed extension: FileExtMismatchSettingsPanel.newExtPrompt.message=Add an allowed extension:
FileExtMismatchSettingsPanel.newExtPrompt.title=New allowed extension FileExtMismatchSettingsPanel.newExtPrompt.title=New allowed extension
FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty\! FileExtMismatchSettingsPanel.newExtPrompt.empty.message=Extension text is empty!
FileExtMismatchSettingsPanel.newExtPrompt.empty.title=Extension text empty FileExtMismatchSettingsPanel.newExtPrompt.empty.title=Extension text empty
FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected\! FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.message=No MIME type selected!
FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.title=No MIME type selected FileExtMismatchSettingsPanel.newExtPrompt.noMimeType.title=No MIME type selected
FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists\! FileExtMismatchSettingsPanel.newExtPrompt.extExists.message=Extension already exists!
FileExtMismatchSettingsPanel.newExtPrompt.extExists.title=Extension already exists FileExtMismatchSettingsPanel.newExtPrompt.extExists.title=Extension already exists
FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected\! FileExtMismatchSettingsPanel.removeExtButton.noneSelected.message=No extension selected!
FileExtMismatchSettingsPanel.removeExtButton.noneSelected.title=No extension selected FileExtMismatchSettingsPanel.removeExtButton.noneSelected.title=No extension selected
FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected\! FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.message=No MIME type selected!
FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.title=No MIME type selected FileExtMismatchSettingsPanel.removeExtButton.noMimeTypeSelected.title=No MIME type selected
FileExtMismatchSettingsPanel.removeTypeButton.toolTipText= FileExtMismatchSettingsPanel.removeTypeButton.toolTipText=
FileExtMismatchModuleSettingsPanel.checkAllRadioButton.text=Check all file types FileExtMismatchModuleSettingsPanel.checkAllRadioButton.text=Check all file types

View File

@ -49,10 +49,7 @@ ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash se
ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed
ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=\ OpenIDE-Module-Long-Description=Hash Set ingest module. \n\nThe ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\nThe module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration.
Hash Set ingest module. \n\n\
The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\
The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration.
OpenIDE-Module-Name=HashDatabases OpenIDE-Module-Name=HashDatabases
OptionsCategory_Name_HashDatabase=Hash Sets OptionsCategory_Name_HashDatabase=Hash Sets
OptionsCategory_Keywords_HashDatabase=Hash Sets OptionsCategory_Keywords_HashDatabase=Hash Sets
@ -181,10 +178,7 @@ HashDbSearchThread.name.searching=Searching
HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found. HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found.
ModalNoButtons.indexingDbsTitle=Indexing hash sets ModalNoButtons.indexingDbsTitle=Indexing hash sets
ModalNoButtons.indexingDbTitle=Indexing hash set ModalNoButtons.indexingDbTitle=Indexing hash set
ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\ ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \nThe generated index will be left unusable. If you choose to continue,\nplease delete the corresponding -md5.idx file in the hash folder.\nExit indexing?
The generated index will be left unusable. If you choose to continue,\n\
please delete the corresponding -md5.idx file in the hash folder.\n\
Exit indexing?
ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing
ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set
ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0} ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0}

View File

@ -83,8 +83,8 @@ FilesSetRulePanel.nameTextField.text=
FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional): FilesSetRulePanel.ruleNameLabel.text=Rule Name (Optional):
FilesSetRulePanel.messages.emptyNameCondition=You must specify a name pattern for this rule. FilesSetRulePanel.messages.emptyNameCondition=You must specify a name pattern for this rule.
FilesSetRulePanel.messages.invalidNameRegex=The name regular expression is not valid:\n\n{0} FilesSetRulePanel.messages.invalidNameRegex=The name regular expression is not valid:\n\n{0}
FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, \", <, or > unless it is a regular expression. FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, ", <, or > unless it is a regular expression.
FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, \", <, or > unless it is a regular expression. FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, ", <, or > unless it is a regular expression.
FilesSetRulePanel.messages.invalidPathRegex=The path regular expression is not valid:\n\n{0} FilesSetRulePanel.messages.invalidPathRegex=The path regular expression is not valid:\n\n{0}
FilesSetDefsPanel.doFileSetsDialog.duplicateRuleSet.text=Rule set with name {0} already exists. FilesSetDefsPanel.doFileSetsDialog.duplicateRuleSet.text=Rule set with name {0} already exists.
FilesSetRulePanel.pathSeparatorInfoLabel.text=Folder must be in parent path. Use '/' to give consecutive names FilesSetRulePanel.pathSeparatorInfoLabel.text=Folder must be in parent path. Use '/' to give consecutive names

View File

@ -21,7 +21,7 @@ PhotoRecIngestModule.complete.totalParsetime=Total Parsing Time:
PhotoRecIngestModule.complete.photoRecResults=PhotoRec Results PhotoRecIngestModule.complete.photoRecResults=PhotoRec Results
PhotoRecIngestModule.NotEnoughDiskSpace.detail.msg=PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space. PhotoRecIngestModule.NotEnoughDiskSpace.detail.msg=PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.
PhotoRecIngestModule.cancelledByUser=PhotoRec cancelled by user. PhotoRecIngestModule.cancelledByUser=PhotoRec cancelled by user.
PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value \= {0} when scanning {1} PhotoRecIngestModule.error.exitValue=PhotoRec carver returned error exit value = {0} when scanning {1}
PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver. PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver.
PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving: PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving:
PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings

View File

@ -5,8 +5,8 @@ ReportHTML.getName.text=HTML Report
ReportHTML.getDesc.text=A report about results and tagged items in HTML format. ReportHTML.getDesc.text=A report about results and tagged items in HTML format.
ReportHTML.writeIndex.title=for case {0} ReportHTML.writeIndex.title=for case {0}
ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup. ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup.
ReportHTML.writeIndex.noFrames.seeNav=Please see <a href\="content\nav.html">the navigation page</a> for artifact links, ReportHTML.writeIndex.noFrames.seeNav=Please see <a href="content\nav.html">the navigation page</a> for artifact links,
ReportHTML.writeIndex.seeSum=and <a href\="content\summary.html">the summary page</a> for a case summary. ReportHTML.writeIndex.seeSum=and <a href="contentsummary.html">the summary page</a> for a case summary.
ReportHTML.writeNav.title=Report Navigation ReportHTML.writeNav.title=Report Navigation
ReportHTML.writeNav.h1=Report Navigation ReportHTML.writeNav.h1=Report Navigation
ReportHTML.writeNav.summary=Case Summary ReportHTML.writeNav.summary=Case Summary
@ -16,7 +16,7 @@ ReportHTML.writeSum.caseNumber=Case Number:
ReportHTML.writeSum.caseNumImages=Number of Images: ReportHTML.writeSum.caseNumImages=Number of Images:
ReportHTML.writeSum.examiner=Examiner: ReportHTML.writeSum.examiner=Examiner:
ReportHTML.writeSum.title=Case Summary ReportHTML.writeSum.title=Case Summary
ReportHTML.writeSum.warningMsg=<span>Warning, this report was run before ingest services completed\!</span> ReportHTML.writeSum.warningMsg=<span>Warning, this report was run before ingest services completed!</span>
# #
# autopsy/test/scripts/regression.py._html_report_diff() uses reportGenOn.text, caseName, caseNum, # autopsy/test/scripts/regression.py._html_report_diff() uses reportGenOn.text, caseName, caseNum,
# examiner as a regex signature to skip report.html and summary.html # examiner as a regex signature to skip report.html and summary.html

View File

@ -23,6 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -55,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceDeletedEvent;
import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
@ -102,7 +104,8 @@ public final class ImageGalleryController {
Case.Events.CURRENT_CASE, Case.Events.CURRENT_CASE,
Case.Events.DATA_SOURCE_ADDED, Case.Events.DATA_SOURCE_ADDED,
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_ADDED,
Case.Events.CONTENT_TAG_DELETED Case.Events.CONTENT_TAG_DELETED,
Case.Events.DATA_SOURCE_DELETED
); );
/* /*
@ -802,6 +805,17 @@ public final class ImageGalleryController {
} }
} }
break; break;
case DATA_SOURCE_DELETED:
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
final DataSourceDeletedEvent dataSourceDeletedEvent = (DataSourceDeletedEvent) event;
long dataSourceObjId = dataSourceDeletedEvent.getDataSourceId();
try {
drawableDB.deleteDataSource(dataSourceObjId);
} catch (SQLException | TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to delete data source (obj_id = %d)", dataSourceObjId), ex); //NON-NLS
}
}
break;
case CONTENT_TAG_ADDED: case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event; final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event;
long objId = tagAddedEvent.getAddedTag().getContent().getId(); long objId = tagAddedEvent.getAddedTag().getContent().getId();

View File

@ -100,15 +100,15 @@ public final class DrawableDB {
private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS
private static final String IG_DB_INFO_TABLE = "image_gallery_db_info"; private static final String IG_DB_INFO_TABLE = "image_gallery_db_info";
private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION"; private static final String IG_SCHEMA_MAJOR_VERSION_KEY = "IG_SCHEMA_MAJOR_VERSION";
private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION"; private static final String IG_SCHEMA_MINOR_VERSION_KEY = "IG_SCHEMA_MINOR_VERSION";
private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION"; private static final String IG_CREATION_SCHEMA_MAJOR_VERSION_KEY = "IG_CREATION_SCHEMA_MAJOR_VERSION";
private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION"; private static final String IG_CREATION_SCHEMA_MINOR_VERSION_KEY = "IG_CREATION_SCHEMA_MINOR_VERSION";
private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version private static final VersionNumber IG_STARTING_SCHEMA_VERSION = new VersionNumber(1, 0, 0); // IG Schema Starting version - DO NOT CHANGE
private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 1, 0); // IG Schema Current version private static final VersionNumber IG_SCHEMA_VERSION = new VersionNumber(1, 2, 0); // IG Schema Current version
private PreparedStatement insertHashSetStmt; private PreparedStatement insertHashSetStmt;
private List<PreparedStatement> preparedStatements = new ArrayList<>(); private List<PreparedStatement> preparedStatements = new ArrayList<>();
@ -146,6 +146,8 @@ public final class DrawableDB {
private PreparedStatement pathGroupFilterByDataSrcStmt; private PreparedStatement pathGroupFilterByDataSrcStmt;
private PreparedStatement deleteDataSourceStmt;
/** /**
* map from {@link DrawableAttribute} to the {@link PreparedStatement} that * map from {@link DrawableAttribute} to the {@link PreparedStatement} that
* is used to select groups for that attribute * is used to select groups for that attribute
@ -263,6 +265,7 @@ public final class DrawableDB {
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS
removeHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS removeHashHitStmt = prepareStatement("DELETE FROM hash_set_hits WHERE obj_id = ?"); //NON-NLS
deleteDataSourceStmt = prepareStatement("DELETE FROM datasources where ds_obj_id = ?"); //NON-NLS
return true; return true;
} catch (TskCoreException | SQLException ex) { } catch (TskCoreException | SQLException ex) {
logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to prepare all statements", ex); //NON-NLS
@ -404,11 +407,12 @@ public final class DrawableDB {
/** /**
* Checks if the specified table exists in Drawable DB * Checks if the specified table exists in Drawable DB
* *
* @param tableName table to check * @param tableName table to check
*
* @return true if the table exists in the database * @return true if the table exists in the database
* *
* @throws SQLException * @throws SQLException
*/ */
private boolean doesTableExist(String tableName) throws SQLException { private boolean doesTableExist(String tableName) throws SQLException {
ResultSet tableQueryResults = null; ResultSet tableQueryResults = null;
@ -421,15 +425,14 @@ public final class DrawableDB {
break; break;
} }
} }
} } finally {
finally {
if (tableQueryResults != null) { if (tableQueryResults != null) {
tableQueryResults.close(); tableQueryResults.close();
} }
} }
return tableExists; return tableExists;
} }
private static void deleteDatabaseIfOlderVersion(Path dbPath) throws SQLException, IOException { private static void deleteDatabaseIfOlderVersion(Path dbPath) throws SQLException, IOException {
if (Files.exists(dbPath)) { if (Files.exists(dbPath)) {
boolean hasDrawableFilesTable = false; boolean hasDrawableFilesTable = false;
@ -475,7 +478,7 @@ public final class DrawableDB {
//allow to query while in transaction - no need read locks //allow to query while in transaction - no need read locks
statement.execute("PRAGMA read_uncommitted = True;"); //NON-NLS statement.execute("PRAGMA read_uncommitted = True;"); //NON-NLS
//TODO: do we need this? //used for data source deletion
statement.execute("PRAGMA foreign_keys = ON"); //NON-NLS statement.execute("PRAGMA foreign_keys = ON"); //NON-NLS
//TODO: test this //TODO: test this
@ -515,7 +518,7 @@ public final class DrawableDB {
dbWriteLock(); dbWriteLock();
try { try {
boolean drawableDbTablesExist = true; boolean drawableDbTablesExist = true;
if (isClosed()) { if (isClosed()) {
logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS logger.log(Level.SEVERE, "The drawables database is closed"); //NON-NLS
return false; return false;
@ -532,31 +535,31 @@ public final class DrawableDB {
* Create tables in the drawables database. * Create tables in the drawables database.
*/ */
try (Statement stmt = con.createStatement()) { try (Statement stmt = con.createStatement()) {
// Check if the database is new or an existing database // Check if the database is new or an existing database
drawableDbTablesExist = doesTableExist("drawable_files"); drawableDbTablesExist = doesTableExist("drawable_files");
if (false == doesTableExist(IG_DB_INFO_TABLE)) { if (false == doesTableExist(IG_DB_INFO_TABLE)) {
try { try {
VersionNumber ig_creation_schema_version = drawableDbTablesExist VersionNumber ig_creation_schema_version = drawableDbTablesExist
? IG_STARTING_SCHEMA_VERSION ? IG_STARTING_SCHEMA_VERSION
: IG_SCHEMA_VERSION; : IG_SCHEMA_VERSION;
stmt.execute("CREATE TABLE IF NOT EXISTS " + IG_DB_INFO_TABLE + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)"); stmt.execute("CREATE TABLE IF NOT EXISTS " + IG_DB_INFO_TABLE + " (name TEXT PRIMARY KEY, value TEXT NOT NULL)");
// backfill creation schema ver // backfill creation schema ver
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() )); stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() )); stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
// set current schema ver: at DB initialization - current version is same as starting version // set current schema ver: at DB initialization - current version is same as starting version
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor() )); stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()));
stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor() )); stmt.execute(String.format("INSERT INTO %s (name, value) VALUES ('%s', '%s')", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()));
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to create ig_db_info table", ex); //NON-NLS
return false; return false;
} }
} }
try { try {
String sql = "CREATE TABLE IF NOT EXISTS datasources " //NON-NLS String sql = "CREATE TABLE IF NOT EXISTS datasources " //NON-NLS
+ "( id INTEGER PRIMARY KEY, " //NON-NLS + "( id INTEGER PRIMARY KEY, " //NON-NLS
@ -578,7 +581,8 @@ public final class DrawableDB {
+ " modified_time integer, " //NON-NLS + " modified_time integer, " //NON-NLS
+ " make TEXT DEFAULT NULL, " //NON-NLS + " make TEXT DEFAULT NULL, " //NON-NLS
+ " model TEXT DEFAULT NULL, " //NON-NLS + " model TEXT DEFAULT NULL, " //NON-NLS
+ " analyzed integer DEFAULT 0)"; //NON-NLS + " analyzed integer DEFAULT 0, " //NON-NLS
+ " FOREIGN KEY (data_source_obj_id) REFERENCES datasources(ds_obj_id) ON DELETE CASCADE)"; //NON-NLS
stmt.execute(sql); stmt.execute(sql);
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to create drawable_files table", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to create drawable_files table", ex); //NON-NLS
@ -598,8 +602,9 @@ public final class DrawableDB {
try { try {
String sql = "CREATE TABLE if not exists hash_set_hits " //NON-NLS String sql = "CREATE TABLE if not exists hash_set_hits " //NON-NLS
+ "(hash_set_id INTEGER REFERENCES hash_sets(hash_set_id) not null, " //NON-NLS + "(hash_set_id INTEGER REFERENCES hash_sets(hash_set_id) not null, " //NON-NLS
+ " obj_id BIGINT REFERENCES drawable_files(obj_id) not null, " //NON-NLS + " obj_id BIGINT NOT NULL, " //NON-NLS
+ " PRIMARY KEY (hash_set_id, obj_id))"; //NON-NLS + " PRIMARY KEY (hash_set_id, obj_id), " //NON-NLS
+ " FOREIGN KEY (obj_id) REFERENCES drawable_files(obj_id) ON DELETE CASCADE)"; //NON-NLS
stmt.execute(sql); stmt.execute(sql);
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to create hash_set_hits table", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to create hash_set_hits table", ex); //NON-NLS
@ -649,45 +654,45 @@ public final class DrawableDB {
* Create tables in the case database. * Create tables in the case database.
*/ */
String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER"; String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER";
try { try {
boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME); boolean caseDbTablesExist = tskCase.getCaseDbAccessManager().tableExists(GROUPS_TABLENAME);
VersionNumber ig_creation_schema_version = caseDbTablesExist VersionNumber ig_creation_schema_version = caseDbTablesExist
? IG_STARTING_SCHEMA_VERSION ? IG_STARTING_SCHEMA_VERSION
: IG_SCHEMA_VERSION; : IG_SCHEMA_VERSION;
String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, " String tableSchema = "( id " + autogenKeyType + " PRIMARY KEY, "
+ " name TEXT UNIQUE NOT NULL," + " name TEXT UNIQUE NOT NULL,"
+ " value TEXT NOT NULL )"; + " value TEXT NOT NULL )";
tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema); tskCase.getCaseDbAccessManager().createTable(IG_DB_INFO_TABLE, tableSchema);
// backfill creation version // backfill creation version
String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()); String creationMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
String creationMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()); String creationMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_CREATION_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
// set current version - at the onset, current version is same as creation version // set current version - at the onset, current version is same as creation version
String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor()); String currentMajorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MAJOR_VERSION_KEY, ig_creation_schema_version.getMajor());
String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor()); String currentMinorVerSQL = String.format(" (name, value) VALUES ('%s', '%s')", IG_SCHEMA_MINOR_VERSION_KEY, ig_creation_schema_version.getMinor());
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
creationMajorVerSQL += " ON CONFLICT DO NOTHING "; creationMajorVerSQL += " ON CONFLICT DO NOTHING ";
creationMinorVerSQL += " ON CONFLICT DO NOTHING "; creationMinorVerSQL += " ON CONFLICT DO NOTHING ";
currentMajorVerSQL += " ON CONFLICT DO NOTHING "; currentMajorVerSQL += " ON CONFLICT DO NOTHING ";
currentMinorVerSQL += " ON CONFLICT DO NOTHING "; currentMinorVerSQL += " ON CONFLICT DO NOTHING ";
} }
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL); tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMajorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL); tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, creationMinorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL); tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMajorVerSQL);
tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL); tskCase.getCaseDbAccessManager().insert(IG_DB_INFO_TABLE, currentMinorVerSQL);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to create ig_db_info table in Case database", ex); //NON-NLS
return false; return false;
} }
try { try {
String tableSchema String tableSchema
= "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS = "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS
@ -710,7 +715,7 @@ public final class DrawableDB {
+ " examiner_id integer not null, " //NON-NLS + " examiner_id integer not null, " //NON-NLS
+ " seen integer DEFAULT 0, " //NON-NLS + " seen integer DEFAULT 0, " //NON-NLS
+ " UNIQUE(group_id, examiner_id)," + " UNIQUE(group_id, examiner_id),"
+ " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME + "(group_id)," + " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME + "(group_id) ON DELETE CASCADE,"
+ " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)" + " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)"
+ " )"; //NON-NLS + " )"; //NON-NLS
@ -729,20 +734,21 @@ public final class DrawableDB {
/** /**
* Gets the Schema version from DrawableDB * Gets the Schema version from DrawableDB
* *
* @return image gallery schema version in DrawableDB * @return image gallery schema version in DrawableDB
*
* @throws SQLException * @throws SQLException
* @throws TskCoreException * @throws TskCoreException
*/ */
private VersionNumber getDrawableDbIgSchemaVersion() throws SQLException, TskCoreException { private VersionNumber getDrawableDbIgSchemaVersion() throws SQLException, TskCoreException {
Statement statement = con.createStatement(); Statement statement = con.createStatement();
ResultSet resultSet = null; ResultSet resultSet = null;
try { try {
int majorVersion = -1; int majorVersion = -1;
String majorVersionStr = null; String majorVersionStr = null;
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY)); resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY));
if (resultSet.next()) { if (resultSet.next()) {
majorVersionStr = resultSet.getString("value"); majorVersionStr = resultSet.getString("value");
try { try {
@ -751,12 +757,12 @@ public final class DrawableDB {
throw new TskCoreException("Bad value for schema major version = " + majorVersionStr, ex); throw new TskCoreException("Bad value for schema major version = " + majorVersionStr, ex);
} }
} else { } else {
throw new TskCoreException("Failed to read schema major version from ig_db_info table"); throw new TskCoreException("Failed to read schema major version from ig_db_info table");
} }
int minorVersion = -1; int minorVersion = -1;
String minorVersionStr = null; String minorVersionStr = null;
resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY)); resultSet = statement.executeQuery(String.format("SELECT value FROM %s WHERE name='%s'", IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY));
if (resultSet.next()) { if (resultSet.next()) {
minorVersionStr = resultSet.getString("value"); minorVersionStr = resultSet.getString("value");
try { try {
@ -768,9 +774,8 @@ public final class DrawableDB {
throw new TskCoreException("Failed to read schema minor version from ig_db_info table"); throw new TskCoreException("Failed to read schema minor version from ig_db_info table");
} }
return new VersionNumber(majorVersion, minorVersion, 0 ); return new VersionNumber(majorVersion, minorVersion, 0);
} } finally {
finally {
if (resultSet != null) { if (resultSet != null) {
resultSet.close(); resultSet.close();
} }
@ -782,27 +787,28 @@ public final class DrawableDB {
/** /**
* Gets the ImageGallery schema version from CaseDB * Gets the ImageGallery schema version from CaseDB
* *
* @return image gallery schema version in CaseDB * @return image gallery schema version in CaseDB
*
* @throws SQLException * @throws SQLException
* @throws TskCoreException * @throws TskCoreException
*/ */
private VersionNumber getCaseDbIgSchemaVersion() throws TskCoreException { private VersionNumber getCaseDbIgSchemaVersion() throws TskCoreException {
// Callback to process result of get version query // Callback to process result of get version query
class GetSchemaVersionQueryResultProcessor implements CaseDbAccessQueryCallback { class GetSchemaVersionQueryResultProcessor implements CaseDbAccessQueryCallback {
private int version = -1; private int version = -1;
int getVersion() { int getVersion() {
return version; return version;
} }
@Override @Override
public void process(ResultSet resultSet) { public void process(ResultSet resultSet) {
try { try {
if (resultSet.next()) { if (resultSet.next()) {
String versionStr = resultSet.getString("value"); String versionStr = resultSet.getString("value");
try { try {
version = Integer.parseInt(versionStr); version = Integer.parseInt(versionStr);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
@ -811,100 +817,96 @@ public final class DrawableDB {
} else { } else {
logger.log(Level.SEVERE, "Failed to get version"); logger.log(Level.SEVERE, "Failed to get version");
} }
} } catch (SQLException ex) {
catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get version", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to get version", ex); //NON-NLS
} }
} }
} }
GetSchemaVersionQueryResultProcessor majorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor(); GetSchemaVersionQueryResultProcessor majorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor(); GetSchemaVersionQueryResultProcessor minorVersionResultProcessor = new GetSchemaVersionQueryResultProcessor();
String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' "; String versionQueryTemplate = "value FROM %s WHERE name = \'%s\' ";
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor); tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MAJOR_VERSION_KEY), majorVersionResultProcessor);
tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor); tskCase.getCaseDbAccessManager().select(String.format(versionQueryTemplate, IG_DB_INFO_TABLE, IG_SCHEMA_MINOR_VERSION_KEY), minorVersionResultProcessor);
return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0); return new VersionNumber(majorVersionResultProcessor.getVersion(), minorVersionResultProcessor.getVersion(), 0);
} }
/** /**
* Updates the IG schema version in the Drawable DB * Updates the IG schema version in the Drawable DB
* *
* @param version new version number * @param version new version number
* @param transaction transaction under which the update happens * @param transaction transaction under which the update happens
* *
* @throws SQLException * @throws SQLException
*/ */
private void updateDrawableDbIgSchemaVersion(VersionNumber version, DrawableTransaction transaction) throws SQLException, TskCoreException { private void updateDrawableDbIgSchemaVersion(VersionNumber version, DrawableTransaction transaction) throws SQLException, TskCoreException {
if (transaction == null) { if (transaction == null) {
throw new TskCoreException("Schema version update must be done in a transaction"); throw new TskCoreException("Schema version update must be done in a transaction");
} }
dbWriteLock(); dbWriteLock();
try { try {
Statement statement = con.createStatement(); Statement statement = con.createStatement();
// update schema version // update schema version
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY )); statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY));
statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY )); statement.execute(String.format("UPDATE %s SET value = '%s' WHERE name = '%s'", IG_DB_INFO_TABLE, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY));
statement.close(); statement.close();
} } finally {
finally {
dbWriteUnlock(); dbWriteUnlock();
} }
} }
/** /**
* Updates the IG schema version in CaseDB * Updates the IG schema version in CaseDB
* *
* @param version new version number * @param version new version number
* @param caseDbTransaction transaction to use to update the CaseDB * @param caseDbTransaction transaction to use to update the CaseDB
* *
* @throws SQLException * @throws SQLException
*/ */
private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException { private void updateCaseDbIgSchemaVersion(VersionNumber version, CaseDbTransaction caseDbTransaction) throws TskCoreException {
String updateSQLTemplate = " SET value = %s WHERE name = '%s' "; String updateSQLTemplate = " SET value = %s WHERE name = '%s' ";
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction); tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMajor(), IG_SCHEMA_MAJOR_VERSION_KEY), caseDbTransaction);
tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction); tskCase.getCaseDbAccessManager().update(IG_DB_INFO_TABLE, String.format(updateSQLTemplate, version.getMinor(), IG_SCHEMA_MINOR_VERSION_KEY), caseDbTransaction);
} }
/** /**
* Upgrades the DB schema. * Upgrades the DB schema.
* *
* @return true if the upgrade is successful * @return true if the upgrade is successful
* *
* @throws SQLException * @throws SQLException
* *
*/ */
private boolean upgradeDBSchema() throws TskCoreException, SQLException { private boolean upgradeDBSchema() throws TskCoreException, SQLException {
// Read current version from the DBs // Read current version from the DBs
VersionNumber drawableDbIgSchemaVersion = getDrawableDbIgSchemaVersion(); VersionNumber drawableDbIgSchemaVersion = getDrawableDbIgSchemaVersion();
VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion(); VersionNumber caseDbIgSchemaVersion = getCaseDbIgSchemaVersion();
// Upgrade Schema in both DrawableDB and CaseDB // Upgrade Schema in both DrawableDB and CaseDB
CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); CaseDbTransaction caseDbTransaction = tskCase.beginTransaction();
DrawableTransaction transaction = beginTransaction(); DrawableTransaction transaction = beginTransaction();
try { try {
caseDbIgSchemaVersion = upgradeCaseDbIgSchema1dot0TO1dot1(caseDbIgSchemaVersion, caseDbTransaction); caseDbIgSchemaVersion = upgradeCaseDbIgSchema1dot0TO1dot1(caseDbIgSchemaVersion, caseDbTransaction);
drawableDbIgSchemaVersion = upgradeDrawableDbIgSchema1dot0TO1dot1(drawableDbIgSchemaVersion, transaction); drawableDbIgSchemaVersion = upgradeDrawableDbIgSchema1dot0TO1dot1(drawableDbIgSchemaVersion, transaction);
// update the versions in the tables // update the versions in the tables
updateCaseDbIgSchemaVersion(caseDbIgSchemaVersion, caseDbTransaction ); updateCaseDbIgSchemaVersion(caseDbIgSchemaVersion, caseDbTransaction);
updateDrawableDbIgSchemaVersion(drawableDbIgSchemaVersion, transaction); updateDrawableDbIgSchemaVersion(drawableDbIgSchemaVersion, transaction);
caseDbTransaction.commit(); caseDbTransaction.commit();
caseDbTransaction = null; caseDbTransaction = null;
commitTransaction(transaction, false); commitTransaction(transaction, false);
transaction = null; transaction = null;
} } catch (TskCoreException | SQLException ex) {
catch (TskCoreException | SQLException ex) {
if (null != caseDbTransaction) { if (null != caseDbTransaction) {
try { try {
caseDbTransaction.rollback(); caseDbTransaction.rollback();
@ -921,57 +923,59 @@ public final class DrawableDB {
} }
throw ex; throw ex;
} }
return true; return true;
} }
/** /**
* Upgrades IG tables in CaseDB from 1.0 to 1.1 * Upgrades IG tables in CaseDB from 1.0 to 1.1 Does nothing if the incoming
* Does nothing if the incoming version is not 1.0 * version is not 1.0
* *
* @param currVersion version to upgrade from * @param currVersion version to upgrade from
* @param caseDbTransaction transaction to use for all updates * @param caseDbTransaction transaction to use for all updates
* *
* @return new version number * @return new version number
* @throws TskCoreException *
* @throws TskCoreException
*/ */
private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction ) throws TskCoreException { private VersionNumber upgradeCaseDbIgSchema1dot0TO1dot1(VersionNumber currVersion, CaseDbTransaction caseDbTransaction) throws TskCoreException {
// Upgrade if current version is 1.0 // Upgrade if current version is 1.0
// or 1.1 - a bug in versioning alllowed some databases to be versioned as 1.1 without the actual corresponding upgrade. This allows such databases to be fixed, if needed. // or 1.1 - a bug in versioning alllowed some databases to be versioned as 1.1 without the actual corresponding upgrade. This allows such databases to be fixed, if needed.
if (!(currVersion.getMajor() == 1 && if (!(currVersion.getMajor() == 1
(currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) { && (currVersion.getMinor() == 0 || currVersion.getMinor() == 1))) {
return currVersion; return currVersion;
} }
// Add a 'is_analyzed' column to groups table in CaseDB // Add a 'is_analyzed' column to groups table in CaseDB
String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS String alterSQL = " ADD COLUMN is_analyzed integer DEFAULT 1 "; //NON-NLS
if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction )) { if (false == tskCase.getCaseDbAccessManager().columnExists(GROUPS_TABLENAME, "is_analyzed", caseDbTransaction)) {
tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction); tskCase.getCaseDbAccessManager().alterTable(GROUPS_TABLENAME, alterSQL, caseDbTransaction);
} }
return new VersionNumber(1,1,0); return new VersionNumber(1, 1, 0);
} }
/** /**
* Upgrades IG tables in DrawableDB from 1.0 to 1.1 * Upgrades IG tables in DrawableDB from 1.0 to 1.1 Does nothing if the
* Does nothing if the incoming version is not 1.0 * incoming version is not 1.0
* *
* @param currVersion version to upgrade from * @param currVersion version to upgrade from
* @param transaction transaction to use for all updates * @param transaction transaction to use for all updates
* *
* @return new version number * @return new version number
* @throws TskCoreException *
* @throws TskCoreException
*/ */
private VersionNumber upgradeDrawableDbIgSchema1dot0TO1dot1(VersionNumber currVersion, DrawableTransaction transaction ) throws TskCoreException { private VersionNumber upgradeDrawableDbIgSchema1dot0TO1dot1(VersionNumber currVersion, DrawableTransaction transaction) throws TskCoreException {
if (currVersion.getMajor() != 1 || if (currVersion.getMajor() != 1
currVersion.getMinor() != 0) { || currVersion.getMinor() != 0) {
return currVersion; return currVersion;
} }
// There are no changes in DrawableDB schema in 1.0 -> 1.1 // There are no changes in DrawableDB schema in 1.0 -> 1.1
return new VersionNumber(1,1,0); return new VersionNumber(1, 1, 0);
} }
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
/* /*
@ -1143,10 +1147,10 @@ public final class DrawableDB {
} }
/** /**
* Record in the DB that the group with the given key is seen * Record in the DB that the group with the given key is seen by given
* by given examiner id. * examiner id.
* *
* @param groupKey key identifying the group. * @param groupKey key identifying the group.
* @param examinerID examiner id. * @param examinerID examiner id.
* *
* @throws TskCoreException * @throws TskCoreException
@ -1154,16 +1158,16 @@ public final class DrawableDB {
public void markGroupSeen(GroupKey<?> groupKey, long examinerID) throws TskCoreException { public void markGroupSeen(GroupKey<?> groupKey, long examinerID) throws TskCoreException {
/* /*
* Check the groupSeenCache to see if the seen status for this group was set recently. * Check the groupSeenCache to see if the seen status for this group was
* If recently set to seen, there's no need to update it * set recently. If recently set to seen, there's no need to update it
*/ */
Boolean cachedValue = groupSeenCache.getIfPresent(groupKey); Boolean cachedValue = groupSeenCache.getIfPresent(groupKey);
if (cachedValue != null && cachedValue == true) { if (cachedValue != null && cachedValue == true) {
return; return;
} }
// query to find the group id from attribute/value // query to find the group id from attribute/value
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME //NON-NLS String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME //NON-NLS
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )", //NON-NLS + " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )", //NON-NLS
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()), SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()), SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
@ -1180,8 +1184,8 @@ public final class DrawableDB {
} }
/** /**
* Record in the DB that given group is unseen. * Record in the DB that given group is unseen. The group is marked unseen
* The group is marked unseen for ALL examiners that have seen the group. * for ALL examiners that have seen the group.
* *
* @param groupKey key identifying the group. * @param groupKey key identifying the group.
* *
@ -1190,20 +1194,20 @@ public final class DrawableDB {
public void markGroupUnseen(GroupKey<?> groupKey) throws TskCoreException { public void markGroupUnseen(GroupKey<?> groupKey) throws TskCoreException {
/* /*
* Check the groupSeenCache to see if the seen status for this group was set recently. * Check the groupSeenCache to see if the seen status for this group was
* If recently set to unseen, there's no need to update it * set recently. If recently set to unseen, there's no need to update it
*/ */
Boolean cachedValue = groupSeenCache.getIfPresent(groupKey); Boolean cachedValue = groupSeenCache.getIfPresent(groupKey);
if (cachedValue != null && cachedValue == false) { if (cachedValue != null && cachedValue == false) {
return; return;
} }
String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")" ); //NON-NLS String updateSQL = String.format(" SET seen = 0 WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")"); //NON-NLS
tskCase.getCaseDbAccessManager().update(GROUPS_SEEN_TABLENAME, updateSQL); tskCase.getCaseDbAccessManager().update(GROUPS_SEEN_TABLENAME, updateSQL);
groupSeenCache.put(groupKey, false); groupSeenCache.put(groupKey, false);
} }
/** /**
* Sets the isAnalysed flag in the groups table for the given group to true. * Sets the isAnalysed flag in the groups table for the given group to true.
* *
@ -1213,7 +1217,6 @@ public final class DrawableDB {
*/ */
public void markGroupAnalyzed(GroupKey<?> groupKey) throws TskCoreException { public void markGroupAnalyzed(GroupKey<?> groupKey) throws TskCoreException {
String updateSQL = String.format(" SET is_analyzed = %d " String updateSQL = String.format(" SET is_analyzed = %d "
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ", + " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
1, 1,
@ -1223,7 +1226,7 @@ public final class DrawableDB {
tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL);
} }
/** /**
* Removes a file from the drawables databse. * Removes a file from the drawables databse.
* *
@ -1254,11 +1257,11 @@ public final class DrawableDB {
/** /**
* Updates the image file. * Updates the image file.
* *
* @param f file to update. * @param f file to update.
* *
* @throws TskCoreException * @throws TskCoreException
* @throws SQLException * @throws SQLException
*/ */
public void updateFile(DrawableFile f) throws TskCoreException, SQLException { public void updateFile(DrawableFile f) throws TskCoreException, SQLException {
DrawableTransaction trans = null; DrawableTransaction trans = null;
@ -1288,14 +1291,13 @@ public final class DrawableDB {
} }
} }
/** /**
* Update an existing entry (or make a new one) into the DB that includes * Update an existing entry (or make a new one) into the DB that includes
* group information. Called when a file has been analyzed or during a bulk * group information. Called when a file has been analyzed or during a bulk
* rebuild * rebuild
* *
* @param f file to update * @param f file to update
* @param tr * @param tr
* @param caseDbTransaction * @param caseDbTransaction
*/ */
public void updateFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) { public void updateFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) {
@ -1601,24 +1603,24 @@ public final class DrawableDB {
} }
return map; return map;
} }
/** /**
* Get the build status for the given data source. * Get the build status for the given data source. Will return UNKNOWN if
* Will return UNKNOWN if the data source is not yet in the database. * the data source is not yet in the database.
* *
* @param dataSourceId * @param dataSourceId
* *
* @return The status of the data source or UKNOWN if it is not found. * @return The status of the data source or UKNOWN if it is not found.
* *
* @throws TskCoreException * @throws TskCoreException
*/ */
public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException { public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException {
Map<Long, DrawableDbBuildStatusEnum> statusMap = getDataSourceDbBuildStatus(); Map<Long, DrawableDbBuildStatusEnum> statusMap = getDataSourceDbBuildStatus();
if (statusMap.containsKey(dataSourceId) == false) { if (statusMap.containsKey(dataSourceId) == false) {
return DrawableDbBuildStatusEnum.UNKNOWN; return DrawableDbBuildStatusEnum.UNKNOWN;
} }
return statusMap.get(dataSourceId); return statusMap.get(dataSourceId);
} }
/** /**
* Insert/update given data source object id and it's DB rebuild status in * Insert/update given data source object id and it's DB rebuild status in
@ -1687,47 +1689,50 @@ public final class DrawableDB {
} }
/** /**
* Returns whether or not the given group is analyzed and ready to be viewed. * Returns whether or not the given group is analyzed and ready to be
* * viewed.
*
* @param groupKey group key. * @param groupKey group key.
*
* @return true if the group is analyzed. * @return true if the group is analyzed.
*
* @throws SQLException * @throws SQLException
* @throws TskCoreException * @throws TskCoreException
*/ */
public Boolean isGroupAnalyzed(GroupKey<?> groupKey) throws SQLException, TskCoreException { public Boolean isGroupAnalyzed(GroupKey<?> groupKey) throws SQLException, TskCoreException {
// Callback to process result of isAnalyzed query // Callback to process result of isAnalyzed query
class IsGroupAnalyzedQueryResultProcessor implements CaseDbAccessQueryCallback { class IsGroupAnalyzedQueryResultProcessor implements CaseDbAccessQueryCallback {
private boolean isAnalyzed = false; private boolean isAnalyzed = false;
boolean getIsAnalyzed() { boolean getIsAnalyzed() {
return isAnalyzed; return isAnalyzed;
} }
@Override @Override
public void process(ResultSet resultSet) { public void process(ResultSet resultSet) {
try { try {
if (resultSet.next()) { if (resultSet.next()) {
isAnalyzed = resultSet.getInt("is_analyzed") == 1 ? true: false; isAnalyzed = resultSet.getInt("is_analyzed") == 1 ? true : false;
} }
} catch (SQLException ex) { } catch (SQLException ex) {
logger.log(Level.SEVERE, "Failed to get group is_analyzed", ex); //NON-NLS logger.log(Level.SEVERE, "Failed to get group is_analyzed", ex); //NON-NLS
} }
} }
} }
IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor(); IsGroupAnalyzedQueryResultProcessor queryResultProcessor = new IsGroupAnalyzedQueryResultProcessor();
try { try {
String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + GROUPS_TABLENAME String groupAnalyzedQueryStmt = String.format("is_analyzed FROM " + GROUPS_TABLENAME
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ", + " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d ",
SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()), SleuthkitCase.escapeSingleQuotes(groupKey.getAttribute().attrName.toString()),
SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()), SleuthkitCase.escapeSingleQuotes(groupKey.getValueDisplayName()),
groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0); groupKey.getAttribute() == DrawableAttribute.PATH ? groupKey.getDataSourceObjId() : 0);
tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor); tskCase.getCaseDbAccessManager().select(groupAnalyzedQueryStmt, queryResultProcessor);
return queryResultProcessor.getIsAnalyzed(); return queryResultProcessor.getIsAnalyzed();
} catch ( TskCoreException ex) { } catch (TskCoreException ex) {
String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS String msg = String.format("Failed to get group is_analyzed for group key %s", groupKey.getValueDisplayName()); //NON-NLS
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
} }
@ -1833,7 +1838,7 @@ public final class DrawableDB {
// skip any null/blank values // skip any null/blank values
query.append("WHERE LENGTH(" + groupBy.attrName.toString() + ") > 0 "); query.append("WHERE LENGTH(" + groupBy.attrName.toString() + ") > 0 ");
if (dataSource != null) { if (dataSource != null) {
query.append(" AND data_source_obj_id = ").append(dataSource.getId()); query.append(" AND data_source_obj_id = ").append(dataSource.getId());
} }
@ -1922,7 +1927,7 @@ public final class DrawableDB {
return; return;
} }
int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1; int isAnalyzed = (groupBy == DrawableAttribute.PATH) ? 0 : 1;
String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)", String insertSQL = String.format(" (data_source_obj_id, value, attribute, is_analyzed) VALUES (%d, \'%s\', \'%s\', %d)",
ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed); ds_obj_id, SleuthkitCase.escapeSingleQuotes(value), SleuthkitCase.escapeSingleQuotes(groupBy.attrName.toString()), isAnalyzed);
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
@ -1995,7 +2000,6 @@ public final class DrawableDB {
return countFilesWhere(" 1 "); return countFilesWhere(" 1 ");
} }
/** /**
* delete the row with obj_id = id. * delete the row with obj_id = id.
* *
@ -2027,6 +2031,37 @@ public final class DrawableDB {
} }
} }
/**
* Deletes a cascading delete of a data source, starting from the
* datasources table.
*
* @param dataSourceID The object ID of the data source to delete.
*
* @throws SQLException
* @throws TskCoreException
*/
public void deleteDataSource(long dataSourceID) throws SQLException, TskCoreException {
dbWriteLock();
DrawableTransaction trans = null;
try {
trans = beginTransaction();
deleteDataSourceStmt.setLong(1, dataSourceID);
deleteDataSourceStmt.executeUpdate();
commitTransaction(trans, true);
} catch (SQLException | TskCoreException ex) {
if (null != trans) {
try {
rollbackTransaction(trans);
} catch (SQLException ex2) {
logger.log(Level.SEVERE, String.format("Failed to roll back drawables db transaction after error: %s", ex.getMessage()), ex2); //NON-NLS
}
}
throw ex;
} finally {
dbWriteUnlock();
}
}
public class MultipleTransactionException extends IllegalStateException { public class MultipleTransactionException extends IllegalStateException {
public MultipleTransactionException() { public MultipleTransactionException() {
@ -2203,11 +2238,11 @@ public final class DrawableDB {
// The files are processed ORDERED BY parent path // The files are processed ORDERED BY parent path
// We want to preserve that order here, so that we can detect a // We want to preserve that order here, so that we can detect a
// change in path, and thus mark the path group as analyzed // change in path, and thus mark the path group as analyzed
// Hence we use a LinkedHashSet here. // Hence we use a LinkedHashSet here.
private final Set<Long> updatedFiles = new LinkedHashSet<>(); private final Set<Long> updatedFiles = new LinkedHashSet<>();
private final Set<Long> removedFiles = new LinkedHashSet<>(); private final Set<Long> removedFiles = new LinkedHashSet<>();
private boolean completed; private boolean completed;
private DrawableTransaction() throws TskCoreException, SQLException { private DrawableTransaction() throws TskCoreException, SQLException {

View File

@ -298,6 +298,7 @@ GlobalEditListPanel.editWordButton.text=Edit Keyword
SolrSearchService.ServiceName=Solr Keyword Search Service SolrSearchService.ServiceName=Solr Keyword Search Service
SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only
SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html> SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0}
ExtractedContentPanel.jLabel1.text=Text Source: ExtractedContentPanel.jLabel1.text=Text Source:
ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton
ExtractedContentPanel.pagePreviousButton.text= ExtractedContentPanel.pagePreviousButton.text=

View File

@ -357,6 +357,7 @@ SolrSearchService.indexingError=Unable to index blackboard artifact.
SolrSearchService.ServiceName=Solr Keyword Search Service SolrSearchService.ServiceName=Solr Keyword Search Service
SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only
SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html> SolrSearchService.IndexReadOnlyDialog.msg=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0}
ExtractedContentPanel.jLabel1.text=Text Source: ExtractedContentPanel.jLabel1.text=Text Source:
ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton
ExtractedContentPanel.pagePreviousButton.text= ExtractedContentPanel.pagePreviousButton.text=

View File

@ -189,16 +189,16 @@ public class Server {
} }
}, },
/** /**
* termfreq is a function which returns the number of times the term appears. * termfreq is a function which returns the number of times the term
* This is not an actual field defined in schema.xml, but can be gotten from returned documents * appears. This is not an actual field defined in schema.xml, but can
* in the same way as fields. * be gotten from returned documents in the same way as fields.
*/ */
TERMFREQ { TERMFREQ {
@Override @Override
public String toString() { public String toString() {
return "termfreq"; //NON-NLS return "termfreq"; //NON-NLS
} }
} }
}; };
public static final String HL_ANALYZE_CHARS_UNLIMITED = "500000"; //max 1MB in a chunk. use -1 for unlimited, but -1 option may not be supported (not documented) public static final String HL_ANALYZE_CHARS_UNLIMITED = "500000"; //max 1MB in a chunk. use -1 for unlimited, but -1 option may not be supported (not documented)
@ -525,7 +525,7 @@ public class Server {
@Override @Override
public void run() { public void run() {
MessageNotifyUtil.Notify.error( MessageNotifyUtil.Notify.error(
NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"), NbBundle.getMessage(this.getClass(), "Installer.errorInitKsmMsg"),
Bundle.Server_status_failed_msg()); Bundle.Server_status_failed_msg());
} }
}); });
@ -890,28 +890,30 @@ public class Server {
throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex); throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex);
} }
} }
/** /**
* Get the host and port for a multiuser case. * Get the host and port for a multiuser case. If the file solrserver.txt
* If the file solrserver.txt exists, then use the values from that file. * exists, then use the values from that file. Otherwise use the settings
* Otherwise use the settings from the properties file. * from the properties file.
* *
* @param caseDirectory Current case directory * @param caseDirectory Current case directory
* @return IndexingServerProperties containing the solr host/port for this case *
* @return IndexingServerProperties containing the solr host/port for this
* case
*/ */
public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) { public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) {
Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt"); Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt");
if(serverFilePath.toFile().exists()){ if (serverFilePath.toFile().exists()) {
try{ try {
List<String> lines = Files.readAllLines(serverFilePath); List<String> lines = Files.readAllLines(serverFilePath);
if(lines.isEmpty()) { if (lines.isEmpty()) {
logger.log(Level.SEVERE, "solrserver.txt file does not contain any data"); logger.log(Level.SEVERE, "solrserver.txt file does not contain any data");
} else if (! lines.get(0).contains(",")) { } else if (!lines.get(0).contains(",")) {
logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
} else { } else {
String[] parts = lines.get(0).split(","); String[] parts = lines.get(0).split(",");
if(parts.length != 2) { if (parts.length != 2) {
logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0));
} else { } else {
return new IndexingServerProperties(parts[0], parts[1]); return new IndexingServerProperties(parts[0], parts[1]);
@ -921,102 +923,104 @@ public class Server {
logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex); logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex);
} }
} }
// Default back to the user preferences if the solrserver.txt file was not found or if an error occurred // Default back to the user preferences if the solrserver.txt file was not found or if an error occurred
String host = UserPreferences.getIndexingServerHost(); String host = UserPreferences.getIndexingServerHost();
String port = UserPreferences.getIndexingServerPort(); String port = UserPreferences.getIndexingServerPort();
return new IndexingServerProperties(host, port); return new IndexingServerProperties(host, port);
} }
/** /**
* Pick a solr server to use for this case and record it in the case directory. * Pick a solr server to use for this case and record it in the case
* Looks for a file named "solrServerList.txt" in the root output directory - * directory. Looks for a file named "solrServerList.txt" in the root output
* if this does not exist then no server is recorded. * directory - if this does not exist then no server is recorded.
* *
* Format of solrServerList.txt: * Format of solrServerList.txt: (host),(port) Ex: 10.1.2.34,8983
* (host),(port) *
* Ex: 10.1.2.34,8983
*
* @param rootOutputDirectory * @param rootOutputDirectory
* @param caseDirectoryPath * @param caseDirectoryPath
* @throws KeywordSearchModuleException *
* @throws KeywordSearchModuleException
*/ */
public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException { public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException {
// Look for the solr server list file // Look for the solr server list file
String serverListName = "solrServerList.txt"; String serverListName = "solrServerList.txt";
Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName); Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName);
if(serverListPath.toFile().exists()){ if (serverListPath.toFile().exists()) {
// Read the list of solr servers // Read the list of solr servers
List<String> lines; List<String> lines;
try{ try {
lines = Files.readAllLines(serverListPath); lines = Files.readAllLines(serverListPath);
} catch (IOException ex){ } catch (IOException ex) {
throw new KeywordSearchModuleException(serverListName + " could not be read", ex); throw new KeywordSearchModuleException(serverListName + " could not be read", ex);
} }
// Remove any lines that don't contain a comma (these are likely just whitespace) // Remove any lines that don't contain a comma (these are likely just whitespace)
for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) { for (Iterator<String> iterator = lines.iterator(); iterator.hasNext();) {
String line = iterator.next(); String line = iterator.next();
if (! line.contains(",")) { if (!line.contains(",")) {
// Remove the current element from the iterator and the list. // Remove the current element from the iterator and the list.
iterator.remove(); iterator.remove();
} }
} }
if(lines.isEmpty()) { if (lines.isEmpty()) {
throw new KeywordSearchModuleException(serverListName + " had no valid server information"); throw new KeywordSearchModuleException(serverListName + " had no valid server information");
} }
// Choose which server to use // Choose which server to use
int rnd = new Random().nextInt(lines.size()); int rnd = new Random().nextInt(lines.size());
String[] parts = lines.get(rnd).split(","); String[] parts = lines.get(rnd).split(",");
if(parts.length != 2) { if (parts.length != 2) {
throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
} }
// Split it up just to do a sanity check on the data // Split it up just to do a sanity check on the data
String host = parts[0]; String host = parts[0];
String port = parts[1]; String port = parts[1];
if(host.isEmpty() || port.isEmpty()) { if (host.isEmpty() || port.isEmpty()) {
throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd));
} }
// Write the server data to a file // Write the server data to a file
Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt"); Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt");
try { try {
caseDirectoryPath.toFile().mkdirs(); caseDirectoryPath.toFile().mkdirs();
if (! caseDirectoryPath.toFile().exists()) { if (!caseDirectoryPath.toFile().exists()) {
throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist"); throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist");
} }
Files.write(serverFile, lines.get(rnd).getBytes()); Files.write(serverFile, lines.get(rnd).getBytes());
} catch (IOException ex){ } catch (IOException ex) {
throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex); throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex);
} }
} }
} }
/** /**
* Helper class to store the current server properties * Helper class to store the current server properties
*/ */
public static class IndexingServerProperties { public static class IndexingServerProperties {
private final String host; private final String host;
private final String port; private final String port;
IndexingServerProperties (String host, String port) { IndexingServerProperties(String host, String port) {
this.host = host; this.host = host;
this.port = port; this.port = port;
} }
/** /**
* Get the host * Get the host
*
* @return host * @return host
*/ */
public String getHost() { public String getHost() {
return host; return host;
} }
/** /**
* Get the port * Get the port
*
* @return port * @return port
*/ */
public String getPort() { public String getPort() {
@ -1265,6 +1269,26 @@ public class Server {
} }
} }
/**
* Delete a data source from SOLR.
*
* @param dataSourceId to delete
*
* @throws NoOpenCoreException
*/
void deleteDataSource(Long dataSourceId) throws IOException, KeywordSearchModuleException, NoOpenCoreException, SolrServerException {
try {
currentCoreLock.writeLock().lock();
if (null == currentCore) {
throw new NoOpenCoreException();
}
currentCore.deleteDataSource(dataSourceId);
currentCore.commit();
} finally {
currentCoreLock.writeLock().unlock();
}
}
/** /**
* Get the text contents of the given file as stored in SOLR. * Get the text contents of the given file as stored in SOLR.
* *
@ -1375,10 +1399,10 @@ public class Server {
* @throws IOException * @throws IOException
*/ */
void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException {
TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check"); TimingMetric metric = HealthMonitor.getTimingMetric("Solr: Connectivity check");
CoreAdminRequest statusRequest = new CoreAdminRequest(); CoreAdminRequest statusRequest = new CoreAdminRequest();
statusRequest.setCoreName( null ); statusRequest.setCoreName(null);
statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
statusRequest.setIndexInfoNeeded(false); statusRequest.setIndexInfoNeeded(false);
statusRequest.process(solrServer); statusRequest.process(solrServer);
HealthMonitor.submitTimingMetric(metric); HealthMonitor.submitTimingMetric(metric);
@ -1435,7 +1459,7 @@ public class Server {
// the server to access a core needs to be built from a URL with the // the server to access a core needs to be built from a URL with the
// core in it, and is only good for core-specific operations // core in it, and is only good for core-specific operations
private final HttpSolrServer solrCore; private final HttpSolrServer solrCore;
private final int QUERY_TIMEOUT_MILLISECONDS = 86400000; // 24 Hours = 86,400,000 Milliseconds private final int QUERY_TIMEOUT_MILLISECONDS = 86400000; // 24 Hours = 86,400,000 Milliseconds
private Core(String name, CaseType caseType, Index index) { private Core(String name, CaseType caseType, Index index) {
@ -1447,7 +1471,7 @@ public class Server {
//TODO test these settings //TODO test these settings
// socket read timeout, make large enough so can index larger files // socket read timeout, make large enough so can index larger files
solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS); solrCore.setSoTimeout(QUERY_TIMEOUT_MILLISECONDS);
//solrCore.setConnectionTimeout(1000); //solrCore.setConnectionTimeout(1000);
solrCore.setDefaultMaxConnectionsPerHost(32); solrCore.setDefaultMaxConnectionsPerHost(32);
solrCore.setMaxTotalConnections(32); solrCore.setMaxTotalConnections(32);
@ -1506,6 +1530,13 @@ public class Server {
} }
} }
private void deleteDataSource(Long dsObjId) throws IOException, SolrServerException {
String dataSourceId = Long.toString(dsObjId);
String deleteQuery = "image_id:" + dataSourceId;
solrCore.deleteByQuery(deleteQuery);
}
void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException { void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException {
try { try {
solrCore.add(doc); solrCore.add(doc);
@ -1527,7 +1558,8 @@ public class Server {
* @param chunkID Chunk ID of the Solr document * @param chunkID Chunk ID of the Solr document
* *
* @return Text from matching Solr document (as String). Null if no * @return Text from matching Solr document (as String). Null if no
* matching Solr document found or error while getting content from Solr * matching Solr document found or error while getting content
* from Solr
*/ */
private String getSolrContent(long contentID, int chunkID) { private String getSolrContent(long contentID, int chunkID) {
final SolrQuery q = new SolrQuery(); final SolrQuery q = new SolrQuery();

View File

@ -60,8 +60,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* text indexing and search. * text indexing and search.
*/ */
@ServiceProviders(value = { @ServiceProviders(value = {
@ServiceProvider(service = KeywordSearchService.class) @ServiceProvider(service = KeywordSearchService.class),
,
@ServiceProvider(service = AutopsyService.class) @ServiceProvider(service = AutopsyService.class)
}) })
public class SolrSearchService implements KeywordSearchService, AutopsyService { public class SolrSearchService implements KeywordSearchService, AutopsyService {
@ -194,6 +193,26 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
} }
} }
/**
* Deletes a data source from Solr for a case.
*
* @param dataSourceId the id of the data source to delete.
*
* @throws
* org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException
*/
@Override
public void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException {
try {
Server ddsServer = KeywordSearch.getServer();
ddsServer.deleteDataSource(dataSourceId);
} catch (IOException | KeywordSearchModuleException | NoOpenCoreException | SolrServerException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
}
}
/** /**
* Deletes Solr core for a case. * Deletes Solr core for a case.
* *
@ -275,7 +294,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService {
String caseDirPath = context.getCase().getCaseDirectory(); String caseDirPath = context.getCase().getCaseDirectory();
Case theCase = context.getCase(); Case theCase = context.getCase();
List<Index> indexes = new ArrayList<>(); List<Index> indexes = new ArrayList<>();
progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits); progress.progress(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
if (IndexMetadata.isMetadataFilePresent(caseDirPath)) { if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
try { try {
// metadata file exists, get list of existing Solr cores for this case // metadata file exists, get list of existing Solr cores for this case

View File

@ -1,5 +1,5 @@
#Updated by build script #Updated by build script
#Fri, 04 Oct 2019 14:30:10 -0400 #Tue, 12 Nov 2019 17:21:46 -0500
LBL_splash_window_title=Starting Autopsy LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314 SPLASH_HEIGHT=314
SPLASH_WIDTH=538 SPLASH_WIDTH=538

View File

@ -1,4 +1,4 @@
#Updated by build script #Updated by build script
#Fri, 04 Oct 2019 14:30:10 -0400 #Tue, 12 Nov 2019 17:21:46 -0500
CTL_MainWindow_Title=Autopsy 4.13.0 CTL_MainWindow_Title=Autopsy 4.13.0
CTL_MainWindow_Title_No_Project=Autopsy 4.13.0 CTL_MainWindow_Title_No_Project=Autopsy 4.13.0