diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AccessLimiterUtils.java b/Core/src/org/sleuthkit/autopsy/access/AccessLimiterUtils.java similarity index 92% rename from Core/src/org/sleuthkit/autopsy/casemodule/AccessLimiterUtils.java rename to Core/src/org/sleuthkit/autopsy/access/AccessLimiterUtils.java index 3b2a201e07..31f3a36bc8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AccessLimiterUtils.java +++ b/Core/src/org/sleuthkit/autopsy/access/AccessLimiterUtils.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.casemodule; +package org.sleuthkit.autopsy.access; import java.io.File; 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 * */ -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_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. */ - static boolean limitMultiUserAccess() { + public static boolean limitMultiUserAccess() { return new File(MULTI_USER_ACCESS_FILE_PATH).exists(); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED index e6f0b377a2..a3a13c0cff 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/actions/Bundle.properties-MERGED @@ -23,7 +23,6 @@ DeleteContentTagAction.deleteTag=Remove Selected Tag(s) DeleteContentTagAction.tagDelErr=Tag Deletion Error # {0} - tagName DeleteContentTagAction.unableToDelTag.msg=Unable to delete tag {0}. -DeleteDataSourceAction.name.text=Delete Data Source DeleteFileBlackboardArtifactTagAction.deleteTag=Remove Result Tag # {0} - artifactID DeleteFileBlackboardArtifactTagAction.deleteTag.alert=Unable to untag artifact {0}. diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteDataSourceAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteDataSourceAction.java deleted file mode 100644 index b4c29f03ab..0000000000 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteDataSourceAction.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2019 Basis Technology Corp. - * Contact: carrier sleuthkit 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; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 0e16ccde21..824ee91474 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -10,7 +10,6 @@ Case.deleteCaseConfirmationDialog.title=Delete Current Case? Case.deleteCaseFailureMessageBox.message=Error deleting case: {0} Case.deleteCaseFailureMessageBox.title=Failed to Delete 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.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. @@ -38,6 +37,8 @@ Case.exceptionMessage.couldNotUpdateCaseNodeData=Failed to update coordination s Case.exceptionMessage.deletionInterrupted=Deletion of the case {0} was cancelled. Case.exceptionMessage.emptyCaseDir=Must specify a case directory path. 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. # {0} - exception message 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.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.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.creatingCase=Creating Case Case.progressIndicatorTitle.deletingCase=Deleting Case +Case.progressIndicatorTitle.deletingDataSource=Deleting Data Source Case.progressIndicatorTitle.openingCase=Opening Case -Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case. Case.progressMessage.cancelling=Cancelling... Case.progressMessage.clearingTempDirectory=Clearing case temp directory... Case.progressMessage.closingApplicationServiceResources=Closing case-specific application service resources... @@ -124,6 +122,10 @@ CTL_CaseDetailsAction=Case Details CTL_CaseDeleteAction=Delete Case CTL_CaseOpenAction=Open 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.saveButton.text=Save GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 80142dfbc5..715a4383b7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.access.AccessLimiterUtils; import com.google.common.annotations.Beta; import com.google.common.eventbus.Subscribe; 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.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; 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 * exception will have a user-friendly message @@ -751,64 +753,20 @@ public class Case { * exception. */ @Messages({ - "Case.progressIndicatorTitle_deletingDataSource=Deleting Data Source from the case.", - "Case.progressIndicatorStatus_closingCase=Closing Case to Deleting Data Source.", - "Case.progressIndicatorStatus_openingCase=Opening Case to Deleting Data Source.", - "Case.progressIndicatorStatus_deletingDataSource=Deleting Data Source.",}) - public static void deleteDataSourceFromCurrentCase(Long dataSourceId) throws CaseActionException { + "Case.progressIndicatorTitle.deletingDataSource=Deleting Data Source" + }) + public static void deleteDataSourceFromCurrentCase(Long dataSourceObjectID) throws CaseActionException { synchronized (caseActionSerializationLock) { if (null == currentCase) { return; } - CaseMetadata newMetadata = null; - 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(); - } + Case theCase = currentCase; closeCurrentCase(); - progressIndicator.switchToIndeterminate(Bundle.Case_progressIndicatorStatus_openingCase()); - progressIndicator.start(Bundle.Case_progressIndicatorStatus_deletingDataSource()); - deleteDataSource(dataSourceId, progressIndicator, metadata); - progressIndicator.finish(); - openAsCurrentCase(new Case(newMetadata), false); + theCase.doCaseAction(Bundle.Case_progressIndicatorTitle_deletingDataSource(), null, CaseLockType.EXCLUSIVE, false, dataSourceObjectID); + openAsCurrentCase(theCase, 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." * 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. */ @Messages({ + "Case.progressIndicatorTitle.creatingCase=Creating Case", + "Case.progressIndicatorTitle.openingCase=Opening Case", "Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window" }) private static void openAsCurrentCase(Case newCurrentCase, boolean isNewCase) throws CaseActionException, CaseActionCancelledException { @@ -887,7 +847,16 @@ public class Case { } try { 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 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; 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()) { @@ -1553,18 +1522,6 @@ public class Case { 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. * @@ -1795,23 +1752,30 @@ public class Case { } /** - * Opens this case by creating a task running in the same non-UI thread that - * will be used to close the case. If the case is a single-user case, this - * supports cancelling creation of the case by cancelling the task. If the - * case is a multi-user case, this ensures ensures that case directory lock - * held as long as the case is open is released in the same thread in which - * it was acquired, as is required by the coordination service. + * Performs a case action by creating a task running in the same non-UI + * thread that will be used to close the case. For both single-user and + * mulit-user cases, this supports cancelling the case action by cancelling + * the task. If the case is a multi-user case, this also ensures that the + * case lock is released in the same thread in which it was acquired, which + * 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 - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @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.progressIndicatorTitle.creatingCase=Creating Case", - "Case.progressIndicatorTitle.openingCase=Opening Case", "Case.progressIndicatorCancelButton.label=Cancel", "Case.progressMessage.preparing=Preparing...", "Case.progressMessage.preparingToOpenCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case.", @@ -1819,62 +1783,66 @@ public class Case { "Case.exceptionMessage.cancelledByUser=Cancelled by user.", "# {0} - exception message", "Case.exceptionMessage.execExceptionWrapperMessage={0}" }) - private void open(boolean isNewCase) throws CaseActionException { + private void doCaseAction(String progressIndicatorTitle, CaseAction caseAction, CaseLockType caseLockType, boolean allowCancellation, Object additionalParams) throws CaseActionException { /* - * Create and start either a GUI progress indicator with a Cancel button - * or a logging progress indicator. + * Create and start either a GUI progress indicator or a logging + * progress indicator. */ CancelButtonListener cancelButtonListener = null; ProgressIndicator progressIndicator; if (RuntimeProperties.runningWithGUI()) { - cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling()); - String progressIndicatorTitle = isNewCase ? Bundle.Case_progressIndicatorTitle_creatingCase() : Bundle.Case_progressIndicatorTitle_openingCase(); - progressIndicator = new ModalDialogProgressIndicator( - mainFrame, - progressIndicatorTitle, - new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, - Bundle.Case_progressIndicatorCancelButton_label(), - cancelButtonListener); + if (allowCancellation) { + cancelButtonListener = new CancelButtonListener(Bundle.Case_progressMessage_cancelling()); + progressIndicator = new ModalDialogProgressIndicator( + mainFrame, + progressIndicatorTitle, + new String[]{Bundle.Case_progressIndicatorCancelButton_label()}, + Bundle.Case_progressIndicatorCancelButton_label(), + cancelButtonListener); + } else { + progressIndicator = new ModalDialogProgressIndicator( + mainFrame, + progressIndicatorTitle); + } } else { progressIndicator = new LoggingProgressIndicator(); } progressIndicator.start(Bundle.Case_progressMessage_preparing()); /* - * Creating/opening a case is always done by creating a task running in - * the same non-UI thread that will be used to close the case, so a + * A case action is always done by creating a task running in the same + * non-UI thread that will be used to close the case, so a * single-threaded executor service is created here and saved as case * state (must be volatile for cancellation to work). * - * --- If the case is a single-user case, this supports cancelling - * opening of the case by cancelling the task. + * --- If the case is a single-user case, this supports cancelling the + * case action 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. + * cancellation, but it also makes it possible for the case 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())); caseLockingExecutor = Executors.newSingleThreadExecutor(threadFactory); Future future = caseLockingExecutor.submit(() -> { if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) { - open(isNewCase, progressIndicator); + caseAction.execute(progressIndicator, additionalParams); } else { /* - * First, acquire a shared case directory lock that will be held - * 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. + * First, acquire a case lock that will be held 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()); + acquireCaseLock(caseLockType, metadata.getCaseDirectory()); try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(metadata.getCaseDirectory())) { if (null == resourcesLock) { throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireResourcesLock()); } - open(isNewCase, progressIndicator); + caseAction.execute(progressIndicator, additionalParams); } catch (CaseActionException ex) { releaseSharedCaseDirLock(getMetadata().getCaseDirectory()); 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 { future.get(); } catch (InterruptedException discarded) { /* * 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 * completed with a cancellation condition, the case will have been - * closed and the case directory lock released will have been - * released. + * closed and the case lock will have been released. */ if (null != cancelButtonListener) { cancelButtonListener.actionPerformed(null); @@ -1908,21 +1875,20 @@ public class Case { ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); } 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 * the task is completed with a cancellation condition, the case - * will have been closed and the case directory lock released will - * have been released. + * will have been closed and the case lock will have been released. */ ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); throw new CaseActionCancelledException(Bundle.Case_exceptionMessage_cancelledByUser()); } 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 * 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. + * case will have been closed and the case lock will have been + * released. */ ThreadUtils.shutDownTaskExecutor(caseLockingExecutor); 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) 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 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 - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * @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(boolean isNewCase, ProgressIndicator progressIndicator) throws CaseActionException { + private Void createCase(ProgressIndicator progressIndicator, Object additionalParams) throws CaseActionException { + assert (additionalParams == null); try { checkForUserCancellation(); createCaseDirectoryIfDoesNotExist(progressIndicator); checkForUserCancellation(); switchLoggingToCaseLogsDirectory(progressIndicator); checkForUserCancellation(); - if (isNewCase) { - saveCaseMetadataToFile(progressIndicator); - } + saveCaseMetadataToFile(progressIndicator); checkForUserCancellation(); - if (isNewCase) { - createCaseNodeData(progressIndicator); - } else { - updateCaseNodeData(progressIndicator); - } + createCaseNodeData(progressIndicator); checkForUserCancellation(); - if (!isNewCase) { - deleteTempfilesFromCaseDirectory(progressIndicator); - } checkForUserCancellation(); - if (isNewCase) { - createCaseDatabase(progressIndicator); - } else { - openCaseDataBase(progressIndicator); - } + createCaseDatabase(progressIndicator); checkForUserCancellation(); openCaseLevelServices(progressIndicator); checkForUserCancellation(); openAppServiceCaseResources(progressIndicator); checkForUserCancellation(); openCommunicationChannels(progressIndicator); + return null; } catch (CaseActionException ex) { /* @@ -1991,6 +1950,96 @@ public class Case { } } + /** + * A case action (interface CaseAction) 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) 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 * @@ -2475,7 +2524,7 @@ public class Case { /* * The wait has been interrupted by interrupting the thread running * 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. */ } catch (ExecutionException ex) { @@ -2595,9 +2644,13 @@ public class Case { * cannot be acquired. */ @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 { - 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) { throw new CaseActionException(Bundle.Case_creationException_couldNotAcquireDirLock()); } @@ -2994,6 +3047,15 @@ public class Case { } } + private interface CaseAction { + + R execute(T t, V v) throws CaseActionException; + } + + private enum CaseLockType { + SHARED, EXCLUSIVE; + } + /** * A case operation Cancel button listener for use with a * ModalDialogProgressIndicator when running with a GUI. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/DeleteDataSourceAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteDataSourceAction.java new file mode 100644 index 0000000000..528042d63d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/DeleteDataSourceAction.java @@ -0,0 +1,107 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit 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() { + + @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; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java index 789913d78a..ec959cbaf4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseVisualPanel1.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import org.sleuthkit.autopsy.access.AccessLimiterUtils; import java.awt.Component; import org.openide.util.NbBundle; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index bdf53880c4..ca93ba9040 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * 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.PropertyChangeListener; -import java.io.File; -import java.nio.file.Paths; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -35,14 +33,14 @@ import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; 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.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; import org.sleuthkit.autopsy.directorytree.FileSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; @@ -66,10 +64,6 @@ public class ImageNode extends AbstractContentNode { private static final Logger logger = Logger.getLogger(ImageNode.class.getName()); private static final Set 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 @@ -128,7 +122,7 @@ public class ImageNode extends AbstractContentNode { actionsList.add(new RunIngestModulesAction(Collections.singletonList(content))); actionsList.add(new NewWindowViewAction( NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this)); - if (checkSchemaVersion() && checkMuAdmin()) { + if (canAddDeleteDataSourceAction()) { actionsList.add(new DeleteDataSourceAction(content.getId())); } return actionsList.toArray(new Action[0]); @@ -218,31 +212,19 @@ public class ImageNode extends AbstractContentNode { return getClass().getName(); } - private Boolean checkSchemaVersion() { - try { - CaseDbSchemaVersionNumber creationVersion = Case.getCurrentCaseThrows().getSleuthkitCase().getDBSchemaCreationVersion(); - - if ((creationVersion.getMajor() == 8 && creationVersion.getMinor() >= 3) || creationVersion.getMajor() > 8) { - return true; - } - } catch (NoCurrentCaseException ex) { - 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); + /** + * Determines whether or not the delete data source action can be added. + * @return True or false. + */ + private Boolean canAddDeleteDataSourceAction() { + 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(); } - return true; + return canAddAction; } - + /* * This property change listener refreshes the tree when a new file is * carved out of this image (i.e, the image is being treated as raw bytes