integrate and update data src deletion

This commit is contained in:
Richard Cordovano 2019-10-30 16:42:20 -04:00
parent 37fb384192
commit 8db2acfe16
8 changed files with 336 additions and 261 deletions

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.access;
import java.io.File; import java.io.File;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -26,7 +26,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
* Class for methods to check if access should be limited to a feature * Class for methods to check if access should be limited to a feature
* *
*/ */
final class AccessLimiterUtils { final public class AccessLimiterUtils {
private final static String MULTI_USER_ACCESS_FILE_NAME = "mualimit"; // NON-NLS 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(); private final static String MULTI_USER_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), MULTI_USER_ACCESS_FILE_NAME).toString();
@ -36,7 +36,7 @@ final class AccessLimiterUtils {
* *
* @return True if privileges should be restricted, false otherwise. * @return True if privileges should be restricted, false otherwise.
*/ */
static boolean limitMultiUserAccess() { public static boolean limitMultiUserAccess() {
return new File(MULTI_USER_ACCESS_FILE_PATH).exists(); return new File(MULTI_USER_ACCESS_FILE_PATH).exists();
} }

View File

@ -23,7 +23,6 @@ DeleteContentTagAction.deleteTag=Remove Selected Tag(s)
DeleteContentTagAction.tagDelErr=Tag Deletion Error DeleteContentTagAction.tagDelErr=Tag Deletion Error
# {0} - tagName # {0} - tagName
DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}. DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}.
DeleteDataSourceAction.name.text=Delete Data Source
DeleteFileBlackboardArtifactTagAction.deleteTag=Remove Result Tag DeleteFileBlackboardArtifactTagAction.deleteTag=Remove Result Tag
# {0} - artifactID # {0} - artifactID
DeleteFileBlackboardArtifactTagAction.deleteTag.alert=Unable to untag artifact {0}. DeleteFileBlackboardArtifactTagAction.deleteTag.alert=Unable to untag artifact {0}.

View File

@ -1,78 +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.actions;
import java.awt.event.ActionEvent;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException;
import org.sleuthkit.datamodel.TskCoreException;
/**
* An Action that allows a user to delete a data source.
*/
public final class DeleteDataSourceAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DeleteDataSourceAction.class.getName());
private long dataSourceID;
/**
* Constructs an Action that allows a user to delete a data source.
*
* @param dataSourceID The object ID of the data source to be deleted.
*/
@NbBundle.Messages({
"DeleteDataSourceAction.name.text=Delete Data Source"
})
public DeleteDataSourceAction(Long dataSourceID) {
super(Bundle.DeleteDataSourceAction_name_text());
this.dataSourceID = dataSourceID;
}
@Override
public void actionPerformed(ActionEvent event) {
// try {
// Case.getCurrentCaseThrows().getSleuthkitCase().deleteDataSource(dataSourceID);
// KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class);
// kwsService.deleteDataSource(dataSourceID);
// Case.getCurrentCaseThrows().notifyDataSourceDeleted(dataSourceID);
// } catch (NoCurrentCaseException | TskCoreException | KeywordSearchServiceException e) {
// logger.log(Level.SEVERE, String.format("Error Deleting data source (obj_id=%d)", dataSourceID), e);
// }
}
@Override
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
clonedObject.setDataSourceID(this.dataSourceID);
return clonedObject;
}
private void setDataSourceID(long dataSourceID) {
this.dataSourceID = dataSourceID;
}
}

View File

@ -10,7 +10,6 @@ Case.deleteCaseConfirmationDialog.title=Delete Current Case?
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.DeletingDataSourceFromCase=Deleting the Data Source from the case. Case.DeletingDataSourceFromCase=Deleting the Data Source from the case.
Case.ErrorDeletingDataSource.name.text=Error Deleting Data Source
Case.exceptionMessage.cancelledByUser=Cancelled by user. Case.exceptionMessage.cancelledByUser=Cancelled by user.
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.
@ -38,6 +37,8 @@ Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination s
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.
# {0} - exception message
Case.exceptionMessage.errorDeletingDataSource=An error occurred while deleting the data source:\n{0}.
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}
@ -54,14 +55,11 @@ Case.exceptionMessage.metadataUpdateError=Failed to update case metadata
Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}. Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.
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.progressIndicatorStatus_closingCase=Closing Case to Deleting Data Source.
Case.progressIndicatorStatus_deletingDataSource=Deleting Data Source.
Case.progressIndicatorStatus_openingCase=Opening Case to Deleting Data Source.
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=Deleting Data Source
Case.progressIndicatorTitle.openingCase=Opening Case Case.progressIndicatorTitle.openingCase=Opening Case
Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case.
Case.progressMessage.cancelling=Cancelling... Case.progressMessage.cancelling=Cancelling...
Case.progressMessage.clearingTempDirectory=Clearing case temp directory... Case.progressMessage.clearingTempDirectory=Clearing case temp directory...
Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources... Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources...
@ -124,6 +122,10 @@ 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 delete the selected data source from the case?
DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to reopen the case.
DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while deleting the data source.\nPlease see the application log for details.
DeleteDataSourceAction.name.text=Delete 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.access.AccessLimiterUtils;
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;
@ -52,6 +53,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -741,9 +743,9 @@ public class Case {
} }
/** /**
* Deletes the selected data source. * Deletes a data source from the current cases.
* *
* @param dataSourceId id of the data source to delete. * @param dataSourceObjectID The object ID of the data source to delete.
* *
* @throws CaseActionException If there is a problem deleting the case. The * @throws CaseActionException If there is a problem deleting the case. The
* exception will have a user-friendly message * exception will have a user-friendly message
@ -751,64 +753,20 @@ public class Case {
* exception. * exception.
*/ */
@Messages({ @Messages({
"Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case.", "Case.progressIndicatorTitle.deletingDataSource=Deleting Data Source"
"Case.progressIndicatorStatus_closingCase=Closing Case to Deleting Data Source.", })
"Case.progressIndicatorStatus_openingCase=Opening Case to Deleting Data Source.", public static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException {
"Case.progressIndicatorStatus_deletingDataSource=Deleting Data Source.",})
public static void deleteDataSourceFromCurrentCase(Long dataSourceId) throws CaseActionException {
synchronized (caseActionSerializationLock) { synchronized (caseActionSerializationLock) {
if (null == currentCase) { if (null == currentCase) {
return; return;
} }
CaseMetadata newMetadata = null; Case theCase = currentCase;
CaseMetadata metadata = currentCase.getMetadata();
String caseDir = metadata.getFilePath().toString();
try {
newMetadata = new CaseMetadata(Paths.get(caseDir));
} catch (CaseMetadataException ex) {
logger.log(Level.WARNING, String.format("Error Getting Case Dir %s", caseDir), ex);
}
ProgressIndicator progressIndicator;
if (RuntimeProperties.runningWithGUI()) {
progressIndicator = new ModalDialogProgressIndicator(mainFrame, Bundle.Case_progressIndicatorTitle_deletingDataSource());
} else {
progressIndicator = new LoggingProgressIndicator();
}
closeCurrentCase(); closeCurrentCase();
progressIndicator.switchToIndeterminate(Bundle.Case_progressIndicatorStatus_openingCase()); theCase.doCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), null, CaseLockType.EXCLUSIVE, false, dataSourceObjectID);
progressIndicator.start(Bundle.Case_progressIndicatorStatus_deletingDataSource()); openAsCurrentCase(theCase, false);
deleteDataSource(dataSourceId, progressIndicator, metadata);
progressIndicator.finish();
openAsCurrentCase(new Case(newMetadata), false);
} }
} }
/**
* Delete a data source from the current case.
*
* @param dataSourceId id of the data source to delete.
* @param progressIndicator
*/
@Messages({
"Case.DeletingDataSourceFromCase=Deleting the Data Source from the case.",
"Case.ErrorDeletingDataSource.name.text=Error Deleting Data Source"
})
static void deleteDataSource(Long dataSourceId, ProgressIndicator progressIndicator, CaseMetadata metadata) throws CaseActionException {
// get case actions lock
closeCurrentCase();
// If multi-user case, get exclusive lock on case, else continue w/o lock
Case theCase = new Case(metadata);
theCase.openCaseDataBase(progressIndicator);
theCase.openAppServiceCaseResources(progressIndicator);
try {
SleuthkitCaseAdmin.deleteDataSource(null, dataSourceId);
} catch (TskCoreException ex) {
}
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceId));
theCase.close(progressIndicator);
openAsCurrentCase(new Case(metadata), 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.
@ -870,6 +828,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 {
@ -887,7 +847,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> caseAction;
if (isNewCase) {
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_creatingCase();
caseAction = newCurrentCase::createCase;
} else {
progressIndicatorTitle = Bundle.Case_progressIndicatorTitle_openingCase();
caseAction = newCurrentCase::openCase;
}
newCurrentCase.doCaseAction(progressIndicatorTitle, caseAction, 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()) {
@ -1553,18 +1522,6 @@ public class Case {
eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName)); eventPublisher.publish(new DataSourceNameChangedEvent(dataSource, newName));
} }
/**
* Notifies case event subscribers that a data source has been deleted from
* the case database.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param dataSourceId The object ID of the data source that was deleted.
*/
public void notifyDataSourceDeleted(Long dataSourceId) {
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceId));
}
/** /**
* Notifies case event subscribers that a content tag has been added. * Notifies case event subscribers that a content tag has been added.
* *
@ -1795,23 +1752,30 @@ public class Case {
} }
/** /**
* Opens this case by creating a task running in the same non-UI thread that * Performs a case action by creating a task running in the same non-UI
* will be used to close the case. If the case is a single-user case, this * thread that will be used to close the case. For both single-user and
* supports cancelling creation of the case by cancelling the task. If the * mulit-user cases, this supports cancelling the case action by cancelling
* case is a multi-user case, this ensures ensures that case directory lock * the task. If the case is a multi-user case, this also ensures that the
* held as long as the case is open is released in the same thread in which * case lock is released in the same thread in which it was acquired, which
* it was acquired, as is required by the coordination service. * is required by the coordination service.
* *
* @param isNewCase True for a new case, false otherwise. * @param progressIndicatorTitle A title for the progress indicator for the
* case action.
* @param caseAction The case action method.
* @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. 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.
*/ */
@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.preparingToOpenCaseResources=<html>Preparing to open case resources.<br>This may take time if another user is upgrading the case.</html>",
@ -1819,62 +1783,66 @@ public class Case {
"Case.exceptionMessage.cancelledByUser=Cancelled by user.", "Case.exceptionMessage.cancelledByUser=Cancelled by user.",
"# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}" "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}"
}) })
private void open(boolean isNewCase) throws CaseActionException { private void doCaseAction(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 or a logging
* or a logging progress indicator. * progress indicator.
*/ */
CancelButtonListener cancelButtonListener = null; CancelButtonListener cancelButtonListener = null;
ProgressIndicator progressIndicator; ProgressIndicator progressIndicator;
if (RuntimeProperties.runningWithGUI()) { if (RuntimeProperties.runningWithGUI()) {
if (allowCancellation) {
cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling()); cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling());
String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase();
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 * A case action is always done by creating a task running in the same
* the same non-UI thread that will be used to close the case, so a * non-UI thread that will be used to close the case, so a
* single-threaded executor service is created here and saved as case * single-threaded executor service is created here and saved as case
* state (must be volatile for cancellation to work). * state (must be volatile for cancellation to work).
* *
* --- If the case is a single-user case, this supports cancelling * --- If the case is a single-user case, this supports cancelling the
* opening of the case by cancelling the task. * case action by cancelling the task.
* *
* --- If the case is a multi-user case, this still supports * --- If the case is a multi-user case, this still supports
* cancellation, but it also makes it possible for the shared case * cancellation, but it also makes it possible for the case lock held as
* directory lock held as long as the case is open to be released in the * long as the case is open to be released in the same thread in which
* same thread in which it was acquired, as is required by the * it was acquired, as is required by the coordination service.
* 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); caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory);
Future<Void> future = caseLockingExecutor.submit(() -> { Future<Void> future = caseLockingExecutor.submit(() -> {
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
open(isNewCase, progressIndicator); caseAction.execute(progressIndicator, additionalParams);
} else { } else {
/* /*
* First, acquire a shared case directory lock that will be held * First, acquire a case lock that will be held as long as this
* as long as this node has this case open. This will prevent * node has this case open. This will prevent deletion of the
* deletion of the case by another node. Next, acquire an * case by another node. Next, acquire an exclusive case
* exclusive case resources lock to ensure only one node at a * resources lock to ensure only one node at a time can
* time can create/open/upgrade/close the case resources. * create/open/upgrade/close the case resources.
*/ */
progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources()); progressIndicator.progress(Bundle.Case_progressMessage_preparingToOpenCaseResources());
acquireSharedCaseDirLock(metadata.getCaseDirectory()); acquireCaseLock(caseLockType, metadata.getCaseDirectory());
try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(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()); releaseSharedCaseDirLock(getMetadata().getCaseDirectory());
throw ex; throw ex;
@ -1887,18 +1855,17 @@ 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. Cancel
* the create/open task, wait for it to finish, and shut down the * the case action task, wait for it to finish, and shut down the
* executor. This can be done safely because if the task is * executor. This can be done safely because if the task is
* completed with a cancellation condition, the case will have been * completed with a cancellation condition, the case will have been
* closed and the case directory lock released will have been * closed and the case lock will have been released.
* released.
*/ */
if (null != cancelButtonListener) { if (null != cancelButtonListener) {
cancelButtonListener.actionPerformed(null); cancelButtonListener.actionPerformed(null);
@ -1908,21 +1875,20 @@ public class Case {
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
} catch (CancellationException discarded) { } catch (CancellationException discarded) {
/* /*
* The create/open task has been cancelled. Wait for it to finish, * The case action task has been cancelled. Wait for it to finish,
* and shut down the executor. This can be done safely because if * and shut down the executor. This can be done safely because if
* the task is completed with a cancellation condition, the case * the task is completed with a cancellation condition, the case
* will have been closed and the case directory lock released will * will have been closed and the case lock will have been released.
* have been released.
*/ */
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser());
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
/* /*
* The create/open task has thrown an exception. Wait for it to * The case action task has thrown an exception. Wait for it to
* finish, and shut down the executor. This can be done safely * finish, and shut down the executor. This can be done safely
* because if the task is completed with an execution condition, the * because if the task is completed with an execution condition, the
* case will have been closed and the case directory lock released * case will have been closed and the case lock will have been
* will have been released. * released.
*/ */
ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); ThreadUtils.shutDownTaskExecutor(caseLockingExecutor);
throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex); throw new CaseActionException(Bundle.Case_exceptionMessage_execExceptionWrapperMessage(ex.getCause().getLocalizedMessage()), ex);
@ -1932,48 +1898,41 @@ 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 createCase(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams == null);
try { try {
checkForUserCancellation(); checkForUserCancellation();
createCaseDirectoryIfDoesNotExist(progressIndicator); createCaseDirectoryIfDoesNotExist(progressIndicator);
checkForUserCancellation(); checkForUserCancellation();
switchLoggingToCaseLogsDirectory(progressIndicator); switchLoggingToCaseLogsDirectory(progressIndicator);
checkForUserCancellation(); checkForUserCancellation();
if (isNewCase) {
saveCaseMetadataToFile(progressIndicator); saveCaseMetadataToFile(progressIndicator);
}
checkForUserCancellation(); checkForUserCancellation();
if (isNewCase) {
createCaseNodeData(progressIndicator); createCaseNodeData(progressIndicator);
} else {
updateCaseNodeData(progressIndicator);
}
checkForUserCancellation(); checkForUserCancellation();
if (!isNewCase) {
deleteTempfilesFromCaseDirectory(progressIndicator);
}
checkForUserCancellation(); checkForUserCancellation();
if (isNewCase) {
createCaseDatabase(progressIndicator); createCaseDatabase(progressIndicator);
} else {
openCaseDataBase(progressIndicator);
}
checkForUserCancellation(); checkForUserCancellation();
openCaseLevelServices(progressIndicator); openCaseLevelServices(progressIndicator);
checkForUserCancellation(); checkForUserCancellation();
openAppServiceCaseResources(progressIndicator); openAppServiceCaseResources(progressIndicator);
checkForUserCancellation(); checkForUserCancellation();
openCommunicationChannels(progressIndicator); openCommunicationChannels(progressIndicator);
return null;
} catch (CaseActionException ex) { } catch (CaseActionException ex) {
/* /*
@ -1991,6 +1950,96 @@ public class Case {
} }
} }
/**
* A case action (interface CaseAction<T, V, R>) that opens the case
* database and 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 openCase(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams == null);
try {
checkForUserCancellation();
switchLoggingToCaseLogsDirectory(progressIndicator);
checkForUserCancellation();
updateCaseNodeData(progressIndicator);
checkForUserCancellation();
deleteTempfilesFromCaseDirectory(progressIndicator);
checkForUserCancellation();
openCaseDataBase(progressIndicator);
checkForUserCancellation();
openCaseLevelServices(progressIndicator);
checkForUserCancellation();
openAppServiceCaseResources(progressIndicator);
checkForUserCancellation();
openCommunicationChannels(progressIndicator);
return null;
} catch (CaseActionException ex) {
/*
* Cancellation or failure. Clean up by calling the close method.
* 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>) for a closed case that
* opens the case database and application services for this case, deletes a
* data source from the case, and publishes an application event indicasting
* the data source has been deleted.
*
* Note that this case action does not support cancellation.
*
* @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.DeletingDataSourceFromCase=Deleting the Data Source from the case.",
"# {0} - exception message", "Case.exceptionMessage.errorDeletingDataSource=An error occurred while deleting the data source:\n{0}."
})
Void deleteDataSource(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException {
assert (additionalParams instanceof Long);
openCaseDataBase(progressIndicator);
openAppServiceCaseResources(progressIndicator);
Long dataSourceObjectID = (Long) additionalParams;
try {
SleuthkitCaseAdmin.deleteDataSource(this.caseDb, dataSourceObjectID);
} catch (TskCoreException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSource(ex.getMessage()), ex);
}
try {
this.caseServices.getKeywordSearchService().deleteDataSource(dataSourceObjectID);
} catch (KeywordSearchServiceException ex) {
throw new CaseActionException(Bundle.Case_exceptionMessage_errorDeletingDataSource(ex.getMessage()), ex);
}
eventPublisher.publish(new DataSourceDeletedEvent(dataSourceObjectID));
return null;
}
/** /**
* Create an empty portable case from the current case * Create an empty portable case from the current case
* *
@ -2475,7 +2524,7 @@ 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) {
@ -2595,9 +2644,13 @@ public class Case {
* cannot be acquired. * cannot be acquired.
*/ */
@Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"}) @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory"})
private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { private void acquireCaseLock(CaseLockType lockType, String caseDir) throws CaseActionException {
try { try {
caseDirLock = CoordinationService.getInstance().tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS); boolean flag = true;
CoordinationService coordinationService = CoordinationService.getInstance();
caseDirLock = lockType == CaseLockType.SHARED
? coordinationService.tryGetSharedLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS)
: coordinationService.tryGetExclusiveLock(CategoryNode.CASES, caseDir, DIR_LOCK_TIMOUT_HOURS, TimeUnit.HOURS);
if (null == caseDirLock) { if (null == caseDirLock) {
throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock());
} }
@ -2994,6 +3047,15 @@ public class Case {
} }
} }
private interface CaseAction<T, V, R> {
R execute(T t, V v) throws CaseActionException;
}
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,107 @@
/*
* 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;
/**
* An Action that allows a user to delete 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 delete a data source.
*
* @param dataSourceObjectID The object ID of the data source to be deleted.
*/
@NbBundle.Messages({
"DeleteDataSourceAction.name.text=Delete Data Source"
})
public DeleteDataSourceAction(Long dataSourceObjectID) {
super(Bundle.DeleteDataSourceAction_name_text());
this.dataSourceObjectID = dataSourceObjectID;
}
@NbBundle.Messages({
"DeleteDataSourceAction.confirmationDialog.message=Are you sure you want to delete the selected data source from the case?",
"DeleteDataSourceAction.exceptionMessage.dataSourceDeletionError=An error occurred while deleting the data source.\nPlease see the application log for details.",
"DeleteDataSourceAction.exceptionMessage.couldNotReopenCase=Failed to reopen the case.",})
@Override
public void actionPerformed(ActionEvent event) {
if (MessageNotifyUtil.Message.confirm(Bundle.DeleteDataSourceAction_confirmationDialog_message())) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
caseMetadataFilePath = Case.getCurrentCase().getMetadata().getFilePath();
/*
* Note that the case is closed and re-opened by this case
* action.
*/
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(), MessageNotifyUtil.MessageType.ERROR);
if (!Case.isCaseOpen()) {
try {
Case.openAsCurrentCase(caseMetadataFilePath.toString());
} catch (CaseActionException ex2) {
logger.log(Level.SEVERE, "Failed to reopen the case after data source deletion error", ex2);
MessageNotifyUtil.Message.show(Bundle.DeleteDataSourceAction_exceptionMessage_couldNotReopenCase(), MessageNotifyUtil.MessageType.ERROR);
StartupWindowProvider.getInstance().open();
}
}
}
}
}.execute();
}
}
@Override
public DeleteDataSourceAction clone() throws CloneNotSupportedException {
DeleteDataSourceAction clonedObject = ((DeleteDataSourceAction) super.clone());
clonedObject.setDataSourceID(this.dataSourceObjectID);
return clonedObject;
}
private void setDataSourceID(long dataSourceID) {
this.dataSourceObjectID = dataSourceID;
}
}

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.casemodule;
import org.sleuthkit.autopsy.access.AccessLimiterUtils;
import java.awt.Component; import java.awt.Component;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;

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");
@ -20,8 +20,6 @@ package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.io.File;
import java.nio.file.Paths;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@ -35,14 +33,14 @@ import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet; 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.actions.DeleteDataSourceAction; import org.sleuthkit.autopsy.access.AccessLimiterUtils;
import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
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;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
@ -66,10 +64,6 @@ public class ImageNode extends AbstractContentNode<Image> {
private static final Logger logger = Logger.getLogger(ImageNode.class.getName()); private static final Logger logger = Logger.getLogger(ImageNode.class.getName());
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED); private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED);
private final static String ADMIN_ACCESS_FILE_NAME = "admin"; // NON-NLS
private final static String ADMIN_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_ACCESS_FILE_NAME).toString();
private final static String ADMIN_EXT_ACCESS_FILE_NAME = "adminext"; // NON-NLS
private final static String ADMIN_EXT_ACCESS_FILE_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), ADMIN_EXT_ACCESS_FILE_NAME).toString();
/** /**
* Helper so that the display name and the name used in building the path * Helper so that the display name and the name used in building the path
@ -128,7 +122,7 @@ public class ImageNode extends AbstractContentNode<Image> {
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));
if (checkSchemaVersion() && checkMuAdmin()) { if (canAddDeleteDataSourceAction()) {
actionsList.add(new DeleteDataSourceAction(content.getId())); actionsList.add(new DeleteDataSourceAction(content.getId()));
} }
return actionsList.toArray(new Action[0]); return actionsList.toArray(new Action[0]);
@ -218,29 +212,17 @@ public class ImageNode extends AbstractContentNode<Image> {
return getClass().getName(); return getClass().getName();
} }
private Boolean checkSchemaVersion() { /**
try { * Determines whether or not the delete data source action can be added.
CaseDbSchemaVersionNumber creationVersion = Case.getCurrentCaseThrows().getSleuthkitCase().getDBSchemaCreationVersion(); * @return True or false.
*/
if ((creationVersion.getMajor() == 8 && creationVersion.getMinor() >= 3) || creationVersion.getMajor() > 8) { private Boolean canAddDeleteDataSourceAction() {
return true; boolean canAddAction = false;
CaseDbSchemaVersionNumber creationVersion = Case.getCurrentCase().getSleuthkitCase().getDBSchemaCreationVersion();
if ((creationVersion.getMajor() == 8 && creationVersion.getMinor() >= 3) || (creationVersion.getMajor() > 8)) {
canAddAction = Case.getCurrentCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE || !AccessLimiterUtils.limitMultiUserAccess();
} }
} catch (NoCurrentCaseException ex) { return canAddAction;
logger.log(Level.WARNING, "Failed to get creation schema version: ", ex);
}
return false;
}
private Boolean checkMuAdmin() {
try {
if (Case.CaseType.MULTI_USER_CASE == Case.getCurrentCaseThrows().getCaseType()) {
return new File(ADMIN_ACCESS_FILE_PATH).exists() || new File(ADMIN_EXT_ACCESS_FILE_PATH).exists();
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get the Create Major and Minor Schema Versions", ex);
}
return true;
} }
/* /*