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:

+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:

+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(); }