diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED index 9b43833ee2..345d5a420e 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED @@ -7,8 +7,10 @@ AbstractSqlEamDb.cannotUpgrage.message=Currently selected database platform "{0} AbstractSqlEamDb.failedToReadMajorVersion.message=Failed to read schema version for Central Repository. AbstractSqlEamDb.failedToReadMinorVersion.message=Failed to read schema minor version for Central Repository. AbstractSqlEamDb.upgradeSchema.incompatible=The selected Central Repository is not compatible with the current version of the application, please upgrade the application if you wish to use this Central Repository. +CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database. CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'. CorrelationAttributeInstance.nullName.message=Database name is null. +CorrelationAttributeUtil.emailaddresses.text=Email Addresses CorrelationType.DOMAIN.displayName=Domains CorrelationType.EMAIL.displayName=Email Addresses CorrelationType.FILES.displayName=Files @@ -23,7 +25,6 @@ DataSourceUpdateService.serviceName.text=Update Central Repository Data Sources EamArtifactInstances.knownStatus.bad=Bad EamArtifactInstances.knownStatus.known=Known EamArtifactInstances.knownStatus.unknown=Unknown -EamArtifactUtil.emailaddresses.text=Email Addresses EamCase.title.caseDisplayName=Case Name: EamCase.title.caseNumber=Case Number: EamCase.title.caseUUID=Case UUID: diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java new file mode 100644 index 0000000000..70d6f99e37 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccount.java @@ -0,0 +1,137 @@ +/* + * 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 org.sleuthkit.datamodel.Account; + + +/** + * This class abstracts an Account as stored in the CR database. + */ +public final class CentralRepoAccount { + + // primary key in the Accounts table in CR database + private final long accountId; + + private final CentralRepoAccountType accountType; + // type specifc unique account id + private final String typeSpecificId; + + /** + * Encapsulates a central repo account type and the correlation type + * that it maps to. + */ + public static final class CentralRepoAccountType { + + // id is the primary key in the account_types table + private final int accountTypeId; + private final Account.Type acctType; + private final int correlationTypeId; + + CentralRepoAccountType(int acctTypeID, Account.Type acctType, int correlationTypeId) { + this.acctType = acctType; + this.correlationTypeId = correlationTypeId; + this.accountTypeId = acctTypeID; + } + + + /** + * @return the acctType + */ + public Account.Type getAcctType() { + return acctType; + } + + public int getCorrelationTypeId() { + return this.correlationTypeId; + } + + public int getAccountTypeId() { + return this.accountTypeId; + } + } + + public CentralRepoAccount(long accountId, CentralRepoAccountType accountType, String typeSpecificId) { + this.accountId = accountId; + this.accountType = accountType; + this.typeSpecificId = typeSpecificId; + } + + /** + * Gets unique identifier (assigned by a provider) for the account. Example + * includes an email address, a phone number, or a website username. + * + * @return type specific account id. + */ + public String getTypeSpecificId() { + return this.typeSpecificId; + } + + /** + * Gets the account type + * + * @return account type + */ + public CentralRepoAccountType getAccountType() { + return this.accountType; + } + + /** + * Gets the unique row id for this account in the database. + * + * @return unique row id. + */ + public long getAccountId() { + return this.accountId; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 43 * hash + (int) (this.accountId ^ (this.accountId >>> 32)); + hash = 43 * hash + (this.accountType != null ? this.accountType.hashCode() : 0); + hash = 43 * hash + (this.typeSpecificId != null ? this.typeSpecificId.hashCode() : 0); + 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 CentralRepoAccount other = (CentralRepoAccount) obj; + if (this.accountId != other.getAccountId()) { + return false; + } + if ((this.typeSpecificId == null) ? (other.getTypeSpecificId() != null) : !this.typeSpecificId.equals(other.getTypeSpecificId())) { + return false; + } + if (this.accountType != other.getAccountType() && (this.accountType == null || !this.accountType.equals(other.getAccountType()))) { + return false; + } + return true; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java new file mode 100755 index 0000000000..d2647965a5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbManager.java @@ -0,0 +1,407 @@ +/* + * 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.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.coreutils.Logger; + +/** + * Contains business logic for saving and validating settings for central repo + */ +public class CentralRepoDbManager { + + private static final Logger logger = Logger.getLogger(CentralRepoDbManager.class.getName()); + + private static final String CENTRAL_REPO_DB_NAME = "central_repository"; + + /** + * obtains the database connectivity for central repository + * + * @return the CentralRepository object to connect to + * @throws CentralRepoException + */ + private static CentralRepository obtainCentralRepository() throws CentralRepoException { + //get connection + try { + return CentralRepository.getInstance(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex); + onUpgradeError("Error updating central repository, unable to make connection", + Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } + + // will never be reached + return null; + } + + /** + * obtains central repository lock + * + * @param db the database connection + * @return the lock if acquired + * @throws CentralRepoException + */ + private static CoordinationService.Lock obtainCentralRepoLock(CentralRepository db) throws CentralRepoException { + try { + // This may return null if locking isn't supported, which is fine. It will + // throw an exception if locking is supported but we can't get the lock + // (meaning the database is in use by another user) + return db.getExclusiveMultiUserDbLock(); + //perform upgrade + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex); + onUpgradeError("Error updating central repository, unable to acquire exclusive lock", + Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } + + // will never be reached + return null; + } + + /** + * updates central repository schema if necessary + * + * @param db the database connectivity + * @param lock the acquired lock + * @throws CentralRepoException + */ + private static void updatedDbSchema(CentralRepository db, CoordinationService.Lock lock) throws CentralRepoException { + try { + db.upgradeSchema(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error updating central repository", ex); + onUpgradeError("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error updating central repository", ex); + onUpgradeError("Error updating central repository", + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } catch (IncompatibleCentralRepoException ex) { + logger.log(Level.SEVERE, "Error updating central repository", ex); + onUpgradeError("Error updating central repository", + ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } finally { + if (lock != null) { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.SEVERE, "Error releasing database lock", ex); + } + } + } + } + + /** + * Upgrade 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. + */ + @NbBundle.Messages(value = {"EamDbUtil.centralRepoDisabled.message= The Central Repository has been disabled.", "EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository.", "EamDbUtil.centralRepoConnectionFailed.message=Unable to connect to Central Repository.", "EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository."}) + public static void upgradeDatabase() throws CentralRepoException { + if (!CentralRepository.isEnabled()) { + return; + } + + CentralRepository db = obtainCentralRepository(); + + //get lock necessary for upgrade + if (db != null) { + CoordinationService.Lock lock = obtainCentralRepoLock(db); + updatedDbSchema(db, lock); + } else { + onUpgradeError("Unable to connect to database", + Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), null); + } + } + + private static void onUpgradeError(String message, String desc, Exception innerException) throws CentralRepoException { + // Disable the central repo and clear the current settings. + try { + if (null != CentralRepository.getInstance()) { + CentralRepository.getInstance().shutdownConnections(); + } + } catch (CentralRepoException ex2) { + logger.log(Level.SEVERE, "Error shutting down central repo connection pool", ex2); + } + CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.DISABLED.name()); + CentralRepoPlatforms.saveSelectedPlatform(); + if (innerException == null) { + throw new CentralRepoException(message, desc); + } else { + throw new CentralRepoException(message, desc, innerException); + } + } + + private DatabaseTestResult testingStatus; + private CentralRepoPlatforms selectedPlatform; + + private final PostgresCentralRepoSettings dbSettingsPostgres; + private final SqliteCentralRepoSettings dbSettingsSqlite; + + private boolean configurationChanged = false; + + public CentralRepoDbManager() { + dbSettingsPostgres = new PostgresCentralRepoSettings(); + 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; + } + } + + public PostgresCentralRepoSettings getDbSettingsPostgres() { + return dbSettingsPostgres; + } + + public SqliteCentralRepoSettings getDbSettingsSqlite() { + return dbSettingsSqlite; + } + + /** + * setup sqlite db 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; + dbSettingsSqlite.setupDefaultSettings(); + + // if db is not present, attempt to create it + DatabaseTestResult curStatus = testStatus(); + if (curStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) { + createDb(); + curStatus = testStatus(); + } + + // the only successful setup status is tested ok + if (curStatus != DatabaseTestResult.TESTEDOK) { + throw new CentralRepoException("Unable to successfully create sqlite database"); + } + + // if successfully got here, then save the settings + CentralRepoDbUtil.setUseCentralRepo(true); + saveNewCentralRepo(); + } + + /** + * 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 + */ + 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 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); + } + } + + public boolean createDb() throws CentralRepoException { + boolean result = false; + boolean dbCreated = true; + + CentralRepoDbSettings selectedDbSettings = getSelectedSettings(); + + if (!selectedDbSettings.verifyDatabaseExists()) { + dbCreated = selectedDbSettings.createDatabase(); + } + if (dbCreated) { + try { + RdbmsCentralRepoFactory centralRepoSchemaFactory = getDbFactory(); + + result = centralRepoSchemaFactory.initializeDatabaseSchema() + && centralRepoSchemaFactory.insertDefaultDatabaseContent(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Unable to create database for central repository with settings " + selectedDbSettings, ex); + throw ex; + } + } + if (!result) { + // Remove the incomplete database + if (dbCreated) { + // RAMAN TBD: migrate deleteDatabase() to RdbmsCentralRepoFactory + selectedDbSettings.deleteDatabase(); + } + + String schemaError = "Unable to initialize database schema or insert contents into central repository."; + logger.severe(schemaError); + throw new CentralRepoException(schemaError); + } + + testingStatus = DatabaseTestResult.TESTEDOK; + return true; + } + + /** + * 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 { + /** + * We have to shutdown the previous platform's connection pool first; + * assuming it wasn't DISABLED. This will close any existing idle + * connections. + * + * The next use of an EamDb API method will start a new connection pool + * using those new settings. + */ + try { + CentralRepository previousDbManager = CentralRepository.getInstance(); + if (null != previousDbManager) { + // NOTE: do not set/save the seleted platform before calling this. + CentralRepository.getInstance().shutdownConnections(); + } + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Failed to close database connections in previously selected platform.", ex); // NON-NLS + throw ex; + } + + // 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(); + + CentralRepoDbSettings 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) { + try { + logger.info("Creating central repo db with settings: " + selectedDbSettings); + CentralRepository.getInstance().updateSettings(); + configurationChanged = true; + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, Bundle.CentralRepoDbManager_connectionErrorMsg_text(), ex); //NON-NLS + return; + } + } + } + + public DatabaseTestResult getStatus() { + return testingStatus; + } + + public CentralRepoPlatforms getSelectedPlatform() { + return selectedPlatform; + } + + public void clearStatus() { + testingStatus = DatabaseTestResult.UNTESTED; + } + + public void setSelectedPlatform(CentralRepoPlatforms newSelected) { + selectedPlatform = newSelected; + testingStatus = DatabaseTestResult.UNTESTED; + } + + /** + * Tests whether or not the database settings are valid. + * + * @return True or false. + */ + 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); + } + + return true; + } + + 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; + } + } + + return testingStatus; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java new file mode 100755 index 0000000000..f9bf520654 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbSettings.java @@ -0,0 +1,55 @@ +/* + * 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/CentralRepoDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java index 4ba8ac579e..60701b44e5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java @@ -25,9 +25,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService; -import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION; @@ -166,80 +163,6 @@ public class CentralRepoDbUtil { return true; } - /** - * Upgrade 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. - */ - @Messages({"EamDbUtil.centralRepoDisabled.message= The Central Repository has been disabled.", - "EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository.", - "EamDbUtil.centralRepoConnectionFailed.message=Unable to connect to Central Repository.", - "EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository."}) - public static void upgradeDatabase() throws CentralRepoException { - if (!CentralRepository.isEnabled()) { - return; - } - CentralRepository db = null; - CoordinationService.Lock lock = null; - - //get connection - try { - try { - db = CentralRepository.getInstance(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex); - throw new CentralRepoException("Error updating central repository, unable to make connection", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); - } - //get lock necessary for upgrade - if (db != null) { - try { - // This may return null if locking isn't supported, which is fine. It will - // throw an exception if locking is supported but we can't get the lock - // (meaning the database is in use by another user) - lock = db.getExclusiveMultiUserDbLock(); - //perform upgrade - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex); - throw new CentralRepoException("Error updating central repository, unable to acquire exclusive lock", Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); - } - - try { - db.upgradeSchema(); - } catch (CentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository", ex); - throw new CentralRepoException("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository", ex); - throw new CentralRepoException("Error updating central repository", Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); - } catch (IncompatibleCentralRepoException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository", ex); - throw new CentralRepoException("Error updating central repository", ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); - } finally { - if (lock != null) { - try { - lock.release(); - } catch (CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, "Error releasing database lock", ex); - } - } - } - } else { - throw new CentralRepoException("Unable to connect to database", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message()); - } - } catch (CentralRepoException ex) { - // Disable the central repo and clear the current settings. - try { - if (null != CentralRepository.getInstance()) { - CentralRepository.getInstance().shutdownConnections(); - } - } catch (CentralRepoException ex2) { - LOGGER.log(Level.SEVERE, "Error shutting down central repo connection pool", ex2); - } - CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.DISABLED.name()); - CentralRepoPlatforms.saveSelectedPlatform(); - throw ex; - } - } /** * Get the default organization name diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index fe54161762..904712c73c 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Set; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; /** @@ -808,8 +809,32 @@ public interface CentralRepository { /** * Returns list of all correlation types. * - * @return list of Correlation types + * @return list of Correlation types * @throws CentralRepoException */ List getCorrelationTypes() throws CentralRepoException; + + + /** + * Get account type by type name. + * + * @param accountTypeName account type name to look for + * @return CR account type + * @throws CentralRepoException + */ + CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException; + + /** + * Get an account from the accounts table matching the given type/ID. + * Inserts a row if one doesn't exists. + * + * @param crAccountType CR account type to look for or create + * @param accountUniqueID type specific unique account id + * @return CR account + * + * @throws CentralRepoException + */ + CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException; + + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index 12fd9eac04..1ab978bc84 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -50,6 +50,7 @@ public class CorrelationAttributeInstance implements Serializable { private String comment; private TskData.FileKnown knownStatus; private Long objectId; + private Long accountId; public CorrelationAttributeInstance( CorrelationAttributeInstance.Type correlationType, @@ -73,6 +74,20 @@ public class CorrelationAttributeInstance implements Serializable { String comment, TskData.FileKnown knownStatus, Long fileObjectId + ) throws CentralRepoException, CorrelationAttributeNormalizationException { + this(type, value, -1, eamCase, eamDataSource, filePath, comment, knownStatus, fileObjectId, (long)-1); + } + CorrelationAttributeInstance( + Type type, + String value, + int instanceId, + CorrelationCase eamCase, + CorrelationDataSource eamDataSource, + String filePath, + String comment, + TskData.FileKnown knownStatus, + Long fileObjectId, + Long accountId ) throws CentralRepoException, CorrelationAttributeNormalizationException { if (filePath == null) { throw new CentralRepoException("file path is null"); @@ -88,6 +103,7 @@ public class CorrelationAttributeInstance implements Serializable { this.comment = comment; this.knownStatus = knownStatus; this.objectId = fileObjectId; + this.accountId = accountId; } public Boolean equals(CorrelationAttributeInstance otherInstance) { @@ -98,7 +114,8 @@ public class CorrelationAttributeInstance implements Serializable { && (this.getCorrelationDataSource().equals(otherInstance.getCorrelationDataSource())) && (this.getFilePath().equals(otherInstance.getFilePath())) && (this.getKnownStatus().equals(otherInstance.getKnownStatus())) - && (this.getComment().equals(otherInstance.getComment()))); + && (this.getComment().equals(otherInstance.getComment())) + && (this.getAccountId().equals(otherInstance.getAccountId()))); } @Override @@ -106,6 +123,7 @@ public class CorrelationAttributeInstance implements Serializable { return this.getID() + this.getCorrelationCase().getCaseUUID() + this.getCorrelationDataSource().getDeviceID() + + this.getAccountId() + this.getFilePath() + this.getCorrelationType().toString() + this.getCorrelationValue() @@ -210,6 +228,24 @@ public class CorrelationAttributeInstance implements Serializable { return objectId; } + /** + * Get the accountId of the account associated with the correlation + * attribute. + * + * @return the accountId of the account + */ + public Long getAccountId() { + return accountId; + } + + /** + * Set the accountId of the account associated with this correlation + * attribute. + */ + void setAccountId(Long accountId) { + this.accountId = accountId; + } + // Type ID's for Default Correlation Types public static final int FILES_TYPE_ID = 0; public static final int DOMAIN_TYPE_ID = 1; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java index 4e5811a0c8..f68dab484a 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java @@ -76,11 +76,23 @@ final public class CorrelationAttributeNormalizer { return normalizeIccid(trimmedData); default: - final String errorMessage = String.format( - "Validator function not found for attribute type: %s", - attributeType.getDisplayName()); - throw new CorrelationAttributeNormalizationException(errorMessage); - } + try { + // If the atttribute is not one of the above + // but is one of the other default correlation types, then let the data go as is + List defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes(); + for (CorrelationAttributeInstance.Type defaultCorrelationType : defaultCorrelationTypes) { + if (defaultCorrelationType.getId() == attributeType.getId()) { + return trimmedData; + } + } + final String errorMessage = String.format( + "Validator function not found for attribute type: %s", + attributeType.getDisplayName()); + throw new CorrelationAttributeNormalizationException(errorMessage); + } catch (CentralRepoException ex) { + throw new CorrelationAttributeNormalizationException("Failed to get default correlation types.", ex); + } + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index 814169ef85..04aa6fc16e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -24,6 +24,7 @@ import java.util.logging.Level; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -222,6 +223,8 @@ public class CorrelationAttributeUtil { /** * Makes a correlation attribute instance for an account artifact. + * + * Also creates an account in the CR DB if it doesn't exist. * * IMPORTANT: The correlation attribute instance is NOT added to the central * repository by this method. @@ -239,13 +242,31 @@ public class CorrelationAttributeUtil { * * @return The correlation attribute instance. */ - private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact) { - // RAMAN TODO: Convert TSK_ACCOUNT_TYPE attribute to correlation attribute type - // RAMAN TODO: Extract TSK_ID as value -// CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, corrAttrValue); -// if (corrAttr != null) { -// corrAttrInstances.add(corrAttr); -// } + private static void makeCorrAttrFromAcctArtifact(List corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException { + + // Get the account type from the artifact + BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); + String accountTypeStr = accountTypeAttribute.getValueString(); + + // Get the corresponding CentralRepoAccountType from the database. + CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + + int corrTypeId = crAccountType.getCorrelationTypeId(); + CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); + + // Get the account identifier + BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID)); + String accountIdStr = accountIdAttribute.getValueString(); + + // add/get the account and get its accountId. + CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr); + + CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr); + if (corrAttr != null) { + // set the account_id in correlation attribute + corrAttr.setAccountId(crAccount.getAccountId()); + corrAttrInstances.add(corrAttr); + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java new file mode 100755 index 0000000000..27a7b16278 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DatabaseTestResult.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * 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; +} + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java index 10204e0ffe..476b67dbc2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresCentralRepoSettings.java @@ -40,7 +40,7 @@ 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 { +public final class PostgresCentralRepoSettings implements CentralRepoDbSettings { private final static Logger LOGGER = Logger.getLogger(PostgresCentralRepoSettings.class.getName()); private final static String DEFAULT_HOST = ""; // NON-NLS @@ -64,6 +64,12 @@ public final class PostgresCentralRepoSettings { loadSettings(); } + @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()) { @@ -187,6 +193,7 @@ public final class PostgresCentralRepoSettings { * * @return true if successfull connection, else false. */ + @Override public boolean verifyConnection() { Connection conn = getEphemeralConnection(true); if (null == conn) { @@ -203,6 +210,7 @@ public final class PostgresCentralRepoSettings { * * @return true if exists, else false */ + @Override public boolean verifyDatabaseExists() { Connection conn = getEphemeralConnection(true); if (null == conn) { @@ -236,6 +244,7 @@ public final class PostgresCentralRepoSettings { * * @return true if successful connection, else false. */ + @Override public boolean verifyDatabaseSchema() { Connection conn = getEphemeralConnection(false); if (null == conn) { @@ -248,6 +257,7 @@ public final class PostgresCentralRepoSettings { return result; } + @Override public boolean createDatabase() { Connection conn = getEphemeralConnection(true); if (null == conn) { @@ -269,6 +279,7 @@ public final class PostgresCentralRepoSettings { } + @Override public boolean deleteDatabase() { Connection conn = getEphemeralConnection(true); if (null == conn) { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index abbae1c867..93f6268979 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -41,12 +41,15 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import org.apache.commons.lang3.tuple.Pair; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil.updateSchemaVersion; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.TskData; @@ -71,6 +74,12 @@ abstract class RdbmsCentralRepo implements CentralRepository { private final Map> bulkArtifacts; private static final int CASE_CACHE_TIMEOUT = 5; private static final int DATA_SOURCE_CACHE_TIMEOUT = 5; + private static final int ACCOUNTS_CACHE_TIMEOUT = 5; + private static final Cache accountTypesCache = CacheBuilder.newBuilder().build(); + private static final Cache, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder() + .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). + build(); + private static final Cache typeCache = CacheBuilder.newBuilder().build(); private static final Cache caseCacheByUUID = CacheBuilder.newBuilder() .expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES). @@ -993,22 +1002,22 @@ abstract class RdbmsCentralRepo implements CentralRepository { public void addArtifactInstance(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { checkAddArtifactInstanceNulls(eamArtifact); - Connection conn = connect(); + - PreparedStatement preparedStatement = null; + // @@@ We should cache the case and data source IDs in memory String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); String sql = "INSERT INTO " + tableName - + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) " - + "VALUES (?, ?, ?, ?, ?, ?, ?) " + + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id, account_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + getConflictClause(); - try { - preparedStatement = conn.prepareStatement(sql); - + try (Connection conn = connect(); + PreparedStatement preparedStatement = conn.prepareStatement(sql);) { + if (!eamArtifact.getCorrelationValue().isEmpty()) { preparedStatement.setInt(1, eamArtifact.getCorrelationCase().getID()); preparedStatement.setInt(2, eamArtifact.getCorrelationDataSource().getID()); @@ -1022,18 +1031,163 @@ abstract class RdbmsCentralRepo implements CentralRepository { preparedStatement.setString(6, eamArtifact.getComment()); } preparedStatement.setLong(7, eamArtifact.getFileObjectId()); + + if (eamArtifact.getAccountId() >= 0) { + preparedStatement.setLong(8, eamArtifact.getAccountId()); + } else { + preparedStatement.setNull(8, Types.INTEGER); + } preparedStatement.executeUpdate(); } } catch (SQLException ex) { throw new CentralRepoException("Error inserting new artifact into artifacts table.", ex); // NON-NLS - } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); - CentralRepoDbUtil.closeConnection(conn); - } + } } + /** + * Gets the Central Repo account for the given account type and account ID. + * Create a new account first, if one doesn't exist + * + * @param accountType account type + * @param accountUniqueID unique account identifier + * + * @return A matching account, either existing or newly created. + * + * @throws TskCoreException exception thrown if a critical error occurs + * within TSK core + */ + @Override + public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { + // Get the account fom the accounts table + CentralRepoAccount account = getAccount(crAccountType, accountUniqueID); + + // account not found in the table, create it + if (null == account) { + + String query = "INSERT INTO accounts (account_type_id, account_unique_identifier) " + + "VALUES ( " + crAccountType.getAccountTypeId() + ", '" + + accountUniqueID + "' )"; + + try (Connection connection = connect(); + Statement s = connection.createStatement();) { + + s.execute(query); + // get the account from the db - should exist now. + account = getAccount(crAccountType, accountUniqueID); + } catch (SQLException ex) { + throw new CentralRepoException("Error adding an account to CR database.", ex); + } + } + + return account; + } + + + @Override + public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException { + try { + return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName)); + } catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) { + throw new CentralRepoException("Error looking up CR account type in cache.", ex); + } + } + + + /** + * Gets the CR account type for the specified type name. + * + * @param accountTypeName account type name to look for + * @return CR account type + * + * @throws CentralRepoException + */ + private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { + + String sql = "SELECT * FROM account_types WHERE type_name = ?"; + try ( Connection conn = connect(); + PreparedStatement preparedStatement = conn.prepareStatement(sql);) { + + preparedStatement.setString(1, accountTypeName); + try (ResultSet resultSet = preparedStatement.executeQuery();) { + if (resultSet.next()) { + Account.Type acctType = new Account.Type(accountTypeName, resultSet.getString("display_name")); + CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id")); + accountTypesCache.put(accountTypeName, crAccountType); + return crAccountType; + } else { + throw new CentralRepoException("Failed to find entry for account type = " + accountTypeName); + } + } + } catch (SQLException ex) { + throw new CentralRepoException("Error getting correlation type by id.", ex); // NON-NLS + } + } + + /** + * Get the CR account with the given account type and the unique account identifier. + * Looks in the cache first. + * If not found in cache, reads from the database and saves in cache. + * + * Returns null if the account is not found in the cache and not in the database. + * + * @param crAccountType account type to look for + * @param accountUniqueID unique account id + * @return CentralRepoAccount for the give type/id. May return null if not found. + * + * @throws CentralRepoException + */ + private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { + + CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID)); + if (crAccount == null) { + crAccount = getCRAccountFromDb(crAccountType, accountUniqueID); + if (crAccount != null) { + accountsCache.put(Pair.of(crAccountType, accountUniqueID), crAccount); + } + } + + return crAccount; + } + + + /** + * Get the Account with the given account type and account identifier, + * from the database. + * + * @param accountType account type + * @param accountUniqueID unique account identifier + * + * @return Account, returns NULL is no matching account found + * + * @throws TskCoreException exception thrown if a critical error occurs + * within TSK core + */ + private CentralRepoAccount getCRAccountFromDb(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException { + + CentralRepoAccount account = null; + + String sql = "SELECT * FROM accounts WHERE account_type_id = ? AND account_unique_identifier = ?"; + try ( Connection connection = connect(); + PreparedStatement preparedStatement = connection.prepareStatement(sql);) { + + preparedStatement.setInt(1, crAccountType.getAccountTypeId()); + preparedStatement.setString(2, accountUniqueID); + + try (ResultSet resultSet = preparedStatement.executeQuery();) { + if (resultSet.next()) { + account = new CentralRepoAccount(resultSet.getInt("id"), crAccountType, resultSet.getString("account_unique_identifier")); //NON-NLS + } + } + } catch (SQLException ex) { + throw new CentralRepoException("Error getting account type id", ex); + } + + return account; + } + + private void checkAddArtifactInstanceNulls(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { if (eamArtifact == null) { throw new CentralRepoException("CorrelationAttribute is null"); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java index 25b71e1dcc..675a164972 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteCentralRepoSettings.java @@ -37,10 +37,10 @@ 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 { +public final class SqliteCentralRepoSettings implements CentralRepoDbSettings { + public final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS private final static Logger LOGGER = Logger.getLogger(SqliteCentralRepoSettings.class.getName()); - private final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS private final static String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS @@ -80,6 +80,18 @@ public final class SqliteCentralRepoSettings { 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 + */ + public void setupDefaultSettings() { + dbName = DEFAULT_DBNAME; + dbDirectory = DEFAULT_DBDIRECTORY; + } public void saveSettings() { createDbDirectory(); @@ -103,6 +115,13 @@ public final class SqliteCentralRepoSettings { return (!dbFile.isDirectory()); } + + @Override + public boolean verifyDatabaseExists() { + return dbDirectoryExists(); + } + + /** * Verify that the db directory path exists. * @@ -122,6 +141,16 @@ public final class SqliteCentralRepoSettings { } + /** + * creates database directory for sqlite database if it does not exist + * @return whether or not operation occurred successfully + */ + @Override + public boolean createDatabase() { + return createDbDirectory(); + } + + /** * Create the db directory if it does not exist. * @@ -325,5 +354,4 @@ public final class SqliteCentralRepoSettings { String getJDBCBaseURI() { return JDBC_BASE_URI; } - } 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 b7c3df2c53..cd654a5b37 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Bundle.properties-MERGED @@ -7,3 +7,6 @@ IngestEventsListener.prevCount.text=Number of previous {0}: {1} 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.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 272a9047bb..208f201a30 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -18,15 +18,20 @@ */ package org.sleuthkit.autopsy.centralrepository.eventlisteners; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.logging.Level; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import org.openide.modules.ModuleInstall; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; 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.CentralRepoDbUtil; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; /** * Install event listeners during module initialization @@ -51,26 +56,109 @@ public class Installer extends ModuleInstall { super(); } - @NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository disabled"}) + @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." + }) @Override public void restored() { Case.addPropertyChangeListener(pcl); ieListener.installListeners(); - // Perform the database upgrade and inform the user if it fails - try { - CentralRepoDbUtil.upgradeDatabase(); - } catch (CentralRepoException ex) { - if (RuntimeProperties.runningWithGUI()) { - WindowManager.getDefault().invokeWhenUIReady(() -> { - JOptionPane.showMessageDialog(null, - ex.getUserMessage(), - NbBundle.getMessage(this.getClass(), - "Installer.centralRepoUpgradeFailed.title"), - JOptionPane.ERROR_MESSAGE); - }); + + 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")); + // if it has been previously set up and is in use, mark as previously initialized and save the settings + if (prevRepo) { + initialized = true; + ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true"); } } + + // if central repository hasn't been previously initialized, initialize it + if (!initialized) { + // if running with a GUI, prompt the user + if (RuntimeProperties.runningWithGUI()) { + try { + SwingUtilities.invokeAndWait(() -> { + try { + 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(); + } + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); + + reportUpgradeError(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); + } + } // if no GUI, just initialize + else { + try { + setupDefaultSqlite(); + } catch (CentralRepoException ex) { + LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex); + + reportUpgradeError(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 { + CentralRepoDbManager manager = new CentralRepoDbManager(); + manager.setupDefaultSqliteDb(); + } + + @NbBundle.Messages({ "Installer.centralRepoUpgradeFailed.title=Central repository disabled" }) + private void reportUpgradeError(CentralRepoException ex) { + 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 diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java index ec00dffe88..e561aeeff6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/CentralRepoIngestModule.java @@ -67,7 +67,7 @@ final class CentralRepoIngestModule implements FileIngestModule { private static final String MODULE_NAME = CentralRepoIngestModuleFactory.getModuleName(); static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = true; - static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = true; + static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = false; static final boolean DEFAULT_CREATE_CR_PROPERTIES = true; private final static Logger logger = Logger.getLogger(CentralRepoIngestModule.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 519a7b2453..25289da2af 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -35,14 +35,15 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; 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.CentralRepoDbManager; import org.sleuthkit.autopsy.corecomponents.TextPrompt; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; -import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms.SQLITE; -import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresCentralRepoSettings; +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; @@ -54,17 +55,12 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory public class EamDbSettingsDialog extends JDialog { private static final Logger logger = Logger.getLogger(EamDbSettingsDialog.class.getName()); - private static final String CENTRAL_REPO_DB_NAME = "central_repository"; - private static final String CENTRAL_REPO_SQLITE_EXT = ".db"; + private static final long serialVersionUID = 1L; private final Collection textBoxes; private final TextBoxChangedListener textBoxChangedListener; + private final CentralRepoDbManager manager = new CentralRepoDbManager(); - private final PostgresCentralRepoSettings dbSettingsPostgres; - private final SqliteCentralRepoSettings dbSettingsSqlite; - private DatabaseTestResult testingStatus; - private CentralRepoPlatforms selectedPlatform; - private boolean configurationChanged = false; /** * Creates new form EamDbSettingsDialog @@ -73,7 +69,6 @@ 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() { super((JFrame) WindowManager.getDefault().getMainWindow(), Bundle.EamDbSettingsDialog_title_text(), @@ -81,12 +76,6 @@ public class EamDbSettingsDialog extends JDialog { textBoxes = new ArrayList<>(); textBoxChangedListener = new TextBoxChangedListener(); - dbSettingsPostgres = new PostgresCentralRepoSettings(); - dbSettingsSqlite = new SqliteCentralRepoSettings(); - selectedPlatform = CentralRepoPlatforms.getSelectedPlatform(); - if (selectedPlatform == null || selectedPlatform.equals(CentralRepoPlatforms.DISABLED)) { - selectedPlatform = CentralRepoPlatforms.POSTGRESQL; - } initComponents(); fcDatabasePath.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); @@ -98,7 +87,7 @@ public class EamDbSettingsDialog extends JDialog { if (pathname.isDirectory()) { return true; } - return pathname.getName().toLowerCase().equals((CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT).toLowerCase()); + return pathname.getName().equalsIgnoreCase(SqliteCentralRepoSettings.DEFAULT_DBNAME); } @Override @@ -106,12 +95,77 @@ public class EamDbSettingsDialog extends JDialog { return "Directories and Central Repository databases"; } }); - cbDatabaseType.setSelectedItem(selectedPlatform); + cbDatabaseType.setSelectedItem(manager.getSelectedPlatform()); customizeComponents(); valid(); display(); } + + + + /** + * 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 + */ + @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.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() { + if (manager.getStatus() == DatabaseTestResult.CONNECTION_FAILED) { + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_message(), + Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_title(), + JOptionPane.WARNING_MESSAGE); + } else if (manager.getStatus() == DatabaseTestResult.SCHEMA_INVALID) { + // There's an existing database or file, but it's not in our format. + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), + Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_message(), + Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_title(), + JOptionPane.WARNING_MESSAGE); + } else if (manager.getStatus() == DatabaseTestResult.DB_DOES_NOT_EXIST) { + //database doesn't exist. do you want to create? + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), + 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(); + } + } + + return (manager.getStatus() == DatabaseTestResult.TESTEDOK); + } + /** * This method is called from within the constructor to initialize the form. @@ -359,21 +413,16 @@ public class EamDbSettingsDialog extends JDialog { private void customizeComponents() { setTextPrompts(); setTextBoxListeners(); - switch (selectedPlatform) { - case SQLITE: - testingStatus = DatabaseTestResult.UNTESTED; - updatePostgresFields(false); - updateSqliteFields(true); - break; - default: - POSTGRESQL: - testingStatus = DatabaseTestResult.UNTESTED; - updatePostgresFields(true); - updateSqliteFields(false); - break; - + manager.clearStatus(); + if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) { + updatePostgresFields(false); + updateSqliteFields(true); } - displayDatabaseSettings(selectedPlatform.equals(CentralRepoPlatforms.POSTGRESQL)); + else { + updatePostgresFields(true); + updateSqliteFields(false); + } + displayDatabaseSettings(CentralRepoPlatforms.POSTGRESQL.equals(manager.getSelectedPlatform())); } private void display() { @@ -383,7 +432,7 @@ public class EamDbSettingsDialog extends JDialog { @Messages({"EamDbSettingsDialog.chooserPath.failedToGetDbPathMsg=Selected database path is invalid. Try again."}) private void bnDatabasePathFileOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDatabasePathFileOpenActionPerformed - fcDatabasePath.setSelectedFile(new File(dbSettingsSqlite.getDbDirectory())); + fcDatabasePath.setSelectedFile(new File(manager.getDbSettingsSqlite().getDbDirectory())); if (fcDatabasePath.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File databaseFile = fcDatabasePath.getSelectedFile(); if (databaseFile.isFile()) { @@ -399,110 +448,38 @@ public class EamDbSettingsDialog extends JDialog { } }//GEN-LAST:event_bnDatabasePathFileOpenActionPerformed - private void testDbSettings() { - switch (selectedPlatform) { - case 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; - } - break; - case 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; - } - break; - } - + @NbBundle.Messages({"EamDbSettingsDialog.okButton.errorTitle.text=Restart Required.", + "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)); + manager.testStatus(); valid(); - } - - @Messages({"EamDbSettingsDialog.okButton.createDbError.title=Unable to Create Database", - "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."}) - private void createDb() { - boolean result = false; - boolean dbCreated = true; - switch (selectedPlatform) { - case POSTGRESQL: - if (!dbSettingsPostgres.verifyDatabaseExists()) { - dbCreated = dbSettingsPostgres.createDatabase(); - } - if (dbCreated) { - try { - RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsPostgres); - - result = centralRepoSchemaFactory.initializeDatabaseSchema() - && centralRepoSchemaFactory.insertDefaultDatabaseContent(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Unable to initialize database schema or insert contents into Postgres central repository.", ex); - } - } - if (!result) { - // Remove the incomplete database - if (dbCreated) { - dbSettingsPostgres.deleteDatabase(); - } - - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - Bundle.EamDbSettingsDialog_okButton_createPostgresDbError_message(), - Bundle.EamDbSettingsDialog_okButton_createDbError_title(), - JOptionPane.WARNING_MESSAGE); - logger.severe("Unable to initialize database schema or insert contents into central repository."); - return; - } - break; - case SQLITE: - if (!dbSettingsSqlite.dbDirectoryExists()) { - dbCreated = dbSettingsSqlite.createDbDirectory(); - } - if (dbCreated) { - try { - RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsSqlite); - result = centralRepoSchemaFactory.initializeDatabaseSchema() - && centralRepoSchemaFactory.insertDefaultDatabaseContent(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Unable to initialize database schema or insert contents into SQLite central repository.", ex); - } - - } - if (!result) { - if (dbCreated) { - dbSettingsSqlite.deleteDatabase(); - } - - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - Bundle.EamDbSettingsDialog_okButton_createSQLiteDbError_message(), - Bundle.EamDbSettingsDialog_okButton_createDbError_title(), - JOptionPane.WARNING_MESSAGE); - logger.severe("Unable to initialize database schema or insert contents into central repository."); - return; - } - break; + + boolean testedOk = promptTestStatusWarnings(); + if (!testedOk) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; } - testingStatus = DatabaseTestResult.TESTEDOK; - valid(); - } + + try{ + manager.saveNewCentralRepo(); + } + catch (CentralRepoException e) { + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog(this, + Bundle.EamDbSettingsDialog_okButton_errorMsg_text(), + Bundle.EamDbSettingsDialog_okButton_errorTitle_text(), + JOptionPane.WARNING_MESSAGE); + }); + } + + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + dispose(); + }//GEN-LAST:event_bnOkActionPerformed + + /** * Returns if changes to the central repository configuration were * successfully applied @@ -510,128 +487,22 @@ public class EamDbSettingsDialog extends JDialog { * @return true if the database configuration was successfully changed false * if it was not */ - boolean wasConfigurationChanged() { - return configurationChanged; + public boolean wasConfigurationChanged() { + return manager.wasConfigurationChanged(); } - @Messages({"EamDbSettingsDialog.okButton.errorTitle.text=Restart Required.", - "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.", - "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."}) - private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - testDbSettings(); - if (testingStatus == DatabaseTestResult.CONNECTION_FAILED) { - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_message(), - Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_title(), - JOptionPane.WARNING_MESSAGE); - } else if (testingStatus == DatabaseTestResult.SCHEMA_INVALID) { - // There's an existing database or file, but it's not in our format. - JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), - Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_message(), - Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_title(), - JOptionPane.WARNING_MESSAGE); - } else if (testingStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) { - //database doesn't exist do you want to create - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), - Bundle.EamDbSettingsDialog_okButton_createDbDialog_message(), - Bundle.EamDbSettingsDialog_okButton_createDbDialog_title(), - JOptionPane.YES_NO_OPTION)) { - createDb(); - } - } - - if (testingStatus != DatabaseTestResult.TESTEDOK) { - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; - } - - /** - * We have to shutdown the previous platform's connection pool first; - * assuming it wasn't DISABLED. This will close any existing idle - * connections. - * - * The next use of an EamDb API method will start a new connection pool - * using those new settings. - */ - try { - CentralRepository previousDbManager = CentralRepository.getInstance(); - if (null != previousDbManager) { - // NOTE: do not set/save the seleted platform before calling this. - CentralRepository.getInstance().shutdownConnections(); - } - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "Failed to close database connections in previously selected platform.", ex); // NON-NLS - SwingUtilities.invokeLater(() -> { - JOptionPane.showMessageDialog(this, - Bundle.EamDbSettingsDialog_okButton_errorMsg_text(), - Bundle.EamDbSettingsDialog_okButton_errorTitle_text(), - JOptionPane.WARNING_MESSAGE); - }); - } - - // 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(); - - switch (selectedPlatform) { - case POSTGRESQL: - // save the new PostgreSQL settings - dbSettingsPostgres.saveSettings(); - // Load those newly saved settings into the postgres db manager instance - // in case we are still using the same instance. - try { - CentralRepository.getInstance().updateSettings(); - configurationChanged = true; - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, Bundle.EamDbSettingsDialog_okButton_connectionErrorMsg_text(), ex); //NON-NLS - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; - } - - break; - case SQLITE: - // save the new SQLite settings - dbSettingsSqlite.saveSettings(); - // Load those newly saved settings into the sqlite db manager instance - // in case we are still using the same instance. - try { - CentralRepository.getInstance().updateSettings(); - configurationChanged = true; - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, Bundle.EamDbSettingsDialog_okButton_connectionErrorMsg_text(), ex); //NON-NLS - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; - } - break; - case DISABLED: - break; - } - - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - dispose(); - }//GEN-LAST:event_bnOkActionPerformed - private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed dispose(); }//GEN-LAST:event_bnCancelActionPerformed private void cbDatabaseTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDatabaseTypeActionPerformed - selectedPlatform = (CentralRepoPlatforms) cbDatabaseType.getSelectedItem(); + manager.setSelectedPlatform((CentralRepoPlatforms) cbDatabaseType.getSelectedItem()); customizeComponents(); }//GEN-LAST:event_cbDatabaseTypeActionPerformed private void updateFullDbPath() { - dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT); + dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + SqliteCentralRepoSettings.DEFAULT_DBNAME); dataBaseFileTextArea.setCaretPosition(dataBaseFileTextArea.getText().length()); } @@ -669,13 +540,13 @@ public class EamDbSettingsDialog extends JDialog { } private void updatePostgresFields(boolean enabled) { - tbDbHostname.setText(enabled ? dbSettingsPostgres.getHost() : ""); + tbDbHostname.setText(enabled ? manager.getDbSettingsPostgres().getHost() : ""); tbDbHostname.setEnabled(enabled); - tbDbPort.setText(enabled ? Integer.toString(dbSettingsPostgres.getPort()) : ""); + tbDbPort.setText(enabled ? Integer.toString(manager.getDbSettingsPostgres().getPort()) : ""); tbDbPort.setEnabled(enabled); - tbDbUsername.setText(enabled ? dbSettingsPostgres.getUserName() : ""); + tbDbUsername.setText(enabled ? manager.getDbSettingsPostgres().getUserName() : ""); tbDbUsername.setEnabled(enabled); - jpDbPassword.setText(enabled ? dbSettingsPostgres.getPassword() : ""); + jpDbPassword.setText(enabled ? manager.getDbSettingsPostgres().getPassword() : ""); jpDbPassword.setEnabled(enabled); } @@ -686,7 +557,7 @@ public class EamDbSettingsDialog extends JDialog { * @param enabled */ private void updateSqliteFields(boolean enabled) { - tfDatabasePath.setText(enabled ? dbSettingsSqlite.getDbDirectory() : ""); + tfDatabasePath.setText(enabled ? manager.getDbSettingsSqlite().getDbDirectory() : ""); tfDatabasePath.setEnabled(enabled); bnDatabasePathFileOpen.setEnabled(enabled); } @@ -739,22 +610,15 @@ public class EamDbSettingsDialog extends JDialog { @Messages({"EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database."}) private boolean databaseFieldsArePopulated() { boolean result = true; - switch (selectedPlatform) { - case POSTGRESQL: - result = !tbDbHostname.getText().trim().isEmpty() - && !tbDbPort.getText().trim().isEmpty() - // && !tbDbName.getText().trim().isEmpty() - && !tbDbUsername.getText().trim().isEmpty() - && 0 < jpDbPassword.getPassword().length; - - break; - - case SQLITE: - result = !tfDatabasePath.getText().trim().isEmpty(); - break; + if (manager.getSelectedPlatform() == CentralRepoPlatforms.POSTGRESQL) { + result = !tbDbHostname.getText().trim().isEmpty() + && !tbDbPort.getText().trim().isEmpty() + // && !tbDbName.getText().trim().isEmpty() + && !tbDbUsername.getText().trim().isEmpty() + && 0 < jpDbPassword.getPassword().length; } - - if (!result) { + else if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) { + result = !tfDatabasePath.getText().trim().isEmpty(); } return result; @@ -770,66 +634,6 @@ public class EamDbSettingsDialog extends JDialog { && databaseSettingsAreValid(); } - /** - * Tests whether or not the database settings are valid. - * - * @return True or false. - */ - private boolean databaseSettingsAreValid() { - boolean result = true; - StringBuilder guidanceText = new StringBuilder(); - - switch (selectedPlatform) { - case POSTGRESQL: - try { - dbSettingsPostgres.setHost(tbDbHostname.getText().trim()); - } catch (CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - - try { - dbSettingsPostgres.setPort(Integer.valueOf(tbDbPort.getText().trim())); - } catch (NumberFormatException | CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - - try { - dbSettingsPostgres.setDbName(CENTRAL_REPO_DB_NAME); - } catch (CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - - try { - dbSettingsPostgres.setUserName(tbDbUsername.getText().trim()); - } catch (CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - - try { - dbSettingsPostgres.setPassword(new String(jpDbPassword.getPassword())); - } catch (CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - break; - case SQLITE: - try { - File databasePath = new File(tfDatabasePath.getText()); - dbSettingsSqlite.setDbName(CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT); - dbSettingsSqlite.setDbDirectory(databasePath.getPath()); - } catch (CentralRepoException ex) { - guidanceText.append(ex.getMessage()); - result = false; - } - break; - } - - return result; - } /** * Validates that the form is filled out correctly for our usage. @@ -856,6 +660,29 @@ public class EamDbSettingsDialog extends JDialog { return true; } + + + + /** + * Tests whether or not the database settings are valid. + * + * @return True or false. + */ + private boolean databaseSettingsAreValid() { + try { + manager.testDatabaseSettingsAreValid( + tbDbHostname.getText().trim(), + tbDbPort.getText().trim(), + tbDbUsername.getText().trim(), + tfDatabasePath.getText().trim(), + new String(jpDbPassword.getPassword())); + } + catch (CentralRepoException | NumberFormatException | IllegalStateException e) { + return false; + } + + return true; + } /** * Used to listen for changes in text boxes. It lets the panel know things @@ -866,7 +693,7 @@ public class EamDbSettingsDialog extends JDialog { @Override public void changedUpdate(DocumentEvent e) { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - testingStatus = DatabaseTestResult.UNTESTED; + manager.clearStatus(); updateFullDbPath(); valid(); } @@ -874,7 +701,7 @@ public class EamDbSettingsDialog extends JDialog { @Override public void insertUpdate(DocumentEvent e) { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - testingStatus = DatabaseTestResult.UNTESTED; + manager.clearStatus(); updateFullDbPath(); valid(); } @@ -882,20 +709,13 @@ public class EamDbSettingsDialog extends JDialog { @Override public void removeUpdate(DocumentEvent e) { firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - testingStatus = DatabaseTestResult.UNTESTED; + manager.clearStatus(); updateFullDbPath(); valid(); } } - private enum DatabaseTestResult { - UNTESTED, - CONNECTION_FAILED, - SCHEMA_INVALID, - DB_DOES_NOT_EXIST, - TESTEDOK; - } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton bnCancel; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index ebd85ba527..ab0ea0401f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -31,6 +31,7 @@ import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; 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.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.events.AutopsyEvent; @@ -86,7 +87,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - CentralRepoDbUtil.upgradeDatabase(); + CentralRepoDbManager.upgradeDatabase(); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } catch (CentralRepoException ex) { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java index 7509de8aba..56a6754e3f 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java @@ -293,13 +293,21 @@ class FileSearch { * @return The beginning of text from the specified AbstractFile. */ private static String getFirstLines(AbstractFile file) { - try (Reader reader = TextExtractorFactory.getExtractor(file, null).getReader()) { + TextExtractor extractor; + try { + extractor = TextExtractorFactory.getExtractor(file, null); + } catch (TextExtractorFactory.NoTextExtractorFound ignored) { + //no extractor found, use Strings Extractor + extractor = TextExtractorFactory.getStringsExtractor(file, null); + } + + try (Reader reader = extractor.getReader()) { char[] cbuf = new char[PREVIEW_SIZE]; reader.read(cbuf, 0, PREVIEW_SIZE); return new String(cbuf); } catch (IOException ex) { return Bundle.FileSearch_documentSummary_noBytes(); - } catch (TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) { + } catch (TextExtractor.InitReaderException ex) { return Bundle.FileSearch_documentSummary_noPreview(); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED index a3ed4a8ad3..d603e85b7b 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -3,6 +3,9 @@ GEOTrack_point_label_header=Trackpoint for track: {0} LastKnownWaypoint_Label=Last Known Location Route_End_Label=End Route_Label=As-the-crow-flies Route +Route_point_label=Waypoints for route Route_Start_Label=Start SearchWaypoint_DisplayLabel=GPS Search +Track_distanceFromHome_displayName=Distance from home point +Track_distanceTraveled_displayName=Distance traveled TrackpointWaypoint_DisplayLabel=GPS Trackpoint diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 155c7989c1..67de1e55ba 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -25,19 +25,25 @@ import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList.GeoWaypoint; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList; /** * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point - * however the class was written with the assumption that routes may have - * more that two points. + * however the class was written with the assumption that routes may have more + * than two points. * */ -public class Route extends GeoPath{ +public class Route extends GeoPath { + private final Long timestamp; // This list is not expected to change after construction so the // constructor will take care of creating an unmodifiable List private final List propertiesList; + + private static final TskGeoWaypointsUtil attributeUtil = new TskGeoWaypointsUtil(); /** * Construct a route for the given artifact. @@ -51,12 +57,11 @@ public class Route extends GeoPath{ }) Route(BlackboardArtifact artifact) throws GeoLocationDataException { super(artifact, Bundle.Route_Label()); - + Map attributeMap = Waypoint.getAttributesFromArtifactAsMap(artifact); - - addToPath(getRouteStartPoint(artifact, attributeMap)); - addToPath(getRouteEndPoint(artifact, attributeMap)); - + + createRoute(artifact, attributeMap); + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); timestamp = attribute != null ? attribute.getValueLong() : null; @@ -82,19 +87,61 @@ public class Route extends GeoPath{ return Collections.unmodifiableList(propertiesList); } + /** + * Returns the route timestamp. + * + * @return Route timestamp + */ public Long getTimestamp() { return timestamp; } - + + /** + * Gets the route waypoint attributes from the map and creates the list of + * route waypoints. + * + * @param artifact Route artifact + * @param attributeMap Map of artifact attributes + * + * @throws GeoLocationDataException + */ + @Messages({ + "Route_point_label=Waypoints for route" + }) + private void createRoute(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS); + + String label = getLabel(); + if (label == null || label.isEmpty()) { + label = Bundle.Route_point_label(); + } else { + label = String.format("%s: %s", Bundle.Route_point_label(), label); + } + + if (attribute != null) { + GeoWaypointList waypoints = attributeUtil.fromAttribute(attribute); + + for(GeoWaypoint waypoint: waypoints) { + addToPath(new Waypoint(artifact, label, null, waypoint.getLatitude(), waypoint.getLongitude(), waypoint.getAltitude(), null, attributeMap, this)); + } + } else { + Waypoint start = getRouteStartPoint(artifact, attributeMap); + Waypoint end = getRouteEndPoint(artifact, attributeMap); + + addToPath(start); + addToPath(end); + } + } + /** * Get the route start point. * * @param artifact * @param attributeMap Map of artifact attributes for this waypoint. - * + * * An exception will be thrown if longitude or latitude is null. * - * @return Start waypoint + * @return Start waypoint * * @throws GeoLocationDataException. */ @@ -106,16 +153,14 @@ public class Route extends GeoPath{ BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); if (latitude != null && longitude != null) { - return new Waypoint(artifact, - Bundle.Route_Start_Label(), - pointTimestamp != null ? pointTimestamp.getValueLong() : null, - latitude.getValueDouble(), + return new RoutePoint(artifact, + Bundle.Route_Start_Label(), + latitude.getValueDouble(), longitude.getValueDouble(), altitude != null ? altitude.getValueDouble() : null, - null, attributeMap, this); + attributeMap); } else { throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude"); } @@ -123,8 +168,8 @@ public class Route extends GeoPath{ /** * Get the route End point. - * - * An exception will be thrown if longitude or latitude is null. + * + * An exception will be thrown if longitude or latitude is null. * * @param artifact * @param attributeMap Map of artifact attributes for this waypoint @@ -140,19 +185,52 @@ public class Route extends GeoPath{ BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); if (latitude != null && longitude != null) { - return new Waypoint(artifact, - Bundle.Route_End_Label(), - pointTimestamp != null ? pointTimestamp.getValueLong() : null, - latitude.getValueDouble(), + return new RoutePoint(artifact, + Bundle.Route_End_Label(), + latitude.getValueDouble(), longitude.getValueDouble(), altitude != null ? altitude.getValueDouble() : null, - null, attributeMap, this); + attributeMap); } else { throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude"); } } + + /** + * Route waypoint specific implementation of Waypoint. + */ + private class RoutePoint extends Waypoint { + + /** + * Construct a RoutePoint + * + * @param artifact BlackboardArtifact for this waypoint + * @param label String waypoint label + * @param latitude Double waypoint latitude + * @param longitude Double waypoint longitude + * + * @param attributeMap A Map of attributes for the given artifact + * + * @throws GeoLocationDataException + */ + RoutePoint(BlackboardArtifact artifact, String label, Double latitude, Double longitude, Double altitude, Map attributeMap) throws GeoLocationDataException { + super(artifact, + label, + null, + latitude, + longitude, + altitude, + null, + attributeMap, + Route.this); + } + + @Override + public Long getTimestamp() { + return ((Route) getParentGeoPath()).getTimestamp(); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java index cb0dd95303..1a816179f6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Track.java @@ -26,8 +26,9 @@ import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints; -import org.sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoint.GeoTrackPoint; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList.GeoTrackPoint; /** * A GPS track with which wraps the TSK_GPS_TRACK artifact. @@ -36,6 +37,8 @@ public final class Track extends GeoPath{ private final Long startTimestamp; private final Long endTimeStamp; + + private static final TskGeoTrackpointsUtil attributeUtil = new TskGeoTrackpointsUtil(); /** * Construct a new Track for the given artifact. @@ -59,11 +62,11 @@ public final class Track extends GeoPath{ private Track(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { super(artifact, getTrackName(attributeMap)); - List points = getPointsList(attributeMap); + GeoTrackPointList points = getPointsList(attributeMap); buildPath(points); - startTimestamp = findStartTime(points); - endTimeStamp = findEndTime(points); + startTimestamp = points.getStartTime(); + endTimeStamp = points.getEndTime(); } /** @@ -111,8 +114,8 @@ public final class Track extends GeoPath{ "# {0} - track name", "GEOTrack_point_label_header=Trackpoint for track: {0}" }) - private void buildPath(List points) throws GeoLocationDataException { - for (GeoTrackPoint point : points) { + private void buildPath(GeoTrackPointList points) throws GeoLocationDataException { + for(GeoTrackPoint point: points) { addToPath(new TrackWaypoint(Bundle.GEOTrack_point_label_header(getLabel()), point)); } } @@ -125,52 +128,12 @@ public final class Track extends GeoPath{ * * @return GeoTrackPoint list empty list if the attribute was not found. */ - private List getPointsList(Map attributeMap) { + private GeoTrackPointList getPointsList(Map attributeMap) { BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); if (attribute != null) { - String value = attribute.getValueString(); - return GeoTrackPoints.deserializePoints(value); + return attributeUtil.fromAttribute(attribute); } - return new ArrayList<>(); - } - - /** - * Return the start time for the track. Assumes the points are in time - * order. - * - * @param points List of GeoTrackPoints. - * - * @return First non-null time stamp or null, if one was not found. - */ - private Long findStartTime(List points) { - if (points != null) { - for (GeoTrackPoint point : points) { - if (point.getTimeStamp() != null) { - return point.getTimeStamp(); - } - } - } - return null; - } - - /** - * Return the ends time for the track. Assumes the points are in time - * order. - * - * @param points List of GeoTrackPoints. - * - * @return First non-null time stamp or null, if one was not found. - */ - private Long findEndTime(List points) { - if (points != null) { - for (int index = points.size() - 1; index >= 0; index--) { - GeoTrackPoint point = points.get(index); - if (point.getTimeStamp() != null) { - return point.getTimeStamp(); - } - } - } return null; } @@ -219,6 +182,10 @@ public final class Track extends GeoPath{ * * @return A list of Waypoint.properies. */ + @Messages({ + "Track_distanceTraveled_displayName=Distance traveled", + "Track_distanceFromHome_displayName=Distance from home point" + }) private List createPropertyList(GeoTrackPoint point) { List list = new ArrayList<>(); @@ -234,12 +201,12 @@ public final class Track extends GeoPath{ value = point.getDistanceTraveled(); if (value != null) { - list.add(new Property(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_DISTANCE_TRAVELED.getDisplayName(), value.toString())); + list.add(new Property(Bundle.Track_distanceTraveled_displayName(), value.toString())); } - value = point.getDistanceFromHP(); + value = point.getDistanceFromHomePoint(); if (value != null) { - list.add(new Property(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_DISTANCE_FROM_HOME_POINT.getDisplayName(), value.toString())); + list.add(new Property(Bundle.Track_distanceFromHome_displayName(), value.toString())); } return list; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index acc9e89352..f0ec1e50d3 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -43,7 +43,7 @@ public class Waypoint { final private String label; final private AbstractFile image; final private BlackboardArtifact artifact; - final private GeoPath path; + final private GeoPath parentGeoPath; final private List propertiesList; @@ -78,7 +78,7 @@ public class Waypoint { * @throws GeoLocationDataException Exception will be thrown if artifact did * not have a valid longitude and latitude. */ - Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap, GeoPath path) throws GeoLocationDataException { + Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap, GeoPath parentGeoPath) throws GeoLocationDataException { if (longitude == null || latitude == null) { throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); } @@ -90,7 +90,7 @@ public class Waypoint { this.longitude = longitude; this.latitude = latitude; this.altitude = altitude; - this.path = path; + this.parentGeoPath = parentGeoPath; propertiesList = createGeolocationProperties(attributeMap); } @@ -173,13 +173,13 @@ public class Waypoint { } /** - * Returns the route that this waypoint is apart of . + * Returns the GeoPath that this waypoint is apart of . * * @return The waypoint route or null if the waypoint is not apart of a * route. */ - public GeoPath getPath() { - return path; + public GeoPath getParentGeoPath() { + return parentGeoPath; } /** @@ -231,6 +231,10 @@ public class Waypoint { } for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) { + // Don't add JSON properties to this list. + if (type.getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) { + continue; + } String key = type.getDisplayName(); String value = attributeMap.get(type).getDisplayString(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index e054e6073c..15fd1f764e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -39,39 +39,49 @@ import org.sleuthkit.datamodel.DataSource; public final class WaypointBuilder { private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); + + private final static String TIME_TYPE_IDS = String.format("%d, %d", + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()); + + private final static String GEO_ATTRIBUTE_TYPE_IDS = String.format("%d, %d, %d", + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID()); - // SELECT statement for getting a list of waypoints. - final static String GEO_ARTIFACT_QUERY + // SELECT statement for getting a list of waypoints where %s is a comma separated list + // of attribute type ids. + private final static String GEO_ARTIFACT_QUERY = "SELECT artifact_id, artifact_type_id " + "FROM blackboard_attributes " - + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + + "WHERE attribute_type_id IN (%s) "; //NON-NLS // SELECT statement to get only artifact_ids - final static String GEO_ARTIFACT_QUERY_ID_ONLY + private final static String GEO_ARTIFACT_QUERY_ID_ONLY = "SELECT artifact_id " + "FROM blackboard_attributes " - + "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + + "WHERE attribute_type_id IN (%s) "; //NON-NLS // This Query will return a list of waypoint artifacts - final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY + private final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY = "SELECT blackboard_attributes.artifact_id " + "FROM blackboard_attributes, blackboard_artifacts " + "WHERE blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " - + "AND blackboard_attributes.attribute_type_id IN(%d, %d) " + + "AND blackboard_attributes.attribute_type_id IN(%s) " + "AND data_source_obj_id IN (%s)"; //NON-NLS // Select will return the "most recent" timestamp from all waypoings - final static String MOST_RECENT_TIME + private final static String MOST_RECENT_TIME = "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day. + "FROM blackboard_attributes " - + "WHERE attribute_type_id IN(%d, %d) " + + "WHERE attribute_type_id IN(%s) " + "AND artifact_id " + "IN ( " + "%s" //GEO_ARTIFACT with or without data source + " )"; // Returns a list of artifacts with no time stamp - final static String SELECT_WO_TIMESTAMP + private final static String SELECT_WO_TIMESTAMP = "SELECT DISTINCT artifact_id, artifact_type_id " + "FROM blackboard_attributes " + "WHERE artifact_id NOT IN (%s) " @@ -132,7 +142,7 @@ public final class WaypointBuilder { public static List getRoutes(List waypoints) { List routeList = new ArrayList<>(); for (Waypoint point : waypoints) { - GeoPath path = point.getPath(); + GeoPath path = point.getParentGeoPath(); if (path instanceof Route) { Route route = (Route) path; if (!routeList.contains(route)) { @@ -154,7 +164,7 @@ public final class WaypointBuilder { public static List getTracks(List waypoints) { List trackList = new ArrayList<>(); for (Waypoint point : waypoints) { - GeoPath path = point.getPath(); + GeoPath path = point.getParentGeoPath(); if (path instanceof Track) { Track route = (Track) path; if (!trackList.contains(route)) { @@ -507,9 +517,7 @@ public final class WaypointBuilder { // FROM blackboard_attributes // WHERE attribute_type_id IN (%d, %d) return String.format(SELECT_WO_TIMESTAMP, - String.format(GEO_ARTIFACT_QUERY_ID_ONLY, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()), + String.format(GEO_ARTIFACT_QUERY_ID_ONLY,TIME_TYPE_IDS), getWaypointListQuery(dataSources)); } @@ -542,15 +550,13 @@ public final class WaypointBuilder { // MOST_RECENT_TIME // SELECT MAX(value_int64) - (%d * 86400) // FROM blackboard_attributes -// WHERE attribute_type_id IN(%d, %d) +// WHERE attribute_type_id IN(%s) // AND artifact_id // IN ( %s ) // mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS String.format(MOST_RECENT_TIME, - cntDaysFromRecent, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), + cntDaysFromRecent, TIME_TYPE_IDS, getWaypointListQuery(dataSources) )); } @@ -558,10 +564,8 @@ public final class WaypointBuilder { // GEO_ARTIFACT_QUERY // SELECT artifact_id, artifact_type_id // FROM blackboard_attributes -// WHERE attribute_type_id IN (%d, %d) - String query = String.format(GEO_ARTIFACT_QUERY, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()); +// WHERE attribute_type_id IN (%s) + String query = String.format(GEO_ARTIFACT_QUERY, TIME_TYPE_IDS); // That are in the list of artifacts for the given data Sources query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS @@ -592,10 +596,8 @@ public final class WaypointBuilder { // GEO_ARTIFACT_QUERY // SELECT artifact_id, artifact_type_id // FROM blackboard_attributes -// WHERE attribute_type_id IN (%d, %d) - return String.format(GEO_ARTIFACT_QUERY, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()); +// WHERE attribute_type_id IN (%s) + return String.format(GEO_ARTIFACT_QUERY, GEO_ATTRIBUTE_TYPE_IDS); } String dataSourceList = ""; @@ -608,9 +610,7 @@ public final class WaypointBuilder { dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1); } - return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), + return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, GEO_ATTRIBUTE_TYPE_IDS, dataSourceList); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java index 256ee59509..97cfbd40be 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java @@ -41,10 +41,11 @@ import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoint.GeoTrackPoint; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList.GeoTrackPoint; import org.sleuthkit.datamodel.blackboardutils.GeoArtifactsHelper; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.Blackboard.BlackboardException; +import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList; /** * Extract drone position data from DJI Phantom drones. @@ -110,10 +111,10 @@ final class DATExtractor extends DroneExtractor { } // Process the csv file - List trackPoints = processCSVFile(context, DATFile, csvFilePath); + GeoTrackPointList trackPoints = processCSVFile(context, DATFile, csvFilePath); if (trackPoints != null && !trackPoints.isEmpty()) { - (new GeoArtifactsHelper(getSleuthkitCase(), getName(), DATFile)).addTrack(DATFile.getName(), trackPoints); + (new GeoArtifactsHelper(getSleuthkitCase(), getName(), "DatCon", DATFile)).addTrack(DATFile.getName(), trackPoints, null); } else { logger.log(Level.INFO, String.format("No trackpoints with valid longitude or latitude found in %s", DATFile.getName())); //NON-NLS } @@ -187,8 +188,8 @@ final class DATExtractor extends DroneExtractor { * * @throws DroneIngestException */ - private List processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException { - List trackPoints = new ArrayList<>(); + private GeoTrackPointList processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException { + GeoTrackPointList trackPoints = new GeoTrackPointList(); try (BufferedReader reader = new BufferedReader(new FileReader(new File(csvFilePath)))) { // First read in the header line and process String line = reader.readLine(); @@ -202,7 +203,7 @@ final class DATExtractor extends DroneExtractor { String[] values = line.split(","); //NON-NLS GeoTrackPoint point = createTrackPoint(headerMap, values); if (point != null) { - trackPoints.add(point); + trackPoints.addPoint(point); } } @@ -258,6 +259,7 @@ final class DATExtractor extends DroneExtractor { return new GeoTrackPoint(latitude, longitude, getDoubleValue(columnLookup.get(HEADER_ALTITUDE), values), + null, getDoubleValue(columnLookup.get(HEADER_VELOCITY), values), getDoubleValue(columnLookup.get(HEADER_DISTANCE_FROM_HP), values), getDoubleValue(columnLookup.get(HEADER_DISTANCE_TRAVELED), values), diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties deleted file mode 100644 index 8909d23e0e..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties +++ /dev/null @@ -1,5 +0,0 @@ -iOSModuleFactory.moduleName=iOS Analyzer -iOSModuleFactory.moduleDescription=Extracts system and 3rd party app data -TextMessageAnalyzer.bbAttribute.incoming=Incoming -TextMessageAnalyzer.bbAttribute.outgoing=Outgoing -TextMessageAnalyzer.bbAttribute.smsMessage=SMS Message diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties-MERGED deleted file mode 100755 index 33c0a3ed08..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle.properties-MERGED +++ /dev/null @@ -1,8 +0,0 @@ -CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search. -ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search. -iOSModuleFactory.moduleName=iOS Analyzer -iOSModuleFactory.moduleDescription=Extracts system and 3rd party app data -TextMessageAnalyzer.bbAttribute.incoming=Incoming -TextMessageAnalyzer.bbAttribute.outgoing=Outgoing -TextMessageAnalyzer.bbAttribute.smsMessage=SMS Message -TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search. diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle_ja.properties deleted file mode 100644 index 03cd3cc41e..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/Bundle_ja.properties +++ /dev/null @@ -1,8 +0,0 @@ -CallLogAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u901a\u8a71\u30ed\u30b0\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 -ContactAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u9023\u7d61\u5148\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 -iOSModuleFactory.moduleName=iOS Analyzer -iOSModuleFactory.moduleDescription=\u30b7\u30b9\u30c6\u30e0\u3068\u30b5\u30fc\u30c9\u30d1\u30fc\u30c6\u30a3\u88fd\u30a2\u30d7\u30ea\u30c7\u30fc\u30bf\u3092\u62bd\u51fa -TextMessageAnalyzer.bbAttribute.incoming=\u53d7\u4fe1 -TextMessageAnalyzer.bbAttribute.outgoing=\u9001\u4fe1 -TextMessageAnalyzer.bbAttribute.smsMessage=SMS\u30e1\u30c3\u30bb\u30fc\u30b8 -TextMessageAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u30c6\u30ad\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java deleted file mode 100644 index 908673f466..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014-2018 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.modules.iOS; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.ingest.IngestJobContext; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Look for call logs and allow resulting blackboard artifacts to be generated. - */ -final class CallLogAnalyzer { - - private Connection connection = null; - private ResultSet resultSet = null; - private Statement statement = null; - private long fileId = 0; - private java.io.File jFile = null; - private final String moduleName = iOSModuleFactory.getModuleName(); - private static final Logger logger = Logger.getLogger(CallLogAnalyzer.class.getName()); - private Blackboard blackboard; - - /** - * Find call logs given an ingest job context and index the results. - * - * @param context The ingest job context. - */ - public void findCallLogs(IngestJobContext context) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - blackboard = openCase.getSleuthkitCase().getBlackboard(); - List absFiles; - try { - SleuthkitCase skCase = openCase.getSleuthkitCase(); - absFiles = skCase.findAllFilesWhere("name ='contacts2.db' OR name ='contacts.db'"); //NON-NLS //get exact file names - if (absFiles.isEmpty()) { - return; - } - for (AbstractFile file : absFiles) { - String dbPath = ""; - try { - jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); - dbPath = jFile.toString(); //path of file as string - fileId = file.getId(); - ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled); - findCallLogsInDB(dbPath, fileId); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS - } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS - } - } - } catch (TskCoreException e) { - logger.log(Level.SEVERE, "Error finding Call logs", e); //NON-NLS - } - } - - /** - * Index results for call logs found in the database. - * - * @param DatabasePath The path to the database. - * @param fileId The ID of the file associated with artifacts. - */ - @Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."}) - private void findCallLogsInDB(String DatabasePath, long fileId) { - if (DatabasePath == null || DatabasePath.isEmpty()) { - return; - } - try { - Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver - connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS - statement = connection.createStatement(); - } catch (ClassNotFoundException | SQLException e) { - logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS - } - - Case currentCase; - try { - currentCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - try { - AbstractFile file = skCase.getAbstractFileById(fileId); - if (file == null) { - logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS - return; - } - - try { - resultSet = statement.executeQuery( - "SELECT number,date,duration,type, name FROM calls ORDER BY date DESC;"); //NON-NLS - - BlackboardArtifact bba; - String name; // name of person dialed or called. null if unregistered - String number; //string phone number - String duration; //duration of call in seconds - String date; // Unix time - String type; // 1 incoming, 2 outgoing, 3 missed - - while (resultSet.next()) { - name = resultSet.getString("name"); //NON-NLS - number = resultSet.getString("number"); //NON-NLS - duration = resultSet.getString("duration"); //NON-NLS - date = resultSet.getString("date"); //NON-NLS - type = resultSet.getString("type"); //NON-NLS - - bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); //create a call log and then add attributes from result set. - Collection attributes = new ArrayList<>(); - if (type.equalsIgnoreCase("outgoing")) { //NON-NLS - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, number)); - } else { /// Covers INCOMING and MISSED - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, number)); - } - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, moduleName, date)); // RC: Should be long! - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, moduleName, duration + date)); // RC: Should be long! - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, type)); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, moduleName, name)); - - bba.addAttributes(attributes); - try { - /* - * post the artifact which will index the artifact for - * keyword search, and fire an event to notify UI of - * this new artifact - */ - blackboard.postArtifact(bba, moduleName); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS - MessageNotifyUtil.Notify.error( - Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName()); - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing Call logs to the Blackboard", e); //NON-NLS - } finally { - try { - resultSet.close(); - statement.close(); - connection.close(); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error closing the database", e); //NON-NLS - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing Call logs to the Blackboard", e); //NON-NLS - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java deleted file mode 100644 index cde321fab4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014-2019 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.modules.iOS; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.ingest.IngestJobContext; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.ReadContentInputStream; -import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Look for call logs and allow resulting blackboard artifacts to be generated. - */ -final class ContactAnalyzer { - - private Connection connection = null; - private String dbPath = ""; - private long fileId = 0; - private java.io.File jFile = null; - private final String moduleName = iOSModuleFactory.getModuleName(); - private static final Logger logger = Logger.getLogger(ContactAnalyzer.class.getName()); - private Blackboard blackboard; - - /** - * Find contacts given an ingest job context and index the results. - * - * @param context The ingest job context. - */ - public void findContacts(IngestJobContext context) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - - blackboard = openCase.getSleuthkitCase().getBlackboard(); - List absFiles; - try { - SleuthkitCase skCase = openCase.getSleuthkitCase(); - absFiles = skCase.findAllFilesWhere("LOWER(name) LIKE LOWER('%call_history%') "); //NON-NLS //get exact file names - if (absFiles.isEmpty()) { - return; - } - for (AbstractFile file : absFiles) { - try { - jFile = new java.io.File(openCase.getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); - dbPath = jFile.toString(); //path of file as string - fileId = file.getId(); - ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled); - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS - } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS - } - } - } catch (TskCoreException e) { - logger.log(Level.SEVERE, "Error finding Contacts", e); //NON-NLS - } - } - - /** - * Create blackboard artifacts and index results for call logs found in the - * database. - * - * @param DatabasePath The path to the database. - * @param fileId The ID of the file associated with artifacts. - */ - @Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."}) - private void findContactsInDB(String DatabasePath, long fileId) { - if (DatabasePath == null || DatabasePath.isEmpty()) { - return; - } - - Case currentCase; - try { - currentCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - - Statement statement = null; - try { - Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver - connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS - statement = connection.createStatement(); - } catch (ClassNotFoundException | SQLException e) { - logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS - } - - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - try { - AbstractFile file = skCase.getAbstractFileById(fileId); - if (file == null) { - logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS - return; - } - - ResultSet resultSet = null; - try { - // get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype) - //sorted by name, so phonenumber/email would be consecutive for a person if they exist. - resultSet = statement.executeQuery( - "SELECT mimetype,data1, name_raw_contact.display_name AS display_name \n" //NON-NLS - + "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n" //NON-NLS - + "JOIN raw_contacts AS name_raw_contact ON(name_raw_contact_id=name_raw_contact._id) " //NON-NLS - + "LEFT OUTER JOIN data ON (data.raw_contact_id=raw_contacts._id) \n" //NON-NLS - + "LEFT OUTER JOIN mimetypes ON (data.mimetype_id=mimetypes._id) \n" //NON-NLS - + "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n" //NON-NLS - + "ORDER BY name_raw_contact.display_name ASC;"); //NON-NLS - - BlackboardArtifact bba; - bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - Collection attributes = new ArrayList<>(); - String name; - String oldName = ""; - String mimetype; // either phone or email - String data1; // the phone number or email - while (resultSet.next()) { - name = resultSet.getString("display_name"); //NON-NLS - data1 = resultSet.getString("data1"); //NON-NLS - mimetype = resultSet.getString("mimetype"); //NON-NLS - if (name.equals(oldName) == false) { - bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); - attributes = new ArrayList<>(); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, moduleName, name)); - } - if (mimetype.equals("vnd.android.cursor.item/phone_v2")) { //NON-NLS - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, moduleName, data1)); - } else { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, moduleName, data1)); - } - - // TODO: If this code comes back to life, add code to create the account - // and relationship between the phone numbers & emails. Also - // investigate if the mimetype "vnd.android.cursor.item/phone_v2" - // makes sense in an ios word - - oldName = name; - - bba.addAttributes(attributes); - try { - // index the artifact for keyword search - blackboard.postArtifact(bba, moduleName); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS - MessageNotifyUtil.Notify.error( - Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName()); - } - } - - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing Contacts to Blackboard", e); //NON-NLS - } finally { - try { - resultSet.close(); - statement.close(); - connection.close(); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error closing database", e); //NON-NLS - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing Contacts to Blackboard", e); //NON-NLS - } - - } - - public static void copyFileUsingStream(AbstractFile file, File jFile) throws IOException { - InputStream is = new ReadContentInputStream(file); - OutputStream os = new FileOutputStream(jFile); - byte[] buffer = new byte[8192]; - int length; - try { - while ((length = is.read(buffer)) != -1) { - os.write(buffer, 0, length); - os.flush(); - - } - - } finally { - is.close(); - os.close(); - } - } - - public static void copyFileUsingStreams(AbstractFile file, File jFile) { - InputStream istream; - OutputStream ostream = null; - int c; - final int EOF = -1; - istream = new ReadContentInputStream(file); - try { - ostream = new FileOutputStream(jFile); - while ((c = istream.read()) != EOF) { - ostream.write(c); - } - } catch (IOException e) { - logger.log(Level.WARNING, "Error copying file", e); - } finally { - try { - istream.close(); - ostream.close(); - } catch (IOException e) { - logger.log(Level.WARNING, "File did not close", e); - } - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java deleted file mode 100644 index c541bf608a..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014-2018 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.modules.iOS; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.ingest.IngestJobContext; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Blackboard; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.ReadContentInputStream; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Look for text messages and allow resulting blackboard artifacts to be - * generated. - */ -class TextMessageAnalyzer { - - private Connection connection = null; - private ResultSet resultSet = null; - private Statement statement = null; - private String dbPath = ""; - private long fileId = 0; - private java.io.File jFile = null; - List absFiles; - private final String moduleName = iOSModuleFactory.getModuleName(); - private static final Logger logger = Logger.getLogger(TextMessageAnalyzer.class.getName()); - private Blackboard blackboard; - - /** - * Find text messages given an ingest job context and index the results. - * - * @param context The ingest job context. - */ - void findTexts(IngestJobContext context) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - blackboard = openCase.getSleuthkitCase().getBlackboard(); - try { - SleuthkitCase skCase = openCase.getSleuthkitCase(); - absFiles = skCase.findAllFilesWhere("name ='mmssms.db'"); //NON-NLS //get exact file name - if (absFiles.isEmpty()) { - return; - } - for (AbstractFile file : absFiles) { - try { - jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", "")); - dbPath = jFile.toString(); //path of file as string - fileId = file.getId(); - ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled); - findTextsInDB(dbPath, fileId); - } catch (ReadContentInputStream.ReadContentInputStreamException ex) { - logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS - } catch (Exception ex) { - logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS - } - } - } catch (TskCoreException e) { - logger.log(Level.SEVERE, "Error finding text messages", e); //NON-NLS - } - } - - /** - * Create blackboard artifacts and index results for text messages found in - * the database. - * - * @param DatabasePath The path to the database. - * @param fileId The ID of the file associated with artifacts. - */ - @Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."}) - private void findTextsInDB(String DatabasePath, long fileId) { - if (DatabasePath == null || DatabasePath.isEmpty()) { - return; - } - Case currentCase; - try { - currentCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return; - } - try { - Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver - connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS - statement = connection.createStatement(); - } catch (ClassNotFoundException | SQLException e) { - logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS - } - - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - try { - AbstractFile file = skCase.getAbstractFileById(fileId); - if (file == null) { - logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS - return; - } - - try { - resultSet = statement.executeQuery( - "SELECT address,date,type,subject,body FROM sms;"); //NON-NLS - - BlackboardArtifact bba; - String address; // may be phone number, or other addresses - String date;//unix time - String type; // message received in inbox = 1, message sent = 2 - String subject;//message subject - String body; //message body - while (resultSet.next()) { - address = resultSet.getString("address"); //NON-NLS - date = resultSet.getString("date"); //NON-NLS - type = resultSet.getString("type"); //NON-NLS - subject = resultSet.getString("subject"); //NON-NLS - body = resultSet.getString("body"); //NON-NLS - - bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); //create Message artifact and then add attributes from result set. - Collection attributes = new ArrayList<>(); - // @@@ NEed to put into more specific TO or FROM - if (type.equals("1")) { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.incoming"))); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, address)); - } else { - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.outgoing"))); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, address)); - } - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, moduleName, date)); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, type)); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, moduleName, subject)); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, moduleName, body)); - attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.smsMessage"))); - - bba.addAttributes(attributes); - try { - /* - * post the artifact which will index the artifact for - * keyword search, and fire an event to notify UI of - * this new artifact - */ blackboard.postArtifact(bba, moduleName); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS - MessageNotifyUtil.Notify.error( - Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName()); - } - } - - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing text messages to Blackboard", e); //NON-NLS - } finally { - try { - resultSet.close(); - statement.close(); - connection.close(); - } catch (Exception e) { - logger.log(Level.SEVERE, "Error closing database", e); //NON-NLS - } - } - } catch (Exception e) { - logger.log(Level.SEVERE, "Error parsing text messages to Blackboard", e); //NON-NLS - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSIngestModule.java deleted file mode 100644 index b0aacc9119..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSIngestModule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014 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.modules.iOS; - -import java.util.HashMap; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; -import org.sleuthkit.autopsy.ingest.IngestModule; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; -import org.sleuthkit.autopsy.ingest.IngestJobContext; -import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; -import org.sleuthkit.autopsy.ingest.IngestServices; - -class iOSIngestModule implements DataSourceIngestModule { - - private static final HashMap fileCountsForIngestJobs = new HashMap<>(); - private IngestJobContext context = null; - private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); - private static final Logger logger = Logger.getLogger(iOSModuleFactory.class.getName()); - private IngestServices services = IngestServices.getInstance(); - - @Override - public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { - this.context = context; - } - - @Override - public IngestModule.ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { - ContactAnalyzer FindContacts = new ContactAnalyzer(); - FindContacts.findContacts(context); - return IngestModule.ProcessResult.OK; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSModuleFactory.java deleted file mode 100644 index 942dca5379..0000000000 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/iOSModuleFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2014 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.modules.iOS; - -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.Version; -import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; -import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; -import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; - -//@ServiceProvider(service = IngestModuleFactory.class) // -public class iOSModuleFactory extends IngestModuleFactoryAdapter { - - static String getModuleName() { - return NbBundle.getMessage(iOSModuleFactory.class, "iOSModuleFactory.moduleName"); - } - - @Override - public String getModuleDisplayName() { - return getModuleName(); - } - - @Override - public String getModuleDescription() { - return NbBundle.getMessage(iOSModuleFactory.class, "iOSModuleFactory.moduleDescription"); - } - - @Override - public String getModuleVersionNumber() { - return Version.getVersion(); - } - - @Override - public boolean isDataSourceIngestModuleFactory() { - return true; - } - - @Override - public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { - return new iOSIngestModule(); - } - -} diff --git a/InternalPythonModules/android/googlemaplocation.py b/InternalPythonModules/android/googlemaplocation.py index d17162fb65..c04aadfba3 100644 --- a/InternalPythonModules/android/googlemaplocation.py +++ b/InternalPythonModules/android/googlemaplocation.py @@ -41,6 +41,8 @@ from org.sleuthkit.datamodel import BlackboardArtifact from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel.blackboardutils import GeoArtifactsHelper +from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil import GeoWaypointList import traceback import general @@ -52,15 +54,25 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer): def __init__(self): self._logger = Logger.getLogger(self.__class__.__name__) + self.current_case = None + self.PROGRAM_NAME = "Google Maps History" + self.CAT_DESTINATION = "Destination" def analyze(self, dataSource, fileManager, context): + try: + self.current_case = Case.getCurrentCaseThrows() + except NoCurrentCaseException as ex: + self._logger.log(Level.WARNING, "No case currently open.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) + return + try: absFiles = fileManager.findFiles(dataSource, "da_destination_history") if absFiles.isEmpty(): return for abstractFile in absFiles: try: - jFile = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName()) + jFile = File(self.current_case.getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName()) ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled) self.__findGeoLocationsInDB(jFile.toString(), abstractFile) except Exception as ex: @@ -75,6 +87,8 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer): return try: + artifactHelper = GeoArtifactsHelper(self.current_case.getSleuthkitCase(), + general.MODULE_NAME, self.PROGRAM_NAME, abstractFile) Class.forName("org.sqlite.JDBC") # load JDBC driver connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath) statement = connection.createStatement() @@ -101,31 +115,21 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer): source_lat = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lat")) source_lng = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lng")) - attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, general.MODULE_NAME, "Destination")) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, time)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, general.MODULE_NAME, dest_lat)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END, general.MODULE_NAME, dest_lng)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START, general.MODULE_NAME, source_lat)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START, general.MODULE_NAME, source_lng)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, dest_title)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, general.MODULE_NAME, dest_address)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, general.MODULE_NAME, "Google Maps History")) - - artifact.addAttributes(attributes) - try: - # index the artifact for keyword search - blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() - blackboard.postArtifact(artifact, general.MODULE_NAME) - except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) - self._logger.log(Level.SEVERE, traceback.format_exc()) - MessageNotifyUtil.Notify.error("Failed to index GPS route artifact for keyword search.", artifact.getDisplayName()) + waypointlist = GeoWaypointList() + waypointlist.addPoint(source_lat, source_lng, None, None) + waypointlist.addPoint(dest_lat, dest_lng, None, dest_address) + + artifactHelper.addRoute(dest_title, time, waypointlist, None) except SQLException as ex: # Unable to execute Google map locations SQL query against database. pass + except TskCoreException as ex: + self._logger.log(Level.SEVERE, "Failed to add route artifacts.", ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + except BlackboardException as ex: + self._logger.log(Level.WARNING, "Failed to post artifacts.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) except Exception as ex: self._logger.log(Level.SEVERE, "Error processing google maps history.", ex) self._logger.log(Level.SEVERE, traceback.format_exc()) diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index 25e05e30fa..2de991d105 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -276,8 +276,8 @@ release/modules/ext/icu4j-3.8.jar - ext/guava-17.0.jar - release/modules/ext/guava-17.0.jar + ext/guava-19.0.jar + release/modules/ext/guava-19.0.jar ext/language-detector-0.6.jar