diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties
new file mode 100644
index 0000000000..caab36116d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties
@@ -0,0 +1,9 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+
+CTIntegrationMissingDialog.title=Cyber Triage Importer Module Required
+CTIntegrationMissingDialog.descriptionLabel.text=
The Cyber Triage Importer Module is required to open this case.
To open this case:
- Extract the module from the Integrations tab in the Cyber Triage options panel.
- Select the 'Plugins' option from the 'Tools' menu, and go to the 'Downloaded' tab.
- Click 'Add Plugins...' and select the path of the plugin.
- Press 'Install' to finish the installation.
+CTIntegrationMissingDialog.docsLabel.text=For more information, refer to the Cyber Triage Users Guide
+CTIntegrationMissingDialog.okButton.text=OK
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED
new file mode 100644
index 0000000000..caab36116d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED
@@ -0,0 +1,9 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+
+CTIntegrationMissingDialog.title=Cyber Triage Importer Module Required
+CTIntegrationMissingDialog.descriptionLabel.text=The Cyber Triage Importer Module is required to open this case.
To open this case:
- Extract the module from the Integrations tab in the Cyber Triage options panel.
- Select the 'Plugins' option from the 'Tools' menu, and go to the 'Downloaded' tab.
- Click 'Add Plugins...' and select the path of the plugin.
- Press 'Install' to finish the installation.
+CTIntegrationMissingDialog.docsLabel.text=For more information, refer to the Cyber Triage Users Guide
+CTIntegrationMissingDialog.okButton.text=OK
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form
new file mode 100644
index 0000000000..e61f2f6cbd
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form
@@ -0,0 +1,131 @@
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java
new file mode 100644
index 0000000000..9e534c9833
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java
@@ -0,0 +1,165 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 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 com.basistech.df.cybertriage.autopsy;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
+import javax.swing.JComponent;
+import org.sleuthkit.autopsy.coreutils.Logger;
+
+/**
+ * Provides directions with how to enable CT integration with Autopsy when
+ * trying to open a CT exported case.
+ */
+public class CTIntegrationMissingDialog extends javax.swing.JDialog {
+
+ private static final String DOCS_PAGE_URL = "https://docs.cybertriage.com/en/latest/chapters/integrations/autopsy.html";
+
+ private static final Logger LOGGER = Logger.getLogger(CTIntegrationMissingDialog.class.getName());
+
+ /**
+ * Creates new form CTIntegrationMissingDialog
+ */
+ public CTIntegrationMissingDialog(java.awt.Frame parent, boolean modal) {
+ super(parent, modal);
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ javax.swing.JLabel descriptionLabel = new javax.swing.JLabel();
+ javax.swing.JLabel docsLabel = new javax.swing.JLabel();
+ link = new javax.swing.JLabel();
+ javax.swing.JPanel paddingPanel = new javax.swing.JPanel();
+ javax.swing.JButton okButton = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.title")); // NOI18N
+ setAlwaysOnTop(true);
+ setResizable(false);
+ getContentPane().setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.descriptionLabel.text")); // NOI18N
+ descriptionLabel.setMinimumSize(new java.awt.Dimension(483, 116));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ getContentPane().add(descriptionLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(docsLabel, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.docsLabel.text")); // NOI18N
+ docsLabel.setMinimumSize(new java.awt.Dimension(312, 16));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0);
+ getContentPane().add(docsLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(link, "" + DOCS_PAGE_URL + "");
+ link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
+ link.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ linkMouseClicked(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(link, gridBagConstraints);
+
+ javax.swing.GroupLayout paddingPanelLayout = new javax.swing.GroupLayout(paddingPanel);
+ paddingPanel.setLayout(paddingPanelLayout);
+ paddingPanelLayout.setHorizontalGroup(
+ paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+ paddingPanelLayout.setVerticalGroup(
+ paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.weighty = 1.0;
+ getContentPane().add(paddingPanel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.okButton.text")); // NOI18N
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.insets = new java.awt.Insets(10, 5, 5, 5);
+ getContentPane().add(okButton, gridBagConstraints);
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void linkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_linkMouseClicked
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().browse(new URI(DOCS_PAGE_URL));
+ } catch (IOException | URISyntaxException e) {
+ LOGGER.log(Level.SEVERE, "Error opening link to: " + DOCS_PAGE_URL, e);
+ }
+ } else {
+ LOGGER.log(Level.WARNING, "Desktop API is not supported. Link cannot be opened.");
+ }
+ }//GEN-LAST:event_linkMouseClicked
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ dispose();
+ }//GEN-LAST:event_okButtonActionPerformed
+
+ public void showDialog(JComponent parentComp) {
+ setLocationRelativeTo(parentComp == null ? getParent() : parentComp);
+ pack();
+ setVisible(true);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel link;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
index b671940118..dc96292d9a 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
@@ -16,6 +16,7 @@ Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it mus
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.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case.
+Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.
# {0} - exception message
Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}.
# {0} - exception message
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 6ece795e35..b1d2f28c31 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 com.basistech.df.cybertriage.autopsy.CTIntegrationMissingDialog;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import com.google.common.annotations.Beta;
import com.google.common.eventbus.Subscribe;
@@ -177,6 +178,7 @@ public class Case {
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
+ private static final String CT_PROVIDER_PREFIX = "CTStandardContentProvider_";
private static final Logger logger = Logger.getLogger(Case.class.getName());
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
private static final Object caseActionSerializationLock = new Object();
@@ -2729,6 +2731,7 @@ public class Case {
"Case.progressMessage.openingCaseDatabase=Opening case database...",
"# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
"# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
+ "Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.",
"Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
})
private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
@@ -2737,14 +2740,15 @@ public class Case {
String databaseName = metadata.getCaseDatabaseName();
ContentStreamProvider contentProvider = loadContentProvider(metadata.getContentProviderName());
+ if (StringUtils.isNotBlank(metadata.getContentProviderName()) && contentProvider == null) {
+ if (metadata.getContentProviderName().trim().toUpperCase().startsWith(CT_PROVIDER_PREFIX.toUpperCase())) {
+ new CTIntegrationMissingDialog(WindowManager.getDefault().getMainWindow(), true).showDialog(null);
+ }
+ throw new CaseActionException(Bundle.Case_exceptionMessage_contentProviderCouldNotBeFound());
+ }
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
- // only prefix with metadata directory if databaseName is a relative path
- String fullDatabasePath = (new File(databaseName).isAbsolute())
- ? databaseName
- : Paths.get(metadata.getCaseDirectory(), databaseName).toString();
-
- caseDb = SleuthkitCase.openCase(fullDatabasePath, contentProvider);
+ caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider);
} else if (UserPreferences.getIsMultiUserModeEnabled()) {
caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider);
} else {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
index c9170b1e68..91566a01bb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -29,16 +29,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -51,13 +43,10 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
-import org.openide.util.Lookup;
import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
@@ -121,7 +110,6 @@ public final class CaseMetadata {
private static final String SCHEMA_VERSION_SIX = "6.0";
private final static String CONTENT_PROVIDER_ELEMENT_NAME = "ContentProvider";
private final static String CONTENT_PROVIDER_NAME_ELEMENT_NAME = "Name";
- private final static String CONTENT_PROVIDER_ARG_DEFAULT_KEY = "DEFAULT";
/*
* Unread fields, regenerated on save.
@@ -136,7 +124,7 @@ public final class CaseMetadata {
private String caseName;
private CaseDetails caseDetails;
private String caseDatabaseName;
- private String caseDatabasePath; // Legacy
+ private String caseDatabasePath;
private String textIndexName; // Legacy
private String createdDate;
private String createdByVersion;
@@ -258,7 +246,9 @@ public final class CaseMetadata {
* @return The case directory.
*/
public String getCaseDirectory() {
- return metadataFilePath.getParent().toString();
+ return StringUtils.isBlank(this.caseDatabasePath)
+ ? metadataFilePath.getParent().toString()
+ : Paths.get(this.caseDatabasePath).getParent().toString();
}
/**
@@ -637,6 +627,7 @@ public final class CaseMetadata {
this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
break;
default:
+ this.caseDatabasePath = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, false);
this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
break;
@@ -650,11 +641,9 @@ public final class CaseMetadata {
*/
Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
Path caseDirectoryPath = Paths.get(getCaseDirectory());
- if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
+ if (possibleAbsoluteCaseDbPath.toFile().isAbsolute()) {
this.caseDatabasePath = this.caseDatabaseName;
this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
- } else {
- this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
}
} catch (ParserConfigurationException | SAXException | IOException ex) {
@@ -719,12 +708,12 @@ public final class CaseMetadata {
* @return The full path to the case database file for a single-user case.
*
* @throws UnsupportedOperationException If called for a multi-user case.
- * @deprecated Do not use.
*/
- @Deprecated
public String getCaseDatabasePath() throws UnsupportedOperationException {
if (Case.CaseType.SINGLE_USER_CASE == caseType) {
- return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
+ return StringUtils.isBlank(this.caseDatabasePath)
+ ? this.metadataFilePath.getParent().resolve(this.caseDatabaseName).toString()
+ : this.caseDatabasePath;
} else {
throw new UnsupportedOperationException();
}