diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index e684b4af4b..b70845e3df 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -1114,7 +1114,7 @@ public class Case { CallableSystemAction.get(CaseCloseAction.class).setEnabled(true); CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true); CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true); - CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); + CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase()); CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true); CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true); CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java index a389aca979..6bd0231405 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseDeleteAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ import org.openide.util.NbBundle.Messages; import org.openide.util.actions.CallableSystemAction; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; /** * The action associated with the Delete button of the Case Properties panel. It @@ -54,7 +55,7 @@ final class CaseDeleteAction extends CallableSystemAction { /* * A value of 'null' signifies that there is no case open. */ - setEnabled(null != evt.getNewValue()); + setEnabled(null != evt.getNewValue() && FeatureAccessUtils.canDeleteCurrentCase()); }); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbChoice.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbChoice.java new file mode 100644 index 0000000000..86f80e843a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbChoice.java @@ -0,0 +1,76 @@ +/* + * Central Repository + * + * Copyright 2015-2020 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.centralrepository.datamodel; + +import org.openide.util.NbBundle.Messages; + +/** + * This represents a database choices available for central repo. + */ +@Messages({ + "CentralRepoDbChoice.Disabled.Text=Disabled", + "CentralRepoDbChoice.Sqlite.Text=SQLite", + "CentralRepoDbChoice.PostgreSQL_Multiuser.Text=PostgreSQL using multi-user settings", + "CentralRepoDbChoice.PostgreSQL.Text=Custom PostgreSQL", +}) +public enum CentralRepoDbChoice { + DISABLED("Disabled", Bundle.CentralRepoDbChoice_Disabled_Text(), CentralRepoPlatforms.DISABLED), + SQLITE("Sqlite", Bundle.CentralRepoDbChoice_Sqlite_Text(), CentralRepoPlatforms.SQLITE), + POSTGRESQL_MULTIUSER("PostgreSQL_Multiuser", Bundle.CentralRepoDbChoice_PostgreSQL_Multiuser_Text(), CentralRepoPlatforms.POSTGRESQL), + POSTGRESQL_CUSTOM("PostgreSQL", Bundle.CentralRepoDbChoice_PostgreSQL_Text(), CentralRepoPlatforms.POSTGRESQL); + + public static final CentralRepoDbChoice[] DB_CHOICES = new CentralRepoDbChoice[]{ + SQLITE, POSTGRESQL_MULTIUSER, POSTGRESQL_CUSTOM + }; + + + private final String settingKey; + private final String title; + private final CentralRepoPlatforms platform; + + CentralRepoDbChoice(String key, String title, CentralRepoPlatforms platform) { + this.settingKey = key; + this.title = title; + this.platform = platform; + } + + /** + * This is the value of this setting when saved to central repo properties. + * @return The value associated with this choice. + */ + public String getSettingKey() { + return settingKey; + } + + /** + * This is the human-readable title for this choice. + * @return The human-readable title for this choice. + */ + public String getTitle() { + return title; + } + + /** + * This represents the database type (i.e. Postgres, SQLite) associated with this choice. + * @return The database type associated with this choice. + */ + public CentralRepoPlatforms getDbPlatform() { + return platform; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbConnectivityManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbConnectivityManager.java new file mode 100755 index 0000000000..ade4094013 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbConnectivityManager.java @@ -0,0 +1,76 @@ +/* + * Central Repository + * + * Copyright 2015-2020 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.centralrepository.datamodel; + +/** + * This class is a common interface for settings pertaining to the database in central repository. + */ +public interface CentralRepoDbConnectivityManager { + /** + * This method loads the current settings for this connection. + */ + void loadSettings(); + + /** + * This method saves the altered settings to disk. + */ + void saveSettings(); + + /** + * This method will create a central repository database if necessary. + * @return Whether or not the operation was successful. + */ + boolean createDatabase(); + + /** + * This method deletes a central repository database (used for deleting a corrupted database). + * @return Whether or not the operation was successful. + */ + boolean deleteDatabase(); + + /** + * This method uses the current settings and the validation query to test the connection + * to the database. + * + * @return True if successfull connection, else false. + */ + boolean verifyConnection(); + + /** + * This method checks to see if the database exists. + * + * @return True if exists, else false. + */ + boolean verifyDatabaseExists(); + + /** + * This method is uses the current settings and the schema version query to test the + * database schema. + * + * @return True if successful connection, else false. + */ + boolean verifyDatabaseSchema(); + + /** + * This method tests the connectivity status of this connection and returns the testing result. + * @return The result of testing the database connectivity status. + */ + DatabaseTestResult testStatus(); + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java index d2647965a5..3fab7af47f 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java @@ -18,26 +18,172 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; import java.io.File; import java.sql.SQLException; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** - * Contains business logic for saving and validating settings for central repo + * This class contains business logic for saving and validating settings for central repository. */ public class CentralRepoDbManager { private static final Logger logger = Logger.getLogger(CentralRepoDbManager.class.getName()); private static final String CENTRAL_REPO_DB_NAME = "central_repository"; + private static final String CENTRAL_REPOSITORY_SETTINGS_KEY = "CentralRepository"; + private static final String DB_SELECTED_PLATFORM_KEY = "db.selectedPlatform"; + private static final String DISABLED_DUE_TO_FAILURE_KEY = "disabledDueToFailure"; + + private static volatile CentralRepoDbChoice savedChoice = null; + + private static final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(CentralRepoDbManager.class); + + private static final Object dbChoiceLock = new Object(); + private static final Object disabledDueToFailureLock = new Object(); + + + + /** + * This saves the currently selected database choice and clears any disabledDueToFailure flag. + * @param choice The choice to save. + * @return The newly saved choice. + */ + public static CentralRepoDbChoice saveDbChoice(CentralRepoDbChoice choice) { + return saveDbChoice(choice, true); + } + + /** + * This saves the currently selected database choice. + * @param choice The choice to save. + * @param clearDisabledDueToError Whether or not to clear the 'disabledDueToFailure' settings key. + * @return The newly saved choice. + */ + public static CentralRepoDbChoice saveDbChoice(CentralRepoDbChoice choice, boolean clearDisabledDueToError) { + synchronized(dbChoiceLock) { + // clear disabling due to a failure + if (clearDisabledDueToError) + setDisabledDueToFailure(false); + + // change the settings + CentralRepoDbChoice newChoice = (choice == null) ? CentralRepoDbChoice.DISABLED : choice; + CentralRepoDbChoice oldChoice = savedChoice; + savedChoice = newChoice; + ModuleSettings.setConfigSetting(CENTRAL_REPOSITORY_SETTINGS_KEY, DB_SELECTED_PLATFORM_KEY, newChoice.getSettingKey()); + propertyChangeSupport.firePropertyChange("savedChoice", oldChoice, newChoice); + return newChoice; + } + + } + + /** + * This method indicates whether or not 'PostgreSQL using multi-user settings' is a valid option. + * @return True if 'PostgreSQL using multi-user settings' is valid. + */ + public static boolean isPostgresMultiuserAllowed() { + // if multi user mode is not enabled, then this cannot be used + if (!UserPreferences.getIsMultiUserModeEnabled()) + return false; + + // also validate the connection as well + PostgresCentralRepoSettings multiUserSettings = + new PostgresCentralRepoSettings(PostgresSettingsLoader.MULTIUSER_SETTINGS_LOADER); + + return multiUserSettings.testStatus() == DatabaseTestResult.TESTED_OK; + } + + + /** + * This method loads the selectedPlatform boolean from the config file if it is set. + */ + public static CentralRepoDbChoice getSavedDbChoice() { + synchronized(dbChoiceLock) { + if (savedChoice == null) { + String selectedPlatformString = ModuleSettings.getConfigSetting(CENTRAL_REPOSITORY_SETTINGS_KEY, DB_SELECTED_PLATFORM_KEY); // NON-NLS + savedChoice = fromKey(selectedPlatformString); + } + + return savedChoice; + } + } + + /** + * This method disables the central repository and indicates through a flag that this was due to a failure during database setup. + * This is used when re-enabling multi-user as a flag to determine whether or not CR should be re-enabled. + */ + public static void disableDueToFailure() { + CentralRepoDbUtil.setUseCentralRepo(false); + setDisabledDueToFailure(true); + } + + /** + * This method sets whether or not the repository has been disabled due to a database setup issue; + * This is used when re-enabling multi-user as a flag to determine whether or not CR should be re-enabled. + * + * @param disabledDueToFailure Whether or not the repository has been disabled due to a database setup issue. + */ + private static void setDisabledDueToFailure(boolean disabledDueToFailure) { + synchronized(disabledDueToFailureLock) { + boolean oldValue = isDisabledDueToFailure(); + ModuleSettings.setConfigSetting(CENTRAL_REPOSITORY_SETTINGS_KEY, DISABLED_DUE_TO_FAILURE_KEY, Boolean.toString(disabledDueToFailure)); + propertyChangeSupport.firePropertyChange("disabledDueToFailure", oldValue, disabledDueToFailure); + } + } /** - * obtains the database connectivity for central repository + * This method retrieves setting whether or not the repository has been disabled due to a database setup issue; + * this is used when re-enabling multi-user as a flag to determine whether or not CR should be re-enabled. + * + * @return Whether or not the repository has been disabled due to a database setup issue. + */ + public static boolean isDisabledDueToFailure() { + synchronized(disabledDueToFailureLock) { + return Boolean.toString(true).equals(ModuleSettings.getConfigSetting(CENTRAL_REPOSITORY_SETTINGS_KEY, DISABLED_DUE_TO_FAILURE_KEY)); + } + } + + /** + * This method adds a property change listener. + * NOTE: currently only listening for changes in currently saved db choice and disabling due to failure. + * + * @param listener The listener for the event. + */ + public static void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + /** + * This method removes a propert change listener. + * @param listener The listener to remove. + */ + public static void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + + + private static CentralRepoDbChoice fromKey(String keyName) { + for (CentralRepoDbChoice dbChoice : CentralRepoDbChoice.values()) { + if (dbChoice.getSettingKey().equalsIgnoreCase(keyName)) { + return dbChoice; + } + } + + return CentralRepoDbChoice.DISABLED; + } + + + + /** + * This method obtains the database connectivity for central repository. * - * @return the CentralRepository object to connect to + * @return The CentralRepository object that will be used for connection. * @throws CentralRepoException */ private static CentralRepository obtainCentralRepository() throws CentralRepoException { @@ -55,10 +201,10 @@ public class CentralRepoDbManager { } /** - * obtains central repository lock + * This method obtains a central repository lock. * - * @param db the database connection - * @return the lock if acquired + * @param db The database connection. + * @return The lock if acquired. * @throws CentralRepoException */ private static CoordinationService.Lock obtainCentralRepoLock(CentralRepository db) throws CentralRepoException { @@ -79,10 +225,10 @@ public class CentralRepoDbManager { } /** - * updates central repository schema if necessary + * This method updates the central repository schema if necessary. * - * @param db the database connectivity - * @param lock the acquired lock + * @param db The database connectivity object. + * @param lock The acquired lock. * @throws CentralRepoException */ private static void updatedDbSchema(CentralRepository db, CoordinationService.Lock lock) throws CentralRepoException { @@ -111,7 +257,7 @@ public class CentralRepoDbManager { } /** - * Upgrade the current Central Reposity schema to the newest version. If the + * This method upgrades the current Central Reposity schema to the newest version. If the * upgrade fails, the Central Repository will be disabled and the current * settings will be cleared. */ @@ -142,8 +288,7 @@ public class CentralRepoDbManager { } catch (CentralRepoException ex2) { logger.log(Level.SEVERE, "Error shutting down central repo connection pool", ex2); } - CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.DISABLED.name()); - CentralRepoPlatforms.saveSelectedPlatform(); + saveDbChoice(CentralRepoDbChoice.DISABLED, false); if (innerException == null) { throw new CentralRepoException(message, desc); } else { @@ -151,41 +296,56 @@ public class CentralRepoDbManager { } } + + private DatabaseTestResult testingStatus; - private CentralRepoPlatforms selectedPlatform; + private CentralRepoDbChoice selectedDbChoice; private final PostgresCentralRepoSettings dbSettingsPostgres; + private final PostgresCentralRepoSettings dbSettingsMultiUser; private final SqliteCentralRepoSettings dbSettingsSqlite; private boolean configurationChanged = false; public CentralRepoDbManager() { - dbSettingsPostgres = new PostgresCentralRepoSettings(); + selectedDbChoice = getSavedDbChoice(); + dbSettingsPostgres = new PostgresCentralRepoSettings(PostgresSettingsLoader.CUSTOM_SETTINGS_LOADER); + dbSettingsMultiUser = new PostgresCentralRepoSettings(PostgresSettingsLoader.MULTIUSER_SETTINGS_LOADER); dbSettingsSqlite = new SqliteCentralRepoSettings(); - selectedPlatform = CentralRepoPlatforms.getSelectedPlatform(); - - // set the default selected platform for displaying in the ui of EamDbSettingsDialog - // if selected option is not applicable - if (selectedPlatform == null || selectedPlatform.equals(CentralRepoPlatforms.DISABLED)) { - selectedPlatform = CentralRepoPlatforms.POSTGRESQL; - } } + + /** + * This method retrieves the current multi-user database settings. + * @return The current multi-user database settings. + */ + public PostgresCentralRepoSettings getDbSettingsMultiUser() { + return dbSettingsMultiUser; + } + + /** + * This method retrieves the current custom postgres database settings. + * @return The current custom postgres database settings. + */ public PostgresCentralRepoSettings getDbSettingsPostgres() { return dbSettingsPostgres; } + /** + * This method returns the current SQLite database settings for central repository. + * @return The current SQLite database settings + */ public SqliteCentralRepoSettings getDbSettingsSqlite() { return dbSettingsSqlite; } /** - * setup sqlite db with default settings - * @throws CentralRepoException if unable to successfully set up database + * This method sets up the sqlite database with default settings. + * @throws CentralRepoException if unable to successfully set up database. */ public void setupDefaultSqliteDb() throws CentralRepoException { // change in-memory settings to default sqlite - selectedPlatform = CentralRepoPlatforms.SQLITE; + selectedDbChoice = CentralRepoDbChoice.SQLITE; dbSettingsSqlite.setupDefaultSettings(); // if db is not present, attempt to create it @@ -196,7 +356,7 @@ public class CentralRepoDbManager { } // the only successful setup status is tested ok - if (curStatus != DatabaseTestResult.TESTEDOK) { + if (curStatus != DatabaseTestResult.TESTED_OK) { throw new CentralRepoException("Unable to successfully create sqlite database"); } @@ -206,48 +366,55 @@ public class CentralRepoDbManager { } /** - * Returns if changes to the central repository configuration were - * successfully applied + * This method returns if changes to the central repository configuration were + * successfully applied. * - * @return true if the database configuration was successfully changed false - * if it was not + * @return Returns true if the database configuration was successfully changed false + * if it was not. */ public boolean wasConfigurationChanged() { return configurationChanged; } - private CentralRepoDbSettings getSelectedSettings() throws CentralRepoException { - switch (selectedPlatform) { - case POSTGRESQL: - return dbSettingsPostgres; - case SQLITE: - return dbSettingsSqlite; - case DISABLED: - return null; - default: - throw new CentralRepoException("Unknown database type: " + selectedPlatform); - } + private CentralRepoDbConnectivityManager getSelectedSettings() throws CentralRepoException { + if (selectedDbChoice == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) + return dbSettingsMultiUser; + if (selectedDbChoice == CentralRepoDbChoice.POSTGRESQL_CUSTOM) + return dbSettingsPostgres; + if (selectedDbChoice == CentralRepoDbChoice.SQLITE) + return dbSettingsSqlite; + if (selectedDbChoice == CentralRepoDbChoice.DISABLED) + return null; + + throw new CentralRepoException("Unknown database type: " + selectedDbChoice); } private RdbmsCentralRepoFactory getDbFactory() throws CentralRepoException { - switch (selectedPlatform) { - case POSTGRESQL: - return new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsPostgres); - case SQLITE: - return new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsSqlite); - case DISABLED: - return null; - default: - throw new CentralRepoException("Unknown database type: " + selectedPlatform); - } + if (selectedDbChoice == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) + return new RdbmsCentralRepoFactory(CentralRepoPlatforms.POSTGRESQL, dbSettingsMultiUser); + if (selectedDbChoice == CentralRepoDbChoice.POSTGRESQL_CUSTOM) + return new RdbmsCentralRepoFactory(CentralRepoPlatforms.POSTGRESQL, dbSettingsPostgres); + if (selectedDbChoice == CentralRepoDbChoice.SQLITE) + return new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettingsSqlite); + if (selectedDbChoice == CentralRepoDbChoice.DISABLED) + return null; + + throw new CentralRepoException("Unknown database type: " + selectedDbChoice); } + /** + * This method creates a central repo database if it does not already exist. + * @return True if successful; false if unsuccessful. + * @throws CentralRepoException + */ public boolean createDb() throws CentralRepoException { + CentralRepoDbConnectivityManager selectedDbSettings = getSelectedSettings(); + if (selectedDbSettings == null) + throw new CentralRepoException("Unable to derive connectivity manager from settings: " + selectedDbChoice); + boolean result = false; boolean dbCreated = true; - - CentralRepoDbSettings selectedDbSettings = getSelectedSettings(); - + if (!selectedDbSettings.verifyDatabaseExists()) { dbCreated = selectedDbSettings.createDatabase(); } @@ -265,7 +432,6 @@ public class CentralRepoDbManager { if (!result) { // Remove the incomplete database if (dbCreated) { - // RAMAN TBD: migrate deleteDatabase() to RdbmsCentralRepoFactory selectedDbSettings.deleteDatabase(); } @@ -274,12 +440,12 @@ public class CentralRepoDbManager { throw new CentralRepoException(schemaError); } - testingStatus = DatabaseTestResult.TESTEDOK; + testingStatus = DatabaseTestResult.TESTED_OK; return true; } /** - * saves a new central repository based on current settings + * This method saves a new central repository based on current settings. */ @NbBundle.Messages({"CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database."}) public void saveNewCentralRepo() throws CentralRepoException { @@ -305,18 +471,18 @@ public class CentralRepoDbManager { // Even if we fail to close the existing connections, make sure that we // save the new connection settings, so an Autopsy restart will correctly // start with the new settings. - CentralRepoPlatforms.setSelectedPlatform(selectedPlatform.name()); - CentralRepoPlatforms.saveSelectedPlatform(); + CentralRepoDbUtil.setUseCentralRepo(selectedDbChoice != CentralRepoDbChoice.DISABLED); + saveDbChoice(selectedDbChoice); - CentralRepoDbSettings selectedDbSettings = getSelectedSettings(); + CentralRepoDbConnectivityManager selectedDbSettings = getSelectedSettings(); // save the new settings selectedDbSettings.saveSettings(); // Load those newly saved settings into the postgres db manager instance // in case we are still using the same instance. - if (selectedPlatform == CentralRepoPlatforms.POSTGRESQL || selectedPlatform == CentralRepoPlatforms.SQLITE) { + if (selectedDbChoice != null && selectedDbChoice != CentralRepoDbChoice.DISABLED) { try { - logger.info("Creating central repo db with settings: " + selectedDbSettings); + logger.info("Saving central repo settings for db: " + selectedDbSettings); CentralRepository.getInstance().updateSettings(); configurationChanged = true; } catch (CentralRepoException ex) { @@ -326,82 +492,82 @@ public class CentralRepoDbManager { } } + /** + * This method retrieves the current status. + * Note: this could be a dirty value if testing of the connection has not been performed. + * @return The current status of the database connection. + */ public DatabaseTestResult getStatus() { return testingStatus; } - public CentralRepoPlatforms getSelectedPlatform() { - return selectedPlatform; + /** + * This method retrieves the currently selected database choice. + * NOTE: This choice may not align with the saved setting. + * @return The currently selected database choice. + */ + public CentralRepoDbChoice getSelectedDbChoice() { + return selectedDbChoice; } + /** + * This method clears the current database testing status. + */ public void clearStatus() { testingStatus = DatabaseTestResult.UNTESTED; } - public void setSelectedPlatform(CentralRepoPlatforms newSelected) { - selectedPlatform = newSelected; + /** + * This method sets the currently selected database choice and sets the testing status to untested. + * @param newSelected The new database choice. + */ + public void setSelctedDbChoice(CentralRepoDbChoice newSelected) { + selectedDbChoice = newSelected; testingStatus = DatabaseTestResult.UNTESTED; } /** - * Tests whether or not the database settings are valid. + * This method tests whether or not the settings have been filled in for the UI. + * NOTE: This does not check the connectivity status of these settings. * - * @return True or false. + * @return True if database settings are valid. */ public boolean testDatabaseSettingsAreValid( String tbDbHostname, String tbDbPort, String tbDbUsername, String tfDatabasePath, String jpDbPassword) throws CentralRepoException, NumberFormatException { - switch (selectedPlatform) { - case POSTGRESQL: - dbSettingsPostgres.setHost(tbDbHostname); - dbSettingsPostgres.setPort(Integer.parseInt(tbDbPort)); - dbSettingsPostgres.setDbName(CENTRAL_REPO_DB_NAME); - dbSettingsPostgres.setUserName(tbDbUsername); - dbSettingsPostgres.setPassword(jpDbPassword); - break; - case SQLITE: - File databasePath = new File(tfDatabasePath); - dbSettingsSqlite.setDbName(SqliteCentralRepoSettings.DEFAULT_DBNAME); - dbSettingsSqlite.setDbDirectory(databasePath.getPath()); - break; - default: - throw new IllegalStateException("Central Repo has an unknown selected platform: " + selectedPlatform); + if (selectedDbChoice == CentralRepoDbChoice.POSTGRESQL_CUSTOM) { + dbSettingsPostgres.setHost(tbDbHostname); + dbSettingsPostgres.setPort(Integer.parseInt(tbDbPort)); + dbSettingsPostgres.setDbName(CENTRAL_REPO_DB_NAME); + dbSettingsPostgres.setUserName(tbDbUsername); + dbSettingsPostgres.setPassword(jpDbPassword); + } + else if (selectedDbChoice == CentralRepoDbChoice.SQLITE) { + File databasePath = new File(tfDatabasePath); + dbSettingsSqlite.setDbName(SqliteCentralRepoSettings.DEFAULT_DBNAME); + dbSettingsSqlite.setDbDirectory(databasePath.getPath()); + } + else if (selectedDbChoice != CentralRepoDbChoice.POSTGRESQL_MULTIUSER) { + throw new IllegalStateException("Central Repo has an unknown selected platform: " + selectedDbChoice); } return true; } + /** + * This method tests the current database settings to see if a valid connection can be made. + * @return The result of testing the connection. + */ public DatabaseTestResult testStatus() { - if (selectedPlatform == CentralRepoPlatforms.POSTGRESQL) { - if (dbSettingsPostgres.verifyConnection()) { - if (dbSettingsPostgres.verifyDatabaseExists()) { - if (dbSettingsPostgres.verifyDatabaseSchema()) { - testingStatus = DatabaseTestResult.TESTEDOK; - } else { - testingStatus = DatabaseTestResult.SCHEMA_INVALID; - } - } else { - testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST; - } - } else { - testingStatus = DatabaseTestResult.CONNECTION_FAILED; - } - } else if (selectedPlatform == CentralRepoPlatforms.SQLITE) { - if (dbSettingsSqlite.dbFileExists()) { - if (dbSettingsSqlite.verifyConnection()) { - if (dbSettingsSqlite.verifyDatabaseSchema()) { - testingStatus = DatabaseTestResult.TESTEDOK; - } else { - testingStatus = DatabaseTestResult.SCHEMA_INVALID; - } - } else { - testingStatus = DatabaseTestResult.SCHEMA_INVALID; - } - } else { - testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST; - } + try { + CentralRepoDbConnectivityManager manager = getSelectedSettings(); + if (manager != null) + testingStatus = manager.testStatus(); } - + catch (CentralRepoException e) { + logger.log(Level.WARNING, "unable to test status of db connection in central repo", e); + } + return testingStatus; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java deleted file mode 100755 index f9bf520654..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Central Repository - * - * Copyright 2015-2020 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.centralrepository.datamodel; - -/** - * common interface for settings pertaining to the database in central repository - */ -public interface CentralRepoDbSettings { - - void saveSettings(); - - boolean createDatabase(); - - boolean deleteDatabase(); - - /** - * Use the current settings and the validation query to test the connection - * to the database. - * - * @return true if successfull connection, else false. - */ - boolean verifyConnection(); - - /** - * Check to see if the database exists. - * - * @return true if exists, else false - */ - boolean verifyDatabaseExists(); - - /** - * Use the current settings and the schema version query to test the - * database schema. - * - * @return true if successful connection, else false. - */ - boolean verifyDatabaseSchema(); - -} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader.java new file mode 100644 index 0000000000..ffd0d2936e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader.java @@ -0,0 +1,41 @@ +/* + * Central Repository + * + * Copyright 2020 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.centralrepository.datamodel; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; + +/** + * Common interface to upgrade central repository database schema. + */ +public interface CentralRepoDbUpgrader { + + /** + * Updates the Central Repository schema using the given open connection. + * + * @param dbSchemaVersion Current schema version. + * @param connection Connection to use for upgrade. + * + * @throws CentralRepoException If there is an error in upgrade. + * @throws SQLException If there is any SQL errors. + */ + void upgradeSchema(CaseDbSchemaVersionNumber dbSchemaVersion, Connection connection) throws CentralRepoException, SQLException; + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java new file mode 100644 index 0000000000..9d6473055e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java @@ -0,0 +1,115 @@ +/* + * Central Repository + * + * Copyright 2020 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.centralrepository.datamodel; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; + + +/** + * This class updates CR schema to 1.4 + * + * New correlation types for accounts are added, as well as some accounts related new tables are added in this version. + * + */ +public class CentralRepoDbUpgrader13To14 implements CentralRepoDbUpgrader { + + @Override + public void upgradeSchema(CaseDbSchemaVersionNumber dbSchemaVersion, Connection connection) throws CentralRepoException, SQLException { + + if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 4)) < 0) { + + try (Statement statement = connection.createStatement();) { + + CentralRepoPlatforms selectedPlatform = CentralRepoDbManager.getSavedDbChoice().getDbPlatform(); + + // Create account_types and accounts tables which are referred by X_instances tables + statement.execute(RdbmsCentralRepoFactory.getCreateAccountTypesTableStatement(selectedPlatform)); + statement.execute(RdbmsCentralRepoFactory.getCreateAccountsTableStatement(selectedPlatform)); + + for (CorrelationAttributeInstance.Type type : CorrelationAttributeInstance.getDefaultCorrelationTypes()) { + String instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type); + + if (type.getId() >= CorrelationAttributeInstance.ADDITIONAL_TYPES_BASE_ID) { + + // these are new Correlation types - new tables need to be created + statement.execute(String.format(RdbmsCentralRepoFactory.getCreateArtifactInstancesTableTemplate(selectedPlatform), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddCaseIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddDataSourceIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddValueIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddKnownStatusIndexTemplate(), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getAddObjectIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); + + // add new correlation type + CentralRepoDbUtil.insertCorrelationType(connection, type); + + } else { + + // Alter the existing X_Instance tables to add account_id column + String sqlStr = String.format(getAlterArtifactInstancesAddAccountIdTemplate(selectedPlatform), instance_type_dbname); + statement.execute(sqlStr); + + // SQLite does NOT allow adding a constraint with Alter Table statement. + // The alternative would be to create new tables, copy all data over, and delete old tables - potentially a time consuming process. + // We decided to not add this constraint for SQLite, since there likely aren't many users using SQLite based Central Repo. + if (selectedPlatform == CentralRepoPlatforms.POSTGRESQL) { + sqlStr = String.format(getAlterArtifactInstancesAddAccountIdConstraintTemplate(), instance_type_dbname); + statement.execute(sqlStr); + } + } + } + + // insert default accounts data + RdbmsCentralRepoFactory.insertDefaultAccountsTablesContent(connection, selectedPlatform); + } + } + + } + + /** + * Returns ALTER TABLE SQL string template to add an account_id column to a + * TYPE_instances table. + * + * @param selectedPlatform + * + * @return SQL string template to alter the table. + */ + static String getAlterArtifactInstancesAddAccountIdTemplate(CentralRepoPlatforms selectedPlatform) { + // Each "%s" will be replaced with the relevant TYPE_instances table name. + return "ALTER TABLE %s" + + " ADD account_id " + RdbmsCentralRepoFactory.getBigIntType(selectedPlatform) + " DEFAULT NULL"; + + } + + /** + * Returns ALTER TABLE SQL string template to add a Foreign Key constraint + * to a TYPE_instances table. + * + * @return SQL string template to alter the table. + */ + static String getAlterArtifactInstancesAddAccountIdConstraintTemplate() { + // Each "%s" will be replaced with the relevant TYPE_instances table name. + return "ALTER TABLE %s" + + " ADD CONSTRAINT account_id_fk foreign key (account_id) references accounts(id)"; + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java index 60701b44e5..826c315c02 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java @@ -123,6 +123,28 @@ public class CentralRepoDbUtil { return true; } + /** + * Inserts the specified correlation type into the database. + * + * @param conn Open connection to use. + * @param correlationType New correlation type to add. + * + */ + public static void insertCorrelationType(Connection conn, CorrelationAttributeInstance.Type correlationType) throws SQLException { + + String sql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)"; + try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) { + + preparedStatement.setInt(1, correlationType.getId()); + preparedStatement.setString(2, correlationType.getDisplayName()); + preparedStatement.setString(3, correlationType.getDbTableName()); + preparedStatement.setInt(4, correlationType.isSupported() ? 1 : 0); + preparedStatement.setInt(5, correlationType.isEnabled() ? 1 : 0); + + preparedStatement.execute(); + } + } + /** * Writes the current schema version into the database. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPlatforms.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPlatforms.java index bcdf009ca3..b830e98d16 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPlatforms.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPlatforms.java @@ -18,102 +18,11 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; - /** - * + * This enum describes the possible database types for central repo. */ public enum CentralRepoPlatforms { - DISABLED("Disabled", true), - SQLITE("SQLite", false), - POSTGRESQL("PostgreSQL", false); - - private final String platformName; - private Boolean selected; - - CentralRepoPlatforms(String name, Boolean selected) { - this.platformName = name; - this.selected = selected; - loadSettings(); - } - - /** - * Load the selectedPlatform boolean from the config file, if it is set. - */ - private void loadSettings() { - String selectedPlatformString = ModuleSettings.getConfigSetting("CentralRepository", "db.selectedPlatform"); // NON-NLS - - if (null != selectedPlatformString) { - selected = this.toString().equalsIgnoreCase(selectedPlatformString); - } else if (this == DISABLED) { - selected = true; - } - } - - @Override - public String toString() { - return platformName; - } - - private void setSelected(Boolean selected) { - this.selected = selected; - } - - public Boolean isSelected() { - return selected; - } - - public static CentralRepoPlatforms fromString(String pName) { - if (null == pName) { - return DISABLED; - } - - for (CentralRepoPlatforms p : CentralRepoPlatforms.values()) { - if (p.toString().equalsIgnoreCase(pName)) { - return p; - } - } - return DISABLED; - } - - /** - * Save the selected platform to the config file. - */ - public static void saveSelectedPlatform() { - CentralRepoPlatforms selectedPlatform = DISABLED; - for (CentralRepoPlatforms p : CentralRepoPlatforms.values()) { - if (p.isSelected()) { - selectedPlatform = p; - } - } - ModuleSettings.setConfigSetting("CentralRepository", "db.selectedPlatform", selectedPlatform.name()); // NON-NLS - } - - /** - * Set the selected db platform. Other platforms will be set as not - * selected. - * - * @param platformString The name of the selected platform. - */ - public static void setSelectedPlatform(String platformString) { - CentralRepoPlatforms pSelected = CentralRepoPlatforms.fromString(platformString); - for (CentralRepoPlatforms p : CentralRepoPlatforms.values()) { - p.setSelected(p == pSelected); - } - } - - /** - * Get the selected platform. - * - * @return The selected platform, or if not platform is selected, default to - * DISABLED. - */ - public static CentralRepoPlatforms getSelectedPlatform() { - for (CentralRepoPlatforms p : CentralRepoPlatforms.values()) { - if (p.isSelected()) { - return p; - } - } - return DISABLED; - } + DISABLED, + SQLITE, + POSTGRESQL } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java new file mode 100644 index 0000000000..3f75e9e183 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java @@ -0,0 +1,173 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2012-2020 Basis Technology Corp. + * + * Copyright 2012 42six Solutions. + * Contact: aebadirad 42six com + * Project Contact/Architect: 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.centralrepository.datamodel; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.TextConverter; +import org.sleuthkit.autopsy.coreutils.TextConverterException; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; + +/** + * This class handles saving and loading of postgres settings for central repository. + */ +public class CentralRepoPostgresSettingsUtil { + private final static Logger LOGGER = Logger.getLogger(CentralRepoPostgresSettingsUtil.class.getName()); + + private static final String PASSWORD_KEY = "db.postgresql.password"; + private static final String BULK_THRESHOLD_KEY = "db.postgresql.bulkThreshold"; + private static final String PORT_KEY = "db.postgresql.port"; + private static final String USER_KEY = "db.postgresql.user"; + private static final String DBNAME_KEY = "db.postgresql.dbName"; + private static final String HOST_KEY = "db.postgresql.host"; + + private static final String MODULE_KEY = "CentralRepository"; + + private static CentralRepoPostgresSettingsUtil instance = null; + + /** + * This method retrieves a singleton instance of this class. + * @return The singleton instance of this class. + */ + public static synchronized CentralRepoPostgresSettingsUtil getInstance() { + if (instance == null) + instance = new CentralRepoPostgresSettingsUtil(); + + return instance; + } + + private CentralRepoPostgresSettingsUtil() {} + + private void logException(TryHandler handler) { + try { + handler.operation(); + } + catch (CentralRepoException | NumberFormatException e) { + LOGGER.log(Level.WARNING, "There was an error in converting central repo postgres settings", e); + } + } + + /** + * This interface represents an action that potentially throws an exception. + */ + private interface TryHandler { + void operation() throws CentralRepoException, NumberFormatException; + } + + /** + * This method loads multi-user settings to be used as a postgres connection to central repository. If + * settings could not be loaded, default values will be returned. + * + * @return The settings loaded from multi-user settings. + */ + public PostgresConnectionSettings loadMultiUserSettings() { + PostgresConnectionSettings settings = new PostgresConnectionSettings(); + + CaseDbConnectionInfo muConn; + try { + muConn = UserPreferences.getDatabaseConnectionInfo(); + } catch (UserPreferencesException ex) { + LOGGER.log(Level.SEVERE, "Failed to import settings from multi-user settings.", ex); + return settings; + } + + logException(() -> settings.setHost(muConn.getHost())); + logException(() -> settings.setDbName(PostgresConnectionSettings.DEFAULT_DBNAME)); + logException(() -> settings.setUserName(muConn.getUserName())); + + logException(() -> settings.setPort(Integer.parseInt(muConn.getPort()))); + logException(() -> settings.setBulkThreshold(RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD)); + + logException(() -> settings.setPassword(muConn.getPassword())); + + return settings; + } + + + /** + * This method loads the custom postgres settings for central repository. If + * settings could not be loaded, default values will be returned. + * + * @return The settings loaded from custom postgres settings. + */ + public PostgresConnectionSettings loadCustomSettings() { + PostgresConnectionSettings settings = new PostgresConnectionSettings(); + Map keyVals = ModuleSettings.getConfigSettings(MODULE_KEY); + + + logException(() -> settings.setHost(keyVals.get(HOST_KEY))); + logException(() -> settings.setDbName(keyVals.get(DBNAME_KEY))); + logException(() -> settings.setUserName(keyVals.get(USER_KEY))); + + logException(() -> settings.setPort(Integer.parseInt(keyVals.get(PORT_KEY)))); + logException(() -> settings.setBulkThreshold(Integer.parseInt(keyVals.get((BULK_THRESHOLD_KEY))))); + + String passwordHex = keyVals.get(PASSWORD_KEY); + String password; + try { + password = TextConverter.convertHexTextToText(passwordHex); + } catch (TextConverterException ex) { + LOGGER.log(Level.WARNING, "Failed to convert password from hex text to text.", ex); + password = null; + } + + final String finalPassword = password; + logException(() -> settings.setPassword(finalPassword)); + return settings; + } + + /** + * This method saves the settings for a custom postgres central repository connection. + * @param settings The settings to save. + */ + public void saveCustomSettings(PostgresConnectionSettings settings) { + Map map = new HashMap(); + map.put(HOST_KEY, settings.getHost()); + map.put(PORT_KEY, Integer.toString(settings.getPort())); + map.put(DBNAME_KEY, settings.getDbName()); + map.put(BULK_THRESHOLD_KEY, Integer.toString(settings.getBulkThreshold())); + map.put(USER_KEY, settings.getUserName()); + try { + map.put(PASSWORD_KEY, TextConverter.convertTextToHexText(settings.getPassword())); // NON-NLS + } catch (TextConverterException ex) { + LOGGER.log(Level.SEVERE, "Failed to convert password from text to hex text.", ex); + } + + ModuleSettings.setConfigSettings(MODULE_KEY, map); + } + + /** + * This method checks if saved settings differ from the in-memory object provided in the 'settings' parameter. + * @param settings The in-memory object. + * @return Whether or not settings parameter differs from saved custom settings. + */ + public boolean areCustomSettingsChanged(PostgresConnectionSettings settings) { + PostgresConnectionSettings saved = loadCustomSettings(); + return saved.equals(settings); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index 904712c73c..5b81dc6b3b 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -42,7 +42,7 @@ public interface CentralRepository { CentralRepoPlatforms selectedPlatform = CentralRepoPlatforms.DISABLED; if (CentralRepoDbUtil.allowUseOfCentralRepository()) { - selectedPlatform = CentralRepoPlatforms.getSelectedPlatform(); + selectedPlatform = CentralRepoDbManager.getSavedDbChoice().getDbPlatform(); } switch (selectedPlatform) { case POSTGRESQL: @@ -93,7 +93,7 @@ public interface CentralRepository { */ static boolean isEnabled() { return CentralRepoDbUtil.allowUseOfCentralRepository() - && CentralRepoPlatforms.getSelectedPlatform() != CentralRepoPlatforms.DISABLED; + && CentralRepoDbManager.getSavedDbChoice() != CentralRepoDbChoice.DISABLED; } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java index 27a7b16278..ecd72341cf 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java @@ -19,13 +19,13 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; /** - * provides the status of the database after attempting to validate central repo settings + * This enum provides the status of the database after attempting to validate central repo settings. */ public enum DatabaseTestResult { UNTESTED, CONNECTION_FAILED, SCHEMA_INVALID, DB_DOES_NOT_EXIST, - TESTEDOK; + TESTED_OK; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java index 476b67dbc2..165d4ca6e0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java @@ -24,15 +24,9 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.List; import java.util.Properties; import java.util.logging.Level; -import java.util.regex.Pattern; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ModuleSettings; -import org.sleuthkit.autopsy.coreutils.TextConverter; -import org.sleuthkit.autopsy.coreutils.TextConverterException; -import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION; /** * Settings for the Postgres implementation of the Central Repository database @@ -40,106 +34,84 @@ import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo * NOTE: This is public scope because the options panel calls it directly to * set/get */ -public final class PostgresCentralRepoSettings implements CentralRepoDbSettings { +public final class PostgresCentralRepoSettings implements CentralRepoDbConnectivityManager { private final static Logger LOGGER = Logger.getLogger(PostgresCentralRepoSettings.class.getName()); - private final static String DEFAULT_HOST = ""; // NON-NLS - private final static int DEFAULT_PORT = 5432; - private final static String DEFAULT_DBNAME = "central_repository"; // NON-NLS - private final static String DEFAULT_USERNAME = ""; - private final static String DEFAULT_PASSWORD = ""; private final static String VALIDATION_QUERY = "SELECT version()"; // NON-NLS private final static String JDBC_BASE_URI = "jdbc:postgresql://"; // NON-NLS private final static String JDBC_DRIVER = "org.postgresql.Driver"; // NON-NLS - private final static String DB_NAMES_REGEX = "[a-z][a-z0-9_]*"; // only lower case - private final static String DB_USER_NAMES_REGEX = "[a-zA-Z]\\w*"; - private String host; - private int port; - private String dbName; - private int bulkThreshold; - private String userName; - private String password; - - public PostgresCentralRepoSettings() { + + + private final PostgresSettingsLoader loader; + private PostgresConnectionSettings connSettings; + + private static PostgresSettingsLoader getLoaderFromSaved() throws CentralRepoException { + CentralRepoDbChoice choice = CentralRepoDbManager.getSavedDbChoice(); + if (choice == CentralRepoDbChoice.POSTGRESQL_CUSTOM) + return PostgresSettingsLoader.CUSTOM_SETTINGS_LOADER; + else if (choice == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) + return PostgresSettingsLoader.MULTIUSER_SETTINGS_LOADER; + else + throw new CentralRepoException("cannot load or save postgres settings for selection: " + choice); + } + + /** + * This method loads the settings with a custom {@link PostgresSettingsLoader PostgresSettingsLoader} object. + * @param loader The loader to be used. + */ + public PostgresCentralRepoSettings(PostgresSettingsLoader loader) { + this.loader = loader; loadSettings(); } + + /** + * This is the default constructor that loads settings from selected db choice. + */ + public PostgresCentralRepoSettings() throws CentralRepoException { + this(getLoaderFromSaved()); + } + + @Override + public void loadSettings() { + this.connSettings = loader.loadSettings(); + } + + @Override + public void saveSettings() { + loader.saveSettings(connSettings); + } + + @Override public String toString() { return String.format("PostgresCentralRepoSettings: [db type: postgres, host: %s:%d, db name: %s, username: %s]", getHost(), getPort(), getDbName(), getUserName()); } - public void loadSettings() { - host = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS - if (host == null || host.isEmpty()) { - host = DEFAULT_HOST; - } - - try { - String portString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.port"); // NON-NLS - if (portString == null || portString.isEmpty()) { - port = DEFAULT_PORT; - } else { - port = Integer.parseInt(portString); - if (port < 0 || port > 65535) { - port = DEFAULT_PORT; - } - } - } catch (NumberFormatException ex) { - port = DEFAULT_PORT; - } - - dbName = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.dbName"); // NON-NLS - if (dbName == null || dbName.isEmpty()) { - dbName = DEFAULT_DBNAME; - } - - try { - String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.bulkThreshold"); // NON-NLS - if (bulkThresholdString == null || bulkThresholdString.isEmpty()) { - this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; - } else { - this.bulkThreshold = Integer.parseInt(bulkThresholdString); - if (getBulkThreshold() <= 0) { - this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; - } - } - } catch (NumberFormatException ex) { - this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; - } - - userName = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.user"); // NON-NLS - if (userName == null || userName.isEmpty()) { - userName = DEFAULT_USERNAME; - } - - password = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.password"); // NON-NLS - if (password == null || password.isEmpty()) { - password = DEFAULT_PASSWORD; - } else { - try { - password = TextConverter.convertHexTextToText(password); - } catch (TextConverterException ex) { - LOGGER.log(Level.WARNING, "Failed to convert password from hex text to text.", ex); - password = DEFAULT_PASSWORD; - } - } + + /** + * @return the VALIDATION_QUERY + */ + String getValidationQuery() { + return VALIDATION_QUERY; } - public void saveSettings() { - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.host", getHost()); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.port", Integer.toString(port)); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.dbName", getDbName()); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.bulkThreshold", Integer.toString(getBulkThreshold())); // NON-NLS - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.user", getUserName()); // NON-NLS - try { - ModuleSettings.setConfigSetting("CentralRepository", "db.postgresql.password", TextConverter.convertTextToHexText(getPassword())); // NON-NLS - } catch (TextConverterException ex) { - LOGGER.log(Level.SEVERE, "Failed to convert password from text to hex text.", ex); - } + /** + * @return the POSTGRES_DRIVER + */ + String getDriver() { + return JDBC_DRIVER; } + /** + * @return the JDBC_BASE_URI + */ + String getJDBCBaseURI() { + return JDBC_BASE_URI; + } + + /** * Get the full connection URL as a String * @@ -301,73 +273,33 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbSettings } - - - - - - - - - - - - - - - - - - - - - boolean isChanged() { - String hostString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS - String portString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.port"); // NON-NLS - String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.dbName"); // NON-NLS - String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.bulkThreshold"); // NON-NLS - String userNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.user"); // NON-NLS - String userPasswordString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.password"); // NON-NLS - - return !host.equals(hostString) || !Integer.toString(port).equals(portString) - || !dbName.equals(dbNameString) || !Integer.toString(bulkThreshold).equals(bulkThresholdString) - || !userName.equals(userNameString) || !password.equals(userPasswordString); - } /** * @return the host */ public String getHost() { - return host; + return connSettings.getHost(); } /** * @param host the host to set */ public void setHost(String host) throws CentralRepoException { - if (null != host && !host.isEmpty()) { - this.host = host; - } else { - throw new CentralRepoException("Invalid host name. Cannot be empty."); // NON-NLS - } + connSettings.setHost(host); } /** * @return the port */ public int getPort() { - return port; + return connSettings.getPort(); } /** * @param port the port to set */ public void setPort(int port) throws CentralRepoException { - if (port > 0 && port < 65535) { - this.port = port; - } else { - throw new CentralRepoException("Invalid port. Must be a number greater than 0."); // NON-NLS - } + connSettings.setPort(port); } /** @@ -377,95 +309,72 @@ public final class PostgresCentralRepoSettings implements CentralRepoDbSettings * @return the dbName */ public String getDbName() { - return dbName.toLowerCase(); + return connSettings.getDbName() == null ? null : connSettings.getDbName().toLowerCase(); } /** * @param dbName the dbName to set */ public void setDbName(String dbName) throws CentralRepoException { - if (dbName == null || dbName.isEmpty()) { - throw new CentralRepoException("Invalid database name. Cannot be empty."); // NON-NLS - } else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) { - throw new CentralRepoException("Invalid database name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."); // NON-NLS - } - - this.dbName = dbName.toLowerCase(); + connSettings.setDbName(dbName); } /** * @return the bulkThreshold */ int getBulkThreshold() { - return bulkThreshold; + return connSettings.getBulkThreshold(); } /** * @param bulkThreshold the bulkThreshold to set */ public void setBulkThreshold(int bulkThreshold) throws CentralRepoException { - if (bulkThreshold > 0) { - this.bulkThreshold = bulkThreshold; - } else { - throw new CentralRepoException("Invalid bulk threshold."); // NON-NLS - } + connSettings.setBulkThreshold(bulkThreshold); } /** * @return the userName */ public String getUserName() { - return userName; + return connSettings.getUserName(); } /** * @param userName the userName to set */ public void setUserName(String userName) throws CentralRepoException { - if (userName == null || userName.isEmpty()) { - throw new CentralRepoException("Invalid user name. Cannot be empty."); // NON-NLS - } else if (!Pattern.matches(DB_USER_NAMES_REGEX, userName)) { - throw new CentralRepoException("Invalid user name. Name must start with a letter and can only contain letters, numbers, and '_'."); // NON-NLS - } - this.userName = userName; + connSettings.setUserName(userName); } /** * @return the password */ public String getPassword() { - return password; + return connSettings.getPassword(); } /** * @param password the password to set */ public void setPassword(String password) throws CentralRepoException { - if (password == null || password.isEmpty()) { - throw new CentralRepoException("Invalid user password. Cannot be empty."); // NON-NLS + connSettings.setPassword(password); + } + + @Override + public DatabaseTestResult testStatus() { + if (verifyConnection()) { + if (verifyDatabaseExists()) { + if (verifyDatabaseSchema()) { + return DatabaseTestResult.TESTED_OK; + } else { + return DatabaseTestResult.SCHEMA_INVALID; + } + } else { + return DatabaseTestResult.DB_DOES_NOT_EXIST; + } + } else { + return DatabaseTestResult.CONNECTION_FAILED; } - this.password = password; } - - /** - * @return the VALIDATION_QUERY - */ - String getValidationQuery() { - return VALIDATION_QUERY; - } - - /** - * @return the POSTGRES_DRIVER - */ - String getDriver() { - return JDBC_DRIVER; - } - - /** - * @return the JDBC_BASE_URI - */ - String getJDBCBaseURI() { - return JDBC_BASE_URI; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresConnectionSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresConnectionSettings.java new file mode 100644 index 0000000000..c835d0c8fc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresConnectionSettings.java @@ -0,0 +1,222 @@ +/* + * Central Repository + * + * Copyright 2015-2020 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.centralrepository.datamodel; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * + * This class is a POJO for postgres settings to be used with central repository. + */ +public class PostgresConnectionSettings { + private final static String DB_NAMES_REGEX = "[a-z][a-z0-9_]*"; // only lower case + private final static String DB_USER_NAMES_REGEX = "[a-zA-Z]\\w*"; + + public final static String DEFAULT_HOST = ""; // NON-NLS + public final static int DEFAULT_PORT = 5432; + public final static String DEFAULT_DBNAME = "central_repository"; // NON-NLS + public final static String DEFAULT_USERNAME = ""; + public final static String DEFAULT_PASSWORD = ""; + + private static void validateStr(String s, String errMessage) throws CentralRepoException { + if (null == s || s.isEmpty()) + throw new CentralRepoException(errMessage); + } + + private static void validateRegex(String s, String pattern, String errMessage) throws CentralRepoException { + if (!Pattern.matches(pattern, s)) + throw new CentralRepoException(errMessage); + } + + private static void validateNum(int num, Integer min, Integer max, String errMessage) throws CentralRepoException { + if ((min != null && num < min) || (max != null && num > max)) + throw new CentralRepoException(errMessage); + } + + private String host = DEFAULT_HOST; + private int port = DEFAULT_PORT; + private String dbName = DEFAULT_DBNAME; + private int bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; + private String userName = DEFAULT_USERNAME; + private String password = DEFAULT_PASSWORD; + + /** + * This method retrieves the postgres host. + * @return The host for these settings. + */ + public String getHost() { + return host; + } + + /** + * This method returns the port number for these settings. + * @return The port number for these settings. + */ + public int getPort() { + return port; + } + + /** + * This method returns the database name for these settings. + * @return The database name for these settings. + */ + public String getDbName() { + return dbName; + } + + /** + * This method returns the bulk threshold. + * @return The bulk threshold. + */ + public int getBulkThreshold() { + return bulkThreshold; + } + + /** + * This method returns the username to use for this connection. + * @return The username to use. + */ + public String getUserName() { + return userName; + } + + /** + * This method returns the password to use for this connection. + * @return The password to use for this connection. + */ + public String getPassword() { + return password; + } + + + /** + * This method sets the host for this connection. + * NOTE: must be non-empty string. + * @param host the host to set + */ + public void setHost(String host) throws CentralRepoException { + validateStr(host, "Invalid host name. Cannot be empty."); + this.host = host; + } + + + /** + * This method sets the port for this connection. + * @param port The port to set (must be [1,65535]). + */ + public void setPort(int port) throws CentralRepoException { + validateNum(port, 1, 65535, "Invalid port. Must be a number greater than 0."); + this.port = port; + } + + + /** + * This methods sets the name of the database. + * NOTE: this name needs to be a valid postgres database name. + * @param dbName The database name. + */ + public void setDbName(String dbName) throws CentralRepoException { + validateStr(dbName, "Invalid database name. Cannot be empty."); // NON-NLS + validateRegex(dbName, DB_NAMES_REGEX, + "Invalid database name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."); // NON-NLS + + this.dbName = dbName.toLowerCase(); + } + + + /** + * This method sets the bulk threshold of this connection. + * @param bulkThreshold The bulk threshold to set (must be greater than 0). + */ + public void setBulkThreshold(int bulkThreshold) throws CentralRepoException { + validateNum(bulkThreshold, 1, null, "Invalid bulk threshold."); + this.bulkThreshold = bulkThreshold; + } + + + /** + * This method sets the username for this connection. + * NOTE: must be a valid postgres username. + * @param userName The user name to set. + */ + public void setUserName(String userName) throws CentralRepoException { + validateStr(userName, "Invalid user name. Cannot be empty."); // NON-NLS + validateRegex(userName, DB_USER_NAMES_REGEX, + "Invalid user name. Name must start with a letter and can only contain letters, numbers, and '_'."); + + this.userName = userName; + } + + + /** + * This method sets the password for this connection. + * @param password The password to set. + */ + public void setPassword(String password) throws CentralRepoException { + validateStr(password, "Invalid user password. Cannot be empty."); + this.password = password; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 43 * hash + Objects.hashCode(this.host); + hash = 43 * hash + this.port; + hash = 43 * hash + Objects.hashCode(this.dbName); + hash = 43 * hash + this.bulkThreshold; + hash = 43 * hash + Objects.hashCode(this.userName); + hash = 43 * hash + Objects.hashCode(this.password); + return hash; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PostgresConnectionSettings other = (PostgresConnectionSettings) obj; + if (this.port != other.port) { + return false; + } + if (this.bulkThreshold != other.bulkThreshold) { + return false; + } + if (!Objects.equals(this.host, other.host)) { + return false; + } + if (!Objects.equals(this.dbName, other.dbName)) { + return false; + } + if (!Objects.equals(this.userName, other.userName)) { + return false; + } + if (!Objects.equals(this.password, other.password)) { + return false; + } + return true; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresSettingsLoader.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresSettingsLoader.java new file mode 100644 index 0000000000..b3514d0ccb --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresSettingsLoader.java @@ -0,0 +1,77 @@ +/* + * Central Repository + * + * Copyright 2015-2020 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.centralrepository.datamodel; + +/** + * This is an interface to load or save postgres settings. + */ +public interface PostgresSettingsLoader { + /** + * This method loads the current settings. + * @return The settings that were loaded. + */ + PostgresConnectionSettings loadSettings(); + + /** + * This method saves the current settings. + * @param settings The settings to save. + */ + void saveSettings(PostgresConnectionSettings settings); + + PostgresSettingsLoader CUSTOM_SETTINGS_LOADER = new Custom(); + PostgresSettingsLoader MULTIUSER_SETTINGS_LOADER = new MultiUser(); + CentralRepoPostgresSettingsUtil SETTINGS_UTIL = CentralRepoPostgresSettingsUtil.getInstance(); + + + /** + * This class loads and saves custom postgres settings. + */ + class Custom implements PostgresSettingsLoader { + @Override + public PostgresConnectionSettings loadSettings() { + return SETTINGS_UTIL.loadCustomSettings(); + } + + @Override + public void saveSettings(PostgresConnectionSettings settings) { + SETTINGS_UTIL.saveCustomSettings(settings); + } + } + + + /** + * This class loads multi-user postgres settings to be used with central repo. + * NOTE: This class does not save settings on save operation as this is merely a proxy. + */ + class MultiUser implements PostgresSettingsLoader { + + @Override + public PostgresConnectionSettings loadSettings() { + return SETTINGS_UTIL.loadMultiUserSettings(); + } + + /** + * NOTE: This action does not do anything. There is no need to save since + * this is just a proxy to multi user settings. + * @param settings The settings to save. + */ + @Override + public void saveSettings(PostgresConnectionSettings settings) {} + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index 93f6268979..63f8e2f13a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -65,7 +65,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { static final String SCHEMA_MINOR_VERSION_KEY = "SCHEMA_MINOR_VERSION"; static final String CREATION_SCHEMA_MAJOR_VERSION_KEY = "CREATION_SCHEMA_MAJOR_VERSION"; static final String CREATION_SCHEMA_MINOR_VERSION_KEY = "CREATION_SCHEMA_MINOR_VERSION"; - static final CaseDbSchemaVersionNumber SOFTWARE_CR_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 3); + static final CaseDbSchemaVersionNumber SOFTWARE_CR_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 4); protected final List defaultCorrelationTypes; @@ -3553,7 +3553,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { conn = connect(false); conn.setAutoCommit(false); statement = conn.createStatement(); - selectedPlatform = CentralRepoPlatforms.getSelectedPlatform(); + selectedPlatform = CentralRepoDbManager.getSavedDbChoice().getDbPlatform(); int minorVersion = 0; String minorVersionStr = null; resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='" + RdbmsCentralRepo.SCHEMA_MINOR_VERSION_KEY + "'"); @@ -3808,6 +3808,10 @@ abstract class RdbmsCentralRepo implements CentralRepository { throw new CentralRepoException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); } } + + // Upgrade to 1.4 + (new CentralRepoDbUpgrader13To14()).upgradeSchema(dbSchemaVersion, conn); + updateSchemaVersion(conn); conn.commit(); logger.log(Level.INFO, String.format("Central Repository schema updated to version %s", SOFTWARE_CR_DB_SCHEMA_VERSION)); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java index 2fa248598e..1232d708a6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java @@ -134,7 +134,7 @@ public class RdbmsCentralRepoFactory { stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')"); stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')"); - // Create account_types and accounts tab;es which are referred by X_instances tables + // Create account_types and accounts tables which are referred by X_instances tables stmt.execute(getCreateAccountTypesTableStatement(selectedPlatform)); stmt.execute(getCreateAccountsTableStatement(selectedPlatform)); @@ -161,7 +161,8 @@ public class RdbmsCentralRepoFactory { stmt.execute(String.format(getReferenceTypeValueKnownstatusIndexTemplate(), reference_type_dbname, reference_type_dbname)); } } - createPersonaTables(stmt); + // @TODO: uncomment this when ready to create Persona tables. + //createPersonaTables(stmt); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS return false; @@ -191,8 +192,10 @@ public class RdbmsCentralRepoFactory { } result = CentralRepoDbUtil.insertDefaultCorrelationTypes(conn) - && CentralRepoDbUtil.insertDefaultOrganization(conn) - && insertDefaultPersonaTablesContent(conn); + && CentralRepoDbUtil.insertDefaultOrganization(conn) && + insertDefaultAccountsTablesContent(conn); + // @TODO: uncomment when ready to create/populate persona tables + // && insertDefaultPersonaTablesContent(conn); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in CR tables."), ex); @@ -529,7 +532,7 @@ public class RdbmsCentralRepoFactory { * * @return SQL clause. */ - private static String getBigIntType(CentralRepoPlatforms selectedPlatform) { + static String getBigIntType(CentralRepoPlatforms selectedPlatform) { switch (selectedPlatform) { case POSTGRESQL: return " BIGINT "; @@ -576,7 +579,6 @@ public class RdbmsCentralRepoFactory { stmt.execute(getCreateConfidenceTableStatement(selectedPlatform)); stmt.execute(getCreateExaminersTableStatement(selectedPlatform)); stmt.execute(getCreatePersonaStatusTableStatement(selectedPlatform)); - stmt.execute(getCreateAliasesTableStatement(selectedPlatform)); stmt.execute(getCreatePersonasTableStatement(selectedPlatform)); stmt.execute(getCreatePersonaAliasTableStatement(selectedPlatform)); @@ -653,20 +655,6 @@ public class RdbmsCentralRepoFactory { + ")"; } - /** - * Get the SQL String for creating a new aliases table in a central - * repository. - * - * @return SQL string for creating aliases table - */ - static String getCreateAliasesTableStatement(CentralRepoPlatforms selectedPlatform) { - - return "CREATE TABLE IF NOT EXISTS aliases (" - + getNumericPrimaryKeyClause("id", selectedPlatform) - + "alias TEXT NOT NULL," - + "CONSTRAINT alias_unique UNIQUE(alias)" - + ")"; - } /** * Get the SQL String for creating a new accounts table in a central @@ -719,13 +707,12 @@ public class RdbmsCentralRepoFactory { return "CREATE TABLE IF NOT EXISTS persona_alias (" + getNumericPrimaryKeyClause("id", selectedPlatform) + "persona_id " + getBigIntType(selectedPlatform) + " ," - + "alias_id " + getBigIntType(selectedPlatform) + " ," + + "alias TEXT NOT NULL, " + "justification TEXT NOT NULL," + "confidence_id integer NOT NULL," + "date_added " + getBigIntType(selectedPlatform) + " ," + "examiner_id integer NOT NULL," + "FOREIGN KEY (persona_id) REFERENCES personas(id)," - + "FOREIGN KEY (alias_id) REFERENCES aliases(id)," + "FOREIGN KEY (confidence_id) REFERENCES confidence(confidence_id)," + "FOREIGN KEY (examiner_id) REFERENCES examiners(id)" + ")"; @@ -779,6 +766,33 @@ public class RdbmsCentralRepoFactory { } + /** + * Inserts the default content in accounts related tables. + * + * @param conn Database connection to use. + * + * @return True if success, false otherwise. + */ + private boolean insertDefaultAccountsTablesContent(Connection conn) { + + try (Statement stmt = conn.createStatement()) { + // Populate the account_types table + for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { + int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type); + if (correlationTypeId > 0) { + String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), + type.getTypeName(), type.getDisplayName(), correlationTypeId); + stmt.execute(sqlString); + } + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Accounts tables."), ex); + return false; + } + + return true; + } + /** * Inserts the default content in persona related tables. * @@ -786,12 +800,9 @@ public class RdbmsCentralRepoFactory { * * @return True if success, false otherwise. */ - private boolean insertDefaultPersonaTablesContent(Connection conn) { + private static boolean insertDefaultPersonaTablesContent(Connection conn, CentralRepoPlatforms selectedPlatform) { - Statement stmt = null; - try { - stmt = conn.createStatement(); - + try (Statement stmt = conn.createStatement()) { // populate the confidence table for (Confidence confidence : Persona.Confidence.values()) { String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevel() + ", '" + confidence.toString() + "')" //NON-NLS @@ -806,27 +817,38 @@ public class RdbmsCentralRepoFactory { stmt.execute(sqlString); } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Persona tables."), ex); + return false; + } + + return true; + } + + /** + * Inserts the default content in accounts related tables. + * + * @param conn Database connection to use. + * + * @return True if success, false otherwise. + */ + static boolean insertDefaultAccountsTablesContent(Connection conn, CentralRepoPlatforms selectedPlatform) { + + try (Statement stmt = conn.createStatement();) { + // Populate the account_types table for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type); if (correlationTypeId > 0) { - String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), - type.getTypeName(), type.getDisplayName(), correlationTypeId); + String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), + type.getTypeName(), type.getDisplayName(), correlationTypeId); stmt.execute(sqlString); } } - + } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Persona tables."), ex); + LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in account_types table."), ex); return false; - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex2) { - LOGGER.log(Level.SEVERE, "Error closing statement.", ex2); - } - } } return true; @@ -841,7 +863,7 @@ public class RdbmsCentralRepoFactory { * ' * @return correlation type id. */ - private int getCorrelationTypeIdForAccountType(Connection conn, Account.Type accountType) { + static int getCorrelationTypeIdForAccountType(Connection conn, Account.Type accountType) { int typeId = -1; if (accountType == Account.Type.EMAIL) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java index 675a164972..a938dd166a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; * NOTE: This is public scope because the options panel calls it directly to * set/get */ -public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { +public final class SqliteCentralRepoSettings implements CentralRepoDbConnectivityManager { public final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS private final static Logger LOGGER = Logger.getLogger(SqliteCentralRepoSettings.class.getName()); @@ -45,7 +45,7 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS private final static String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS - + private final static String DB_NAMES_REGEX = "[a-z][a-z0-9_]*(\\.db)?"; private String dbName; private String dbDirectory; @@ -80,11 +80,11 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; } } - + public String toString() { return String.format("SqliteCentralRepoSettings: [db type: sqlite, directory: %s, name: %s]", getDbDirectory(), getDbName()); } - + /** * sets database directory and name to defaults */ @@ -115,13 +115,11 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { return (!dbFile.isDirectory()); } - @Override public boolean verifyDatabaseExists() { return dbDirectoryExists(); } - /** * Verify that the db directory path exists. * @@ -143,6 +141,7 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { /** * creates database directory for sqlite database if it does not exist + * * @return whether or not operation occurred successfully */ @Override @@ -150,7 +149,6 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { return createDbDirectory(); } - /** * Create the db directory if it does not exist. * @@ -354,4 +352,21 @@ public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { String getJDBCBaseURI() { return JDBC_BASE_URI; } + + @Override + public DatabaseTestResult testStatus() { + if (dbFileExists()) { + if (verifyConnection()) { + if (verifyDatabaseSchema()) { + return DatabaseTestResult.TESTED_OK; + } else { + return DatabaseTestResult.SCHEMA_INVALID; + } + } else { + return DatabaseTestResult.SCHEMA_INVALID; + } + } else { + return DatabaseTestResult.DB_DOES_NOT_EXIST; + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED index cd654a5b37..e3c99ded13 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED @@ -8,5 +8,5 @@ IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) Installer.centralRepoUpgradeFailed.title=Central repository disabled Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. You can use this to ignore previously seen files and make connections between cases. -Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to? +Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it? Installer.initialCreateSqlite.title=Enable Central Repository? diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index 208f201a30..9f888e43f2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -1,7 +1,7 @@ /* - * Central Repository + * Autopsy Forensic Browser * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2017-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,19 +32,40 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.Version; /** - * Install event listeners during module initialization + * Adds/removes application event listeners responsible for adding data to the + * central repository, sets up a default, single-user SQLite central repository + * if no central repository is configured, and updates the central repository + * schema as required. + * + * TODO (Jira-6108): At first glance, this package seems to have become a rather + * strange package for the "package installer" for the CR to reside in. The + * org.sleuthkit.autopsy.centralrepository package would seem to be more + * appropriate with so much going on. However, having a central repository + * schema update occur in a "package installer" with no user feedback is not + * optimal. Furthermore, for a multi-user (collaborative) installation, a schema + * update should be done in a more controlled way by acquiring an exclusive + * coordination service lock and requiring shared locks to be acquired by nodes + * with open cases. */ public class Installer extends ModuleInstall { - private static final Logger LOGGER = Logger.getLogger(Installer.class.getName()); + private static final Logger logger = Logger.getLogger(Installer.class.getName()); private static final long serialVersionUID = 1L; - private final CaseEventListener pcl = new CaseEventListener(); - private final IngestEventsListener ieListener = new IngestEventsListener(); - private static Installer instance; + private final CaseEventListener caseEventListener = new CaseEventListener(); + private final IngestEventsListener ingestEventListener = new IngestEventsListener(); + /** + * Gets the singleton "package installer" used by the registered Installer + * for the Autopsy-Core module located in the org.sleuthkit.autopsy.core + * package. + * + * @return The "package installer" singleton for the + * org.sleuthkit.autopsy.centralrepository.eventlisteners package. + */ public synchronized static Installer getDefault() { if (instance == null) { instance = new Installer(); @@ -52,28 +73,64 @@ public class Installer extends ModuleInstall { return instance; } + /** + * Constructs the singleton "package installer" used by the registered + * Installer for the Autopsy-Core module located in the + * org.sleuthkit.autopsy.core package. + */ private Installer() { super(); } + /* + * Adds/removes application event listeners responsible for adding data to + * the central repository, sets up a default, single-user SQLite central + * repository if no central repository is configured, and updates the + * central repository schema as required. + * + * Called by the registered Installer for the Autopsy-Core module located in + * the org.sleuthkit.autopsy.core package when the already installed + * Autopsy-Core module is restored (during application startup). + */ @NbBundle.Messages({ "Installer.initialCreateSqlite.title=Enable Central Repository?", "Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?", - "Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. " + - "You can use this to ignore previously seen files and make connections between cases." + "Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. " + + "You can use this to ignore previously seen files and make connections between cases." }) @Override public void restored() { - Case.addPropertyChangeListener(pcl); - ieListener.installListeners(); + addApplicationEventListeners(); - + if (Version.getBuildType() == Version.Type.RELEASE) { + setupDefaultCentralRepository(); + } + + updateCentralRepoSchema(); + } + + /** + * Adds the application event listeners responsible for adding data to the + * central repository. + */ + private void addApplicationEventListeners() { + Case.addPropertyChangeListener(caseEventListener); + ingestEventListener.installListeners(); + } + + /** + * Checks if the central repository has been set up and configured. If not, + * either offers to perform set up (running with a GUI) or does the set up + * unconditionally (not running with a GUI, e.g., in an automated ingest + * node). + */ + private void setupDefaultCentralRepository() { Map centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository"); String initializedStr = centralRepoSettings.get("initialized"); - + // check to see if the repo has been initialized asking to setup cr boolean initialized = Boolean.parseBoolean(initializedStr); - + // if it hasn't received that flag, check for a previous install where cr is already setup if (!initialized) { boolean prevRepo = Boolean.parseBoolean(centralRepoSettings.get("db.useCentralRepo")); @@ -91,92 +148,114 @@ public class Installer extends ModuleInstall { try { SwingUtilities.invokeAndWait(() -> { try { - String dialogText = - "" + - "
" + - "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "

" + - "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "

" + - "
" + - ""; + String dialogText + = "" + + "
" + + "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "

" + + "

" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "

" + + "
" + + ""; if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), dialogText, NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"), JOptionPane.YES_NO_OPTION)) { - setupDefaultSqlite(); + setupDefaultSqliteCentralRepo(); } } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); + logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); - reportUpgradeError(ex); + doMessageBoxIfRunningInGUI(ex); } }); } catch (InterruptedException | InvocationTargetException ex) { - LOGGER.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex); + logger.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex); } } // if no GUI, just initialize else { try { - setupDefaultSqlite(); + setupDefaultSqliteCentralRepo(); } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); + logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); - reportUpgradeError(ex); + doMessageBoxIfRunningInGUI(ex); } } ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); - } - - // now run regular module startup code - try { - CentralRepoDbManager.upgradeDatabase(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "There was an error while upgrading the central repository database", ex); - if (RuntimeProperties.runningWithGUI()) { - reportUpgradeError(ex); - } } } - private void setupDefaultSqlite() throws CentralRepoException { + /** + * Sets up a default single-user SQLite central repository. + * + * @throws CentralRepoException If there is an error setting up teh central + * repository. + */ + private void setupDefaultSqliteCentralRepo() throws CentralRepoException { CentralRepoDbManager manager = new CentralRepoDbManager(); manager.setupDefaultSqliteDb(); } - @NbBundle.Messages({ "Installer.centralRepoUpgradeFailed.title=Central repository disabled" }) - private void reportUpgradeError(CentralRepoException ex) { + /** + * Update the central repository schema. + */ + private void updateCentralRepoSchema() { try { - SwingUtilities.invokeAndWait(() -> { - JOptionPane.showMessageDialog(null, - ex.getUserMessage(), - NbBundle.getMessage(this.getClass(), - "Installer.centralRepoUpgradeFailed.title"), - JOptionPane.ERROR_MESSAGE); - }); - } catch (InterruptedException | InvocationTargetException e) { - LOGGER.log(Level.WARNING, e.getMessage(), e); + CentralRepoDbManager.upgradeDatabase(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "An error occurred updating the central repository schema", ex); + if (RuntimeProperties.runningWithGUI()) { + doMessageBoxIfRunningInGUI(ex); + } } - } - @Override - public boolean closing() { - //platform about to close - - return true; + /** + * Display a central repository exception in a message box if running with a + * GUI. + * + * @param ex The exception. + */ + @NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository disabled"}) + private void doMessageBoxIfRunningInGUI(CentralRepoException ex) { + if (RuntimeProperties.runningWithGUI()) { + try { + SwingUtilities.invokeAndWait(() -> { + JOptionPane.showMessageDialog(null, + ex.getUserMessage(), + NbBundle.getMessage(this.getClass(), "Installer.centralRepoUpgradeFailed.title"), + JOptionPane.ERROR_MESSAGE); + }); + } catch (InterruptedException | InvocationTargetException e) { + logger.log(Level.WARNING, e.getMessage(), e); + } + } } @Override public void uninstalled() { - //module is being unloaded - - Case.removePropertyChangeListener(pcl); - pcl.shutdown(); - ieListener.shutdown(); - ieListener.uninstallListeners(); - - // TODO: remove thread pool + /* + * TODO (Jira-6108): This code is erronoeous. As documented at + * http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/ModuleInstall.html#uninstalled-- + * + * "Called when the module is disabled while the application is still + * running. Should remove whatever functionality that it had registered + * in ModuleInstall.restored(). + * + * Beware: in practice there is no way to + * ensure that this method will really be called. The module might + * simply be deleted or disabled while the application is not running. + * In fact this is always the case in NetBeans 6.0; the Plugin Manager + * only uninstalls or disables modules between restarts. This method + * will still be called if you reload a module during development." + * + * THIS CODE IS NEVER EXECUTED. + */ + Case.removePropertyChangeListener(caseEventListener); + caseEventListener.shutdown(); + ingestEventListener.shutdown(); + ingestEventListener.uninstallListeners(); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java index e561aeeff6..1f7472d330 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListener; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; @@ -274,7 +275,7 @@ final class CentralRepoIngestModule implements FileIngestModule { // Don't allow sqlite central repo databases to be used for multi user cases if ((autopsyCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) - && (CentralRepoPlatforms.getSelectedPlatform() == CentralRepoPlatforms.SQLITE)) { + && (CentralRepoDbManager.getSavedDbChoice().getDbPlatform() == CentralRepoPlatforms.SQLITE)) { logger.log(Level.SEVERE, "Cannot run correlation engine on a multi-user case with a SQLite central repository."); throw new IngestModuleException("Cannot run on a multi-user case with a SQLite central repository."); // NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index 7277910e7d..9ea90a452a 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -19,7 +19,7 @@ EamDbSettingsDialog.okButton.createDbDialog.title=Database Does Not Exist EamDbSettingsDialog.okButton.createDbError.title=Unable to Create Database EamDbSettingsDialog.okButton.createPostgresDbError.message=Unable to create Postgres Database, please ensure address, port, and login credentials are correct for Postgres server and try again. EamDbSettingsDialog.okButton.createSQLiteDbError.message=Unable to create SQLite Database, please ensure location exists and you have write permissions and try again. -EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database please check your settings and try again. +EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database. Please check your settings and try again. EamDbSettingsDialog.okButton.databaseConnectionFailed.title=Database Connection Failed EamDbSettingsDialog.okButton.errorMsg.text=Please restart Autopsy to begin using the new database platform. EamDbSettingsDialog.okButton.errorTitle.text=Restart Required. @@ -33,6 +33,12 @@ EamDbSettingsDialog.validation.finished=Click OK to save your database settings EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database. EamOptionsController.moduleErr=Error processing value changes. EamOptionsController.moduleErr.msg=Value change processing failed. +GlobalSettingsPanel.onMultiUserChange.disabledMu.description=The Central Repository will be reconfigured to use a local SQLite database. +GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=Press Configure PostgreSQL to change to a PostgreSQL database. +GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary +GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database? +GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases. +GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository? GlobalSettingsPanel.updateFailed.title=Central repository disabled GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running. GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form index 27eae7629c..5a1e2de77e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form @@ -138,16 +138,16 @@ - + - - - + + + @@ -320,7 +320,7 @@ - + @@ -330,7 +330,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 25289da2af..3b047b0e10 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -19,25 +19,31 @@ package org.sleuthkit.autopsy.centralrepository.optionspanel; import java.awt.Color; +import java.awt.Component; import java.awt.Cursor; +import java.awt.HeadlessException; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; +import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; +import javax.swing.plaf.basic.BasicComboBoxRenderer; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.corecomponents.TextPrompt; import org.sleuthkit.autopsy.coreutils.Logger; @@ -45,8 +51,6 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; import org.sleuthkit.autopsy.centralrepository.datamodel.DatabaseTestResult; import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteCentralRepoSettings; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; -import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory; /** * Configuration dialog for Central Repository database settings. @@ -57,11 +61,42 @@ public class EamDbSettingsDialog extends JDialog { private static final Logger logger = Logger.getLogger(EamDbSettingsDialog.class.getName()); private static final long serialVersionUID = 1L; + + /** + * This class handles displaying and rendering drop down menu for database choices in central repo. + */ + private class DbChoiceRenderer extends BasicComboBoxRenderer { + private static final long serialVersionUID = 1L; + + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + + CentralRepoDbChoice item = (CentralRepoDbChoice) value; + + // disable cell if it is the db connection from multi user settings + // and that option is not enabled in multi user settings + setText(item.getTitle()); + setEnabled(isDbChoiceSelectable(item)); + return this; + } + } + + private final Collection textBoxes; private final TextBoxChangedListener textBoxChangedListener; private final CentralRepoDbManager manager = new CentralRepoDbManager(); - - + private final boolean isMultiUserSelectable = CentralRepoDbManager.isPostgresMultiuserAllowed(); + private final DbChoiceRenderer DB_CHOICE_RENDERER = new DbChoiceRenderer(); + + public EamDbSettingsDialog() { + this(null); + } + + private boolean isDbChoiceSelectable(CentralRepoDbChoice item) { + return (item != CentralRepoDbChoice.POSTGRESQL_MULTIUSER || isMultiUserSelectable); + } + + /** * Creates new form EamDbSettingsDialog */ @@ -69,7 +104,7 @@ public class EamDbSettingsDialog extends JDialog { "EamDbSettingsDialog.lbSingleUserSqLite.text=SQLite should only be used by one examiner at a time.", "EamDbSettingsDialog.lbDatabaseType.text=Database Type :", "EamDbSettingsDialog.fcDatabasePath.title=Select location for central_repository.db"}) - public EamDbSettingsDialog() { + public EamDbSettingsDialog(CentralRepoDbChoice initialMenuItem) { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.EamDbSettingsDialog_title_text(), true); @@ -95,29 +130,45 @@ public class EamDbSettingsDialog extends JDialog { return "Directories and Central Repository databases"; } }); - cbDatabaseType.setSelectedItem(manager.getSelectedPlatform()); - customizeComponents(); + + setupDbChoice(initialMenuItem); valid(); display(); - + } + + + private void setupDbChoice(CentralRepoDbChoice initialMenuItem) { + // setup initially selected item + CentralRepoDbChoice toSelect = (initialMenuItem == null) ? + (Arrays.asList(CentralRepoDbChoice.DB_CHOICES).contains(manager.getSelectedDbChoice())) ? + manager.getSelectedDbChoice() : + CentralRepoDbChoice.DB_CHOICES[0] : + initialMenuItem; + + // set the renderer so item is unselectable if inappropriate + cbDatabaseType.setRenderer(DB_CHOICE_RENDERER); + + changeDbSelection(toSelect); } /** - * prompts user based on testing status (i.e. failure to connect, invalid schema, db does not exist, etc.) - * @return whether or not the ultimate status after prompts is okay to continue + * This method prompts user based on testing status (i.e. failure to connect, invalid schema, db does not exist, etc.). + * @param manager The manager to use when setting up the database. + * @param dialog If non-null value, validates settings and updates 'okay' button enabled state. + * @return Whether or not the ultimate status after prompts is okay to continue. */ @NbBundle.Messages({"EamDbSettingsDialog.okButton.corruptDatabaseExists.title=Error Loading Database", "EamDbSettingsDialog.okButton.corruptDatabaseExists.message=Database exists but is not the right format. Manually delete it or choose a different path (if applicable).", "EamDbSettingsDialog.okButton.createDbDialog.title=Database Does Not Exist", "EamDbSettingsDialog.okButton.createDbDialog.message=Database does not exist, would you like to create it?", "EamDbSettingsDialog.okButton.databaseConnectionFailed.title=Database Connection Failed", - "EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database please check your settings and try again.", + "EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database. Please check your settings and try again.", "EamDbSettingsDialog.okButton.createSQLiteDbError.message=Unable to create SQLite Database, please ensure location exists and you have write permissions and try again.", "EamDbSettingsDialog.okButton.createPostgresDbError.message=Unable to create Postgres Database, please ensure address, port, and login credentials are correct for Postgres server and try again.", "EamDbSettingsDialog.okButton.createDbError.title=Unable to Create Database"}) - private boolean promptTestStatusWarnings() { + private static boolean promptTestStatusWarnings(CentralRepoDbManager manager, EamDbSettingsDialog dialog) { if (manager.getStatus() == DatabaseTestResult.CONNECTION_FAILED) { JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_message(), @@ -135,36 +186,49 @@ public class EamDbSettingsDialog extends JDialog { Bundle.EamDbSettingsDialog_okButton_createDbDialog_message(), Bundle.EamDbSettingsDialog_okButton_createDbDialog_title(), JOptionPane.YES_NO_OPTION)) { - try { - manager.createDb(); - } - catch (CentralRepoException e) { - // in the event that there is a failure to connect, notify user with corresponding message - String errorMessage; - switch (manager.getSelectedPlatform()) { - case POSTGRESQL: - errorMessage = Bundle.EamDbSettingsDialog_okButton_createPostgresDbError_message(); - break; - case SQLITE: - errorMessage = Bundle.EamDbSettingsDialog_okButton_createSQLiteDbError_message(); - break; - default: - errorMessage = ""; - break; - } - - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - errorMessage, - Bundle.EamDbSettingsDialog_okButton_createDbError_title(), - JOptionPane.WARNING_MESSAGE); - } - - valid(); + onUserPromptCreateDb(manager, dialog); } } - return (manager.getStatus() == DatabaseTestResult.TESTEDOK); + return (manager.getStatus() == DatabaseTestResult.TESTED_OK); } + + /** + * When a new database needs to be created on user selecting cr, this code will be ran when user selects create cr. + * @param manager The manager handling the database creation. + * @param dialog The dialog that prompted database creation. + */ + private static void onUserPromptCreateDb(CentralRepoDbManager manager, EamDbSettingsDialog dialog) { + try { + manager.createDb(); + } catch (CentralRepoException e) { + onPromptStatusError(manager); + } + if (dialog != null) + dialog.valid(); + } + + + /** + * When an error occurs while going through promptTestStatusWarning, this method is called. + * @param manager1 The manager to use as service class. + * @throws HeadlessException + */ + private static void onPromptStatusError(CentralRepoDbManager manager1) { + // in the event that there is a failure to connect, notify user with corresponding message + String errorMessage = ""; + if (manager1 == null || manager1.getSelectedDbChoice() == null) { + errorMessage = ""; + } else if (manager1.getSelectedDbChoice().getDbPlatform() == CentralRepoPlatforms.POSTGRESQL) { + errorMessage = Bundle.EamDbSettingsDialog_okButton_createPostgresDbError_message(); + } else if (manager1.getSelectedDbChoice().getDbPlatform() == CentralRepoPlatforms.SQLITE) { + errorMessage = Bundle.EamDbSettingsDialog_okButton_createSQLiteDbError_message(); + } + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + errorMessage, + Bundle.EamDbSettingsDialog_okButton_createDbError_title(), + JOptionPane.WARNING_MESSAGE); + } /** @@ -281,7 +345,7 @@ public class EamDbSettingsDialog extends JDialog { jpDbPassword.setPreferredSize(new java.awt.Dimension(509, 20)); - cbDatabaseType.setModel(new javax.swing.DefaultComboBoxModel<>(new CentralRepoPlatforms[]{CentralRepoPlatforms.POSTGRESQL, CentralRepoPlatforms.SQLITE})); + cbDatabaseType.setModel(new javax.swing.DefaultComboBoxModel<>(org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice.DB_CHOICES)); cbDatabaseType.setPreferredSize(new java.awt.Dimension(120, 20)); cbDatabaseType.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -323,18 +387,18 @@ public class EamDbSettingsDialog extends JDialog { .addComponent(lbUserName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(lbDatabaseDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbDatabaseDesc, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 94, Short.MAX_VALUE) .addComponent(lbUserPassword, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addGap(10, 10, 10) - .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addComponent(tfDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(bnDatabasePathFileOpen)) .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() - .addComponent(cbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(cbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, 210, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.PREFERRED_SIZE, 1, Short.MAX_VALUE)) .addComponent(jpDbPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(tbDbUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(tbDbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -414,15 +478,20 @@ public class EamDbSettingsDialog extends JDialog { setTextPrompts(); setTextBoxListeners(); manager.clearStatus(); - if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) { + if (manager.getSelectedDbChoice() == CentralRepoDbChoice.SQLITE) { updatePostgresFields(false); updateSqliteFields(true); } - else { + else if (manager.getSelectedDbChoice() == CentralRepoDbChoice.POSTGRESQL_CUSTOM) { updatePostgresFields(true); updateSqliteFields(false); } - displayDatabaseSettings(CentralRepoPlatforms.POSTGRESQL.equals(manager.getSelectedPlatform())); + else { + updatePostgresFields(false); + updateSqliteFields(false); + } + + displayDatabaseSettings(manager.getSelectedDbChoice()); } private void display() { @@ -452,14 +521,40 @@ public class EamDbSettingsDialog extends JDialog { "EamDbSettingsDialog.okButton.errorMsg.text=Please restart Autopsy to begin using the new database platform.", "EamDbSettingsDialog.okButton.connectionErrorMsg.text=Failed to connect to central repository database."}) private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + testStatusAndCreate(this, manager, this); + dispose(); + }//GEN-LAST:event_bnOkActionPerformed + + + /** + * This method tests status for central repo db / creation and prompts user accordingly. + * @param parent The parent component (the anchor for displaying dialogs). + * @param manager The central repo db manager with settings to be tested and saved. + * @return Whether or not central repo db was successfully be created or found. + */ + public static boolean testStatusAndCreate(Component parent, CentralRepoDbManager manager) { + return testStatusAndCreate(parent, manager, null); + } + + + /** + * This method tests status for central repo db / creation and prompts user accordingly. + * @param parent The parent component (the anchor for displaying dialogs). + * @param manager The central repo db manager with settings to be tested and saved. + * @param dialog The db settings dialog; if non-null, will validate okay button state. + * @return Whether or not central repo db was successfully be created or found. + */ + private static boolean testStatusAndCreate(Component parent, CentralRepoDbManager manager, EamDbSettingsDialog dialog) { + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); manager.testStatus(); - valid(); - boolean testedOk = promptTestStatusWarnings(); + if (dialog != null) + dialog.valid(); + + boolean testedOk = promptTestStatusWarnings(manager, dialog); if (!testedOk) { - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return false; } try{ @@ -467,25 +562,26 @@ public class EamDbSettingsDialog extends JDialog { } catch (CentralRepoException e) { SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(this, + JOptionPane.showMessageDialog(parent, Bundle.EamDbSettingsDialog_okButton_errorMsg_text(), Bundle.EamDbSettingsDialog_okButton_errorTitle_text(), JOptionPane.WARNING_MESSAGE); }); + + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return false; } - - - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - dispose(); - }//GEN-LAST:event_bnOkActionPerformed + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return true; + } /** - * Returns if changes to the central repository configuration were - * successfully applied + * This method returns if changes to the central repository configuration were + * successfully applied. * - * @return true if the database configuration was successfully changed false - * if it was not + * @return True if the database configuration was successfully changed; false + * if it was not. */ public boolean wasConfigurationChanged() { return manager.wasConfigurationChanged(); @@ -497,22 +593,38 @@ public class EamDbSettingsDialog extends JDialog { private void cbDatabaseTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDatabaseTypeActionPerformed - manager.setSelectedPlatform((CentralRepoPlatforms) cbDatabaseType.getSelectedItem()); - customizeComponents(); + CentralRepoDbChoice selectedItem = (CentralRepoDbChoice) cbDatabaseType.getSelectedItem(); + changeDbSelection(selectedItem); }//GEN-LAST:event_cbDatabaseTypeActionPerformed + private void changeDbSelection(CentralRepoDbChoice selectedItem) { + if (isDbChoiceSelectable(selectedItem)) { + manager.setSelctedDbChoice(selectedItem); + cbDatabaseType.setSelectedItem(selectedItem); + } + else { + cbDatabaseType.setSelectedItem(manager.getSelectedDbChoice()); + } + + customizeComponents(); + } + private void updateFullDbPath() { dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + SqliteCentralRepoSettings.DEFAULT_DBNAME); dataBaseFileTextArea.setCaretPosition(dataBaseFileTextArea.getText().length()); } - private void displayDatabaseSettings(boolean isPostgres) { - lbDatabasePath.setVisible(!isPostgres); - tfDatabasePath.setVisible(!isPostgres); - lbDatabaseDesc.setVisible(!isPostgres); - dataBaseFileTextArea.setVisible(!isPostgres); - lbSingleUserSqLite.setVisible(!isPostgres); - bnDatabasePathFileOpen.setVisible(!isPostgres); + private void displayDatabaseSettings(CentralRepoDbChoice choice) { + boolean isSqlite = choice == CentralRepoDbChoice.SQLITE; + boolean isPostgres = choice == CentralRepoDbChoice.POSTGRESQL_CUSTOM; + + lbDatabasePath.setVisible(isSqlite); + tfDatabasePath.setVisible(isSqlite); + lbDatabaseDesc.setVisible(isSqlite); + dataBaseFileTextArea.setVisible(isSqlite); + lbSingleUserSqLite.setVisible(isSqlite); + bnDatabasePathFileOpen.setVisible(isSqlite); + lbHostName.setVisible(isPostgres); tbDbHostname.setVisible(isPostgres); lbPort.setVisible(isPostgres); @@ -610,14 +722,14 @@ public class EamDbSettingsDialog extends JDialog { @Messages({"EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database."}) private boolean databaseFieldsArePopulated() { boolean result = true; - if (manager.getSelectedPlatform() == CentralRepoPlatforms.POSTGRESQL) { + if (manager.getSelectedDbChoice() == CentralRepoDbChoice.POSTGRESQL_CUSTOM) { result = !tbDbHostname.getText().trim().isEmpty() && !tbDbPort.getText().trim().isEmpty() // && !tbDbName.getText().trim().isEmpty() && !tbDbUsername.getText().trim().isEmpty() && 0 < jpDbPassword.getPassword().length; } - else if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) { + else if (manager.getSelectedDbChoice() == CentralRepoDbChoice.SQLITE) { result = !tfDatabasePath.getText().trim().isEmpty(); } @@ -722,7 +834,7 @@ public class EamDbSettingsDialog extends JDialog { private javax.swing.JButton bnDatabasePathFileOpen; private javax.swing.ButtonGroup bnGrpDatabasePlatforms; private javax.swing.JButton bnOk; - private javax.swing.JComboBox cbDatabaseType; + private javax.swing.JComboBox cbDatabaseType; private javax.swing.JScrollPane dataBaseFileScrollPane; private javax.swing.JTextArea dataBaseFileTextArea; private javax.swing.JFileChooser fcDatabasePath; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form index 13ba7876cd..99594871b0 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.form @@ -61,7 +61,7 @@ - + @@ -258,7 +258,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index ab0ea0401f..78b2e9ca87 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -33,33 +33,39 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; -import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; -import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms.DISABLED; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresCentralRepoSettings; import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteCentralRepoSettings; +import java.awt.Component; +import java.util.logging.Level; /** * Main settings panel for the Central Repository */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { - + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(GlobalSettingsPanel.class.getName()); private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private final IngestJobEventPropertyChangeListener ingestJobEventListener; - + + + /** * Creates new form EamOptionsPanel */ public GlobalSettingsPanel() { ingestJobEventListener = new IngestJobEventPropertyChangeListener(); - + + // listen for change events in currently saved choice + CentralRepoDbManager.addPropertyChangeListener((PropertyChangeEvent evt) -> ingestStateUpdated(Case.isCaseOpen())); initComponents(); customizeComponents(); addIngestJobEventsListener(); @@ -68,7 +74,8 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ingestStateUpdated(evt.getNewValue() != null); }); } - + + private void customizeComponents() { setName(NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.pnCorrelationProperties.border.title")); } @@ -78,26 +85,163 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ingestStateUpdated(Case.isCaseOpen()); } - @Messages({"GlobalSettingsPanel.updateFailed.title=Central repository disabled"}) private void updateDatabase() { + updateDatabase(this); + } + + /** + * This method invokes central repository database choice selection as well as input for necessary configuration. + * @param parent The parent component for displaying dialogs. + * @param initialSelection If non-null, the menu item will be set to this choice; if null, + * the currently selected db choice will be selected. + * @return True if there was a change. + */ + private static boolean invokeCrChoice(Component parent, CentralRepoDbChoice initialSelection) { + EamDbSettingsDialog dialog = (initialSelection != null) ? + new EamDbSettingsDialog(initialSelection) : + new EamDbSettingsDialog(); + + if (dialog.wasConfigurationChanged()) { + updateDatabase(parent); + return true; + } + + return false; + } + + + /** + * When multi user settings are updated, this function triggers pertinent updates for central repository. + * NOTE: If multi user settings were previously enabled and multi user settings are currently selected, this function assumes + * there is a change in the postgres connectivity. + * + * @param parent The swing component that serves as a parent for dialogs that may arise. + * @param muPreviouslySelected If multi user settings were previously enabled. + * @param muCurrentlySelected If multi user settings are currently enabled as of most recent change. + */ + @NbBundle.Messages({ + "GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository?", + "GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database?", + "GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases." + }) + public static void onMultiUserChange(Component parent, boolean muPreviouslySelected, boolean muCurrentlySelected) { + boolean crEnabled = CentralRepoDbUtil.allowUseOfCentralRepository(); + boolean crMultiUser = CentralRepoDbManager.getSavedDbChoice() == CentralRepoDbChoice.POSTGRESQL_MULTIUSER; + boolean crDisabledDueToFailure = CentralRepoDbManager.isDisabledDueToFailure(); + + if (!muPreviouslySelected && muCurrentlySelected) { + SwingUtilities.invokeLater(() -> { + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent, + "" + + "
" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description") + "

" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description2") + "

" + + "
" + + "", + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.title"), + JOptionPane.YES_NO_OPTION)) { + + // setup database for CR + CentralRepoDbUtil.setUseCentralRepo(true); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.POSTGRESQL_MULTIUSER); + handleDbChange(parent); + } + }); + } + // moving from selected to not selected && 'PostgreSQL using multi-user settings' is selected + else if (muPreviouslySelected && !muCurrentlySelected && crEnabled && crMultiUser) { + SwingUtilities.invokeLater(() -> { + askForCentralRepoDbChoice(parent); + }); + } + // changing multi-user settings connection && 'PostgreSQL using multi-user settings' is selected && + // central repo either enabled or was disabled due to error + else if (muPreviouslySelected && muCurrentlySelected && crMultiUser && (crEnabled || crDisabledDueToFailure)) { + // test databse for CR change + CentralRepoDbUtil.setUseCentralRepo(true); + handleDbChange(parent); + } + } - if (CentralRepoPlatforms.getSelectedPlatform().equals(DISABLED)) { + + /** + * This method is called when a user must select a new database other than using database from multi user settings. + * @param parent The parent component to use for displaying dialogs in reference. + */ + @NbBundle.Messages({ + "GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary", + "GlobalSettingsPanel.onMultiUserChange.disabledMu.description=The Central Repository will be reconfigured to use a local SQLite database.", + "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2=Press Configure PostgreSQL to change to a PostgreSQL database." + }) + private static void askForCentralRepoDbChoice(Component parent) { + // disable central repository until user makes choice + CentralRepoDbUtil.setUseCentralRepo(false); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.DISABLED, false); + + Object[] options = { + "Use SQLite", + "Configure PostgreSQL", + "Disable Central Repository" + }; + + int result = JOptionPane.showOptionDialog( + parent, + "" + + "
" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description") + "

" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2") + "

" + + "
" + + "", + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.title"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + options, + options[0] + ); + + if (JOptionPane.YES_OPTION == result) { + invokeCrChoice(parent, CentralRepoDbChoice.SQLITE); + } + else if (JOptionPane.NO_OPTION == result) { + invokeCrChoice(parent, CentralRepoDbChoice.POSTGRESQL_CUSTOM); + } + } + + + private static void handleDbChange(Component parent) { + SwingUtilities.invokeLater(() -> { + boolean successful = EamDbSettingsDialog.testStatusAndCreate(parent, new CentralRepoDbManager()); + if (successful) { + updateDatabase(parent); + } + else { + // disable central repository due to error + CentralRepoDbManager.disableDueToFailure(); + } + }); + } + + + @Messages({"GlobalSettingsPanel.updateFailed.title=Central repository disabled"}) + private static void updateDatabase(Component parent) { + if (CentralRepoDbChoice.DISABLED.equals(CentralRepoDbManager.getSavedDbChoice())) { return; } - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { CentralRepoDbManager.upgradeDatabase(); - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } catch (CentralRepoException ex) { - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog(this, + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + JOptionPane.showMessageDialog(parent, ex.getUserMessage(), - NbBundle.getMessage(this.getClass(), + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.updateFailed.title"), JOptionPane.WARNING_MESSAGE); } finally { - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } @@ -425,9 +569,8 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void bnDbConfigureActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDbConfigureActionPerformed store(); - EamDbSettingsDialog dialog = new EamDbSettingsDialog(); - if (dialog.wasConfigurationChanged()) { - updateDatabase(); + boolean changed = invokeCrChoice(this, null); + if (changed) { load(); // reload db settings content and update buttons firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } @@ -446,6 +589,16 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void cbUseCentralRepoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseCentralRepoActionPerformed //if saved setting is disabled checkbox should be disabled already store(); + + // if moving to using CR, multi-user mode is disabled and selection is multiuser settings, set to disabled + if (cbUseCentralRepo.isSelected() && + !CentralRepoDbManager.isPostgresMultiuserAllowed() && + CentralRepoDbManager.getSavedDbChoice() == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) { + + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.DISABLED); + } + + updateDatabase(); load(); this.ingestStateUpdated(Case.isCaseOpen()); @@ -457,31 +610,35 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i public void load() { tbOops.setText(""); enableButtonSubComponents(false); - CentralRepoPlatforms selectedPlatform = CentralRepoPlatforms.getSelectedPlatform(); + CentralRepoDbChoice selectedChoice = CentralRepoDbManager.getSavedDbChoice(); cbUseCentralRepo.setSelected(CentralRepoDbUtil.allowUseOfCentralRepository()); // NON-NLS - switch (selectedPlatform) { - case POSTGRESQL: - PostgresCentralRepoSettings dbSettingsPg = new PostgresCentralRepoSettings(); - lbDbPlatformValue.setText(CentralRepoPlatforms.POSTGRESQL.toString()); - lbDbNameValue.setText(dbSettingsPg.getDbName()); - lbDbLocationValue.setText(dbSettingsPg.getHost()); - enableButtonSubComponents(cbUseCentralRepo.isSelected()); - break; - case SQLITE: + + lbDbPlatformValue.setText(selectedChoice.getTitle()); + CentralRepoPlatforms selectedDb = selectedChoice.getDbPlatform(); + + if (selectedChoice == null || selectedDb == CentralRepoPlatforms.DISABLED) { + lbDbNameValue.setText(""); + lbDbLocationValue.setText(""); + tbOops.setText(Bundle.GlobalSettingsPanel_validationerrMsg_mustConfigure()); + } + else { + enableButtonSubComponents(cbUseCentralRepo.isSelected()); + if (selectedDb == CentralRepoPlatforms.POSTGRESQL) { + try { + PostgresCentralRepoSettings dbSettingsPg = new PostgresCentralRepoSettings(); + lbDbNameValue.setText(dbSettingsPg.getDbName()); + lbDbLocationValue.setText(dbSettingsPg.getHost()); + } + catch (CentralRepoException e) { + logger.log(Level.WARNING, "Unable to load settings into global panel for postgres settings", e); + } + } + else if (selectedDb == CentralRepoPlatforms.SQLITE) { SqliteCentralRepoSettings dbSettingsSqlite = new SqliteCentralRepoSettings(); - lbDbPlatformValue.setText(CentralRepoPlatforms.SQLITE.toString()); lbDbNameValue.setText(dbSettingsSqlite.getDbName()); lbDbLocationValue.setText(dbSettingsSqlite.getDbDirectory()); - enableButtonSubComponents(cbUseCentralRepo.isSelected()); - break; - default: - lbDbPlatformValue.setText(CentralRepoPlatforms.DISABLED.toString()); - lbDbNameValue.setText(""); - lbDbLocationValue.setText(""); - tbOops.setText(Bundle.GlobalSettingsPanel_validationerrMsg_mustConfigure()); - break; + } } - } @Override @@ -490,12 +647,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i } /** - * Validates that the dialog/panel is filled out correctly for our usage. + * This method validates that the dialog/panel is filled out correctly for our usage. * - * @return true if it's okay, false otherwise. + * @return True if it is okay, false otherwise. */ public boolean valid() { - return !cbUseCentralRepo.isSelected() || !lbDbPlatformValue.getText().equals(DISABLED.toString()); + return !cbUseCentralRepo.isSelected() || !lbDbPlatformValue.getText().equals(CentralRepoDbChoice.DISABLED.toString()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java index 3dfaf53001..f5d9a5c641 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java @@ -343,6 +343,7 @@ public class CommandLineIngestManager { * @param baseCaseName Case name * @param rootOutputDirectory Full path to directory in which case * output folder will be created + * @param caseType Type of case being created * * @throws CaseActionException */ diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 13f3ef0710..737b6900d4 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -168,3 +168,7 @@ MediaPlayerPanel.playBackSpeedLabel.text=Speed: SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName SQLiteViewer.selectTable.errorText=Error getting row count for table: {0} +TextTranslatableComponent.setPanelContent.onSetContentError=Unable to display text at this time. +TextTranslatableComponent.setTranslated.onTranslateError=Unable to translate text at this time. +TranslatablePanel.comboBoxOption.originalText=Original Text +TranslatablePanel.comboBoxOption.translatedText=Translated Text diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index c518abdd45..53cf9e8bf4 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -241,7 +241,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { - if (progressSlider.getValueIsAdjusting()) { + if (progressSlider.getValueIsAdjusting() && gstPlayBin != null) { long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE; long newStartTime = (long) (relativePosition * duration); @@ -264,20 +264,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Manage the video while the user is performing actions on the track. progressSlider.addMouseListener(new MouseListener() { private State previousState = State.NULL; - + @Override public void mousePressed(MouseEvent e) { - previousState = gstPlayBin.getState(); - gstPlayBin.pause(); + if (gstPlayBin != null) { + previousState = gstPlayBin.getState(); + gstPlayBin.pause(); + } } @Override public void mouseReleased(MouseEvent e) { - if(previousState.equals(State.PLAYING)) { + if (previousState.equals(State.PLAYING) && gstPlayBin != null) { gstPlayBin.play(); } previousState = State.NULL; } + @Override public void mouseClicked(MouseEvent e) { } @@ -289,11 +292,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void mouseExited(MouseEvent e) { } - + }); //Manage the audio level when the user is adjusting the volume slider audioSlider.addChangeListener((ChangeEvent event) -> { - if (audioSlider.getValueIsAdjusting()) { + if (audioSlider.getValueIsAdjusting() && gstPlayBin != null) { double audioPercent = (audioSlider.getValue() * 2.0) / 100.0; gstPlayBin.setVolume(audioPercent); } @@ -327,11 +330,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - gstPlayBin.seek(ClockTime.ZERO); - /** - * Keep the video from automatically playing - */ - Gst.getExecutor().submit(() -> gstPlayBin.pause()); + if (gstPlayBin != null) { + gstPlayBin.seek(ClockTime.ZERO); + /** + * Keep the video from automatically playing + */ + Gst.getExecutor().submit(() -> gstPlayBin.pause()); + } } }; } @@ -556,10 +561,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Video is ready for playback. Create new components gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI()); //Configure event handling - Bus playBinBus = gstPlayBin.getBus(); - playBinBus.connect(endOfStreamListener); - playBinBus.connect(stateChangeListener); - playBinBus.connect(errorListener); + if (gstPlayBin != null) { + Bus playBinBus = gstPlayBin.getBus(); + playBinBus.connect(endOfStreamListener); + playBinBus.connect(stateChangeListener); + playBinBus.connect(errorListener); + } if (this.isCancelled()) { return; @@ -570,15 +577,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); videoPanel.add(fxPanel); fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel); - gstPlayBin.setVideoSink(fxAppSink); - + if (gstPlayBin != null) { + gstPlayBin.setVideoSink(fxAppSink); + } if (this.isCancelled()) { return; } - - gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); - gstPlayBin.pause(); - + if (gstPlayBin != null) { + gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); + gstPlayBin.pause(); + } timer.start(); enableComponents(true); } catch (CancellationException ex) { @@ -598,7 +606,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { - if (!progressSlider.getValueIsAdjusting()) { + if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) { sliderLock.acquireUninterruptibly(); long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); @@ -635,13 +643,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * thumb at the given width and height. It also paints the track blue as * the thumb progresses. * - * @param slider JSlider component + * @param slider JSlider component * @param thumbDimension */ public CircularJSliderUI(JSlider slider, Dimension thumbDimension) { super(slider); this.thumbDimension = thumbDimension; - + //Configure track and thumb colors. Color lightBlue = new Color(0, 130, 255); thumbColor = lightBlue; @@ -655,8 +663,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } /** - * Modifies the View to be an oval rather than the underlying - * rectangle Controller. + * Modifies the View to be an oval rather than the underlying rectangle + * Controller. */ @Override public void paintThumb(Graphics graphic) { @@ -705,12 +713,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override protected TrackListener createTrackListener(JSlider slider) { /** - * This track listener will force the thumb to be snapped to the mouse - * location. This makes grabbing and dragging the JSlider much easier. - * Using the default track listener, the user would have to click - * exactly on the slider thumb to drag it. Now the thumb positions - * itself under the mouse so that it can always be dragged. - */ + * This track listener will force the thumb to be snapped to the + * mouse location. This makes grabbing and dragging the JSlider much + * easier. Using the default track listener, the user would have to + * click exactly on the slider thumb to drag it. Now the thumb + * positions itself under the mouse so that it can always be + * dragged. + */ return new TrackListener() { @Override public void mousePressed(MouseEvent e) { @@ -982,87 +991,95 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie }// //GEN-END:initComponents private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Skip 30 seconds. - long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Ensure new video position is within bounds - long newTime = Math.max(currentTime - rewindDelta, 0); - double playBackRate = getPlayBackRate(); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, newTime, - //Do nothing for the end position - SeekType.NONE, -1); - }//GEN-LAST:event_rewindButtonActionPerformed - - private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Skip 30 seconds. - long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Don't allow skipping within 2 seconds of video ending. Skipping right to - //the end causes undefined behavior for some gstreamer plugins. - long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS); - if((duration - currentTime) <= twoSecondsInNano) { - return; - } - - long newTime; - if (currentTime + fastForwardDelta >= duration) { - //If there are less than 30 seconds left, only fast forward to the midpoint. - newTime = currentTime + (duration - currentTime)/2; - } else { - newTime = currentTime + fastForwardDelta; - } - - double playBackRate = getPlayBackRate(); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, newTime, - //Do nothing for the end position - SeekType.NONE, -1); - }//GEN-LAST:event_fastForwardButtonActionPerformed - - private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed - if (gstPlayBin.isPlaying()) { - gstPlayBin.pause(); - } else { - double playBackRate = getPlayBackRate(); + if (gstPlayBin != null) { long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Set playback rate before play. + //Skip 30 seconds. + long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Ensure new video position is within bounds + long newTime = Math.max(currentTime - rewindDelta, 0); + double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline //ACCURATE - video will seek exactly to the position requested EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), //Set the start position to newTime - SeekType.SET, currentTime, + SeekType.SET, newTime, //Do nothing for the end position SeekType.NONE, -1); - gstPlayBin.play(); + } + }//GEN-LAST:event_rewindButtonActionPerformed + + private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed + if (gstPlayBin != null) { + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Don't allow skipping within 2 seconds of video ending. Skipping right to + //the end causes undefined behavior for some gstreamer plugins. + long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS); + if ((duration - currentTime) <= twoSecondsInNano) { + return; + } + + long newTime; + if (currentTime + fastForwardDelta >= duration) { + //If there are less than 30 seconds left, only fast forward to the midpoint. + newTime = currentTime + (duration - currentTime) / 2; + } else { + newTime = currentTime + fastForwardDelta; + } + + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); + } + }//GEN-LAST:event_fastForwardButtonActionPerformed + + private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed + if (gstPlayBin != null) { + if (gstPlayBin.isPlaying()) { + gstPlayBin.pause(); + } else { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Set playback rate before play. + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, currentTime, + //Do nothing for the end position + SeekType.NONE, -1); + gstPlayBin.play(); + } } }//GEN-LAST:event_playButtonActionPerformed private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed - double playBackRate = getPlayBackRate(); - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the position to the currentTime, we are only adjusting the - //playback rate. - SeekType.SET, currentTime, - SeekType.NONE, 0); + if (gstPlayBin != null) { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the position to the currentTime, we are only adjusting the + //playback rate. + SeekType.SET, currentTime, + SeekType.NONE, 0); + } }//GEN-LAST:event_playBackSpeedComboBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form index cf38160cd6..c8ff9674c7 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.form @@ -233,33 +233,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index cadd311f70..8ea3867470 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.datamodel.AttachmentNode; import com.google.gson.Gson; import java.awt.Color; import java.awt.Component; +import java.awt.ComponentOrientation; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.logging.Level; +import javax.swing.JScrollPane; import javax.swing.text.JTextComponent; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; @@ -41,7 +43,9 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.contentviewers.TranslatablePanel.TranslatablePanelException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.corecomponents.AutoWrappingJTextPane; import org.sleuthkit.autopsy.corecomponents.DataResultPanel; import org.sleuthkit.autopsy.corecomponents.TableFilterNode; import org.sleuthkit.autopsy.coreutils.Logger; @@ -84,6 +88,38 @@ import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.URL @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public class MessageContentViewer extends javax.swing.JPanel implements DataContentViewer { + /** + * This is a text component viewer to be a child component to be placed in a {@link TranslatablePanel TranslatablePanel}. + */ + class TextComponent implements TranslatablePanel.ContentComponent { + + private final Component rootComponent; + private final AutoWrappingJTextPane childTextComponent; + + TextComponent() { + childTextComponent = new AutoWrappingJTextPane(); + childTextComponent.setEditable(false); + + JScrollPane parentComponent = new JScrollPane(); + parentComponent.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + parentComponent.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + parentComponent.setViewportView(childTextComponent); + rootComponent = parentComponent; + } + + @Override + public Component getRootComponent() { + return rootComponent; + } + + @Override + public void setContent(String content, ComponentOrientation orientation) throws TranslatablePanelException { + childTextComponent.setText(content == null ? "" : content); + childTextComponent.setComponentOrientation(orientation); + childTextComponent.setCaretPosition(0); + } + } + private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(MessageContentViewer.class.getName()); private static final BlackboardAttribute.Type TSK_ASSOCIATED_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT); @@ -96,6 +132,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont private final List textAreas; private final org.sleuthkit.autopsy.contentviewers.HtmlPanel htmlPanel = new org.sleuthkit.autopsy.contentviewers.HtmlPanel(); + private final TranslatablePanel textPanel = new TranslatablePanel(new TextComponent()); /** * Artifact currently being displayed */ @@ -113,13 +150,21 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont envelopePanel.setBackground(new Color(0, 0, 0, 38)); drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null); attachmentsScrollPane.setViewportView(drp); + + msgbodyTabbedPane.insertTab( + NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle"), + null, + textPanel, + null, + TEXT_TAB_INDEX); + msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); /* * HTML tab uses the HtmlPanel instead of an internal text pane, so we * use 'null' for that index. */ - textAreas = Arrays.asList(headersTextArea, textbodyTextArea, null, rtfbodyTextPane); + textAreas = Arrays.asList(headersTextArea, null, null, rtfbodyTextPane); Utilities.configureTextPaneAsRtf(rtfbodyTextPane); resetComponent(); @@ -159,8 +204,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont msgbodyTabbedPane = new javax.swing.JTabbedPane(); headersScrollPane = new javax.swing.JScrollPane(); headersTextArea = new javax.swing.JTextArea(); - textbodyScrollPane = new javax.swing.JScrollPane(); - textbodyTextArea = new javax.swing.JTextArea(); htmlPane = new javax.swing.JPanel(); rtfbodyScrollPane = new javax.swing.JScrollPane(); rtfbodyTextPane = new javax.swing.JTextPane(); @@ -263,17 +306,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.headersScrollPane.TabConstraints.tabTitle"), headersScrollPane); // NOI18N - textbodyScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - textbodyScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - textbodyTextArea.setEditable(false); - textbodyTextArea.setLineWrap(true); - textbodyTextArea.setRows(5); - textbodyTextArea.setWrapStyleWord(true); - textbodyScrollPane.setViewportView(textbodyTextArea); - - msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.textbodyScrollPane.TabConstraints.tabTitle"), textbodyScrollPane); // NOI18N - htmlPane.setLayout(new java.awt.BorderLayout()); msgbodyTabbedPane.addTab(org.openide.util.NbBundle.getMessage(MessageContentViewer.class, "MessageContentViewer.htmlPane.TabConstraints.tabTitle"), htmlPane); // NOI18N @@ -335,6 +367,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont .addComponent(msgbodyTabbedPane) .addGap(5, 5, 5)) ); + + msgbodyTabbedPane.getAccessibleContext().setAccessibleParent(null); }// //GEN-END:initComponents private void viewInNewWindowButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewInNewWindowButtonActionPerformed @@ -360,8 +394,6 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont private javax.swing.JTextPane rtfbodyTextPane; private javax.swing.JLabel subjectLabel; private javax.swing.JLabel subjectText; - private javax.swing.JScrollPane textbodyScrollPane; - private javax.swing.JTextArea textbodyTextArea; private javax.swing.JLabel toLabel; private javax.swing.JLabel toText; private javax.swing.JButton viewInNewWindowButton; @@ -407,11 +439,11 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont * Get the artifact associated with the given artifact, if there is one. * * @param artifact The artifact to get the associated artifact from. Must - * not be null + * not be null * * @throws TskCoreException If there is a critical error querying the DB. * @return An optional containing the artifact associated with the given - * artifact, if there is one. + * artifact, if there is one. */ private static Optional getAssociatedArtifact(final BlackboardArtifact artifact) throws TskCoreException { BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_TYPE); @@ -462,7 +494,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont headersTextArea.setText(""); rtfbodyTextPane.setText(""); htmlPanel.reset(); - textbodyTextArea.setText(""); + textPanel.reset(); msgbodyTabbedPane.setEnabled(false); drp.setNode(null); } @@ -491,10 +523,10 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont * Is the given artifact one that can be shown in this viewer? * * @param nodeArtifact An artifact that might be a message. Must not be - * null. + * null. * * @return True if the given artifact can be shown as a message in this - * viewer. + * viewer. */ private static boolean isMessageArtifact(BlackboardArtifact nodeArtifact) { final int artifactTypeID = nodeArtifact.getArtifactTypeID(); @@ -540,14 +572,14 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont return nodeArtifact; } - + @Override public int isPreferred(Node node) { // For message artifacts this is a high priority viewer, // but for attachment files, this a lower priority vewer. if (isSupported(node)) { BlackboardArtifact nodeArtifact = node.getLookup().lookup(BlackboardArtifact.class); - if (nodeArtifact != null) { + if (nodeArtifact != null) { return 7; } else { return 1; @@ -560,7 +592,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont * Configure the text area at the given index to show the content of the * given type. * - * @param type The ATTRIBUT_TYPE to show in the indexed tab. + * @param type The ATTRIBUT_TYPE to show in the indexed tab. * @param index The index of the text area to configure. * * @throws TskCoreException @@ -570,6 +602,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont if (index == HTML_TAB_INDEX && StringUtils.isNotBlank(attributeText)) { htmlPanel.setHtmlText(attributeText); + } else if (index == TEXT_TAB_INDEX && StringUtils.isNotBlank(attributeText)) { + textPanel.setContent(attributeText, artifact.toString()); } else { JTextComponent textComponent = textAreas.get(index); if (textComponent != null) { @@ -595,34 +629,34 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont } private void configureAttachments() throws TskCoreException { - + final Set attachments; - + // Attachments are specified in an attribute TSK_ATTACHMENTS as JSON attribute BlackboardAttribute attachmentsAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS)); - if(attachmentsAttr != null) { - + if (attachmentsAttr != null) { + attachments = new HashSet<>(); - String jsonVal = attachmentsAttr.getValueString(); + String jsonVal = attachmentsAttr.getValueString(); MessageAttachments msgAttachments = new Gson().fromJson(jsonVal, MessageAttachments.class); - + Collection fileAttachments = msgAttachments.getFileAttachments(); - for (FileAttachment fileAttachment: fileAttachments) { + for (FileAttachment fileAttachment : fileAttachments) { attachments.add(fileAttachment); } Collection urlAttachments = msgAttachments.getUrlAttachments(); - for (URLAttachment urlAttachment: urlAttachments) { + for (URLAttachment urlAttachment : urlAttachments) { attachments.add(urlAttachment); } } else { // For backward compatibility - email attachements are derived files and children of the email message artifact - attachments = new HashSet<>(); - for (Content child: artifact.getChildren()) { - if (child instanceof AbstractFile) { - attachments.add(new FileAttachment((AbstractFile)child)); - } + attachments = new HashSet<>(); + for (Content child : artifact.getChildren()) { + if (child instanceof AbstractFile) { + attachments.add(new FileAttachment((AbstractFile) child)); } + } } - + final int numberOfAttachments = attachments.size(); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, numberOfAttachments > 0); @@ -710,9 +744,8 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont return doc.html(); } - /** - * Creates child nodes for message attachments. + * Creates child nodes for message attachments. */ private static class AttachmentsChildren extends Children.Keys { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.form new file mode 100644 index 0000000000..cd272658bf --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.form @@ -0,0 +1,97 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.java new file mode 100644 index 0000000000..4f2508be28 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/TranslatablePanel.java @@ -0,0 +1,434 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.contentviewers; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationException; +import org.sleuthkit.autopsy.texttranslation.ui.TranslateTextTask; + +/** + * This is a panel for translation with a subcomponent that allows for translation. + */ +class TranslatablePanel extends JPanel { + + /** + * This is an exception that can occur during the normal operation of the translatable + * panel. For instance, this exception can be thrown if it is not possible to set the child + * content to the provided content string. + */ + class TranslatablePanelException extends Exception { + public static final long serialVersionUID = 1L; + + TranslatablePanelException(String message) { + super(message); + } + + TranslatablePanelException(String message, Throwable cause) { + super(message, cause); + } + } + + + /** + * This describes a child component to be placed as a child of this panel. The child received + * from {@link #getRootComponent() getRootComponent() } will listen for content updates from setContent(). + */ + interface ContentComponent { + /** + * This method gets root component of the translation panel. + * @return the root component to insert into the translatable panel + */ + Component getRootComponent(); + + /** + * This method sets the content of the component to the provided content. + * @param content the content to be displayed + * @param orientation how it should be displayed + * @throws Exception if there is an error in rendering the content + */ + void setContent(String content, ComponentOrientation orientation) throws TranslatablePanelException; + } + + + /** + * This is an option in drop down of whether or not to translate. + */ + private static class TranslateOption { + + private final String text; + private final boolean translate; + + TranslateOption(String text, boolean translate) { + this.text = text; + this.translate = translate; + } + + String getText() { + return text; + } + + @Override + public String toString() { + return text; + } + + boolean shouldTranslate() { + return translate; + } + } + + /** + * This represents the cached result of translating the current content. + */ + private static class TranslatedText { + + private final String text; + private final ComponentOrientation orientation; + + TranslatedText(String text, ComponentOrientation orientation) { + this.text = text; + this.orientation = orientation; + } + + String getText() { + return text; + } + + ComponentOrientation getOrientation() { + return orientation; + } + } + + /** + * This connects the swing worker specified by + * {@link org.sleuthkit.autopsy.texttranslation.ui.TranslateTextTask TranslateTextTask} to this component. + */ + private class OnTranslation extends TranslateTextTask { + + OnTranslation() { + super(true, contentDescriptor == null ? "" : contentDescriptor); + } + + @Override + protected String translate(String input) throws NoServiceProviderException, TranslationException { + // This defers to the outer class method so that it can be overridden for items like html, rtf, etc. + return retrieveTranslation(input); + } + + @Override + protected void onProgressDisplay(String text, ComponentOrientation orientation, int font) { + setStatus(text, false); + } + + @Override + protected void onErrorDisplay(String text, ComponentOrientation orientation, int font) { + setStatus(text, true); + } + + @Override + protected String retrieveText() throws IOException, InterruptedException, IllegalStateException { + return content == null ? "" : content; + } + + @Override + protected void onTextDisplay(String text, ComponentOrientation orientation, int font) { + // On successful acquire, this caches the result and set the text. + setCachedTranslated(new TranslatedText(text, orientation)); + setChildComponentContent(text, orientation); + + // This clears any status that may be present. + clearStatus(); + } + } + + private static final long serialVersionUID = 1L; + private static final ComponentOrientation DEFAULT_ORIENTATION = ComponentOrientation.LEFT_TO_RIGHT; + + private final ImageIcon warningIcon = new ImageIcon(TranslatablePanel.class.getResource("/org/sleuthkit/autopsy/images/warning16.png")); + + private final ContentComponent contentComponent; + private final TextTranslationService translationService; + private final ThreadFactory translationThreadFactory = new ThreadFactoryBuilder().setNameFormat("translatable-panel-%d").build(); + private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory); + + private final Object cachedTranslatedLock = new Object(); + private final Object backgroundTaskLock = new Object(); + + private String content; + private String contentDescriptor; + private boolean prevTranslateSelection; + + private volatile TranslatedText cachedTranslated; + private volatile OnTranslation backgroundTask = null; + + @Messages({"TranslatablePanel.comboBoxOption.originalText=Original Text", + "TranslatablePanel.comboBoxOption.translatedText=Translated Text"}) + TranslatablePanel(ContentComponent contentComponent) { + this( + contentComponent, + Bundle.TranslatablePanel_comboBoxOption_originalText(), + Bundle.TranslatablePanel_comboBoxOption_translatedText(), + null, + TextTranslationService.getInstance()); + } + + /** + * This creates a new panel using @{link ContentPanel ContentPanel} as a child. + */ + TranslatablePanel(ContentComponent contentComponent, String origOptionText, String translatedOptionText, String origContent, + TextTranslationService translationService) { + this.contentComponent = contentComponent; + this.translationService = translationService; + + initComponents(); + additionalInit(contentComponent.getRootComponent(), origOptionText, translatedOptionText); + reset(); + } + + + + /** + * @return the cached translated text or returns null + */ + private TranslatedText getCachedTranslated() { + synchronized (cachedTranslatedLock) { + return cachedTranslated; + } + } + + /** + * @param translated the translated text to be cached + */ + private void setCachedTranslated(TranslatedText translated) { + synchronized (cachedTranslatedLock) { + this.cachedTranslated = translated; + } + } + + /** + * If a translation worker is running, this is called to cancel the worker. + */ + private void cancelPendingTranslation() { + synchronized (backgroundTaskLock) { + if (backgroundTask != null && !backgroundTask.isDone()) { + backgroundTask.cancel(true); + } + backgroundTask = null; + } + } + + /** + * This runs a translation worker to translate the text. + */ + private void runTranslationTask() { + synchronized (backgroundTaskLock) { + cancelPendingTranslation(); + backgroundTask = new OnTranslation(); + + //Pass the background task to a single threaded pool to keep + //the number of jobs running to one. + executorService.execute(backgroundTask); + } + } + + /** + * This resets the component to an empty state and sets the translation bar visibility + * based on whether there is a provider. + */ + final void reset() { + setContent(null, null); + } + + /** + * This method sets the content for the component; this also clears the status. + * @param content the content for the panel + * @param contentDescriptor the content descriptor to be used in error messages + */ + void setContent(String content, String contentDescriptor) { + cancelPendingTranslation(); + setTranslationEnabled(); + this.translateComboBox.setSelectedIndex(0); + this.prevTranslateSelection = false; + this.content = content; + this.contentDescriptor = contentDescriptor; + clearStatus(); + setCachedTranslated(null); + setChildComponentContent(content); + } + + /** + * This is where actual translation takes place allowed to be overridden for the + * sake of varying translatable content (i.e. html, rtf, etc). + * + * @param input the input content + * @return the result of translation + * @throws TranslationException + * @throws NoServiceProviderException + */ + protected String retrieveTranslation(String input) throws TranslationException, NoServiceProviderException { + return translationService.translate(input); + } + + /** + * This method clears the status bar. + */ + private void clearStatus() { + setStatus(null, false); + } + + /** + * This sets the status bar message. + * @param msg the status bar message to show + * @param showWarningIcon whether that status is a warning + */ + private synchronized void setStatus(String msg, boolean showWarningIcon) { + statusLabel.setText(msg); + statusLabel.setIcon(showWarningIcon ? warningIcon : null); + } + + /** + * This method sets the translation bar visibility based on whether or not there is a provided. + */ + private void setTranslationEnabled() { + translateComboBox.setEnabled(this.translationService.hasProvider()); + } + + /** + * The child component provided in the constructor will have its content set to the string provided. + * @param content the content to display in the child component + */ + private void setChildComponentContent(String content) { + setChildComponentContent(content, DEFAULT_ORIENTATION); + } + + /** + * The child component provided in the constructor will have its content set to the string provided. + * @param content the content to display in the child component + * @param orientation the orientation for the text + */ + @Messages({"# {0} - exception message", "TranslatablePanel.onSetContentError.text=There was an error displaying the text: {0}"}) + private synchronized void setChildComponentContent(String content, ComponentOrientation orientation) { + SwingUtilities.invokeLater(() -> { + try { + contentComponent.setContent(content, orientation); + } catch (TranslatablePanelException ex) { + setStatus(Bundle.TranslatablePanel_onSetContentError_text(ex.getMessage()), true); + } + }); + } + + /** + * This method is for items that are programmatically initialized. + */ + private void additionalInit(Component rootComponent, String origOptionText, String translatedOptionText) { + add(rootComponent, java.awt.BorderLayout.CENTER); + translateComboBox.removeAllItems(); + translateComboBox.addItem(new TranslateOption(origOptionText, false)); + translateComboBox.addItem(new TranslateOption(translatedOptionText, true)); + } + + /** + * When the combo box choice is selected, this method is fired. + * @param translateOption the current translate option + */ + private void handleComboBoxChange(TranslateOption translateOption) { + boolean curTranslateSelection = translateOption.shouldTranslate(); + if (curTranslateSelection != this.prevTranslateSelection) { + this.prevTranslateSelection = curTranslateSelection; + + cancelPendingTranslation(); + clearStatus(); + + if (curTranslateSelection) { + TranslatedText translated = getCachedTranslated(); + if (translated != null) { + setChildComponentContent(translated.getText(), translated.getOrientation()); + } else { + runTranslationTask(); + } + } else { + setChildComponentContent(content); + } + } + } + + /** + * 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() { + + translationBar = new javax.swing.JPanel(); + translateComboBox = new javax.swing.JComboBox<>(); + statusLabel = new javax.swing.JLabel(); + + setMaximumSize(new java.awt.Dimension(2000, 2000)); + setMinimumSize(new java.awt.Dimension(2, 2)); + setName(""); // NOI18N + setPreferredSize(new java.awt.Dimension(100, 58)); + setVerifyInputWhenFocusTarget(false); + setLayout(new java.awt.BorderLayout()); + + translationBar.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + translationBar.setMaximumSize(new java.awt.Dimension(182, 24)); + translationBar.setPreferredSize(new java.awt.Dimension(182, 24)); + translationBar.setLayout(new java.awt.BorderLayout()); + + translateComboBox.setMaximumSize(new java.awt.Dimension(200, 20)); + translateComboBox.setMinimumSize(new java.awt.Dimension(200, 20)); + translateComboBox.setName(""); // NOI18N + translateComboBox.setPreferredSize(new java.awt.Dimension(200, 20)); + translateComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + translateComboBoxActionPerformed(evt); + } + }); + translationBar.add(translateComboBox, java.awt.BorderLayout.LINE_END); + + statusLabel.setMaximumSize(new java.awt.Dimension(32767, 32767)); + translationBar.add(statusLabel, java.awt.BorderLayout.CENTER); + + add(translationBar, java.awt.BorderLayout.NORTH); + }// //GEN-END:initComponents + + private void translateComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_translateComboBoxActionPerformed + handleComboBoxChange((TranslateOption) translateComboBox.getSelectedItem()); + }//GEN-LAST:event_translateComboBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel statusLabel; + private javax.swing.JComboBox translateComboBox; + private javax.swing.JPanel translationBar; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties index efe92bd0f6..45a1603c17 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties @@ -1,4 +1,9 @@ -ContextViewer.jSourceGoToResultButton.text=Go to Result -ContextViewer.jSourceTextLabel.text=jLabel2 -ContextViewer.jSourceNameLabel.text=jSourceNameLabel -ContextViewer.jSourceLabel.text=Source +ContextUsagePanel.jUsageGoToResultButton.text=Go to Result +ContextSourcePanel.jSourceGoToResultButton.text=Go to Result +ContextUsagePanel.jUsageTextLabel.text=Label2 +ContextUsagePanel.jUsageNameLabel.text=jUsageLabel +ContextSourcePanel.jSourceTextLabel.text=Label2 +ContextSourcePanel.jSourceNameLabel.text=jSourceNameLabel +ContextViewer.jSourceLabel.text=Usage +ContextViewer.jUsageLabel.text=Source +ContextViewer.jUnknownLabel.text=Unknown diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED index b29e7190fd..c8cebe69fc 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties-MERGED @@ -1,18 +1,24 @@ +ContextUsagePanel.jUsageGoToResultButton.text=Go to Result +ContextSourcePanel.jSourceGoToResultButton.text=Go to Result +ContextUsagePanel.jUsageTextLabel.text=Label2 +ContextUsagePanel.jUsageNameLabel.text=jUsageLabel +ContextSourcePanel.jSourceTextLabel.text=Label2 +ContextSourcePanel.jSourceNameLabel.text=jSourceNameLabel ContextViewer.attachmentSource=Attached to: ContextViewer.downloadedOn=On ContextViewer.downloadSource=Downloaded from: ContextViewer.downloadURL=URL ContextViewer.email=Email -ContextViewer.file=File -ContextViewer.jSourceGoToResultButton.text=Go to Result -ContextViewer.jSourceTextLabel.text=jLabel2 -ContextViewer.jSourceNameLabel.text=jSourceNameLabel -ContextViewer.jSourceLabel.text=Source +ContextViewer.jSourceLabel.text=Usage +ContextViewer.jUsageLabel.text=Source +ContextViewer.jUnknownLabel.text=Unknown ContextViewer.message=Message ContextViewer.messageFrom=From ContextViewer.messageOn=On ContextViewer.messageTo=To -ContextViewer.on=On +ContextViewer.on=Opened at ContextViewer.recentDocs=Recent Documents: ContextViewer.title=Context ContextViewer.toolTip=Displays context for selected file. +ContextViewer.unknown=Opened at unknown time +ContextViewer.unknownSource=Unknown diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form new file mode 100644 index 0000000000..c57a0990a8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.form @@ -0,0 +1,82 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java new file mode 100644 index 0000000000..284a66cf04 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextSourcePanel.java @@ -0,0 +1,154 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.contentviewers.contextviewer; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; + +/** + * Displays additional context for the selected file, such as its source, and + * usage, if known. + * + */ +public final class ContextSourcePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + // defines a list of artifacts that provide context for a file + private static final List SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); + static { + SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); + } + + private final BlackboardArtifact sourceContextArtifact; + + /** + * Creates new form ContextViewer + */ + public ContextSourcePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { + + initComponents(); + sourceContextArtifact = associatedArtifact; + setSourceName(sourceName); + setSourceText(sourceText); + } + + /** + * 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() { + + jSourceGoToResultButton = new javax.swing.JButton(); + jSourceNameLabel = new javax.swing.JLabel(); + jSourceTextLabel = new javax.swing.JLabel(); + + setBackground(new java.awt.Color(255, 255, 255)); + setPreferredSize(new java.awt.Dimension(495, 75)); + + org.openide.awt.Mnemonics.setLocalizedText(jSourceGoToResultButton, org.openide.util.NbBundle.getMessage(ContextSourcePanel.class, "ContextSourcePanel.jSourceGoToResultButton.text")); // NOI18N + jSourceGoToResultButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jSourceGoToResultButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jSourceNameLabel, org.openide.util.NbBundle.getMessage(ContextSourcePanel.class, "ContextSourcePanel.jSourceNameLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jSourceTextLabel, org.openide.util.NbBundle.getMessage(ContextSourcePanel.class, "ContextSourcePanel.jSourceTextLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(50, 50, 50) + .addComponent(jSourceNameLabel) + .addGap(36, 36, 36) + .addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(260, 260, 260)) + .addGroup(layout.createSequentialGroup() + .addGap(90, 90, 90) + .addComponent(jSourceGoToResultButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(2, 2, 2) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jSourceNameLabel) + .addComponent(jSourceTextLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSourceGoToResultButton) + .addGap(0, 0, 0)) + ); + }// //GEN-END:initComponents + + private void jSourceGoToResultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jSourceGoToResultButtonActionPerformed + + final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance(); + + // Navigate to the source context artifact. + if (sourceContextArtifact != null) { + dtc.viewArtifact(sourceContextArtifact); + } + }//GEN-LAST:event_jSourceGoToResultButtonActionPerformed + + /** + * Sets the source label string. + * + * @param nameLabel String value for source label. + */ + private void setSourceName(String nameLabel) { + jSourceNameLabel.setText(nameLabel); + } + + /** + * Sets the source text string. + * + * @param text String value for source text. + */ + private void setSourceText(String text) { + jSourceTextLabel.setText(text); + showSourceButton(!text.isEmpty()); + showSourceText(true); + } + + private void showSourceText(boolean show) { + jSourceTextLabel.setVisible(show); + } + + private void showSourceButton(boolean show) { + jSourceGoToResultButton.setVisible(show); + jSourceGoToResultButton.setEnabled(show); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jSourceGoToResultButton; + private javax.swing.JLabel jSourceNameLabel; + private javax.swing.JLabel jSourceTextLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form new file mode 100644 index 0000000000..a3e2a90f84 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.form @@ -0,0 +1,81 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java new file mode 100644 index 0000000000..de87d7a5e5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextUsagePanel.java @@ -0,0 +1,152 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.contentviewers.contextviewer; + +import java.util.ArrayList; +import java.util.List; +import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; + +/** + * Displays additional context for the selected file, such as its source, and + * usage, if known. + * + */ +public final class ContextUsagePanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + // defines a list of artifacts that provide context for a file + private static final List SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); + static { + SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); + } + + private final BlackboardArtifact sourceContextArtifact; + + /** + * Creates new form ContextViewer + */ + public ContextUsagePanel(String sourceName, String sourceText, BlackboardArtifact associatedArtifact) { + + initComponents(); + sourceContextArtifact = associatedArtifact; + setUsageName(sourceName); + setUsageText(sourceText); + } + + /** + * 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() { + + jUsageGoToResultButton = new javax.swing.JButton(); + jUsageNameLabel = new javax.swing.JLabel(); + jUsageTextLabel = new javax.swing.JLabel(); + + setBackground(new java.awt.Color(255, 255, 255)); + setPreferredSize(new java.awt.Dimension(495, 75)); + + org.openide.awt.Mnemonics.setLocalizedText(jUsageGoToResultButton, org.openide.util.NbBundle.getMessage(ContextUsagePanel.class, "ContextUsagePanel.jUsageGoToResultButton.text")); // NOI18N + jUsageGoToResultButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jUsageGoToResultButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jUsageNameLabel, org.openide.util.NbBundle.getMessage(ContextUsagePanel.class, "ContextUsagePanel.jUsageNameLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jUsageTextLabel, org.openide.util.NbBundle.getMessage(ContextUsagePanel.class, "ContextUsagePanel.jUsageTextLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(50, 50, 50) + .addComponent(jUsageNameLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jUsageTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 240, Short.MAX_VALUE) + .addGap(36, 36, 36)) + .addGroup(layout.createSequentialGroup() + .addGap(90, 90, 90) + .addComponent(jUsageGoToResultButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(2, 2, 2) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jUsageTextLabel) + .addComponent(jUsageNameLabel, javax.swing.GroupLayout.Alignment.TRAILING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jUsageGoToResultButton)) + ); + }// //GEN-END:initComponents + + private void jUsageGoToResultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jUsageGoToResultButtonActionPerformed + final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance(); + + // Navigate to the source context artifact. + if (sourceContextArtifact != null) { + dtc.viewArtifact(sourceContextArtifact); + } + }//GEN-LAST:event_jUsageGoToResultButtonActionPerformed + + /** + * Sets the usage label string. + * + * @param nameLabel String value for usage label. + */ + private void setUsageName(String nameLabel) { + jUsageNameLabel.setText(nameLabel); + } + + /** + * Sets the Usage text string. + * + * @param text String value for Usage text. + */ + private void setUsageText(String text) { + jUsageTextLabel.setText(text); + showUsageButton(!text.isEmpty()); + showUsageText(true); + } + + private void showUsageText(boolean show) { + jUsageTextLabel.setVisible(show); + } + + private void showUsageButton(boolean show) { + jUsageGoToResultButton.setVisible(show); + jUsageGoToResultButton.setEnabled(show); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jUsageGoToResultButton; + private javax.swing.JLabel jUsageNameLabel; + private javax.swing.JLabel jUsageTextLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form index f7e70919c0..42c7b19f1a 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.form @@ -1,10 +1,144 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -21,80 +155,24 @@ - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java index 1baa412495..f6d7e8f366 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/contextviewer/ContextViewer.java @@ -24,6 +24,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import javax.swing.BoxLayout; +import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED; import org.apache.commons.lang.StringUtils; import org.openide.nodes.Node; import org.openide.util.NbBundle; @@ -32,7 +34,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.directorytree.DirectoryTreeTopComponent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT; @@ -55,19 +56,20 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte // defines a list of artifacts that provide context for a file private static final List SOURCE_CONTEXT_ARTIFACTS = new ArrayList<>(); + private final List contextSourcePanels = new ArrayList<>(); + private final List contextUsagePanels = new ArrayList<>(); static { SOURCE_CONTEXT_ARTIFACTS.add(TSK_ASSOCIATED_OBJECT); } - private BlackboardArtifact sourceContextArtifact; - /** * Creates new form ContextViewer */ public ContextViewer() { initComponents(); + jScrollPane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); } /** @@ -79,73 +81,96 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte // //GEN-BEGIN:initComponents private void initComponents() { - jSourceGoToResultButton = new javax.swing.JButton(); - jSourceLabel = new javax.swing.JLabel(); - jSourceNameLabel = new javax.swing.JLabel(); - jSourceTextLabel = new javax.swing.JLabel(); + jSourcePanel = new javax.swing.JPanel(); + javax.swing.JLabel jSourceLabel = new javax.swing.JLabel(); + jUsagePanel = new javax.swing.JPanel(); + javax.swing.JLabel jUsageLabel = new javax.swing.JLabel(); + jUnknownPanel = new javax.swing.JPanel(); + javax.swing.JLabel jUnknownLabel = new javax.swing.JLabel(); + jScrollPane = new javax.swing.JScrollPane(); - setBackground(new java.awt.Color(255, 255, 255)); - - org.openide.awt.Mnemonics.setLocalizedText(jSourceGoToResultButton, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceGoToResultButton.text")); // NOI18N - jSourceGoToResultButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jSourceGoToResultButtonActionPerformed(evt); - } - }); + jSourcePanel.setBackground(javax.swing.UIManager.getDefaults().getColor("window")); jSourceLabel.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jSourceLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(jSourceNameLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceNameLabel.text")); // NOI18N + javax.swing.GroupLayout jSourcePanelLayout = new javax.swing.GroupLayout(jSourcePanel); + jSourcePanel.setLayout(jSourcePanelLayout); + jSourcePanelLayout.setHorizontalGroup( + jSourcePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jSourcePanelLayout.createSequentialGroup() + .addGap(40, 40, 40) + .addComponent(jSourceLabel) + .addContainerGap(304, Short.MAX_VALUE)) + ); + jSourcePanelLayout.setVerticalGroup( + jSourcePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jSourcePanelLayout.createSequentialGroup() + .addGap(5, 5, 5) + .addComponent(jSourceLabel) + .addGap(2, 2, 2)) + ); - org.openide.awt.Mnemonics.setLocalizedText(jSourceTextLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceTextLabel.text")); // NOI18N + jUsagePanel.setBackground(javax.swing.UIManager.getDefaults().getColor("window")); + + jUsageLabel.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jUsageLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageLabel.text")); // NOI18N + + javax.swing.GroupLayout jUsagePanelLayout = new javax.swing.GroupLayout(jUsagePanel); + jUsagePanel.setLayout(jUsagePanelLayout); + jUsagePanelLayout.setHorizontalGroup( + jUsagePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jUsagePanelLayout.createSequentialGroup() + .addGap(40, 40, 40) + .addComponent(jUsageLabel) + .addContainerGap(298, Short.MAX_VALUE)) + ); + jUsagePanelLayout.setVerticalGroup( + jUsagePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jUsagePanelLayout.createSequentialGroup() + .addGap(2, 2, 2) + .addComponent(jUsageLabel) + .addGap(2, 2, 2)) + ); + + jUnknownPanel.setBackground(new java.awt.Color(255, 255, 255)); + + org.openide.awt.Mnemonics.setLocalizedText(jUnknownLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUnknownLabel.text")); // NOI18N + + javax.swing.GroupLayout jUnknownPanelLayout = new javax.swing.GroupLayout(jUnknownPanel); + jUnknownPanel.setLayout(jUnknownPanelLayout); + jUnknownPanelLayout.setHorizontalGroup( + jUnknownPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jUnknownPanelLayout.createSequentialGroup() + .addGap(50, 50, 50) + .addComponent(jUnknownLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jUnknownPanelLayout.setVerticalGroup( + jUnknownPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jUnknownPanelLayout.createSequentialGroup() + .addGap(2, 2, 2) + .addComponent(jUnknownLabel) + .addGap(2, 2, 2)) + ); + + setBackground(new java.awt.Color(255, 255, 255)); + setPreferredSize(new java.awt.Dimension(495, 358)); + + jScrollPane.setBackground(new java.awt.Color(255, 255, 255)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSourceLabel) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(jSourceNameLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE))) - .addGap(36, 36, 36)) - .addGroup(layout.createSequentialGroup() - .addComponent(jSourceGoToResultButton) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addComponent(jScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 509, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(jSourceLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jSourceNameLabel) - .addComponent(jSourceTextLabel)) - .addGap(18, 18, 18) - .addComponent(jSourceGoToResultButton) - .addGap(0, 203, Short.MAX_VALUE)) + .addComponent(jScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 335, Short.MAX_VALUE) ); }// //GEN-END:initComponents - private void jSourceGoToResultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jSourceGoToResultButtonActionPerformed - - final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance(); - - // Navigate to the source context artifact. - if (sourceContextArtifact != null) { - dtc.viewArtifact(sourceContextArtifact); - } - - }//GEN-LAST:event_jSourceGoToResultButtonActionPerformed - @Override public void setNode(Node selectedNode) { if ((selectedNode == null) || (!isSupported(selectedNode))) { @@ -188,9 +213,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte @Override public void resetComponent() { - jSourceGoToResultButton.setVisible(false); - setSourceName(""); - setSourceText(""); + contextSourcePanels.clear(); + contextUsagePanels.clear(); } @Override @@ -222,6 +246,9 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte return 1; } + @NbBundle.Messages({ + "ContextViewer.unknownSource=Unknown ", + }) /** * Looks for context providing artifacts for the given file and populates * the source context. @@ -234,7 +261,7 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte private void populateSourceContextData(AbstractFile sourceFile) throws NoCurrentCaseException, TskCoreException { SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - + // Check for all context artifacts boolean foundASource = false; for (BlackboardArtifact.ARTIFACT_TYPE artifactType : SOURCE_CONTEXT_ARTIFACTS) { @@ -245,12 +272,33 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte addSourceEntry(contextArtifact); } } - jSourceGoToResultButton.setVisible(true); - if (foundASource == false) { - setSourceName("Unknown"); - showSourceText(false); + javax.swing.JPanel contextContainer = new javax.swing.JPanel(); + contextContainer.add(jSourcePanel); + contextContainer.setLayout(new BoxLayout(contextContainer, BoxLayout.Y_AXIS)); + if (contextSourcePanels.isEmpty()) { + contextContainer.add(jUnknownPanel); + } else { + for (javax.swing.JPanel sourcePanel : contextSourcePanels) { + contextContainer.add(sourcePanel); + } } - + contextContainer.add(jUsagePanel); + if (contextUsagePanels.isEmpty()) { + contextContainer.add(jUnknownPanel); + } else { + for (javax.swing.JPanel usagePanel : contextUsagePanels) { + contextContainer.add(usagePanel); + } + } + contextContainer.setEnabled(foundASource); + contextContainer.setVisible(foundASource); + jScrollPane.getViewport().setView(contextContainer); + jScrollPane.setEnabled(foundASource); + jScrollPane.setVisible(foundASource); + jScrollPane.repaint(); + jScrollPane.revalidate(); + + } /** @@ -263,15 +311,13 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte * @throws TskCoreException */ private void addSourceEntry(BlackboardArtifact artifact) throws TskCoreException { + if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT.getTypeID() == artifact.getArtifactTypeID()) { BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); if (associatedArtifactAttribute != null) { long artifactId = associatedArtifactAttribute.getValueLong(); BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(artifactId); - //save the artifact for "Go to Result" button - sourceContextArtifact = associatedArtifact; - setSourceFields(associatedArtifact); } } @@ -293,47 +339,27 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte private void setSourceFields(BlackboardArtifact associatedArtifact) throws TskCoreException { if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID() || BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID() == associatedArtifact.getArtifactTypeID()) { - - setSourceName(Bundle.ContextViewer_attachmentSource()); - setSourceText(msgArtifactToAbbreviatedString(associatedArtifact)); + String sourceName = Bundle.ContextViewer_attachmentSource(); + String sourceText = msgArtifactToAbbreviatedString(associatedArtifact); + javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); + contextSourcePanels.add(sourcePanel); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID() == associatedArtifact.getArtifactTypeID() || BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID() == associatedArtifact.getArtifactTypeID()) { + String sourceName = Bundle.ContextViewer_downloadSource(); + String sourceText = webDownloadArtifactToString(associatedArtifact); + javax.swing.JPanel sourcePanel = new ContextSourcePanel(sourceName, sourceText, associatedArtifact); + contextSourcePanels.add(sourcePanel); - setSourceName(Bundle.ContextViewer_downloadSource()); - setSourceText(webDownloadArtifactToString(associatedArtifact)); } else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) { - setSourceName(Bundle.ContextViewer_recentDocs()); - setSourceText(recentDocArtifactToString(associatedArtifact)); + String sourceName = Bundle.ContextViewer_recentDocs(); + String sourceText = recentDocArtifactToString(associatedArtifact); + javax.swing.JPanel usagePanel = new ContextUsagePanel(sourceName, sourceText, associatedArtifact); + contextUsagePanels.add(usagePanel); } } - /** - * Sets the source label string. - * - * @param nameLabel String value for source label. - */ - private void setSourceName(String nameLabel) { - jSourceNameLabel.setText(nameLabel); - } - - /** - * Sets the source text string. - * - * @param text String value for source text. - */ - private void setSourceText(String text) { - jSourceTextLabel.setText(text); - showSourceText(!text.isEmpty()); - } - - private void showSourceText(boolean show) { - jSourceTextLabel.setVisible(show); - jSourceGoToResultButton.setEnabled(show); - jSourceLabel.setVisible(show); - } - /** * Returns a display string with download source URL from the given * artifact. @@ -371,16 +397,21 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte * @throws TskCoreException */ @NbBundle.Messages({ - "ContextViewer.file=File", - "ContextViewer.on=On" + "ContextViewer.on=Opened at", + "ContextViewer.unknown=Opened at unknown time" }) private String recentDocArtifactToString(BlackboardArtifact artifact) throws TskCoreException { StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN); Map attributesMap = getAttributesMap(artifact); + BlackboardAttribute attribute = attributesMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == artifact.getArtifactTypeID()) { - appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, attributesMap, Bundle.ContextViewer_file()); - appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_on()); + if (attribute.getValueLong() > 0) { + appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_on()); + } else { + sb.append(Bundle.ContextViewer_unknown()); + } } return sb.toString(); } @@ -468,9 +499,9 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton jSourceGoToResultButton; - private javax.swing.JLabel jSourceLabel; - private javax.swing.JLabel jSourceNameLabel; - private javax.swing.JLabel jSourceTextLabel; + private javax.swing.JScrollPane jScrollPane; + private javax.swing.JPanel jSourcePanel; + private javax.swing.JPanel jUnknownPanel; + private javax.swing.JPanel jUsagePanel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 409e27f080..116f212dd9 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -75,6 +75,7 @@ public final class UserPreferences { public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags"; public static final String HIDE_SCO_COLUMNS = "HideCentralRepoCommentsAndOccurrences"; //The key for this setting pre-dates the settings current functionality //NON-NLS public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames"; + private static final boolean DISPLAY_TRANSLATED_NAMES_DEFAULT = true; public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath"; public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize"; public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize"; @@ -265,7 +266,7 @@ public final class UserPreferences { } public static boolean displayTranslatedFileNames() { - return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, false); + return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, DISPLAY_TRANSLATED_NAMES_DEFAULT); } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java index 8747d49795..67ce973d3b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java @@ -27,6 +27,7 @@ import javax.swing.event.DocumentListener; import org.openide.util.NbBundle; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.TskData.DbType; +import org.sleuthkit.autopsy.centralrepository.optionspanel.GlobalSettingsPanel; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo; import org.sleuthkit.autopsy.coreutils.Logger; @@ -683,56 +684,93 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { boolean isPwSet = (tbMsgPassword.getPassword().length != 0); return (isUserSet == isPwSet); } + + void store() { + boolean prevSelected = UserPreferences.getIsMultiUserModeEnabled(); + CaseDbConnectionInfo prevConn = null; + try { + prevConn = UserPreferences.getDatabaseConnectionInfo(); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "There was an error while fetching previous connection settings while trying to save", ex); //NON-NLS + } boolean multiUserCasesEnabled = cbEnableMultiUser.isSelected(); UserPreferences.setIsMultiUserModeEnabled(multiUserCasesEnabled); - if (multiUserCasesEnabled == false) { - return; + + CaseDbConnectionInfo info = null; + + if (multiUserCasesEnabled == true) { + + /* + * Currently only supporting multi-user cases with PostgreSQL case + * databases. + */ + DbType dbType = DbType.POSTGRESQL; + info = new CaseDbConnectionInfo( + tbDbHostname.getText().trim(), + tbDbPort.getText().trim(), + tbDbUsername.getText().trim(), + new String(tbDbPassword.getPassword()), + dbType); + try { + UserPreferences.setDatabaseConnectionInfo(info); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error saving case database connection info", ex); //NON-NLS + } + + int msgServicePort = 0; + try { + msgServicePort = Integer.parseInt(this.tbMsgPort.getText().trim()); + } catch (NumberFormatException ex) { + logger.log(Level.SEVERE, "Could not parse messaging service port setting", ex); + } + + MessageServiceConnectionInfo msgServiceInfo = new MessageServiceConnectionInfo( + tbMsgHostname.getText().trim(), + msgServicePort, + tbMsgUsername.getText().trim(), + new String(tbMsgPassword.getPassword())); + + try { + UserPreferences.setMessageServiceConnectionInfo(msgServiceInfo); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error saving messaging service connection info", ex); //NON-NLS + } + + UserPreferences.setIndexingServerHost(tbSolrHostname.getText().trim()); + UserPreferences.setIndexingServerPort(Integer.parseInt(tbSolrPort.getText().trim())); } - /* - * Currently only supporting multi-user cases with PostgreSQL case - * databases. - */ - DbType dbType = DbType.POSTGRESQL; - CaseDbConnectionInfo info = new CaseDbConnectionInfo( - tbDbHostname.getText().trim(), - tbDbPort.getText().trim(), - tbDbUsername.getText().trim(), - new String(tbDbPassword.getPassword()), - dbType); - try { - UserPreferences.setDatabaseConnectionInfo(info); - } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error saving case database connection info", ex); //NON-NLS - } - - int msgServicePort = 0; - try { - msgServicePort = Integer.parseInt(this.tbMsgPort.getText().trim()); - } catch (NumberFormatException ex) { - logger.log(Level.SEVERE, "Could not parse messaging service port setting", ex); - } - - MessageServiceConnectionInfo msgServiceInfo = new MessageServiceConnectionInfo( - tbMsgHostname.getText().trim(), - msgServicePort, - tbMsgUsername.getText().trim(), - new String(tbMsgPassword.getPassword())); - - try { - UserPreferences.setMessageServiceConnectionInfo(msgServiceInfo); - } catch (UserPreferencesException ex) { - logger.log(Level.SEVERE, "Error saving messaging service connection info", ex); //NON-NLS - } - - UserPreferences.setIndexingServerHost(tbSolrHostname.getText().trim()); - UserPreferences.setIndexingServerPort(Integer.parseInt(tbSolrPort.getText().trim())); - + // trigger changes to whether or not user can use multi user settings for central repository + if (prevSelected != multiUserCasesEnabled || !areCaseDbConnectionEqual(prevConn, info)) + GlobalSettingsPanel.onMultiUserChange(this, prevSelected, multiUserCasesEnabled); } + private static boolean arePropsEqual(Object a, Object b) { + if (a == null || b == null) { + return (a == null && b == null); + } + else { + return a.equals(b); + } + } + + private static boolean areCaseDbConnectionEqual(CaseDbConnectionInfo a, CaseDbConnectionInfo b) { + if (a == null || b == null) { + return (a == null && b == null); + } + + return + arePropsEqual(a.getDbType(), b.getDbType()) && + arePropsEqual(a.getHost(), b.getHost()) && + arePropsEqual(a.getPassword(), b.getPassword()) && + arePropsEqual(a.getPort(), b.getPort()) && + arePropsEqual(a.getUserName(), b.getUserName()); + } + + /** * Validates that the form is filled out correctly for our usage. * diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index a60964aa19..1d53e46ca6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -105,7 +105,7 @@ public abstract class AbstractAbstractFileNode extends A this.content.getName(), this.content.getId()), ex); } - if (UserPreferences.displayTranslatedFileNames()) { + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { backgroundTasksPool.submit(new TranslationTask( new WeakReference<>(this), weakPcl)); } @@ -331,7 +331,7 @@ public abstract class AbstractAbstractFileNode extends A * background task that promises to update these values. */ - if (UserPreferences.displayTranslatedFileNames()) { + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, "")); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java new file mode 100755 index 0000000000..368b09b142 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java @@ -0,0 +1,140 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.datamodel.utils; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.WeakReference; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.AbstractContentNode; + +/** + * An abstract base class for background tasks needed to compute values for the + * property sheet of an AbstractNode. + * + * The results of the computation are returned by firing a PropertyChangeEvent + * and the run method has an exception firewall with logging. These features + * relieve the AbstractNode from having to create a thread to block on the get() + * method of the task Future. + * + * Only weak references to the AbstractNode and its PropertyChangeListener are + * held prior to task execution so that a queued task does not interfere with + * garbage collection if the node has been destroyed by the NetBeans framework. + * + * A thread pool with descriptively named threads (node-background-task-N) is + * provided for executing instances of the tasks. + */ +public abstract class AbstractNodePropertySheetTask implements Runnable { + + private static final Logger LOGGER = Logger.getLogger(AbstractContentNode.class.getName()); + private static final Integer THREAD_POOL_SIZE = 10; + private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE, new ThreadFactoryBuilder().setNameFormat("node-background-task-%d").build()); + private final WeakReference weakNodeRef; + private final WeakReference weakListenerRef; + + /** + * Submits a task to compute values for the property sheet of an + * AbstractNode to a thread pool dedicated to such tasks with descriptively + * named threads (node-background-task-N). + * + * @param task The task. + * + * @return The Future of the task, may be used for task cancellation by + * calling Future.cancel(true). + */ + public static Future submitTask(AbstractNodePropertySheetTask task) { + return executor.submit(task); + } + + /** + * Constructs an abstract base class for background tasks needed to compute + * values for the property sheet of an AbstractNode. + * + * The results of the computation are returned by firing a + * PropertyChangeEvent and the run method has an exception firewall with + * logging. These features relieve the AbstractNode from having to create a + * thread to block on the get() method of the task Future. + * + * Only weak references to the AbstractNode and its PropertyChangeListener + * are held prior to task execution so that a queued task does not interfere + * with garbage collection if the node has been destroyed by the NetBeans + * framework. + * + * A thread pool with descriptively named threads (node-background-task-N) + * is provided for executing instances of the tasks. + * + * @param node The node. + * @param listener A property change listener for the node. + */ + protected AbstractNodePropertySheetTask(T node, PropertyChangeListener listener) { + this.weakNodeRef = new WeakReference<>(node); + this.weakListenerRef = new WeakReference<>(listener); + } + + /** + * Computes the values for the property sheet of an AbstractNode. The + * results of the computation are returned as a PropertyChangeEvent which is + * fired to the PropertyChangeEventListener of the node. + * + * IMPORTANT: Implementations of this method should check for cancellation + * by calling Thread.currentThread().isInterrupted() at appropriate + * intervals. + * + * @param node The AbstractNode. + * + * @return The result of the computation as a PropertyChangeEvent. + */ + protected abstract PropertyChangeEvent computePropertyValue(T node) throws Exception; + + @Override + final public void run() { + try { + T node = this.weakNodeRef.get(); + PropertyChangeListener listener = this.weakListenerRef.get(); + if (node == null || listener == null) { + return; + } + + if (Thread.currentThread().isInterrupted()) { + return; + } + + PropertyChangeEvent changeEvent = computePropertyValue(node); + + if (Thread.currentThread().isInterrupted()) { + return; + } + + if (changeEvent != null) { + listener.propertyChange(changeEvent); + } + + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error executing property sheet values computation background task", ex); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/featureaccess/FeatureAccessUtils.java b/Core/src/org/sleuthkit/autopsy/featureaccess/FeatureAccessUtils.java index 87b4fed4f7..54ea246250 100644 --- a/Core/src/org/sleuthkit/autopsy/featureaccess/FeatureAccessUtils.java +++ b/Core/src/org/sleuthkit/autopsy/featureaccess/FeatureAccessUtils.java @@ -73,6 +73,25 @@ final public class FeatureAccessUtils { return dataSourceDeletionAllowed; } + /** + * Indicates whether or not a user can delete the current case. + * + * @return True or false. + */ + public static boolean canDeleteCurrentCase() { + return currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent(); + } + + /** + * Indicates whether or not a user can add hash sets to the central + * repository. + * + * @return True or false. + */ + public static boolean canAddHashSetsToCentralRepo() { + return multiUserCaseRestrictionsFileAbsent(); + } + /** * Indicates whether or not the current case is a single-user case. * @@ -83,12 +102,12 @@ final public class FeatureAccessUtils { } /** - * Indicates whether or not the current user is allowed to create or modify - * (add or delete data sources) multi-user cases. + * Indicates whether or not the multi-user case privileges restriction file + * is absent. * * @return True or false. */ - public static boolean multiUserCaseRestrictionsFileAbsent() { + private static boolean multiUserCaseRestrictionsFileAbsent() { File accessLimitingFile = new File(MULTIUSER_CASE_RESTRICTED_FILE_PATH); return !accessLimitingFile.exists(); } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java index 11f1d79e1d..b4142afab9 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java @@ -667,7 +667,7 @@ class FileSearch { return; } BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS); - //we are height limited here so it can be wider than it can be tall, scalr maintains aspect ratio + //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio. videoThumbnails.add(thumbnail); if (cacheDirectory != null) { try { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 8c6e879c76..0462e77842 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -434,7 +434,7 @@ final public class MapPanel extends javax.swing.JPanel { /** * Find the waypoint that is closest to the given mouse click point. * - * @param mouseClickPoint The mouse click point + * @param clickPoint The mouse click point * * @return A waypoint that is within 10 pixels of the given point, or null * if none was found. diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java index 672ffd6807..3e3e006dd3 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2018 Basis Technology Corp. + * Copyright 2014-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,6 +51,7 @@ final class IngestModuleFactoryLoader { private static final Logger logger = Logger.getLogger(IngestModuleFactoryLoader.class.getName()); private static final String SAMPLE_MODULE_FACTORY_CLASS_NAME = SampleIngestModuleFactory.class.getCanonicalName(); private static final ArrayList coreModuleOrdering = new ArrayList() { + private static final long serialVersionUID = 1L; { // The ordering of the core ingest module factories implemented // using Java is hard-coded. @@ -79,7 +80,7 @@ final class IngestModuleFactoryLoader { * removed between invocations. * * @return A list of objects that implement the IngestModuleFactory - * interface. + * interface. */ static List getIngestModuleFactories() { // A hash set of display names and a hash map of class names to @@ -132,7 +133,7 @@ final class IngestModuleFactoryLoader { factories.add(factory); logger.log(Level.INFO, "Found ingest module factory: name = {0}, version = {1}", new Object[]{factory.getModuleDisplayName(), factory.getModuleVersionNumber()}); //NON-NLS } else { - logger.log(Level.SEVERE, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS + logger.log(Level.WARNING, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( NbBundle.getMessage(IngestModuleFactoryLoader.class, "IngestModuleFactoryLoader.errorMessages.duplicateDisplayName", factory.getModuleDisplayName()), NotifyDescriptor.ERROR_MESSAGE)); @@ -154,11 +155,14 @@ final class IngestModuleFactoryLoader { javaFactoriesByClass.put(factory.getClass().getCanonicalName(), factory); logger.log(Level.INFO, "Found ingest module factory: name = {0}, version = {1}", new Object[]{factory.getModuleDisplayName(), factory.getModuleVersionNumber()}); //NON-NLS } else { - logger.log(Level.SEVERE, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS - DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message( - NbBundle.getMessage(IngestModuleFactoryLoader.class, "IngestModuleFactoryLoader.errorMessages.duplicateDisplayName", factory.getModuleDisplayName()), - NotifyDescriptor.ERROR_MESSAGE)); + logger.log(Level.WARNING, "Found duplicate ingest module display name (name = {0})", factory.getModuleDisplayName()); //NON-NLS } } + /** + * Private constructor to prevent instantiation of this utility class. + */ + private IngestModuleFactoryLoader() { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 6f68a3defc..5470b385fb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerExc import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; /** * Instances of this class allow a user to create a new hash database and add it @@ -124,29 +125,29 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { setLocationRelativeTo(getOwner()); setVisible(true); } - - private void enableComponents(){ - - if(! CentralRepository.isEnabled()){ + + private void enableComponents() { + + if (!CentralRepository.isEnabled() || !FeatureAccessUtils.canAddHashSetsToCentralRepo()) { centralRepoRadioButton.setEnabled(false); fileTypeRadioButton.setSelected(true); } else { populateCombobox(); } - + boolean isFileType = fileTypeRadioButton.isSelected(); // Type type only databasePathLabel.setEnabled(isFileType); databasePathTextField.setEnabled(isFileType); saveAsButton.setEnabled(isFileType); - + // Central repo only - lbOrg.setEnabled(! isFileType); - orgButton.setEnabled(! isFileType); - orgComboBox.setEnabled(! isFileType); + lbOrg.setEnabled(!isFileType); + orgButton.setEnabled(!isFileType); + orgComboBox.setEnabled(!isFileType); } - + @NbBundle.Messages({"HashDbCreateDatabaseDialog.populateOrgsError.message=Failure loading organizations."}) private void populateCombobox() { orgComboBox.removeAllItems(); @@ -155,7 +156,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { orgs = dbManager.getOrganizations(); orgs.forEach((org) -> { orgComboBox.addItem(org.getName()); - if(CentralRepoDbUtil.isDefaultOrg(org)){ + if (CentralRepoDbUtil.isDefaultOrg(org)) { orgComboBox.setSelectedItem(org.getName()); selectedOrg = org; } @@ -167,7 +168,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { JOptionPane.showMessageDialog(this, Bundle.HashDbCreateDatabaseDialog_populateOrgsError_message()); Logger.getLogger(ImportCentralRepoDbProgressDialog.class.getName()).log(Level.SEVERE, "Failure loading organizations", ex); } - } + } /** * This method is called from within the constructor to initialize the form. @@ -413,7 +414,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { path.append(lastBaseDirectory); File hashDbFolder = new File(path.toString()); // create the folder if it doesn't exist - if (!hashDbFolder.exists()){ + if (!hashDbFolder.exists()) { hashDbFolder.mkdir(); } if (!hashSetNameTextField.getText().isEmpty()) { @@ -452,7 +453,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { return; } - if(fileTypeRadioButton.isSelected()){ + if (fileTypeRadioButton.isSelected()) { if (databasePathTextField.getText().isEmpty()) { JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), @@ -463,13 +464,13 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { return; } } else { - if(selectedOrg == null){ + if (selectedOrg == null) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.missingOrg"), - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.createHashDbErr"), - JOptionPane.ERROR_MESSAGE); + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.missingOrg"), + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.createHashDbErr"), + JOptionPane.ERROR_MESSAGE); return; } } @@ -487,7 +488,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { String errorMessage = NbBundle .getMessage(this.getClass(), "HashDbCreateDatabaseDialog.errMsg.hashDbCreationErr"); - if(fileTypeRadioButton.isSelected()){ + if (fileTypeRadioButton.isSelected()) { try { newHashDb = HashDbManager.getInstance().addNewHashDatabaseNoSave(hashSetNameTextField.getText(), fileChooser.getSelectedFile().getCanonicalPath(), true, sendIngestMessagesCheckbox.isSelected(), type); } catch (IOException ex) { @@ -510,17 +511,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { } } else { // Check if a hash set with the same name/version already exists - try{ - if(CentralRepository.getInstance().referenceSetExists(hashSetNameTextField.getText(), "")){ + try { + if (CentralRepository.getInstance().referenceSetExists(hashSetNameTextField.getText(), "")) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.duplicateName"), - NbBundle.getMessage(this.getClass(), - "HashDbCreateDatabaseDialog.createHashDbErr"), - JOptionPane.ERROR_MESSAGE); + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.duplicateName"), + NbBundle.getMessage(this.getClass(), + "HashDbCreateDatabaseDialog.createHashDbErr"), + JOptionPane.ERROR_MESSAGE); return; } - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Error looking up reference set", ex); JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), @@ -528,16 +529,16 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { NbBundle.getMessage(this.getClass(), "HashDbCreateDatabaseDialog.createHashDbErr"), JOptionPane.ERROR_MESSAGE); - return; + return; } - - try{ - int referenceSetID = CentralRepository.getInstance().newReferenceSet(new CentralRepoFileSet(selectedOrg.getOrgID(), hashSetNameTextField.getText(), + + try { + int referenceSetID = CentralRepository.getInstance().newReferenceSet(new CentralRepoFileSet(selectedOrg.getOrgID(), hashSetNameTextField.getText(), "", fileKnown, false, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID))); - newHashDb = HashDbManager.getInstance().addExistingCentralRepoHashSet(hashSetNameTextField.getText(), - "", referenceSetID, + newHashDb = HashDbManager.getInstance().addExistingCentralRepoHashSet(hashSetNameTextField.getText(), + "", referenceSetID, true, sendIngestMessagesCheckbox.isSelected(), type, false); - } catch (CentralRepoException | TskCoreException ex){ + } catch (CentralRepoException | TskCoreException ex) { Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Error creating new reference set", ex); JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), @@ -545,8 +546,8 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { NbBundle.getMessage(this.getClass(), "HashDbCreateDatabaseDialog.createHashDbErr"), JOptionPane.ERROR_MESSAGE); - return; - } + return; + } } dispose(); @@ -561,11 +562,13 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { // update the combobox options if (dialog.isChanged()) { populateCombobox(); - } + } }//GEN-LAST:event_orgButtonActionPerformed private void orgComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_orgComboBoxActionPerformed - if (null == orgComboBox.getSelectedItem()) return; + if (null == orgComboBox.getSelectedItem()) { + return; + } String orgName = this.orgComboBox.getSelectedItem().toString(); for (CentralRepoOrganization org : orgs) { if (org.getName().equals(orgName)) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index 1e34c9bbda..a08f324a44 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFile import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; /** * Instances of this class allow a user to select an existing hash database and @@ -89,7 +90,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { String[] EXTENSION = new String[]{"txt", "kdb", "idx", "hash", "Hash", "hsh"}; //NON-NLS FileNameExtensionFilter filter = new FileNameExtensionFilter( NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.fileNameExtFilter.text"), EXTENSION); - fileChooser.setFileFilter(filter); + fileChooser.setFileFilter(filter); fileChooser.setMultiSelectionEnabled(false); } @@ -105,29 +106,28 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { } return shortenedPath; } - - private void enableComponents(){ - - - if(! CentralRepository.isEnabled()){ + + private void enableComponents() { + + if (!CentralRepository.isEnabled() || !FeatureAccessUtils.canAddHashSetsToCentralRepo()) { centralRepoRadioButton.setEnabled(false); fileTypeRadioButton.setSelected(true); } else { populateCombobox(); } - + boolean isFileType = fileTypeRadioButton.isSelected(); // Central repo only - lbVersion.setEnabled((! isFileType) && (readOnlyCheckbox.isSelected())); - versionTextField.setEnabled((! isFileType) && (readOnlyCheckbox.isSelected())); - - lbOrg.setEnabled(! isFileType); - orgButton.setEnabled(! isFileType); - orgComboBox.setEnabled(! isFileType); - readOnlyCheckbox.setEnabled(! isFileType); + lbVersion.setEnabled((!isFileType) && (readOnlyCheckbox.isSelected())); + versionTextField.setEnabled((!isFileType) && (readOnlyCheckbox.isSelected())); + + lbOrg.setEnabled(!isFileType); + orgButton.setEnabled(!isFileType); + orgComboBox.setEnabled(!isFileType); + readOnlyCheckbox.setEnabled(!isFileType); } - + @NbBundle.Messages({"HashDbImportDatabaseDialog.populateOrgsError.message=Failure loading organizations."}) private void populateCombobox() { orgComboBox.removeAllItems(); @@ -136,7 +136,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { orgs = dbManager.getOrganizations(); orgs.forEach((org) -> { orgComboBox.addItem(org.getName()); - if(CentralRepoDbUtil.isDefaultOrg(org)){ + if (CentralRepoDbUtil.isDefaultOrg(org)) { orgComboBox.setSelectedItem(org.getName()); selectedOrg = org; } @@ -468,28 +468,28 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { JOptionPane.ERROR_MESSAGE); return; } - - if(centralRepoRadioButton.isSelected()){ - if(readOnlyCheckbox.isSelected() && versionTextField.getText().isEmpty()){ + + if (centralRepoRadioButton.isSelected()) { + if (readOnlyCheckbox.isSelected() && versionTextField.getText().isEmpty()) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.missingVersion"), - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.importHashDbErr"), - JOptionPane.ERROR_MESSAGE); + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.missingVersion"), + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.importHashDbErr"), + JOptionPane.ERROR_MESSAGE); return; } - - if(selectedOrg == null){ + + if (selectedOrg == null) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.missingOrg"), - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.importHashDbErr"), - JOptionPane.ERROR_MESSAGE); + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.missingOrg"), + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.importHashDbErr"), + JOptionPane.ERROR_MESSAGE); return; } - } + } if (selectedFilePath.isEmpty()) { JOptionPane.showMessageDialog(this, @@ -500,7 +500,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { JOptionPane.ERROR_MESSAGE); return; } - + File file = new File(selectedFilePath); if (!file.exists()) { JOptionPane.showMessageDialog(this, @@ -523,11 +523,11 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { String errorMessage = NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.unableToCopyToUserDirMsg", locationInUserConfigDir); Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, errorMessage, ex); JOptionPane.showMessageDialog(this, errorMessage, NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.importHashDbErr"), - JOptionPane.ERROR_MESSAGE); + JOptionPane.ERROR_MESSAGE); return; } } - + KnownFilesType type; if (knownRadioButton.isSelected()) { type = KnownFilesType.KNOWN; @@ -536,9 +536,9 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { } String errorMessage = NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg", - selectedFilePath); - if(fileTypeRadioButton.isSelected()){ + "HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg", + selectedFilePath); + if (fileTypeRadioButton.isSelected()) { try { selectedHashDb = HashDbManager.getInstance().addExistingHashDatabaseNoSave(hashSetNameTextField.getText(), selectedFilePath, true, sendIngestMessagesCheckbox.isSelected(), type); @@ -552,19 +552,19 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { return; } } else { - + // Check if a hash set with the same name/version already exists - try{ - if(CentralRepository.getInstance().referenceSetExists(hashSetNameTextField.getText(), versionTextField.getText())){ + try { + if (CentralRepository.getInstance().referenceSetExists(hashSetNameTextField.getText(), versionTextField.getText())) { JOptionPane.showMessageDialog(this, - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.duplicateName"), - NbBundle.getMessage(this.getClass(), - "HashDbImportDatabaseDialog.importHashDbErr"), - JOptionPane.ERROR_MESSAGE); + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.duplicateName"), + NbBundle.getMessage(this.getClass(), + "HashDbImportDatabaseDialog.importHashDbErr"), + JOptionPane.ERROR_MESSAGE); return; } - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Error looking up reference set", ex); JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), @@ -572,20 +572,20 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.importHashDbErr"), JOptionPane.ERROR_MESSAGE); - return; + return; } - + String version; - if(readOnlyCheckbox.isSelected()){ + if (readOnlyCheckbox.isSelected()) { version = versionTextField.getText(); } else { // Editable databases don't have a version version = ""; } ImportCentralRepoDbProgressDialog progressDialog = new ImportCentralRepoDbProgressDialog(); - progressDialog.importFile(hashSetNameTextField.getText(), version, - selectedOrg.getOrgID(), true, sendIngestMessagesCheckbox.isSelected(), type, - readOnlyCheckbox.isSelected(), selectedFilePath); + progressDialog.importFile(hashSetNameTextField.getText(), version, + selectedOrg.getOrgID(), true, sendIngestMessagesCheckbox.isSelected(), type, + readOnlyCheckbox.isSelected(), selectedFilePath); selectedHashDb = progressDialog.getDatabase(); } @@ -609,11 +609,13 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { // update the combobox options if (dialog.isChanged()) { populateCombobox(); - } + } }//GEN-LAST:event_orgButtonActionPerformed private void orgComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_orgComboBoxActionPerformed - if (null == orgComboBox.getSelectedItem()) return; + if (null == orgComboBox.getSelectedItem()) { + return; + } String orgName = this.orgComboBox.getSelectedItem().toString(); for (CentralRepoOrganization org : orgs) { if (org.getName().equals(orgName)) { diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslateTextTask.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslateTextTask.java new file mode 100644 index 0000000000..245052d4a4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslateTextTask.java @@ -0,0 +1,242 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.texttranslation.ui; + +import java.awt.ComponentOrientation; +import java.awt.Font; +import java.io.IOException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TextUtil; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationException; + +/** + * This is an abstract class for translating text and displaying to the user. + */ +public abstract class TranslateTextTask extends SwingWorker { + + private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName()); + + private final boolean translateText; + private final String contentDescriptor; + + /** + * This is a result of running and processing the translation. + */ + public static class TranslateResult { + + private final String errorMessage; + private final String result; + private final boolean successful; + + public static TranslateResult error(String message) { + return new TranslateResult(null, message, false); + } + + public static TranslateResult success(String content) { + return new TranslateResult(content, null, true); + } + + private TranslateResult(String result, String errorMessage, boolean successful) { + this.successful = successful; + this.errorMessage = errorMessage; + this.result = result; + } + + public String getErrorMessage() { + return errorMessage; + } + + public String getResult() { + return result; + } + + public boolean isSuccessful() { + return successful; + } + + } + + /** + * This is the main constructor for the TranslateTextTask. + * @param translateText whether or not to translate text + * @param fileDescriptor the content descriptor for the item being + * translated (used for logging errors) + */ + public TranslateTextTask(boolean translateText, String fileDescriptor) { + this.translateText = translateText; + this.contentDescriptor = fileDescriptor; + } + + /** + * This method retrieves the original text content to be translated. + * @return the original text content + * @throws IOException + * @throws InterruptedException + * @throws IllegalStateException + */ + protected abstract String retrieveText() throws IOException, InterruptedException, IllegalStateException; + + /** + * This method should be overridden when a translated text result is received. + * @param text the text to display + * @param orientation the orientation of the text + * @param font the font style (returns plain) + */ + protected abstract void onTextDisplay(String text, ComponentOrientation orientation, int font); + + /** + * When a progress result is received, this method is called. + * This method can be overridden depending on the scenario, but defaults to just displaying using onTextDisplay. + * @param text the text of the status update + * @param orientation the orientation for the status + * @param font the font style of the status + */ + protected void onProgressDisplay(String text, ComponentOrientation orientation, int font) { + // This defaults to normal display unless overridden. + onTextDisplay(text, orientation, font); + } + + /** + * When an error result is received, this method is called. This method can be overridden depending on the + * scenario but defaults to just displaying using onTextDisplay. + * @param text the text of the error + * @param orientation the orientation for the error + * @param font the font style of the error + */ + protected void onErrorDisplay(String text, ComponentOrientation orientation, int font) { + // This defaults to normal display unless overridden. + onTextDisplay(text, orientation, font); + } + + @NbBundle.Messages({ + "TranslatedContentViewer.translatingText=Translating text, please wait...", + "TranslatedContentViewer.fileHasNoText=File has no text.", + "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.", + "# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})." + }) + @Override + public TranslateResult doInBackground() throws InterruptedException { + if (this.isCancelled()) { + throw new InterruptedException(); + } + + String fileText; + try { + fileText = retrieveText(); + } catch (IOException | IllegalStateException ex) { + return TranslateResult.error(ex.getMessage()); + } + + if (this.isCancelled()) { + throw new InterruptedException(); + } + + if (fileText == null || fileText.isEmpty()) { + return TranslateResult.error(Bundle.TranslatedContentViewer_fileHasNoText()); + } + + if (!this.translateText) { + return TranslateResult.success(fileText); + } + + return translateRetrievedText(fileText); + } + + /** + * This is the final step in the translation swing worker prior to being {@link #done() done()}; translates the text if needed. + * @param fileText the text to translate + * @return the translated text + * @throws InterruptedException if operation is canclled, an interrupted exception is thrown + */ + private TranslateResult translateRetrievedText(String fileText) throws InterruptedException { + SwingUtilities.invokeLater(() -> { + onProgressDisplay(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + }); + + try { + String translation = translate(fileText); + if (this.isCancelled()) { + throw new InterruptedException(); + } + + if (translation == null || translation.isEmpty()) { + return TranslateResult.error(Bundle.TranslatedContentViewer_emptyTranslation()); + } else { + return TranslateResult.success(translation); + } + + } catch (NoServiceProviderException ex) { + logger.log(Level.WARNING, "Error translating text for file " + this.contentDescriptor, ex); + return TranslateResult.error(Bundle.TranslatedContentViewer_noServiceProvider()); + } catch (TranslationException ex) { + logger.log(Level.WARNING, "Error translating text for file " + this.contentDescriptor, ex); + return TranslateResult.error(Bundle.TranslatedContentViewer_translationException(ex.getMessage())); + } + } + + + @Override + public void done() { + try { + TranslateResult executionResult = get(); + if (this.isCancelled()) { + throw new InterruptedException(); + } + + if (executionResult.isSuccessful()) { + String result = executionResult.getResult(); + int len = result.length(); + int maxOrientChars = Math.min(len, 1024); + String orientDetectSubstring = result.substring(0, maxOrientChars); + ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring); + onTextDisplay(result, orientation, Font.PLAIN); + } else { + onErrorDisplay(executionResult.getErrorMessage(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + } + } catch (InterruptedException | CancellationException ignored) { + // Task cancelled, no error. + } catch (ExecutionException ex) { + logger.log(Level.WARNING, "Error occurred during background task execution for file " + this.contentDescriptor, ex); + onErrorDisplay(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + } + } + + /** + * This method passes the translation off to the {@link org.sleuthkit.autopsy.texttranslation.TextTranslationService translation service provider}. + * + * @param input text to be translated + * + * @return translated text or error message + */ + @NbBundle.Messages({ + "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text." + }) + protected String translate(String input) throws NoServiceProviderException, TranslationException { + TextTranslationService translatorInstance = TextTranslationService.getInstance(); + return translatorInstance.translate(input); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 7e07545b49..ab0b9de083 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -28,8 +28,6 @@ import java.awt.event.ActionListener; import java.io.IOException; import java.io.Reader; import java.util.Arrays; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -37,19 +35,15 @@ import org.openide.nodes.Node; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.TextViewer; import org.sleuthkit.datamodel.AbstractFile; -import javax.swing.SwingWorker; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.corecomponents.DataContentViewerUtility; import org.sleuthkit.autopsy.coreutils.ExecUtil.ProcessTerminator; -import org.sleuthkit.autopsy.coreutils.TextUtil; import org.sleuthkit.autopsy.textextractors.TextExtractor; import org.sleuthkit.autopsy.textextractors.TextExtractorFactory; import org.sleuthkit.autopsy.textextractors.configs.ImageConfig; import org.sleuthkit.autopsy.texttranslation.TextTranslationService; -import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; -import org.sleuthkit.autopsy.texttranslation.TranslationException; import org.sleuthkit.datamodel.Content; import java.util.List; import java.util.logging.Level; @@ -160,114 +154,41 @@ public final class TranslatedTextViewer implements TextViewer { /** * Extracts text from a file and optionally translates it. */ - private class ExtractAndTranslateTextTask extends SwingWorker { + private class ExtractAndTranslateTextTask extends TranslateTextTask { private final AbstractFile file; - private final boolean translateText; private ExtractAndTranslateTextTask(AbstractFile file, boolean translateText) { + super(translateText, String.format("%s (objId=%d)", file.getName(), file.getId())); this.file = file; - this.translateText = translateText; - } - - @NbBundle.Messages({ - "TranslatedContentViewer.extractingText=Extracting text, please wait...", - "TranslatedContentViewer.translatingText=Translating text, please wait...", - "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).", - "TranslatedContentViewer.fileHasNoText=File has no text.", - "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.", - "# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})." - }) - @Override - public String doInBackground() throws InterruptedException { - if (this.isCancelled()) { - throw new InterruptedException(); - } - - SwingUtilities.invokeLater(() -> { - panel.display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); - }); - String fileText; - try { - fileText = getFileText(file); - } catch (IOException | TextExtractor.InitReaderException ex) { - logger.log(Level.WARNING, String.format("Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex); - return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage()); - } - - if (this.isCancelled()) { - throw new InterruptedException(); - } - - if (fileText == null || fileText.isEmpty()) { - return Bundle.TranslatedContentViewer_fileHasNoText(); - } - - if (!this.translateText) { - return fileText; - } - - SwingUtilities.invokeLater(() -> { - panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); - }); - String translation; - try { - translation = translate(fileText); - } catch (NoServiceProviderException ex) { - logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); - translation = Bundle.TranslatedContentViewer_noServiceProvider(); - } catch (TranslationException ex) { - logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); - translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage()); - } - - if (this.isCancelled()) { - throw new InterruptedException(); - } - - return translation; - } - - @Override - public void done() { - try { - String result = get(); - if (this.isCancelled()) { - throw new InterruptedException(); - } - int len = result.length(); - int maxOrientChars = Math.min(len, 1024); - String orientDetectSubstring = result.substring(0, maxOrientChars); - ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring); - panel.display(result, orientation, Font.PLAIN); - - } catch (InterruptedException | CancellationException ignored) { - // Task cancelled, no error. - } catch (ExecutionException ex) { - logger.log(Level.WARNING, String.format("Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex); - panel.display(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); - } } /** - * Pass the translation off to the Translation service provider. + * Extracts text from the current node * - * @param input Text to be translated + * @return Extracted text * - * @return Translated text or error message + * @throws Exception */ @NbBundle.Messages({ - "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text." + "TranslatedContentViewer.extractingText=Extracting text, please wait...", + "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).", }) - private String translate(String input) throws NoServiceProviderException, TranslationException { - TextTranslationService translatorInstance = TextTranslationService.getInstance(); - String translatedResult = translatorInstance.translate(input); - if (translatedResult.isEmpty()) { - return Bundle.TranslatedContentViewer_emptyTranslation(); - } - return translatedResult; - } + protected String retrieveText() throws IOException, InterruptedException, IllegalStateException { + SwingUtilities.invokeLater(() -> { + onProgressDisplay(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + }); + try { + return getFileText(file); + } catch (IOException | TextExtractor.InitReaderException ex) { + logger.log(Level.WARNING, String.format("Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex); + // throw new exception with message to be displayed to user + throw new IllegalStateException(Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage()), ex); + } + } + + /** * Extracts text from the given node * @@ -280,9 +201,7 @@ public final class TranslatedTextViewer implements TextViewer { * @throws * org.sleuthkit.autopsy.textextractors.TextExtractor.InitReaderException */ - private String getFileText(AbstractFile file) throws IOException, - InterruptedException, TextExtractor.InitReaderException { - + private String getFileText(AbstractFile file) throws IOException, InterruptedException, TextExtractor.InitReaderException { final boolean isImage = file.getMIMEType().toLowerCase().startsWith("image/"); // NON-NLS String result; if (isImage) { @@ -382,8 +301,15 @@ public final class TranslatedTextViewer implements TextViewer { return TextExtractorFactory.getStringsExtractor(file, context).getReader(); } } + + + @Override + protected void onTextDisplay(String text, ComponentOrientation orientation, int font) { + panel.display(text, orientation, font); + } } + /** * Listens for drop-down selection changes and pushes processing off of the * EDT and into a SwingWorker. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index adf4509e76..cf8e9c46f7 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2019 Basis Technology Corp. + * Copyright 2014-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,7 +72,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; @@ -806,7 +805,6 @@ public class TimeLineController { future.get(); } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, errorLogMessage, ex); - MessageNotifyUtil.Message.error(errorUserMessage); } }, MoreExecutors.directExecutor()); } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index c95b58b874..0016428a0d 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -130,8 +130,7 @@ public class CentralRepoDatamodelTest extends TestCase { dbSettingsSqlite.saveSettings(); CentralRepoDbUtil.setUseCentralRepo(true); - CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.SQLITE.name()); - CentralRepoPlatforms.saveSelectedPlatform(); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE); } catch (CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java index 65b47bcb3b..bd297579a7 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java @@ -47,6 +47,8 @@ import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.TskCoreException; import junit.framework.Assert; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbChoice; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; import org.sleuthkit.autopsy.coreutils.TimeStampUtils; @@ -303,8 +305,7 @@ class InterCaseTestUtils { centralRepoSchemaFactory.insertDefaultDatabaseContent(); crSettings.saveSettings(); - CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.SQLITE.name()); - CentralRepoPlatforms.saveSelectedPlatform(); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE); } /** diff --git a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/ScalrWrapper.java b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/ScalrWrapper.java index f101a0b875..ad9522fb37 100644 --- a/CoreLibs/src/org/sleuthkit/autopsy/corelibs/ScalrWrapper.java +++ b/CoreLibs/src/org/sleuthkit/autopsy/corelibs/ScalrWrapper.java @@ -37,7 +37,7 @@ public class ScalrWrapper { return Scalr.resize(input, size, Scalr.OP_ANTIALIAS); } - public static synchronized BufferedImage resize(BufferedImage bufferedImage, Method method, Scalr.Mode mode, int width, int height, BufferedImageOp... ops) { + public static synchronized BufferedImage resize(BufferedImage bufferedImage, Method method, Scalr.Mode mode, int width, int height, BufferedImageOp ...ops) { return Scalr.resize(bufferedImage, method, mode, width, height, ops); } diff --git a/InternalPythonModules/GPX_Module/GPX_Parser_Module.py b/InternalPythonModules/GPX_Module/GPX_Parser_Module.py index d33084ccac..6aeb84d5be 100644 --- a/InternalPythonModules/GPX_Module/GPX_Parser_Module.py +++ b/InternalPythonModules/GPX_Module/GPX_Parser_Module.py @@ -37,8 +37,12 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import TskCoreException from org.sleuthkit.datamodel.blackboardutils import GeoArtifactsHelper -from org.sleuthkit.datamodel.blackboardutils.attributes import GeoWaypoint -from org.sleuthkit.datamodel.blackboardutils.attributes import GeoTrackPoints +from org.sleuthkit.datamodel.blackboardutils.attributes import TskGeoWaypointsUtil +from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil import GeoWaypointList +from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList import GeoWaypoint +from org.sleuthkit.datamodel.blackboardutils.attributes import TskGeoTrackpointsUtil +from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil import GeoTrackPointList +from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList import GeoTrackPoint from org.sleuthkit.autopsy.datamodel import ContentUtils from org.sleuthkit.autopsy.ingest import IngestModule from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException @@ -63,20 +67,15 @@ import gpxpy.parser class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter): moduleName = "GPX Parser" - - # True - Verbose debugging messages sent to log file. - # False - Verbose debugging turned off. - debuglevel = False def getModuleDisplayName(self): return self.moduleName - # TODO: Give it a description def getModuleDescription(self): return "Module that extracts GEO data from GPX files." def getModuleVersionNumber(self): - return "1.1" + return "1.2" def isDataSourceIngestModuleFactory(self): return True @@ -88,10 +87,11 @@ class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter): # Data Source-level ingest module. One gets created per data source. class GPXParserDataSourceIngestModule(DataSourceIngestModule): - _logger = Logger.getLogger(GPXParserDataSourceIngestModuleFactory.moduleName) + logger = Logger.getLogger(GPXParserDataSourceIngestModuleFactory.moduleName) + writeDebugMsgs = False def log(self, level, msg): - self._logger.logp(level, self.__class__.__name__, inspect.stack()[1][3], msg) + self.logger.logp(level, self.__class__.__name__, inspect.stack()[1][3], msg) def __init__(self): self.context = None @@ -105,179 +105,130 @@ class GPXParserDataSourceIngestModule(DataSourceIngestModule): # We don't know how much work there is yet. progressBar.switchToIndeterminate() - - # This will work in 4.0.1 and beyond. - # Use blackboard class to index blackboard artifacts for keyword search. - blackboard = Case.getCurrentCase().getServices().getBlackboard() - # Get the sleuthkitcase + # Get the case database and its blackboard. skCase = Case.getCurrentCase().getSleuthkitCase() + blackboard = skCase.getBlackboard() - # In the name and then count and read them. - fileManager = Case.getCurrentCase().getServices().getFileManager() - + # Get any files with a .gpx extension. + # It would perhaps be better to get these files by MIME type instead. + # RC: It would also be better if this were a file level ingest module so it could process files extracted from archives. + fileManager = Case.getCurrentCase().getServices().getFileManager() files = fileManager.findFiles(dataSource, "%.gpx") - # TODO: Would like to change this to find files based on mimetype rather than extension. - #files = findFiles(dataSource, "text/xml") - #if (file.isMimeType('text/xml') == False): + # Update the progress bar now that we know how much work there is to do. numFiles = len(files) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "found " + str(numFiles) + " files") + if self.writeDebugMsgs: self.log(Level.INFO, "Found " + str(numFiles) + " GPX files") progressBar.switchToDeterminate(numFiles) - fileCount = 0; - # Get module name for adding attributes + # Get the module name, it will be needed for adding attributes moduleName = GPXParserDataSourceIngestModuleFactory.moduleName + + # Check if a folder for this module is present in the case Temp directory. + # If not, create it. + dirName = os.path.join(Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module") + try: + os.stat(dirName) + except: + os.mkdir(dirName) + + # Create a temp file name. It appears that we cannot close and delete + # this file, but we can overwrite it for each file we need to process. + fileName = os.path.join(dirName, "tmp.gpx") + fileCount = 0; for file in files: - # Get the GeoArtifactsHelper - geoArtifactHelper = GeoArtifactsHelper(skCase, moduleName, file) + # Create a GeoArtifactsHelper for this file. + geoArtifactHelper = GeoArtifactsHelper(skCase, moduleName, None, file) # Check if the user pressed cancel while we were busy. if self.context.isJobCancelled(): return IngestModule.ProcessResult.OK - #self.log(Level.INFO, "GPX: Processing file: " + file.getName()) + if self.writeDebugMsgs: self.log(Level.INFO, "Processing " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") fileCount += 1 - # Check if module folder is present. If not, create it. - dirName = os.path.join(Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module") - try: - os.stat(dirName) - except: - os.mkdir(dirName) - fileName = os.path.join(dirName, "tmp.gpx") - - # Check to see if temporary file exists. If it does, remove it. - if os.path.exists(fileName): - try: - os.remove(fileName) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE DELETED " + fileName ) - except: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE NOT DELETED " + fileName) - - # This writes the file to the local file system. + # Write the file so that it can be parsed by gpxpy. localFile = File(fileName) ContentUtils.writeToFile(file, localFile) - # Send to gpxpy for parsing. + # Send the file to gpxpy for parsing. gpxfile = open(fileName) try: gpx = gpxpy.parse(gpxfile) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE PARSED") - except: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX:\t" + file.getName() + " - FILE NOT PARSED") + if self.writeDebugMsgs: self.log(Level.INFO, "Parsed " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") + except Exception as e: + self.log(Level.WARNING, "Error parsing file " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e)) continue if gpx: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: TRACKS") + if self.writeDebugMsgs: self.log(Level.INFO, "Processing tracks from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") for track in gpx.tracks: for segment in track.segments: - geoPointList = ArrayList() + geoPointList = TskGeoTrackpointsUtil.GeoTrackPointList() for point in segment.points: - + elevation = 0 if point.elevation != None: elevation = point.elevation - dateTime = 0 + timeStamp = 0 try: if (point.time != None): - datetime = long(time.mktime(point.time.timetuple())) - except: - pass + timeStamp = long(time.mktime(point.time.timetuple())) + except Exception as e: + self.log(Level.WARNING, "Error getting track timestamp from " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e)) - geoPointList.add(GeoWaypoint.GeoTrackPoint(point.latitude, point.longitude, elevation, 0, 0, 0, dateTime)) + geoPointList.addPoint(GeoTrackPoint(point.latitude, point.longitude, elevation, None, 0, 0, 0, timeStamp)) try: - # Add the trackpoint using the helper class - geoartifact = geoArtifactHelper.addTrack("Trackpoint", geoPointList) + geoArtifactHelper.addTrack("Track", geoPointList, None) except Blackboard.BlackboardException as e: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard " ) + self.log(Level.SEVERE, "Error posting GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) except TskCoreException as e: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper tskcoreexception" ) + self.log(Level.SEVERE, "Error creating GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: WAYPOINTS") + if self.writeDebugMsgs: self.log(Level.INFO, "Processing waypoints from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") for waypoint in gpx.waypoints: - attributes = ArrayList() - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) - - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), moduleName, waypoint.latitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), moduleName, waypoint.longitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), moduleName, "Waypoint")) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), moduleName, waypoint.name)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), moduleName, "GPXParser")) - - art.addAttributes(attributes) try: - # Post the artifact to blackboard - skCase.getBlackboard().postArtifact(art, moduleName) + art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) + + attributes = ArrayList() + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), moduleName, waypoint.latitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), moduleName, waypoint.longitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), moduleName, "Waypoint")) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), moduleName, waypoint.name)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), moduleName, "GPXParser")) + art.addAttributes(attributes) + + blackboard.postArtifact(art, moduleName) + except Blackboard.BlackboardException as e: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" ) + self.log(Level.SEVERE, "Error posting GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + except TskCoreException as e: + self.log(Level.SEVERE, "Error creating GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: ROUTES") + if self.writeDebugMsgs: self.log(Level.INFO, "Processing routes from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") for route in gpx.routes: - firstTimeThru = 0 - startingPoint = list() - endingPoint = list() + + geoWaypointList = TskGeoWaypointsUtil.GeoWaypointList() + for point in route.points: - # If first time in loop only populate starting point - if (firstTimeThru == 0): - startingPoint.append((point.latitude, point.longitude)) - firstTimeThru = 1 - else: - startingPoint.append((point.latitude, point.longitude)) - endingPoint.append((point.latitude, point.longitude)) + geoWaypointList.addPoint(GeoWaypoint(point.latitude, point.longitude, point.elevation, point.name)) - if (len(endingPoint) > 0): - # get length of ending point as this ensures that we have equal points to process. - for i in range(0,len(endingPoint) -1): - attributes = ArrayList() - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE) - - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), moduleName, startingPoint[i][0])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID(), moduleName, startingPoint[i][1])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID(), moduleName, endingPoint[i][0])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID(), moduleName, endingPoint[i][1])) - - art.addAttributes(attributes) - - try: - # Post the artifact to blackboard - skCase.getBlackboard().postArtifact(art, moduleName) - except Blackboard.BlackboardException as e: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" ) - else: - if (len(startingPoint) > 0): - attributes = ArrayList() - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE) - - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), moduleName, startingPoint[0][0])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID(), moduleName, startingPoint[0][1])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID(), moduleName, startingPoint[0][0])) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID(), moduleName, startingPoint[0][1])) - - art.addAttributes(attributes) - - try: - # Post the artifact to blackboard - skCase.getBlackboard().postArtifact(art, moduleName) - except Blackboard.BlackboardException as e: - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" ) - + try: + geoArtifactHelper.addRoute(None, None, geoWaypointList, None) + except Blackboard.BlackboardException as e: + self.log("Error posting GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + except TskCoreException as e: + self.log(Level.SEVERE, "Error creating GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) # Update the progress bar. progressBar.progress(fileCount) - if os.path.exists(fileName): - try: - os.remove(fileName) - if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE DELETED") - except: - self.log(Level.SEVERE, "GPX:\t" + "FILE NOT DELETED") # Post a message to the ingest messages inbox. - message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, "GPX Parser Data Source Ingest Module", "Found %d files" % fileCount) + message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, moduleName, "Processed %d files" % fileCount) IngestServices.getInstance().postMessage(message) return IngestModule.ProcessResult.OK;