Merged develop

This commit is contained in:
Eugene Livis 2020-03-10 17:08:53 -04:00
commit 29b0f51e57
112 changed files with 5226 additions and 3383 deletions

View File

@ -309,6 +309,8 @@
<package>net.sf.sevenzipjbinding.impl</package>
<package>net.sf.sevenzipjbinding.simple</package>
<package>net.sf.sevenzipjbinding.simple.impl</package>
<package>org.apache.tika</package>
<package>org.apache.tika.io</package>
<package>org.sleuthkit.autopsy.actions</package>
<package>org.sleuthkit.autopsy.appservices</package>
<package>org.sleuthkit.autopsy.casemodule</package>
@ -343,6 +345,7 @@
<package>org.sleuthkit.autopsy.report</package>
<package>org.sleuthkit.autopsy.textextractors</package>
<package>org.sleuthkit.autopsy.textextractors.configs</package>
<package>org.sleuthkit.autopsy.textsummarizer</package>
<package>org.sleuthkit.autopsy.texttranslation</package>
<package>org.sleuthkit.datamodel</package>
<package>org.sleuthkit.datamodel.blackboardutils</package>
@ -804,7 +807,7 @@
<runtime-relative-path>ext/jutf7-1.0.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jutf7-1.0.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/DatCon.jar</runtime-relative-path>
<binary-origin>release/modules/ext/DatCon.jar</binary-origin>
</class-path-extension>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012-2019 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -1114,7 +1114,7 @@ public class Case {
CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
CallableSystemAction.get(CaseDetailsAction.class).setEnabled(true);
CallableSystemAction.get(DataSourceSummaryAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(FeatureAccessUtils.canDeleteCurrentCase());
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -35,6 +35,7 @@ import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
/**
* The action associated with the Delete button of the Case Properties panel. It
@ -54,7 +55,7 @@ final class CaseDeleteAction extends CallableSystemAction {
/*
* A value of 'null' signifies that there is no case open.
*/
setEnabled(null != evt.getNewValue());
setEnabled(null != evt.getNewValue() && FeatureAccessUtils.canDeleteCurrentCase());
});
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -61,10 +61,10 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction {
*/
public AddEditCentralRepoCommentAction(AbstractFile file) {
fileId = file.getId();
correlationAttributeInstance = CorrelationAttributeUtil.getInstanceFromContent(file);
correlationAttributeInstance = CorrelationAttributeUtil.getCorrAttrForFile(file);
if (correlationAttributeInstance == null) {
addToDatabase = true;
correlationAttributeInstance = CorrelationAttributeUtil.makeInstanceFromContent(file);
correlationAttributeInstance = CorrelationAttributeUtil.makeCorrAttrFromFile(file);
}
if (file.getSize() == 0) {
putValue(Action.NAME, Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoCommentEmptyFile());

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2017-2019 Basis Technology Corp.
* Copyright 2017-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -464,7 +464,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
// correlate on blackboard artifact attributes if they exist and supported
BlackboardArtifact bbArtifact = getBlackboardArtifactFromNode(node);
if (bbArtifact != null && CentralRepository.isEnabled()) {
ret.addAll(CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(bbArtifact, false));
ret.addAll(CorrelationAttributeUtil.makeCorrAttrsFromArtifact(bbArtifact));
}
// we can correlate based on the MD5 if it is enabled

View File

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

View File

@ -0,0 +1,137 @@
/*
* Central Repository
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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;
}
}

View File

@ -0,0 +1,407 @@
/*
* Central Repository
*
* Copyright 2015-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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;
}
}

View File

@ -0,0 +1,55 @@
/*
* Central Repository
*
* Copyright 2015-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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();
}

View File

@ -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

View File

@ -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;
/**
@ -802,5 +803,38 @@ public interface CentralRepository {
*
* @throws CentralRepoException
*/
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException;
/**
* Returns list of all correlation types.
*
* @return list of Correlation types
* @throws CentralRepoException
*/
List<CorrelationAttributeInstance.Type> 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;
}

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2015-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -24,6 +24,7 @@ import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.TskData;
/**
@ -49,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,
@ -72,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");
@ -87,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) {
@ -97,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
@ -105,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()
@ -209,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;
@ -220,6 +257,9 @@ public class CorrelationAttributeInstance implements Serializable {
public static final int IMEI_TYPE_ID = 7;
public static final int IMSI_TYPE_ID = 8;
public static final int ICCID_TYPE_ID = 9;
// An offset to assign Ids for additional correlation types.
public static final int ADDITIONAL_TYPES_BASE_ID = 1000;
/**
* Load the default correlation types
@ -238,18 +278,30 @@ public class CorrelationAttributeInstance implements Serializable {
"CorrelationType.IMSI.displayName=IMSI Number",
"CorrelationType.ICCID.displayName=ICCID Number"})
public static List<CorrelationAttributeInstance.Type> getDefaultCorrelationTypes() throws CentralRepoException {
List<CorrelationAttributeInstance.Type> DEFAULT_CORRELATION_TYPES = new ArrayList<>();
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(FILES_TYPE_ID, Bundle.CorrelationType_FILES_displayName(), "file", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(DOMAIN_TYPE_ID, Bundle.CorrelationType_DOMAIN_displayName(), "domain", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(EMAIL_TYPE_ID, Bundle.CorrelationType_EMAIL_displayName(), "email_address", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(PHONE_TYPE_ID, Bundle.CorrelationType_PHONE_displayName(), "phone_number", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(USBID_TYPE_ID, Bundle.CorrelationType_USBID_displayName(), "usb_devices", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(SSID_TYPE_ID, Bundle.CorrelationType_SSID_displayName(), "wireless_networks", true, true)); // NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(MAC_TYPE_ID, Bundle.CorrelationType_MAC_displayName(), "mac_address", true, true)); //NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(IMEI_TYPE_ID, Bundle.CorrelationType_IMEI_displayName(), "imei_number", true, true)); //NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(IMSI_TYPE_ID, Bundle.CorrelationType_IMSI_displayName(), "imsi_number", true, true)); //NON-NLS
DEFAULT_CORRELATION_TYPES.add(new CorrelationAttributeInstance.Type(ICCID_TYPE_ID, Bundle.CorrelationType_ICCID_displayName(), "iccid_number", true, true)); //NON-NLS
return DEFAULT_CORRELATION_TYPES;
List<CorrelationAttributeInstance.Type> defaultCorrelationTypes = new ArrayList<>();
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(FILES_TYPE_ID, Bundle.CorrelationType_FILES_displayName(), "file", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(DOMAIN_TYPE_ID, Bundle.CorrelationType_DOMAIN_displayName(), "domain", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(EMAIL_TYPE_ID, Bundle.CorrelationType_EMAIL_displayName(), "email_address", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(PHONE_TYPE_ID, Bundle.CorrelationType_PHONE_displayName(), "phone_number", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(USBID_TYPE_ID, Bundle.CorrelationType_USBID_displayName(), "usb_devices", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(SSID_TYPE_ID, Bundle.CorrelationType_SSID_displayName(), "wireless_networks", true, true)); // NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(MAC_TYPE_ID, Bundle.CorrelationType_MAC_displayName(), "mac_address", true, true)); //NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(IMEI_TYPE_ID, Bundle.CorrelationType_IMEI_displayName(), "imei_number", true, true)); //NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(IMSI_TYPE_ID, Bundle.CorrelationType_IMSI_displayName(), "imsi_number", true, true)); //NON-NLS
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(ICCID_TYPE_ID, Bundle.CorrelationType_ICCID_displayName(), "iccid_number", true, true)); //NON-NLS
// Create Correlation Types for Accounts.
int correlationTypeId = ADDITIONAL_TYPES_BASE_ID;
for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) {
// Skip Phone and Email accounts as there are already Correlation types defined for those.
if (type != Account.Type.EMAIL && type != Account.Type.PHONE) {
defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(correlationTypeId, type.getDisplayName(), type.getTypeName().toLowerCase() + "_acct", true, true)); //NON-NLS
correlationTypeId++;
}
}
return defaultCorrelationTypes;
}
/**

View File

@ -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<CorrelationAttributeInstance.Type> 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);
}
}
}
/**

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2020 Basis Technology Corp.
* Copyright 2017-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -24,182 +24,307 @@ 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;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.HashUtility;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Utility class for correlation attributes in the central repository
* Utility class for working with correlation attributes in the central
* repository.
*/
public class CorrelationAttributeUtil {
private static final Logger logger = Logger.getLogger(CorrelationAttributeUtil.class.getName());
@Messages({"EamArtifactUtil.emailaddresses.text=Email Addresses"})
public static String getEmailAddressAttrString() {
return Bundle.EamArtifactUtil_emailaddresses_text();
/**
* Gets a string that is expected to be the same string that is stored in
* the correlation_types table in the central repository as the display name
* for the email address correlation attribute type. This string is
* duplicated in the CorrelationAttributeInstance class.
*
* TODO (Jira-6088): We should not have multiple deifnitions of this string.
*
* @return The display name of the email address correlation attribute type.
*/
@Messages({"CorrelationAttributeUtil.emailaddresses.text=Email Addresses"})
private static String getEmailAddressAttrDisplayName() {
return Bundle.CorrelationAttributeUtil_emailaddresses_text();
}
/**
* Static factory method to examine a BlackboardArtifact to determine if it
* has contents that can be used for Correlation. If so, return a
* EamArtifact with a single EamArtifactInstance within. If not, return
* null.
* Makes zero to many correlation attribute instances from the attributes of
* an artifact.
*
* @param artifact BlackboardArtifact to examine
* @param checkEnabled If true, only create a CorrelationAttribute if it is
* enabled
* IMPORTANT: The correlation attribute instances are NOT added to the
* central repository by this method.
*
* @return List of EamArtifacts
* TODO (Jira-6088): The methods in this low-level, utility class should
* throw exceptions instead of logging them. The reason for this is that the
* clients of the utility class, not the utility class itself, should be in
* charge of error handling policy, per the Autopsy Coding Standard. Note
* that clients of several of these methods currently cannot determine
* whether receiving a null return value is an error or not, plus null
* checking is easy to forget, while catching exceptions is enforced.
*
* @param artifact An artifact.
*
* @return A list, possibly empty, of correlation attribute instances for
* the artifact.
*/
public static List<CorrelationAttributeInstance> makeInstancesFromBlackboardArtifact(BlackboardArtifact artifact,
boolean checkEnabled) {
List<CorrelationAttributeInstance> eamArtifacts = new ArrayList<>();
public static List<CorrelationAttributeInstance> makeCorrAttrsFromArtifact(BlackboardArtifact artifact) {
List<CorrelationAttributeInstance> correlationAttrs = new ArrayList<>();
try {
BlackboardArtifact artifactForInstance = null;
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifact.getArtifactTypeID()) {
// Get the associated artifactForInstance
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
if (attribute != null) {
artifactForInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong());
}
} else {
artifactForInstance = artifact;
}
if (artifactForInstance != null) {
int artifactTypeID = artifactForInstance.getArtifactTypeID();
BlackboardArtifact sourceArtifact = getCorrAttrSourceArtifact(artifact);
if (sourceArtifact != null) {
int artifactTypeID = sourceArtifact.getArtifactTypeID();
if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
BlackboardAttribute setNameAttr = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
if (setNameAttr != null
&& CorrelationAttributeUtil.getEmailAddressAttrString().equals(setNameAttr.getValueString())) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID);
BlackboardAttribute setNameAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) {
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID);
}
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) {
makeCorrAttrFromArtifactPhoneAttr(sourceArtifact);
String value = null;
if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
} else if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
} else if (null != artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
value = artifactForInstance.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
}
// Remove all non-numeric symbols to semi-normalize phone numbers, preserving leading "+" character
if (value != null) {
String newValue = value.replaceAll("\\D", "");
if (value.startsWith("+")) {
newValue = "+" + newValue;
}
value = newValue;
// Only add the correlation attribute if the resulting phone number large enough to be of use
// (these 3-5 digit numbers can be valid, but are not useful for correlation)
if (value.length() > 5) {
CorrelationAttributeInstance inst = makeCorrelationAttributeInstanceUsingTypeValue(artifactForInstance, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value);
if (inst != null) {
eamArtifacts.add(inst);
}
}
}
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID);
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SSID, CorrelationAttributeInstance.SSID_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_WIFI_NETWORK_ADAPTER.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_BLUETOOTH_ADAPTER.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_INFO.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID);
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMEI, CorrelationAttributeInstance.IMEI_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_SIM_ATTACHED.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IMSI, CorrelationAttributeInstance.IMSI_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ICCID, CorrelationAttributeInstance.ICCID_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS.getTypeID()) {
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID);
addCorrelationAttributeToList(eamArtifacts, artifactForInstance, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, CorrelationAttributeInstance.PHONE_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, CorrelationAttributeInstance.EMAIL_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
makeCorrAttrFromAcctArtifact(correlationAttrs, sourceArtifact);
}
}
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error getting defined correlation types.", ex); // NON-NLS
return eamArtifacts;
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS
return correlationAttrs;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting attribute while getting type from BlackboardArtifact.", ex); // NON-NLS
return null;
logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS
return correlationAttrs;
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
return null;
logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
return correlationAttrs;
}
return eamArtifacts;
return correlationAttrs;
}
/**
* Add a CorrelationAttributeInstance of the specified type to the provided
* list if the artifactForInstance has an Attribute of the given type with a
* non empty value.
* Gets the associated artifact of a "meta-artifact" such as an interesting
* artifact hit artifact.
*
* @param eamArtifacts the list of CorrelationAttributeInstance objects
* which should be added to
* @param artifact the blackboard artifactForInstance which we are
* creating a CorrelationAttributeInstance for
* @param bbAttributeType the type of BlackboardAttribute we expect to exist
* for a CorrelationAttributeInstance of this type
* generated from this Blackboard Artifact
* @param typeId the integer type id of the
* CorrelationAttributeInstance type
* @param artifact An artifact.
*
* @throws CentralRepoException
* @throws TskCoreException
* @return The associated artifact if the input artifact is a
* "meta-artifact", otherwise the input artifact.
*
* @throws NoCurrentCaseException If there is no open case.
* @throws TskCoreException If there is an error querying thew case
* database.
*/
private static void addCorrelationAttributeToList(List<CorrelationAttributeInstance> eamArtifacts, BlackboardArtifact artifact, ATTRIBUTE_TYPE bbAttributeType, int typeId) throws CentralRepoException, TskCoreException {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(bbAttributeType));
private static BlackboardArtifact getCorrAttrSourceArtifact(BlackboardArtifact artifact) throws NoCurrentCaseException, TskCoreException {
BlackboardArtifact sourceArtifact = null;
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() == artifact.getArtifactTypeID()) {
BlackboardAttribute assocArtifactAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
if (assocArtifactAttr != null) {
sourceArtifact = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboardArtifact(assocArtifactAttr.getValueLong());
}
} else {
sourceArtifact = artifact;
}
return sourceArtifact;
}
/**
* Makes a correlation attribute instance from a phone number attribute of an
* artifact.
*
* @param artifact An artifact with a phone number attribute.
*
* @return The correlation instance artifact or null, if the phone number is
* not a valid correlation attribute.
*
* @throws TskCoreException If there is an error querying the case
* database.
* @throws CentralRepoException If there is an error querying the central
* repository.
*/
private static CorrelationAttributeInstance makeCorrAttrFromArtifactPhoneAttr(BlackboardArtifact artifact) throws TskCoreException, CentralRepoException {
CorrelationAttributeInstance corrAttr = null;
/*
* Extract the phone number from the artifact attribute.
*/
String value = null;
if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
} else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
} else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
}
/*
* Normalize the phone number.
*/
if (value != null) {
String newValue = value.replaceAll("\\D", "");
if (value.startsWith("+")) {
newValue = "+" + newValue;
}
value = newValue;
/*
* Validate the phone number. Three to five digit phone numbers may
* be valid, but they are too short to use as correlation
* attributes.
*/
if (value.length() > 5) {
corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value);
}
}
return corrAttr;
}
/**
* 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.
*
* TODO (Jira-6088): The methods in this low-level, utility class should
* throw exceptions instead of logging them. The reason for this is that the
* clients of the utility class, not the utility class itself, should be in
* charge of error handling policy, per the Autopsy Coding Standard. Note
* that clients of several of these methods currently cannot determine
* whether receiving a null return value is an error or not, plus null
* checking is easy to forget, while catching exceptions is enforced.
*
* @param corrAttrInstances A list of correlation attribute instances.
* @param acctArtifact An account artifact.
*
* @return The correlation attribute instance.
*/
private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> 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);
}
}
/**
* Makes a correlation attribute instance from a specified attribute of an
* artifact. The correlation attribute instance is added to an input list.
*
* @param corrAttrInstances A list of correlation attribute instances.
* @param artifact An artifact.
* @param artAttrType The type of the atrribute of the artifact that
* is to be made into a correlatin attribute
* instance.
* @param typeId The type ID for the desired correlation
* attribute instance.
*
* @throws CentralRepoException If there is an error querying the central
* repository.
* @throws TskCoreException If there is an error querying the case
* database.
*/
private static void makeCorrAttrFromArtifactAttr(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact artifact, ATTRIBUTE_TYPE artAttrType, int typeId) throws CentralRepoException, TskCoreException {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(artAttrType));
if (attribute != null) {
String value = attribute.getValueString();
if ((null != value) && (value.isEmpty() == false)) {
CorrelationAttributeInstance inst = makeCorrelationAttributeInstanceUsingTypeValue(artifact, CentralRepository.getInstance().getCorrelationTypeById(typeId), value);
CorrelationAttributeInstance inst = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(typeId), value);
if (inst != null) {
eamArtifacts.add(inst);
corrAttrInstances.add(inst);
}
}
}
}
/**
* Uses the determined type and vallue, then looks up instance details to
* create proper CorrelationAttributeInstance.
* Makes a correlation attribute instance of a given type from an artifact.
*
* @param bbArtifact the blackboard artifactForInstance
* @param correlationType the given type
* @param value the artifactForInstance value
* @param artifact The artifact.
* @param correlationType the correlation attribute type.
* @param value The correlation attribute value.
*
* @return CorrelationAttributeInstance from details, or null if validation
* failed or another error occurred
* TODO (Jira-6088): The methods in this low-level, utility class should
* throw exceptions instead of logging them. The reason for this is that the
* clients of the utility class, not the utility class itself, should be in
* charge of error handling policy, per the Autopsy Coding Standard. Note
* that clients of several of these methods currently cannot determine
* whether receiving a null return value is an error or not, plus null
* checking is easy to forget, while catching exceptions is enforced.
*
* @return The correlation attribute instance or null, if an error occurred.
*/
private static CorrelationAttributeInstance makeCorrelationAttributeInstanceUsingTypeValue(BlackboardArtifact bbArtifact, CorrelationAttributeInstance.Type correlationType, String value) {
private static CorrelationAttributeInstance makeCorrAttr(BlackboardArtifact artifact, CorrelationAttributeInstance.Type correlationType, String value) {
try {
Case currentCase = Case.getCurrentCaseThrows();
AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(bbArtifact.getObjectID());
AbstractFile bbSourceFile = currentCase.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
if (null == bbSourceFile) {
logger.log(Level.SEVERE, "Error creating artifact instance. Abstract File was null."); // NON-NLS
return null;
}
// make an instance for the BB source file
CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows());
return new CorrelationAttributeInstance(
correlationType,
@ -212,31 +337,34 @@ public class CorrelationAttributeUtil {
bbSourceFile.getId());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting AbstractFile for artifact: " + bbArtifact.toString(), ex); // NON-NLS
logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", artifact), ex); // NON-NLS
return null;
} catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Error creating artifact instance for artifact: " + bbArtifact.toString(), ex); // NON-NLS
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", artifact), ex); // NON-NLS
return null;
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Case is closed.", ex); // NON-NLS
logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
return null;
}
}
/**
* Retrieve CorrelationAttribute from the given Content.
* Gets the correlation attribute instance for a file.
*
* @param content The content object
* @param file The file.
*
* @return The new CorrelationAttribute, or null if retrieval failed.
* TODO (Jira-6088): The methods in this low-level, utility class should
* throw exceptions instead of logging them. The reason for this is that the
* clients of the utility class, not the utility class itself, should be in
* charge of error handling policy, per the Autopsy Coding Standard. Note
* that clients of several of these methods currently cannot determine
* whether receiving a null return value is an error or not, plus null
* checking is easy to forget, while catching exceptions is enforced.
*
* @return The correlation attribute instance or null, if no such
* correlation attribute instance was found or an error occurred.
*/
public static CorrelationAttributeInstance getInstanceFromContent(Content content) {
if (!(content instanceof AbstractFile)) {
return null;
}
final AbstractFile file = (AbstractFile) content;
public static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file) {
if (!isSupportedAbstractFileType(file)) {
return null;
@ -254,11 +382,14 @@ public class CorrelationAttributeUtil {
return null;
}
correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource());
} catch (TskCoreException | CentralRepoException ex) {
logger.log(Level.SEVERE, "Error retrieving correlation attribute.", ex);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error getting querying case database (%s)", file), ex); // NON-NLS
return null;
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
return null;
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Case is closed.", ex);
logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
return null;
}
@ -266,20 +397,22 @@ public class CorrelationAttributeUtil {
try {
correlationAttributeInstance = CentralRepository.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, file.getId());
} catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, String.format(
"Correlation attribute could not be retrieved for '%s' (id=%d): ",
content.getName(), content.getId()), ex);
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
return null;
}
//if there was no correlation attribute found for the item using object_id then check for attributes added with schema 1,1 which lack object_id
/*
* If no correlation attribute instance was found when querying by file
* object ID, try searching by file path instead. This is necessary
* because file object IDs were not stored in the central repository in
* early versions of its schema.
*/
if (correlationAttributeInstance == null && file.getMd5Hash() != null) {
String filePath = (file.getParentPath() + file.getName()).toLowerCase();
try {
correlationAttributeInstance = CentralRepository.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, file.getMd5Hash(), filePath);
} catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, String.format(
"Correlation attribute could not be retrieved for '%s' (id=%d): ",
content.getName(), content.getId()), ex);
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
return null;
}
}
@ -288,32 +421,31 @@ public class CorrelationAttributeUtil {
}
/**
* Create an EamArtifact from the given Content. Will return null if an
* artifactForInstance can not be created - this is not necessarily an error
* case, it just means an artifactForInstance can't be made. If creation
* fails due to an error (and not that the file is the wrong type or it has
* no hash), the error will be logged before returning.
* Makes a correlation attribute instance for a file.
*
* Does not add the artifactForInstance to the database.
* IMPORTANT: The correlation attribute instance is NOT added to the central
* repository by this method.
*
* @param content The content object
* TODO (Jira-6088): The methods in this low-level, utility class should
* throw exceptions instead of logging them. The reason for this is that the
* clients of the utility class, not the utility class itself, should be in
* charge of error handling policy, per the Autopsy Coding Standard. Note
* that clients of several of these methods currently cannot determine
* whether receiving a null return value is an error or not, plus null
* checking is easy to forget, while catching exceptions is enforced.
*
* @return The new EamArtifact or null if creation failed
* @param file The file.
*
* @return The correlation attribute instance or null, if an error occurred.
*/
public static CorrelationAttributeInstance makeInstanceFromContent(Content content) {
public static CorrelationAttributeInstance makeCorrAttrFromFile(AbstractFile file) {
if (!(content instanceof AbstractFile)) {
if (!isSupportedAbstractFileType(file)) {
return null;
}
final AbstractFile af = (AbstractFile) content;
if (!isSupportedAbstractFileType(af)) {
return null;
}
// We need a hash to make the artifactForInstance
String md5 = af.getMd5Hash();
// We need a hash to make the correlation artifact instance.
String md5 = file.getMd5Hash();
if (md5 == null || md5.isEmpty() || HashUtility.isNoDataMd5(md5)) {
return null;
}
@ -324,31 +456,33 @@ public class CorrelationAttributeUtil {
CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCaseThrows());
return new CorrelationAttributeInstance(
filesType,
af.getMd5Hash(),
file.getMd5Hash(),
correlationCase,
CorrelationDataSource.fromTSKDataSource(correlationCase, af.getDataSource()),
af.getParentPath() + af.getName(),
CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource()),
file.getParentPath() + file.getName(),
"",
TskData.FileKnown.UNKNOWN,
af.getId());
file.getId());
} catch (TskCoreException | CentralRepoException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, "Error making correlation attribute.", ex);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error querying case database (%s)", file), ex); // NON-NLS
return null;
} catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, String.format("Error querying central repository (%s)", file), ex); // NON-NLS
return null;
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Case is closed.", ex);
logger.log(Level.SEVERE, "Error getting current case", ex); // NON-NLS
return null;
}
}
/**
* Check whether the given abstract file should be processed for the central
* repository.
* Checks whether or not a file is of a type that can be added to the
* central repository as a correlation attribute instance.
*
* @param file The file to test
* @param file A file.
*
* @return true if the file should be added to the central repo, false
* otherwise
* @return True or false.
*/
public static boolean isSupportedAbstractFileType(AbstractFile file) {
if (file == null) {
@ -375,9 +509,9 @@ public class CorrelationAttributeUtil {
}
/**
* Constructs a new EamArtifactUtil
* Prevent instantiation of this utility class.
*/
private CorrelationAttributeUtil() {
//empty constructor
}
}

View File

@ -0,0 +1,31 @@
/*
* Central Repository
*
* Copyright 2015-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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;
}

View File

@ -0,0 +1,87 @@
/*
* Central Repository
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.datamodel;
/**
* This class abstracts a persona.
*
* An examiner may create a persona from an account.
*
*/
class Persona {
/**
* Defines level of confidence in assigning a persona to an account.
*/
public enum Confidence {
UNKNOWN(1, "Unknown"),
LOW(2, "Low confidence"),
MEDIUM(3, "Medium confidence"),
HIGH(4, "High confidence"),
DERIVED(5, "Derived directly");
private final String name;
private final int level_id;
Confidence(int level, String name) {
this.name = name;
this.level_id = level;
}
@Override
public String toString() {
return name;
}
public int getLevel() {
return this.level_id;
}
}
/**
* Defines status of a persona.
*/
public enum PersonaStatus {
UNKNOWN(1, "Unknown"),
ACTIVE(2, "Active"),
MERGED(3, "Merged"),
SPLIT(4, "Split"),
DELETED(5, "Deleted");
private final String description;
private final int status_id;
PersonaStatus(int status, String description) {
this.status_id = status;
this.description = description;
}
@Override
public String toString() {
return description;
}
public int getStatus() {
return this.status_id;
}
}
}

View File

@ -131,7 +131,9 @@ final class PostgresCentralRepo extends RdbmsCentralRepo {
CentralRepoDbUtil.closeConnection(conn);
}
dbSettings.insertDefaultDatabaseContent();
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.POSTGRESQL, dbSettings);
centralRepoSchemaFactory.insertDefaultDatabaseContent();
}
/**
@ -209,6 +211,10 @@ final class PostgresCentralRepo extends RdbmsCentralRepo {
return CONFLICT_CLAUSE;
}
@Override
protected Connection getEphemeralConnection() {
return this.dbSettings.getEphemeralConnection(false);
}
/**
* Gets an exclusive lock (if applicable). Will return the lock if
* successful, null if unsuccessful because locking isn't supported, and

View File

@ -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()) {
@ -162,7 +168,7 @@ public final class PostgresCentralRepoSettings {
*
* @return Connection or null.
*/
private Connection getEphemeralConnection(boolean usePostgresDb) {
Connection getEphemeralConnection(boolean usePostgresDb) {
Connection conn;
try {
String url = getConnectionURL(usePostgresDb);
@ -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) {
@ -290,308 +301,25 @@ public final class PostgresCentralRepoSettings {
}
/**
* Initialize the database schema.
*
* Requires valid connectionPool.
*
* This method is called from within connect(), so we cannot call connect()
* to get a connection. This method is called after setupConnectionPool(),
* so it is safe to assume that a valid connectionPool exists. The
* implementation of connect() is synchronized, so we can safely use the
* connectionPool object directly.
*/
public boolean initializeDatabaseSchema() {
// The "id" column is an alias for the built-in 64-bit int "rowid" column.
// It is autoincrementing by default and must be of type "integer primary key".
// We've omitted the autoincrement argument because we are not currently
// using the id value to search for specific rows, so we do not care
// if a rowid is re-used after an existing rows was previously deleted.
StringBuilder createOrganizationsTable = new StringBuilder();
createOrganizationsTable.append("CREATE TABLE IF NOT EXISTS organizations (");
createOrganizationsTable.append("id SERIAL PRIMARY KEY,");
createOrganizationsTable.append("org_name text NOT NULL,");
createOrganizationsTable.append("poc_name text NOT NULL,");
createOrganizationsTable.append("poc_email text NOT NULL,");
createOrganizationsTable.append("poc_phone text NOT NULL,");
createOrganizationsTable.append("CONSTRAINT org_name_unique UNIQUE (org_name)");
createOrganizationsTable.append(")");
// NOTE: The organizations will only have a small number of rows, so
// an index is probably not worthwhile.
StringBuilder createCasesTable = new StringBuilder();
createCasesTable.append("CREATE TABLE IF NOT EXISTS cases (");
createCasesTable.append("id SERIAL PRIMARY KEY,");
createCasesTable.append("case_uid text NOT NULL,");
createCasesTable.append("org_id integer,");
createCasesTable.append("case_name text NOT NULL,");
createCasesTable.append("creation_date text NOT NULL,");
createCasesTable.append("case_number text,");
createCasesTable.append("examiner_name text,");
createCasesTable.append("examiner_email text,");
createCasesTable.append("examiner_phone text,");
createCasesTable.append("notes text,");
createCasesTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
createCasesTable.append("CONSTRAINT case_uid_unique UNIQUE (case_uid)");
createCasesTable.append(")");
// NOTE: when there are few cases in the cases table, these indices may not be worthwhile
String casesIdx1 = "CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
String casesIdx2 = "CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
StringBuilder createReferenceSetsTable = new StringBuilder();
createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
createReferenceSetsTable.append("id SERIAL PRIMARY KEY,");
createReferenceSetsTable.append("org_id integer NOT NULL,");
createReferenceSetsTable.append("set_name text NOT NULL,");
createReferenceSetsTable.append("version text NOT NULL,");
createReferenceSetsTable.append("known_status integer NOT NULL,");
createReferenceSetsTable.append("read_only boolean NOT NULL,");
createReferenceSetsTable.append("type integer NOT NULL,");
createReferenceSetsTable.append("import_date text NOT NULL,");
createReferenceSetsTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
createReferenceSetsTable.append("CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
createReferenceSetsTable.append(")");
String referenceSetsIdx1 = "CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
// Each "%s" will be replaced with the relevant reference_TYPE table name.
StringBuilder createReferenceTypesTableTemplate = new StringBuilder();
createReferenceTypesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createReferenceTypesTableTemplate.append("id SERIAL PRIMARY KEY,");
createReferenceTypesTableTemplate.append("reference_set_id integer,");
createReferenceTypesTableTemplate.append("value text NOT NULL,");
createReferenceTypesTableTemplate.append("known_status integer NOT NULL,");
createReferenceTypesTableTemplate.append("comment text,");
createReferenceTypesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE (reference_set_id, value),");
createReferenceTypesTableTemplate.append("foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
createReferenceTypesTableTemplate.append(")");
// Each "%s" will be replaced with the relevant reference_TYPE table name.
String referenceTypesIdx1 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
String referenceTypesIdx2 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
StringBuilder createCorrelationTypesTable = new StringBuilder();
createCorrelationTypesTable.append("CREATE TABLE IF NOT EXISTS correlation_types (");
createCorrelationTypesTable.append("id SERIAL PRIMARY KEY,");
createCorrelationTypesTable.append("display_name text NOT NULL,");
createCorrelationTypesTable.append("db_table_name text NOT NULL,");
createCorrelationTypesTable.append("supported integer NOT NULL,");
createCorrelationTypesTable.append("enabled integer NOT NULL,");
createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
createCorrelationTypesTable.append(")");
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
String instancesValueIdx = getAddValueIndexTemplate();
String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
// NOTE: the db_info table currenly only has 1 row, so having an index
// provides no benefit.
Connection conn = null;
try {
conn = getEphemeralConnection(false);
if (null == conn) {
return false;
}
Statement stmt = conn.createStatement();
stmt.execute(createOrganizationsTable.toString());
stmt.execute(createCasesTable.toString());
stmt.execute(casesIdx1);
stmt.execute(casesIdx2);
stmt.execute(getCreateDataSourcesTableStatement());
stmt.execute(getAddDataSourcesNameIndexStatement());
stmt.execute(getAddDataSourcesObjectIdIndexStatement());
stmt.execute(createReferenceSetsTable.toString());
stmt.execute(referenceSetsIdx1);
stmt.execute(createCorrelationTypesTable.toString());
/*
* Note that the essentially useless id column in the following
* table is required for backwards compatibility. Otherwise, the
* name column could be the primary key.
*/
stmt.execute("CREATE TABLE db_info (id SERIAL, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
// Create a separate instance and reference table for each correlation type
List<CorrelationAttributeInstance.Type> DEFAULT_CORRELATION_TYPES = CorrelationAttributeInstance.getDefaultCorrelationTypes();
String reference_type_dbname;
String instance_type_dbname;
for (CorrelationAttributeInstance.Type type : DEFAULT_CORRELATION_TYPES) {
reference_type_dbname = CentralRepoDbUtil.correlationTypeToReferenceTableName(type);
instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type);
stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
// FUTURE: allow more than the FILES type
if (type.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
}
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS
return false;
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error getting default correlation types. Likely due to one or more Type's with an invalid db table name."); // NON-NLS
return false;
} finally {
CentralRepoDbUtil.closeConnection(conn);
}
return true;
}
/**
* Get the template String for creating a new _instances table in a Postgres
* central repository. %s will exist in the template where the name of the
* new table will be addedd.
*
* @return a String which is a template for cretating a new _instances table
*/
static String getCreateArtifactInstancesTableTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return ("CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY,case_id integer NOT NULL,"
+ "data_source_id integer NOT NULL,value text NOT NULL,file_path text NOT NULL,"
+ "known_status integer NOT NULL,comment text,file_obj_id BIGINT,"
+ "CONSTRAINT %s_multi_unique_ UNIQUE (data_source_id, value, file_path),"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)");
}
/**
* Get the statement String for creating a new data_sources table in a
* Postgres central repository.
*
* @return a String which is a statement for cretating a new data_sources
* table
*/
static String getCreateDataSourcesTableStatement() {
return "CREATE TABLE IF NOT EXISTS data_sources "
+ "(id SERIAL PRIMARY KEY,case_id integer NOT NULL,device_id text NOT NULL,"
+ "name text NOT NULL,datasource_obj_id BIGINT,md5 text DEFAULT NULL,"
+ "sha1 text DEFAULT NULL,sha256 text DEFAULT NULL,"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
}
/**
* Get the statement for creating an index on the name column of the
* data_sources table.
*
* @return a String which is a statement for adding an index on the name
* column of the data_sources table.
*/
static String getAddDataSourcesNameIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
}
/**
* Get the statement for creating an index on the data_sources_object_id
* column of the data_sources table.
*
* @return a String which is a statement for adding an index on the
* data_sources_object_id column of the data_sources table.
*/
static String getAddDataSourcesObjectIdIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
}
/**
* Get the template for creating an index on the case_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the case_id
* column of a _instances table
*/
static String getAddCaseIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
}
/**
* Get the template for creating an index on the data_source_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* data_source_id column of a _instances table
*/
static String getAddDataSourceIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
}
/**
* Get the template for creating an index on the value column of an instance
* table. %s will exist in the template where the name of the new table will
* be addedd.
*
* @return a String which is a template for adding an index to the value
* column of a _instances table
*/
static String getAddValueIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Get the template for creating an index on the known_status column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* known_status column of a _instances table
*/
static String getAddKnownStatusIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
}
/**
* Get the template for creating an index on the file_obj_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* file_obj_id column of a _instances table
*/
static String getAddObjectIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
}
public boolean insertDefaultDatabaseContent() {
Connection conn = getEphemeralConnection(false);
if (null == conn) {
return false;
}
boolean result = CentralRepoDbUtil.insertDefaultCorrelationTypes(conn) && CentralRepoDbUtil.insertDefaultOrganization(conn);
CentralRepoDbUtil.closeConnection(conn);
return result;
}
boolean isChanged() {
String hostString = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS

View File

@ -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<String, Collection<CorrelationAttributeInstance>> 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<String, CentralRepoAccountType> accountTypesCache = CacheBuilder.newBuilder().build();
private static final Cache<Pair<CentralRepoAccountType, String>, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder()
.expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES).
build();
private static final Cache<Integer, CorrelationAttributeInstance.Type> typeCache = CacheBuilder.newBuilder().build();
private static final Cache<String, CorrelationCase> caseCacheByUUID = CacheBuilder.newBuilder()
.expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES).
@ -116,6 +125,10 @@ abstract class RdbmsCentralRepo implements CentralRepository {
*/
protected abstract Connection connect() throws CentralRepoException;
/**
* Get an ephemeral connection.
*/
protected abstract Connection getEphemeralConnection();
/**
* Add a new name/value pair in the db_info table.
*
@ -989,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());
@ -1018,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");
@ -1369,6 +1527,9 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
synchronized (bulkArtifacts) {
if (bulkArtifacts.get(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType())) == null) {
bulkArtifacts.put(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()), new ArrayList<>());
}
bulkArtifacts.get(CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType())).add(eamArtifact);
bulkArtifactsCount++;
@ -2841,6 +3002,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
typeId = newCorrelationTypeKnownId(newType);
}
typeCache.put(newType.getId(), newType);
return typeId;
}
@ -3101,6 +3263,45 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
}
/**
* Returns a list of all correlation types. It uses the cache to build the
* list. If the cache is empty, it reads from the database and loads up the
* cache.
*
* @return List of correlation types.
* @throws CentralRepoException
*/
@Override
public List<CorrelationAttributeInstance.Type> getCorrelationTypes() throws CentralRepoException {
if (typeCache.size() == 0) {
getCorrelationTypesFromCr();
}
return new ArrayList<>(typeCache.asMap().values());
}
/**
* Gets a Correlation type with the specified name.
*
* @param correlationtypeName Correlation type name
* @return Correlation type matching the given name, null if none matches.
*
* @throws CentralRepoException
*/
public CorrelationAttributeInstance.Type getCorrelationTypeByName(String correlationtypeName) throws CentralRepoException {
List<CorrelationAttributeInstance.Type> correlationTypesList = getCorrelationTypes();
CorrelationAttributeInstance.Type correlationType
= correlationTypesList.stream()
.filter(x -> correlationtypeName.equalsIgnoreCase(x.getDisplayName()))
.findAny()
.orElse(null);
return null;
}
/**
* Get the EamArtifact.Type that has the given Type.Id from the central repo
*
@ -3138,6 +3339,30 @@ abstract class RdbmsCentralRepo implements CentralRepository {
}
}
/**
* Reads the correlation types from the database and loads them up in the cache.
*
* @throws CentralRepoException If there is an error.
*/
private void getCorrelationTypesFromCr() throws CentralRepoException {
// clear out the cache
typeCache.invalidateAll();
String sql = "SELECT * FROM correlation_types";
try ( Connection conn = connect();
PreparedStatement preparedStatement = conn.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();) {
while (resultSet.next()) {
CorrelationAttributeInstance.Type aType = getCorrelationTypeFromResultSet(resultSet);
typeCache.put(aType.getId(), aType);
}
} catch (SQLException ex) {
throw new CentralRepoException("Error getting correlation types.", ex); // NON-NLS
}
}
/**
* Convert a ResultSet to a EamCase object
*
@ -3401,39 +3626,27 @@ abstract class RdbmsCentralRepo implements CentralRepository {
*/
if (dbSchemaVersion.compareTo(new CaseDbSchemaVersionNumber(1, 2)) < 0) {
final String addIntegerColumnTemplate = "ALTER TABLE %s ADD COLUMN %s INTEGER;"; //NON-NLS
final String addSsidTableTemplate;
final String addCaseIdIndexTemplate;
final String addDataSourceIdIndexTemplate;
final String addValueIndexTemplate;
final String addKnownStatusIndexTemplate;
final String addObjectIdIndexTemplate;
final String addSsidTableTemplate = RdbmsCentralRepoFactory.getCreateArtifactInstancesTableTemplate(selectedPlatform);
final String addCaseIdIndexTemplate = RdbmsCentralRepoFactory.getAddCaseIdIndexTemplate();
final String addDataSourceIdIndexTemplate = RdbmsCentralRepoFactory.getAddDataSourceIdIndexTemplate();
final String addValueIndexTemplate = RdbmsCentralRepoFactory.getAddValueIndexTemplate();
final String addKnownStatusIndexTemplate = RdbmsCentralRepoFactory.getAddKnownStatusIndexTemplate();
final String addObjectIdIndexTemplate = RdbmsCentralRepoFactory.getAddObjectIdIndexTemplate();
final String addAttributeSql;
//get the data base specific code for creating a new _instance table
switch (selectedPlatform) {
case POSTGRESQL:
addAttributeSql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?) " + getConflictClause(); //NON-NLS
addSsidTableTemplate = PostgresCentralRepoSettings.getCreateArtifactInstancesTableTemplate();
addCaseIdIndexTemplate = PostgresCentralRepoSettings.getAddCaseIdIndexTemplate();
addDataSourceIdIndexTemplate = PostgresCentralRepoSettings.getAddDataSourceIdIndexTemplate();
addValueIndexTemplate = PostgresCentralRepoSettings.getAddValueIndexTemplate();
addKnownStatusIndexTemplate = PostgresCentralRepoSettings.getAddKnownStatusIndexTemplate();
addObjectIdIndexTemplate = PostgresCentralRepoSettings.getAddObjectIdIndexTemplate();
break;
case SQLITE:
addAttributeSql = "INSERT OR IGNORE INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)"; //NON-NLS
addSsidTableTemplate = SqliteCentralRepoSettings.getCreateArtifactInstancesTableTemplate();
addCaseIdIndexTemplate = SqliteCentralRepoSettings.getAddCaseIdIndexTemplate();
addDataSourceIdIndexTemplate = SqliteCentralRepoSettings.getAddDataSourceIdIndexTemplate();
addValueIndexTemplate = SqliteCentralRepoSettings.getAddValueIndexTemplate();
addKnownStatusIndexTemplate = SqliteCentralRepoSettings.getAddKnownStatusIndexTemplate();
addObjectIdIndexTemplate = SqliteCentralRepoSettings.getAddObjectIdIndexTemplate();
break;
default:
throw new CentralRepoException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name()));
}
final String dataSourcesTableName = "data_sources";
final String dataSourceObjectIdColumnName = "datasource_obj_id";
if (!doesColumnExist(conn, dataSourcesTableName, dataSourceObjectIdColumnName)) {
@ -3586,8 +3799,8 @@ abstract class RdbmsCentralRepo implements CentralRepository {
+ "md5 text DEFAULT NULL,sha1 text DEFAULT NULL,sha256 text DEFAULT NULL,"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT datasource_unique UNIQUE (case_id, device_id, name, datasource_obj_id))");
statement.execute(SqliteCentralRepoSettings.getAddDataSourcesNameIndexStatement());
statement.execute(SqliteCentralRepoSettings.getAddDataSourcesObjectIdIndexStatement());
statement.execute(RdbmsCentralRepoFactory.getAddDataSourcesNameIndexStatement());
statement.execute(RdbmsCentralRepoFactory.getAddDataSourcesObjectIdIndexStatement());
statement.execute("INSERT INTO data_sources SELECT * FROM old_data_sources");
statement.execute("DROP TABLE old_data_sources");
break;

View File

@ -0,0 +1,860 @@
/*
* Central Repository
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.datamodel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona.Confidence;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona.PersonaStatus;
import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Account;
/**
* Creates the CR schema and populates it with initial data.
*
*/
public class RdbmsCentralRepoFactory {
private final static Logger LOGGER = Logger.getLogger(RdbmsCentralRepoFactory.class.getName());
private final CentralRepoPlatforms selectedPlatform;
private final SqliteCentralRepoSettings sqliteCentralRepoSettings;
private final PostgresCentralRepoSettings postgresCentralRepoSettings;
// SQLite pragmas
private final static String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF";
private final static String PRAGMA_JOURNAL_WAL = "PRAGMA journal_mode = WAL";
private final static String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True";
private final static String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'";
private final static String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096";
private final static String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON";
public RdbmsCentralRepoFactory(CentralRepoPlatforms selectedPlatform, SqliteCentralRepoSettings repoSettings) throws CentralRepoException {
this.selectedPlatform = selectedPlatform;
this.sqliteCentralRepoSettings = repoSettings;
this.postgresCentralRepoSettings = null;
}
public RdbmsCentralRepoFactory(CentralRepoPlatforms selectedPlatform, PostgresCentralRepoSettings repoSettings) throws CentralRepoException {
this.selectedPlatform = selectedPlatform;
this.postgresCentralRepoSettings = repoSettings;
this.sqliteCentralRepoSettings = null;
}
/**
* Initialize the database schema.
*
* Requires valid connectionPool.
*
* This method is called from within connect(), so we cannot call connect()
* to get a connection. This method is called after setupConnectionPool(),
* so it is safe to assume that a valid connectionPool exists. The
* implementation of connect() is synchronized, so we can safely use the
* connectionPool object directly.
*/
public boolean initializeDatabaseSchema() {
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate(selectedPlatform);
String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
String instancesValueIdx = getAddValueIndexTemplate();
String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
// NOTE: the db_info table currenly only has 1 row, so having an index
// provides no benefit.
try (Connection conn = this.getEphemeralConnection();) {
if (null == conn) {
LOGGER.log(Level.SEVERE, "Cannot initialize CR database, don't have a valid connection."); // NON-NLS
return false;
}
try (Statement stmt = conn.createStatement();) {
// these setting PRAGMAs are SQLIte spcific
if (selectedPlatform == CentralRepoPlatforms.SQLITE) {
stmt.execute(PRAGMA_JOURNAL_WAL);
stmt.execute(PRAGMA_SYNC_OFF);
stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
stmt.execute(PRAGMA_ENCODING_UTF8);
stmt.execute(PRAGMA_PAGE_SIZE_4096);
stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
}
// Create Organizations table
stmt.execute(getCreateOrganizationsTableStatement(selectedPlatform));
// Create Cases table and indexes
stmt.execute(getCreateCasesTableStatement(selectedPlatform));
stmt.execute(getCasesOrgIdIndexStatement());
stmt.execute(getCasesCaseUidIndexStatement());
stmt.execute(getCreateDataSourcesTableStatement(selectedPlatform));
stmt.execute(getAddDataSourcesNameIndexStatement());
stmt.execute(getAddDataSourcesObjectIdIndexStatement());
stmt.execute(getCreateReferenceSetsTableStatement(selectedPlatform));
stmt.execute(getReferenceSetsOrgIdIndexTemplate());
stmt.execute(getCreateCorrelationTypesTableStatement(selectedPlatform));
stmt.execute(getCreateDbInfoTableStatement(selectedPlatform));
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
// Create account_types and accounts tables which are referred by X_instances tables
stmt.execute(getCreateAccountTypesTableStatement(selectedPlatform));
stmt.execute(getCreateAccountsTableStatement(selectedPlatform));
// Create a separate instance and reference table for each artifact type
List<CorrelationAttributeInstance.Type> defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes();
String reference_type_dbname;
String instance_type_dbname;
for (CorrelationAttributeInstance.Type type : defaultCorrelationTypes) {
reference_type_dbname = CentralRepoDbUtil.correlationTypeToReferenceTableName(type);
instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type);
stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
// FUTURE: allow more than the FILES type
if (type.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
stmt.execute(String.format(getReferenceTypesTableTemplate(selectedPlatform), reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(getReferenceTypeValueIndexTemplate(), reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(getReferenceTypeValueKnownstatusIndexTemplate(), reference_type_dbname, reference_type_dbname));
}
}
// @TODO: uncomment this when ready to create Persona tables.
//createPersonaTables(stmt);
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS
return false;
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error getting default correlation types. Likely due to one or more Type's with an invalid db table name."); // NON-NLS
return false;
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to database.", ex); // NON-NLS
return false;
}
return true;
}
/**
* Inserts default data in CR database.
*
* @return True if success, False otherwise.
*/
public boolean insertDefaultDatabaseContent() {
boolean result;
try (Connection conn = this.getEphemeralConnection();) {
if (null == conn) {
return false;
}
result = CentralRepoDbUtil.insertDefaultCorrelationTypes(conn)
&& CentralRepoDbUtil.insertDefaultOrganization(conn) &&
insertDefaultAccountsTablesContent(conn);
// @TODO: uncomment when ready to create/populate persona tables
// && insertDefaultPersonaTablesContent(conn);
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in CR tables."), ex);
return false;
}
return result;
}
private static String getCreateDbInfoTableStatement(CentralRepoPlatforms selectedPlatform) {
/*
* Note that the essentially useless id column in the following
* table is required for backwards compatibility. Otherwise, the
* name column could be the primary key.
*/
return "CREATE TABLE db_info ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "name TEXT UNIQUE NOT NULL,"
+ "value TEXT NOT NULL "
+ ")";
}
/**
* Returns Create Table SQL for Organizations table.
*
* @param selectedPlatform CR database platform.
*
* @return SQL string to create Organizations table.
*/
private static String getCreateOrganizationsTableStatement(CentralRepoPlatforms selectedPlatform) {
// The "id" column is an alias for the built-in 64-bit int "rowid" column.
// It is autoincrementing by default and must be of type "integer primary key".
// We've omitted the autoincrement argument because we are not currently
// using the id value to search for specific rows, so we do not care
// if a rowid is re-used after an existing rows was previously deleted.
return "CREATE TABLE IF NOT EXISTS organizations ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "org_name text NOT NULL,"
+ "poc_name text NOT NULL,"
+ "poc_email text NOT NULL,"
+ "poc_phone text NOT NULL,"
+ "CONSTRAINT org_name_unique UNIQUE (org_name)"
+ ")";
}
/**
* Returns Create Table SQL for Cases table.
*
* @param selectedPlatform CR database platform.
*
* @return SQL string to create Cases table.
*/
private static String getCreateCasesTableStatement(CentralRepoPlatforms selectedPlatform) {
return ("CREATE TABLE IF NOT EXISTS cases (")
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "case_uid text NOT NULL,"
+ "org_id integer,"
+ "case_name text NOT NULL,"
+ "creation_date text NOT NULL,"
+ "case_number text,"
+ "examiner_name text,"
+ "examiner_email text,"
+ "examiner_phone text,"
+ "notes text,"
+ "foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT case_uid_unique UNIQUE(case_uid)" + getOnConflictIgnoreClause(selectedPlatform)
+ ")";
}
private static String getCasesOrgIdIndexStatement() {
return "CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
}
private static String getCasesCaseUidIndexStatement() {
return "CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
}
private static String getCreateReferenceSetsTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS reference_sets ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "org_id integer NOT NULL,"
+ "set_name text NOT NULL,"
+ "version text NOT NULL,"
+ "known_status integer NOT NULL,"
+ "read_only boolean NOT NULL,"
+ "type integer NOT NULL,"
+ "import_date text NOT NULL,"
+ "foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT hash_set_unique UNIQUE (set_name, version)"
+ ")";
}
/**
*
* @return
*/
private static String getReferenceSetsOrgIdIndexTemplate() {
return "CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
}
/**
* Returns the template string to create reference_TYPE tables.
*
* @param selectedPlatform CR database platform.
*
* @return template string to create a reference_TYPE table.
*/
private static String getReferenceTypesTableTemplate(CentralRepoPlatforms selectedPlatform) {
// Each "%s" will be replaced with the relevant reference_TYPE table name.
return "CREATE TABLE IF NOT EXISTS %s ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "reference_set_id integer,"
+ "value text NOT NULL,"
+ "known_status integer NOT NULL,"
+ "comment text,"
+ "CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value)" + getOnConflictIgnoreClause(selectedPlatform) + ","
+ "foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL"
+ ")";
}
/**
* Returns SQL string template to create a value index on
* ReferenceType table.
*/
private static String getReferenceTypeValueIndexTemplate() {
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Returns SQL string template to create a value/known_status index on
* ReferenceType table.
*/
private static String getReferenceTypeValueKnownstatusIndexTemplate() {
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
}
/**
* Returns the SQL statement to create correlation_types table.
*
* @param selectedPlatform CR database platform.
*
* @return SQL string to create correlation_types table.
*/
private static String getCreateCorrelationTypesTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS correlation_types ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "display_name text NOT NULL,"
+ "db_table_name text NOT NULL,"
+ "supported integer NOT NULL,"
+ "enabled integer NOT NULL,"
+ "CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)"
+ ")";
}
/**
* Get the template String for creating a new _instances table in a Sqlite
* central repository. %s will exist in the template where the name of the
* new table will be added.
*
* @return a String which is a template for creating a new _instances table
*/
static String getCreateArtifactInstancesTableTemplate(CentralRepoPlatforms selectedPlatform) {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE TABLE IF NOT EXISTS %s ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "case_id integer NOT NULL,"
+ "data_source_id integer NOT NULL,"
+ "account_id " + getBigIntType(selectedPlatform) + " DEFAULT NULL,"
+ "value text NOT NULL,"
+ "file_path text NOT NULL,"
+ "known_status integer NOT NULL,"
+ "comment text,"
+ "file_obj_id " + getBigIntType(selectedPlatform) + " ,"
+ "CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path)" + getOnConflictIgnoreClause(selectedPlatform) + ","
+ "foreign key (account_id) references accounts(id),"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)";
}
/**
* Get the statement String for creating a new data_sources table in a
* Sqlite central repository.
*
* @return a String which is a statement for creating a new data_sources
* table
*/
static String getCreateDataSourcesTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS data_sources ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "case_id integer NOT NULL,"
+ "device_id text NOT NULL,"
+ "name text NOT NULL,"
+ "datasource_obj_id " + getBigIntType(selectedPlatform) + " ,"
+ "md5 text DEFAULT NULL,"
+ "sha1 text DEFAULT NULL,"
+ "sha256 text DEFAULT NULL,"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
}
/**
* Get the template for creating an index on the case_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be added.
*
* @return a String which is a template for adding an index to the case_id
* column of a _instances table
*/
static String getAddCaseIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
}
/**
* Get the template for creating an index on the data_source_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be added.
*
* @return a String which is a template for adding an index to the
* data_source_id column of a _instances table
*/
static String getAddDataSourceIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
}
/**
* Get the template for creating an index on the value column of an instance
* table. %s will exist in the template where the name of the new table will
* be added.
*
* @return a String which is a template for adding an index to the value
* column of a _instances table
*/
static String getAddValueIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Get the template for creating an index on the known_status column of an
* instance table. %s will exist in the template where the name of the new
* table will be added.
*
* @return a String which is a template for adding an index to the
* known_status column of a _instances table
*/
static String getAddKnownStatusIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
}
/**
* Get the template for creating an index on the file_obj_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be added.
*
* @return a String which is a template for adding an index to the
* file_obj_id column of a _instances table
*/
static String getAddObjectIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
}
/**
* Get the statement for creating an index on the name column of the
* data_sources table.
*
* @return a String which is a statement for adding an index on the name
* column of the data_sources table.
*/
static String getAddDataSourcesNameIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
}
/**
* Get the statement for creating an index on the data_sources_object_id
* column of the data_sources table.
*
* @return a String which is a statement for adding an index on the
* data_sources_object_id column of the data_sources table.
*/
static String getAddDataSourcesObjectIdIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
}
/**
* Builds SQL clause for a numeric primary key. Produces correct SQL based
* on the selected CR platform/RDMBS.
*
* @param pkName name of primary key.
*
* @return SQL clause to be used in a Create table statement
*/
private static String getNumericPrimaryKeyClause(String pkName, CentralRepoPlatforms selectedPlatform) {
switch (selectedPlatform) {
case POSTGRESQL:
return String.format(" %s SERIAL PRIMARY KEY, ", pkName);
case SQLITE:
return String.format(" %s integer primary key autoincrement NOT NULL ,", pkName);
default:
return "";
}
}
/**
* Returns ON CONFLICT IGNORE clause for the specified database platform.
*
*
* @return SQL clause.
*/
private static String getOnConflictIgnoreClause(CentralRepoPlatforms selectedPlatform) {
switch (selectedPlatform) {
case POSTGRESQL:
return "";
case SQLITE:
return " ON CONFLICT IGNORE ";
default:
return "";
}
}
/**
* Returns keyword for big integer for the specified database platform.
*
*
* @return SQL clause.
*/
private static String getBigIntType(CentralRepoPlatforms selectedPlatform) {
switch (selectedPlatform) {
case POSTGRESQL:
return " BIGINT ";
case SQLITE:
return " INTEGER ";
default:
return "";
}
}
private static String getOnConflictDoNothingClause(CentralRepoPlatforms selectedPlatform) {
switch (selectedPlatform) {
case POSTGRESQL:
return "ON CONFLICT DO NOTHING";
case SQLITE:
return "";
default:
return "";
}
}
/**
* Returns an ephemeral connection to the CR database.
*
* @return CR database connection
*/
private Connection getEphemeralConnection() {
switch (selectedPlatform) {
case POSTGRESQL:
return this.postgresCentralRepoSettings.getEphemeralConnection(false);
case SQLITE:
return this.sqliteCentralRepoSettings.getEphemeralConnection();
default:
return null;
}
}
/**
* Creates the tables for Persona.
*
* @return True if success, False otherwise.
*/
private boolean createPersonaTables(Statement stmt) throws SQLException {
stmt.execute(getCreateConfidenceTableStatement(selectedPlatform));
stmt.execute(getCreateExaminersTableStatement(selectedPlatform));
stmt.execute(getCreatePersonaStatusTableStatement(selectedPlatform));
stmt.execute(getCreatePersonasTableStatement(selectedPlatform));
stmt.execute(getCreatePersonaAliasTableStatement(selectedPlatform));
stmt.execute(getCreatePersonaMetadataTableStatement(selectedPlatform));
stmt.execute(getCreatePersonaAccountsTableStatement(selectedPlatform));
return true;
}
/**
* Get the SQL string for creating a new account_types table in a central
* repository.
*
* @return SQL string for creating account_types table
*/
static String getCreateAccountTypesTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS account_types ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "type_name TEXT NOT NULL,"
+ "display_name TEXT NOT NULL,"
+ "correlation_type_id " + getBigIntType(selectedPlatform) + " ,"
+ "CONSTRAINT type_name_unique UNIQUE (type_name),"
+ "FOREIGN KEY (correlation_type_id) REFERENCES correlation_types(id)"
+ ")";
}
/**
* Get the SQL String for creating a new confidence table in a central
* repository.
*
* @return SQL string for creating confidence table
*/
static String getCreateConfidenceTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS confidence ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "confidence_id integer NOT NULL,"
+ "description TEXT,"
+ "CONSTRAINT level_unique UNIQUE (confidence_id)"
+ ")";
}
/**
* Get the SQL String for creating a new examiners table in a central
* repository.
*
* @return SQL string for creating examiners table
*/
static String getCreateExaminersTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS examiners ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "login_name TEXT NOT NULL,"
+ "display_name TEXT,"
+ "CONSTRAINT login_name_unique UNIQUE(login_name)"
+ ")";
}
/**
* Get the SQL String for creating a new persona_status table in a central
* repository.
*
* @return SQL string for creating persona_status table
*/
static String getCreatePersonaStatusTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS persona_status ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "status_id integer NOT NULL,"
+ "status TEXT NOT NULL,"
+ "CONSTRAINT status_unique UNIQUE(status_id)"
+ ")";
}
/**
* Get the SQL String for creating a new accounts table in a central
* repository.
*
* @return SQL string for creating accounts table
*/
static String getCreateAccountsTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS accounts ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "account_type_id integer NOT NULL,"
+ "account_unique_identifier TEXT NOT NULL,"
+ "CONSTRAINT account_unique UNIQUE(account_type_id, account_unique_identifier),"
+ "FOREIGN KEY (account_type_id) REFERENCES account_types(id)"
+ ")";
}
/**
* Get the SQL String for creating a new personas table in a central
* repository.
*
* @return SQL string for creating personas table
*/
static String getCreatePersonasTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS personas ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "uuid TEXT NOT NULL,"
+ "comment TEXT NOT NULL,"
+ "name TEXT NOT NULL,"
+ "created_date " + getBigIntType(selectedPlatform) + " ,"
+ "modified_date " + getBigIntType(selectedPlatform) + " ,"
+ "status_id integer NOT NULL,"
+ "examiner_id integer NOT NULL,"
+ "CONSTRAINT uuid_unique UNIQUE(uuid),"
+ "FOREIGN KEY (status_id) REFERENCES persona_status(status_id), "
+ "FOREIGN KEY (examiner_id) REFERENCES examiners(id)"
+ ")";
}
/**
* Get the SQL String for creating a new persona_alias table in a central
* repository.
*
* @return SQL string for creating persona_alias table
*/
static String getCreatePersonaAliasTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS persona_alias ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "persona_id " + getBigIntType(selectedPlatform) + " ,"
+ "alias TEXT NOT NULL, "
+ "justification TEXT NOT NULL,"
+ "confidence_id integer NOT NULL,"
+ "date_added " + getBigIntType(selectedPlatform) + " ,"
+ "examiner_id integer NOT NULL,"
+ "FOREIGN KEY (persona_id) REFERENCES personas(id),"
+ "FOREIGN KEY (confidence_id) REFERENCES confidence(confidence_id),"
+ "FOREIGN KEY (examiner_id) REFERENCES examiners(id)"
+ ")";
}
/**
* Get the SQL String for creating a new persona_metadata table in a central
* repository.
*
* @return SQL string for creating persona_metadata table
*/
static String getCreatePersonaMetadataTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS persona_metadata ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "persona_id " + getBigIntType(selectedPlatform) + " ,"
+ "name TEXT NOT NULL,"
+ "value TEXT NOT NULL,"
+ "justification TEXT NOT NULL,"
+ "confidence_id integer NOT NULL,"
+ "date_added " + getBigIntType(selectedPlatform) + " ,"
+ "examiner_id integer NOT NULL,"
+ "CONSTRAINT unique_metadata UNIQUE(persona_id, name),"
+ "FOREIGN KEY (persona_id) REFERENCES personas(id),"
+ "FOREIGN KEY (confidence_id) REFERENCES confidence(confidence_id),"
+ "FOREIGN KEY (examiner_id) REFERENCES examiners(id)"
+ ")";
}
/**
* Get the SQL String for creating a new persona_accounts table in a central
* repository.
*
* @return SQL string for creating persona_accounts table
*/
static String getCreatePersonaAccountsTableStatement(CentralRepoPlatforms selectedPlatform) {
return "CREATE TABLE IF NOT EXISTS persona_accounts ("
+ getNumericPrimaryKeyClause("id", selectedPlatform)
+ "persona_id " + getBigIntType(selectedPlatform) + " ,"
+ "account_id " + getBigIntType(selectedPlatform) + " ,"
+ "justification TEXT NOT NULL,"
+ "confidence_id integer NOT NULL,"
+ "date_added " + getBigIntType(selectedPlatform) + " ,"
+ "examiner_id integer NOT NULL,"
+ "FOREIGN KEY (persona_id) REFERENCES personas(id),"
+ "FOREIGN KEY (account_id) REFERENCES accounts(id),"
+ "FOREIGN KEY (confidence_id) REFERENCES confidence(confidence_id),"
+ "FOREIGN KEY (examiner_id) REFERENCES examiners(id)"
+ ")";
}
/**
* Inserts the default content in accounts related tables.
*
* @param conn Database connection to use.
*
* @return True if success, false otherwise.
*/
private boolean insertDefaultAccountsTablesContent(Connection conn) {
try (Statement stmt = conn.createStatement()) {
// Populate the account_types table
for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) {
int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type);
if (correlationTypeId > 0) {
String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform),
type.getTypeName(), type.getDisplayName(), correlationTypeId);
stmt.execute(sqlString);
}
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Accounts tables."), ex);
return false;
}
return true;
}
/**
* Inserts the default content in persona related tables.
*
* @param conn Database connection to use.
*
* @return True if success, false otherwise.
*/
private boolean insertDefaultPersonaTablesContent(Connection conn) {
try (Statement stmt = conn.createStatement()) {
// populate the confidence table
for (Confidence confidence : Persona.Confidence.values()) {
String sqlString = "INSERT INTO confidence (confidence_id, description) VALUES ( " + confidence.getLevel() + ", '" + confidence.toString() + "')" //NON-NLS
+ getOnConflictDoNothingClause(selectedPlatform);
stmt.execute(sqlString);
}
// populate the persona_status table
for (PersonaStatus status : Persona.PersonaStatus.values()) {
String sqlString = "INSERT INTO persona_status (status_id, status) VALUES ( " + status.getStatus() + ", '" + status.toString() + "')" //NON-NLS
+ getOnConflictDoNothingClause(selectedPlatform);
stmt.execute(sqlString);
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Persona tables."), ex);
return false;
}
return true;
}
/**
* Returns the correlation type id for the given account type,
* from the correlation_types table.
*
* @param conn Connection to use for database query.
* @param accountType Account type to look for.
* '
* @return correlation type id.
*/
private int getCorrelationTypeIdForAccountType(Connection conn, Account.Type accountType) {
int typeId = -1;
if (accountType == Account.Type.EMAIL) {
typeId = CorrelationAttributeInstance.EMAIL_TYPE_ID;
} else if (accountType == Account.Type.PHONE) {
typeId = CorrelationAttributeInstance.PHONE_TYPE_ID;
} else {
String querySql = "SELECT * FROM correlation_types WHERE display_name=?";
try ( PreparedStatement preparedStatementQuery = conn.prepareStatement(querySql)) {
preparedStatementQuery.setString(1, accountType.getDisplayName());
try (ResultSet resultSet = preparedStatementQuery.executeQuery();) {
if (resultSet.next()) {
typeId = resultSet.getInt("id");
}
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, String.format("Failed to get correlation typeId for account type %s.", accountType.getTypeName()), ex);
}
}
return typeId;
}
}

View File

@ -144,7 +144,8 @@ final class SqliteCentralRepo extends RdbmsCentralRepo {
CentralRepoDbUtil.closeConnection(conn);
}
dbSettings.insertDefaultDatabaseContent();
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettings);
centralRepoSchemaFactory.insertDefaultDatabaseContent();
} finally {
releaseExclusiveLock();
}
@ -226,6 +227,10 @@ final class SqliteCentralRepo extends RdbmsCentralRepo {
return "";
}
@Override
protected Connection getEphemeralConnection() {
return this.dbSettings.getEphemeralConnection();
}
/**
* Add a new name/value pair in the db_info table.
*

View File

@ -25,14 +25,11 @@ import java.nio.file.InvalidPathException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION;
/**
* Settings for the sqlite implementation of the Central Repository database
@ -40,21 +37,15 @@ 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 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
private final static String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS
private final static String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF";
private final static String PRAGMA_SYNC_NORMAL = "PRAGMA synchronous = NORMAL";
private final static String PRAGMA_JOURNAL_WAL = "PRAGMA journal_mode = WAL";
private final static String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True";
private final static String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'";
private final static String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096";
private final static String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON";
private final static String DB_NAMES_REGEX = "[a-z][a-z0-9_]*(\\.db)?";
private String dbName;
private String dbDirectory;
@ -89,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();
@ -112,6 +115,13 @@ public final class SqliteCentralRepoSettings {
return (!dbFile.isDirectory());
}
@Override
public boolean verifyDatabaseExists() {
return dbDirectoryExists();
}
/**
* Verify that the db directory path exists.
*
@ -131,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.
*
@ -182,7 +202,7 @@ public final class SqliteCentralRepoSettings {
*
* @return Connection or null.
*/
private Connection getEphemeralConnection() {
Connection getEphemeralConnection() {
if (!dbDirectoryExists()) {
return null;
}
@ -233,312 +253,6 @@ public final class SqliteCentralRepoSettings {
return result;
}
/**
* Initialize the database schema.
*
* Requires valid connectionPool.
*
* This method is called from within connect(), so we cannot call connect()
* to get a connection. This method is called after setupConnectionPool(),
* so it is safe to assume that a valid connectionPool exists. The
* implementation of connect() is synchronized, so we can safely use the
* connectionPool object directly.
*/
public boolean initializeDatabaseSchema() {
// The "id" column is an alias for the built-in 64-bit int "rowid" column.
// It is autoincrementing by default and must be of type "integer primary key".
// We've omitted the autoincrement argument because we are not currently
// using the id value to search for specific rows, so we do not care
// if a rowid is re-used after an existing rows was previously deleted.
StringBuilder createOrganizationsTable = new StringBuilder();
createOrganizationsTable.append("CREATE TABLE IF NOT EXISTS organizations (");
createOrganizationsTable.append("id integer primary key autoincrement NOT NULL,");
createOrganizationsTable.append("org_name text NOT NULL,");
createOrganizationsTable.append("poc_name text NOT NULL,");
createOrganizationsTable.append("poc_email text NOT NULL,");
createOrganizationsTable.append("poc_phone text NOT NULL,");
createOrganizationsTable.append("CONSTRAINT org_name_unique UNIQUE (org_name)");
createOrganizationsTable.append(")");
// NOTE: The organizations will only have a small number of rows, so
// an index is probably not worthwhile.
StringBuilder createCasesTable = new StringBuilder();
createCasesTable.append("CREATE TABLE IF NOT EXISTS cases (");
createCasesTable.append("id integer primary key autoincrement NOT NULL,");
createCasesTable.append("case_uid text NOT NULL,");
createCasesTable.append("org_id integer,");
createCasesTable.append("case_name text NOT NULL,");
createCasesTable.append("creation_date text NOT NULL,");
createCasesTable.append("case_number text,");
createCasesTable.append("examiner_name text,");
createCasesTable.append("examiner_email text,");
createCasesTable.append("examiner_phone text,");
createCasesTable.append("notes text,");
createCasesTable.append("CONSTRAINT case_uid_unique UNIQUE(case_uid) ON CONFLICT IGNORE,");
createCasesTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL");
createCasesTable.append(")");
// NOTE: when there are few cases in the cases table, these indices may not be worthwhile
String casesIdx1 = "CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
String casesIdx2 = "CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
StringBuilder createReferenceSetsTable = new StringBuilder();
createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
createReferenceSetsTable.append("id integer primary key autoincrement NOT NULL,");
createReferenceSetsTable.append("org_id integer NOT NULL,");
createReferenceSetsTable.append("set_name text NOT NULL,");
createReferenceSetsTable.append("version text NOT NULL,");
createReferenceSetsTable.append("known_status integer NOT NULL,");
createReferenceSetsTable.append("read_only boolean NOT NULL,");
createReferenceSetsTable.append("type integer NOT NULL,");
createReferenceSetsTable.append("import_date text NOT NULL,");
createReferenceSetsTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
createReferenceSetsTable.append("CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
createReferenceSetsTable.append(")");
String referenceSetsIdx1 = "CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
// Each "%s" will be replaced with the relevant reference_TYPE table name.
StringBuilder createReferenceTypesTableTemplate = new StringBuilder();
createReferenceTypesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
createReferenceTypesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
createReferenceTypesTableTemplate.append("reference_set_id integer,");
createReferenceTypesTableTemplate.append("value text NOT NULL,");
createReferenceTypesTableTemplate.append("known_status integer NOT NULL,");
createReferenceTypesTableTemplate.append("comment text,");
createReferenceTypesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value) ON CONFLICT IGNORE,");
createReferenceTypesTableTemplate.append("foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
createReferenceTypesTableTemplate.append(")");
// Each "%s" will be replaced with the relevant reference_TYPE table name.
String referenceTypesIdx1 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
String referenceTypesIdx2 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
StringBuilder createCorrelationTypesTable = new StringBuilder();
createCorrelationTypesTable.append("CREATE TABLE IF NOT EXISTS correlation_types (");
createCorrelationTypesTable.append("id integer primary key autoincrement NOT NULL,");
createCorrelationTypesTable.append("display_name text NOT NULL,");
createCorrelationTypesTable.append("db_table_name text NOT NULL,");
createCorrelationTypesTable.append("supported integer NOT NULL,");
createCorrelationTypesTable.append("enabled integer NOT NULL,");
createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
createCorrelationTypesTable.append(")");
String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
String instancesValueIdx = getAddValueIndexTemplate();
String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
// NOTE: the db_info table currenly only has 1 row, so having an index
// provides no benefit.
Connection conn = null;
try {
conn = getEphemeralConnection();
if (null == conn) {
return false;
}
Statement stmt = conn.createStatement();
stmt.execute(PRAGMA_JOURNAL_WAL);
stmt.execute(PRAGMA_SYNC_OFF);
stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
stmt.execute(PRAGMA_ENCODING_UTF8);
stmt.execute(PRAGMA_PAGE_SIZE_4096);
stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
stmt.execute(createOrganizationsTable.toString());
stmt.execute(createCasesTable.toString());
stmt.execute(casesIdx1);
stmt.execute(casesIdx2);
stmt.execute(getCreateDataSourcesTableStatement());
stmt.execute(getAddDataSourcesNameIndexStatement());
stmt.execute(getAddDataSourcesObjectIdIndexStatement());
stmt.execute(createReferenceSetsTable.toString());
stmt.execute(referenceSetsIdx1);
stmt.execute(createCorrelationTypesTable.toString());
/*
* Note that the essentially useless id column in the following
* table is required for backwards compatibility. Otherwise, the
* name column could be the primary key.
*/
stmt.execute("CREATE TABLE db_info (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MAJOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + "')");
stmt.execute("INSERT INTO db_info (name, value) VALUES ('" + RdbmsCentralRepo.CREATION_SCHEMA_MINOR_VERSION_KEY + "', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + "')");
// Create a separate instance and reference table for each artifact type
List<CorrelationAttributeInstance.Type> DEFAULT_CORRELATION_TYPES = CorrelationAttributeInstance.getDefaultCorrelationTypes();
String reference_type_dbname;
String instance_type_dbname;
for (CorrelationAttributeInstance.Type type : DEFAULT_CORRELATION_TYPES) {
reference_type_dbname = CentralRepoDbUtil.correlationTypeToReferenceTableName(type);
instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type);
stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
// FUTURE: allow more than the FILES type
if (type.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
}
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS
return false;
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error getting default correlation types. Likely due to one or more Type's with an invalid db table name."); // NON-NLS
return false;
} finally {
CentralRepoDbUtil.closeConnection(conn);
}
return true;
}
/**
* Get the template String for creating a new _instances table in a Sqlite
* central repository. %s will exist in the template where the name of the
* new table will be addedd.
*
* @return a String which is a template for cretating a new _instances table
*/
static String getCreateArtifactInstancesTableTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE TABLE IF NOT EXISTS %s (id integer primary key autoincrement NOT NULL,"
+ "case_id integer NOT NULL,data_source_id integer NOT NULL,value text NOT NULL,"
+ "file_path text NOT NULL,known_status integer NOT NULL,comment text,file_obj_id integer,"
+ "CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path) ON CONFLICT IGNORE,"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)";
}
/**
* Get the statement String for creating a new data_sources table in a
* Sqlite central repository.
*
* @return a String which is a statement for cretating a new data_sources
* table
*/
static String getCreateDataSourcesTableStatement() {
return "CREATE TABLE IF NOT EXISTS data_sources (id integer primary key autoincrement NOT NULL,"
+ "case_id integer NOT NULL,device_id text NOT NULL,name text NOT NULL,datasource_obj_id integer,"
+ "md5 text DEFAULT NULL,sha1 text DEFAULT NULL,sha256 text DEFAULT NULL,"
+ "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
+ "CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
}
/**
* Get the statement for creating an index on the name column of the
* data_sources table.
*
* @return a String which is a statement for adding an index on the name
* column of the data_sources table.
*/
static String getAddDataSourcesNameIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
}
/**
* Get the statement for creating an index on the data_sources_object_id
* column of the data_sources table.
*
* @return a String which is a statement for adding an index on the
* data_sources_object_id column of the data_sources table.
*/
static String getAddDataSourcesObjectIdIndexStatement() {
return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
}
/**
* Get the template for creating an index on the case_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the case_id
* column of a _instances table
*/
static String getAddCaseIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
}
/**
* Get the template for creating an index on the data_source_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* data_source_id column of a _instances table
*/
static String getAddDataSourceIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
}
/**
* Get the template for creating an index on the value column of an instance
* table. %s will exist in the template where the name of the new table will
* be addedd.
*
* @return a String which is a template for adding an index to the value
* column of a _instances table
*/
static String getAddValueIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
}
/**
* Get the template for creating an index on the known_status column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* known_status column of a _instances table
*/
static String getAddKnownStatusIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
}
/**
* Get the template for creating an index on the file_obj_id column of an
* instance table. %s will exist in the template where the name of the new
* table will be addedd.
*
* @return a String which is a template for adding an index to the
* file_obj_id column of a _instances table
*/
static String getAddObjectIdIndexTemplate() {
// Each "%s" will be replaced with the relevant TYPE_instances table name.
return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
}
public boolean insertDefaultDatabaseContent() {
Connection conn = getEphemeralConnection();
if (null == conn) {
return false;
}
boolean result = CentralRepoDbUtil.insertDefaultCorrelationTypes(conn) && CentralRepoDbUtil.insertDefaultOrganization(conn);
CentralRepoDbUtil.closeConnection(conn);
return result;
}
boolean isChanged() {
String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS
String dbDirectoryString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS
@ -640,5 +354,4 @@ public final class SqliteCentralRepoSettings {
String getJDBCBaseURI() {
return JDBC_BASE_URI;
}
}

View File

@ -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 enable it?
Installer.initialCreateSqlite.title=Enable Central Repository?

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2017-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -52,7 +52,6 @@ import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskDataException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
/**
@ -197,7 +196,7 @@ final class CaseEventListener implements PropertyChangeListener {
}
}
final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeInstanceFromContent(af);
final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile(af);
if (eamArtifact != null) {
// send update to Central Repository db
@ -297,7 +296,7 @@ final class CaseEventListener implements PropertyChangeListener {
return;
}
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(bbArtifact, true);
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsFromArtifact(bbArtifact);
for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) {
eamArtifact.setComment(comment);
try {
@ -370,7 +369,7 @@ final class CaseEventListener implements PropertyChangeListener {
if (!hasTagWithConflictingKnownStatus) {
//Get the correlation atttributes that correspond to the current BlackboardArtifactTag if their status should be changed
//with the initial set of correlation attributes this should be a single correlation attribute
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(bbTag.getArtifact(), true);
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsFromArtifact(bbTag.getArtifact());
for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) {
CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus());
}
@ -406,9 +405,12 @@ final class CaseEventListener implements PropertyChangeListener {
}
//if the file will have no tags with a status which would prevent the current status from being changed
if (!hasTagWithConflictingKnownStatus) {
final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeInstanceFromContent(contentTag.getContent());
if (eamArtifact != null) {
CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus());
Content taggedContent = contentTag.getContent();
if (taggedContent instanceof AbstractFile) {
final CorrelationAttributeInstance eamArtifact = CorrelationAttributeUtil.makeCorrAttrFromFile((AbstractFile)taggedContent);
if (eamArtifact != null) {
CentralRepository.getInstance().setAttributeInstanceKnownStatus(eamArtifact, tagName.getKnownStatus());
}
}
}
}
@ -455,7 +457,7 @@ final class CaseEventListener implements PropertyChangeListener {
}
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error adding new data source to the central repository", ex); //NON-NLS
}
}
} // DATA_SOURCE_ADDED
}
@ -495,7 +497,7 @@ final class CaseEventListener implements PropertyChangeListener {
}
} // CURRENT_CASE
}
private final class DataSourceNameChangedTask implements Runnable {
private final CentralRepository dbManager;
@ -508,12 +510,12 @@ final class CaseEventListener implements PropertyChangeListener {
@Override
public void run() {
final DataSourceNameChangedEvent dataSourceNameChangedEvent = (DataSourceNameChangedEvent) event;
Content dataSource = dataSourceNameChangedEvent.getDataSource();
String newName = (String) event.getNewValue();
if (! StringUtils.isEmpty(newName)) {
if (!StringUtils.isEmpty(newName)) {
if (!CentralRepository.isEnabled()) {
return;

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2019 Basis Technology Corp.
* Copyright 2017-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -456,7 +456,7 @@ public class IngestEventsListener {
for (BlackboardArtifact bbArtifact : bbArtifacts) {
// eamArtifact will be null OR a EamArtifact containing one EamArtifactInstance.
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(bbArtifact, true);
List<CorrelationAttributeInstance> convertedArtifacts = CorrelationAttributeUtil.makeCorrAttrsFromArtifact(bbArtifact);
for (CorrelationAttributeInstance eamArtifact : convertedArtifacts) {
try {
// Only do something with this artifact if it's unique within the job

View File

@ -1,7 +1,7 @@
/*
* Central Repository
* Autopsy Forensic Browser
*
* Copyright 2015-2017 Basis Technology Corp.
* Copyright 2017-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,28 +18,54 @@
*/
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;
import org.sleuthkit.autopsy.coreutils.Version;
/**
* Install event listeners during module initialization
* Adds/removes application event listeners responsible for adding data to the
* central repository, sets up a default, single-user SQLite central repository
* if no central repository is configured, and updates the central repository
* schema as required.
*
* TODO (Jira-6108): At first glance, this package seems to have become a rather
* strange package for the "package installer" for the CR to reside in. The
* org.sleuthkit.autopsy.centralrepository package would seem to be more
* appropriate with so much going on. However, having a central repository
* schema update occur in a "package installer" with no user feedback is not
* optimal. Furthermore, for a multi-user (collaborative) installation, a schema
* update should be done in a more controlled way by acquiring an exclusive
* coordination service lock and requiring shared locks to be acquired by nodes
* with open cases.
*/
public class Installer extends ModuleInstall {
private static final Logger LOGGER = Logger.getLogger(Installer.class.getName());
private static final Logger logger = Logger.getLogger(Installer.class.getName());
private static final long serialVersionUID = 1L;
private final CaseEventListener pcl = new CaseEventListener();
private final IngestEventsListener ieListener = new IngestEventsListener();
private static Installer instance;
private final CaseEventListener caseEventListener = new CaseEventListener();
private final IngestEventsListener ingestEventListener = new IngestEventsListener();
/**
* Gets the singleton "package installer" used by the registered Installer
* for the Autopsy-Core module located in the org.sleuthkit.autopsy.core
* package.
*
* @return The "package installer" singleton for the
* org.sleuthkit.autopsy.centralrepository.eventlisteners package.
*/
public synchronized static Installer getDefault() {
if (instance == null) {
instance = new Installer();
@ -47,48 +73,189 @@ public class Installer extends ModuleInstall {
return instance;
}
/**
* Constructs the singleton "package installer" used by the registered
* Installer for the Autopsy-Core module located in the
* org.sleuthkit.autopsy.core package.
*/
private Installer() {
super();
}
@NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository disabled"})
/*
* Adds/removes application event listeners responsible for adding data to
* the central repository, sets up a default, single-user SQLite central
* repository if no central repository is configured, and updates the
* central repository schema as required.
*
* Called by the registered Installer for the Autopsy-Core module located in
* the org.sleuthkit.autopsy.core package when the already installed
* Autopsy-Core module is restored (during application startup).
*/
@NbBundle.Messages({
"Installer.initialCreateSqlite.title=Enable Central Repository?",
"Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?",
"Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. "
+ "You can use this to ignore previously seen files and make connections between cases."
})
@Override
public void restored() {
Case.addPropertyChangeListener(pcl);
ieListener.installListeners();
addApplicationEventListeners();
// Perform the database upgrade and inform the user if it fails
try {
CentralRepoDbUtil.upgradeDatabase();
} catch (CentralRepoException ex) {
if (Version.getBuildType() == Version.Type.RELEASE) {
setupDefaultCentralRepository();
}
updateCentralRepoSchema();
}
/**
* Adds the application event listeners responsible for adding data to the
* central repository.
*/
private void addApplicationEventListeners() {
Case.addPropertyChangeListener(caseEventListener);
ingestEventListener.installListeners();
}
/**
* Checks if the central repository has been set up and configured. If not,
* either offers to perform set up (running with a GUI) or does the set up
* unconditionally (not running with a GUI, e.g., in an automated ingest
* node).
*/
private void setupDefaultCentralRepository() {
Map<String, String> 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()) {
WindowManager.getDefault().invokeWhenUIReady(() -> {
try {
SwingUtilities.invokeAndWait(() -> {
try {
String dialogText
= "<html><body>"
+ "<div style='width: 400px;'>"
+ "<p>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "</p>"
+ "<p style='margin-top: 10px'>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "</p>"
+ "</div>"
+ "</body></html>";
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(),
dialogText,
NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"),
JOptionPane.YES_NO_OPTION)) {
setupDefaultSqliteCentralRepo();
}
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
doMessageBoxIfRunningInGUI(ex);
}
});
} catch (InterruptedException | InvocationTargetException ex) {
logger.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex);
}
} // if no GUI, just initialize
else {
try {
setupDefaultSqliteCentralRepo();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
doMessageBoxIfRunningInGUI(ex);
}
}
ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true");
}
}
/**
* Sets up a default single-user SQLite central repository.
*
* @throws CentralRepoException If there is an error setting up teh central
* repository.
*/
private void setupDefaultSqliteCentralRepo() throws CentralRepoException {
CentralRepoDbManager manager = new CentralRepoDbManager();
manager.setupDefaultSqliteDb();
}
/**
* Update the central repository schema.
*/
private void updateCentralRepoSchema() {
try {
CentralRepoDbManager.upgradeDatabase();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "An error occurred updating the central repository schema", ex);
if (RuntimeProperties.runningWithGUI()) {
doMessageBoxIfRunningInGUI(ex);
}
}
}
/**
* Display a central repository exception in a message box if running with a
* GUI.
*
* @param ex The exception.
*/
@NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository disabled"})
private void doMessageBoxIfRunningInGUI(CentralRepoException ex) {
if (RuntimeProperties.runningWithGUI()) {
try {
SwingUtilities.invokeAndWait(() -> {
JOptionPane.showMessageDialog(null,
ex.getUserMessage(),
NbBundle.getMessage(this.getClass(),
"Installer.centralRepoUpgradeFailed.title"),
NbBundle.getMessage(this.getClass(), "Installer.centralRepoUpgradeFailed.title"),
JOptionPane.ERROR_MESSAGE);
});
} catch (InterruptedException | InvocationTargetException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
@Override
public boolean closing() {
//platform about to close
return true;
}
@Override
public void uninstalled() {
//module is being unloaded
Case.removePropertyChangeListener(pcl);
pcl.shutdown();
ieListener.shutdown();
ieListener.uninstallListeners();
// TODO: remove thread pool
/*
* TODO (Jira-6108): This code is erronoeous. As documented at
* http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/ModuleInstall.html#uninstalled--
*
* "Called when the module is disabled while the application is still
* running. Should remove whatever functionality that it had registered
* in ModuleInstall.restored().
*
* Beware: in practice there is no way to
* ensure that this method will really be called. The module might
* simply be deleted or disabled while the application is not running.
* In fact this is always the case in NetBeans 6.0; the Plugin Manager
* only uninstalls or disables modules between restarts. This method
* will still be called if you reload a module during development."
*
* THIS CODE IS NEVER EXECUTED.
*/
Case.removePropertyChangeListener(caseEventListener);
caseEventListener.shutdown();
ingestEventListener.shutdown();
ingestEventListener.uninstallListeners();
}
}

View File

@ -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());

View File

@ -133,7 +133,7 @@
<Component id="lbUserName" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lbPort" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
<Component id="lbDatabaseDesc" alignment="0" max="32767" attributes="0"/>
<Component id="lbDatabaseDesc" alignment="0" pref="94" max="32767" attributes="0"/>
<Component id="lbUserPassword" alignment="0" max="32767" attributes="0"/>
</Group>
</Group>
@ -410,4 +410,4 @@
</SubComponents>
</Container>
</SubComponents>
</Form>
</Form>

View File

@ -35,16 +35,18 @@ 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;
/**
* Configuration dialog for Central Repository database settings.
@ -53,17 +55,12 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
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<JTextField> 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
@ -72,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(),
@ -80,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);
@ -97,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
@ -105,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.
@ -358,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() {
@ -382,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()) {
@ -398,227 +448,61 @@ 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) {
result = dbSettingsPostgres.initializeDatabaseSchema()
&& dbSettingsPostgres.insertDefaultDatabaseContent();
}
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) {
result = dbSettingsSqlite.initializeDatabaseSchema()
&& dbSettingsSqlite.insertDefaultDatabaseContent();
}
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
*
* @return true if the database configuration was successfully changed false
* if it was not
* 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());
}
@ -656,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);
}
@ -673,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);
}
@ -709,7 +593,7 @@ public class EamDbSettingsDialog extends JDialog {
* Adds a change listener to a collection of text fields.
*
* @param textFields The text fields.
* @param listener The change listener.
* @param listener The change listener.
*/
private static void addDocumentListeners(Collection<JTextField> textFields, TextBoxChangedListener listener) {
textFields.forEach((textField) -> {
@ -726,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;
@ -757,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.
@ -843,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
@ -853,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();
}
@ -861,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();
}
@ -869,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;

View File

@ -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));

View File

@ -42,6 +42,7 @@ class CommandLineCommand {
*/
static enum InputType {
CASE_NAME,
CASE_TYPE,
CASES_BASE_DIR_PATH,
CASE_FOLDER_PATH,
DATA_SOURCE_PATH,

View File

@ -38,6 +38,7 @@ import org.netbeans.spi.sendopts.OptionProcessor;
import org.openide.LifecycleManager;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.casemodule.CaseActionException;
import org.sleuthkit.autopsy.casemodule.CaseDetails;
import org.sleuthkit.autopsy.casemodule.CaseMetadata;
@ -157,7 +158,12 @@ public class CommandLineIngestManager {
Map<String, String> inputs = command.getInputs();
String baseCaseName = inputs.get(CommandLineCommand.InputType.CASE_NAME.name());
String rootOutputDirectory = inputs.get(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name());
openCase(baseCaseName, rootOutputDirectory);
CaseType caseType = CaseType.SINGLE_USER_CASE;
String caseTypeString = inputs.get(CommandLineCommand.InputType.CASE_TYPE.name());
if (caseTypeString != null && caseTypeString.equalsIgnoreCase(CommandLineOptionProcessor.CASETYPE_MULTI)) {
caseType = CaseType.MULTI_USER_CASE;
}
openCase(baseCaseName, rootOutputDirectory, caseType);
String outputDirPath = getOutputDirPath(caseForJob);
OutputGenerator.saveCreateCaseOutput(caseForJob, outputDirPath, baseCaseName);
@ -340,7 +346,7 @@ public class CommandLineIngestManager {
*
* @throws CaseActionException
*/
private void openCase(String baseCaseName, String rootOutputDirectory) throws CaseActionException {
private void openCase(String baseCaseName, String rootOutputDirectory, CaseType caseType) throws CaseActionException {
LOGGER.log(Level.INFO, "Opening case {0} in directory {1}", new Object[]{baseCaseName, rootOutputDirectory});
Path caseDirectoryPath = findCaseDirectory(Paths.get(rootOutputDirectory), baseCaseName);
@ -355,7 +361,7 @@ public class CommandLineIngestManager {
Case.createCaseDirectory(caseDirectoryPath.toString(), Case.CaseType.SINGLE_USER_CASE);
CaseDetails caseDetails = new CaseDetails(baseCaseName);
Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), caseDetails);
Case.createAsCurrentCase(caseType, caseDirectoryPath.toString(), caseDetails);
}
caseForJob = Case.getCurrentCase();

View File

@ -31,6 +31,7 @@ import org.netbeans.spi.sendopts.Env;
import org.netbeans.spi.sendopts.Option;
import org.netbeans.spi.sendopts.OptionProcessor;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
/**
* This class can be used to add command line options to Autopsy
@ -40,6 +41,7 @@ public class CommandLineOptionProcessor extends OptionProcessor {
private static final Logger logger = Logger.getLogger(CommandLineOptionProcessor.class.getName());
private final Option caseNameOption = Option.requiredArgument('n', "caseName");
private final Option caseTypeOption = Option.requiredArgument('t', "caseType");
private final Option caseBaseDirOption = Option.requiredArgument('o', "caseBaseDir");
private final Option createCaseCommandOption = Option.withoutArgument('c', "createCase");
private final Option dataSourcePathOption = Option.requiredArgument('s', "dataSourcePath");
@ -55,11 +57,15 @@ public class CommandLineOptionProcessor extends OptionProcessor {
private final List<CommandLineCommand> commands = new ArrayList<>();
final static String CASETYPE_MULTI = "multi";
final static String CASETYPE_SINGLE = "single";
@Override
protected Set<Option> getOptions() {
Set<Option> set = new HashSet<>();
set.add(createCaseCommandOption);
set.add(caseNameOption);
set.add(caseTypeOption);
set.add(caseBaseDirOption);
set.add(dataSourcePathOption);
set.add(addDataSourceCommandOption);
@ -107,6 +113,37 @@ public class CommandLineOptionProcessor extends OptionProcessor {
}
}
String caseType = "";
if (values.containsKey(caseTypeOption)) {
argDirs = values.get(caseTypeOption);
if (argDirs.length < 1) {
logger.log(Level.SEVERE, "Missing argument 'caseType'");
System.err.println("Missing argument 'caseType'");
return;
}
caseType = argDirs[0];
if (caseType == null || caseType.isEmpty()) {
logger.log(Level.SEVERE, "'caseType' argument is empty");
System.err.println("'caseType' argument is empty");
return;
}
if (!caseType.equalsIgnoreCase(CASETYPE_MULTI) && !caseType.equalsIgnoreCase(CASETYPE_SINGLE)) {
logger.log(Level.SEVERE, "'caseType' argument is invalid");
System.err.println("'caseType' argument is invalid");
return;
}
if (caseType.equalsIgnoreCase(CASETYPE_MULTI) && !FeatureAccessUtils.canCreateMultiUserCases()) {
logger.log(Level.SEVERE, "Unable to create multi user case.");
System.err.println("Unable to create multi user case. Confirm that multi user settings are configured correctly.");
return;
}
}
String caseBaseDir = "";
if (values.containsKey(caseBaseDirOption)) {
argDirs = values.get(caseBaseDirOption);
@ -241,6 +278,7 @@ public class CommandLineOptionProcessor extends OptionProcessor {
CommandLineCommand newCommand = new CommandLineCommand(CommandLineCommand.CommandType.CREATE_CASE);
newCommand.addInputValue(CommandLineCommand.InputType.CASE_NAME.name(), inputCaseName);
newCommand.addInputValue(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name(), caseBaseDir);
newCommand.addInputValue(CommandLineCommand.InputType.CASE_TYPE.name(), caseType);
commands.add(newCommand);
runFromCommandLine = true;
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018-2019 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -123,8 +123,7 @@ final public class CommonAttributeCaseSearchResults {
if (currentCaseDataSourceMap == null) { //there are no results
return filteredCaseNameToDataSourcesTree;
}
CorrelationAttributeInstance.Type attributeType = CorrelationAttributeInstance
.getDefaultCorrelationTypes()
CorrelationAttributeInstance.Type attributeType = CentralRepository.getInstance().getCorrelationTypes()
.stream()
.filter(filterType -> filterType.getId() == resultTypeId)
.findFirst().get();

View File

@ -128,14 +128,13 @@ final public class CommonAttributeCountSearchResults {
return;
}
CorrelationAttributeInstance.Type attributeType = CorrelationAttributeInstance
.getDefaultCorrelationTypes()
CentralRepository eamDb = CentralRepository.getInstance();
CorrelationAttributeInstance.Type attributeType = eamDb.getCorrelationTypes()
.stream()
.filter(filterType -> filterType.getId() == this.resultTypeId)
.findFirst().get();
CentralRepository eamDb = CentralRepository.getInstance();
Map<Integer, List<CommonAttributeValue>> itemsToRemove = new HashMap<>();
//Call countUniqueDataSources once to reduce the number of DB queries needed to get
//the frequencyPercentage

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -255,7 +255,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
filterByDocuments = interCasePanel.documentsCheckboxIsSelected();
}
if (corType == null) {
corType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0);
corType = CentralRepository.getInstance().getCorrelationTypes().get(0);
}
if (caseId == InterCasePanel.NO_CASE_SELECTED) {
builder = new AllInterCaseCommonAttributeSearcher(filterByMedia, filterByDocuments, corType, percentageThreshold);
@ -366,7 +366,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer
filterByDocuments = interCasePanel.documentsCheckboxIsSelected();
}
if (corType == null) {
corType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0);
corType = CentralRepository.getInstance().getCorrelationTypes().get(0);
}
if (caseId == InterCasePanel.NO_CASE_SELECTED) {
builder = new AllInterCaseCommonAttributeSearcher(filterByMedia, filterByDocuments, corType, percentageThreshold);

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018-2019 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -31,6 +31,7 @@ import java.util.logging.Level;
import javax.swing.ComboBoxModel;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
@ -117,7 +118,7 @@ public final class InterCasePanel extends javax.swing.JPanel {
void setupCorrelationTypeFilter() {
this.correlationTypeFilters = new HashMap<>();
try {
List<CorrelationAttributeInstance.Type> types = CorrelationAttributeInstance.getDefaultCorrelationTypes();
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getCorrelationTypes();
for (CorrelationAttributeInstance.Type type : types) {
correlationTypeFilters.put(type.getDisplayName(), type);
this.correlationTypeComboBox.addItem(type.getDisplayName());

View File

@ -16,9 +16,9 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="filterTabPane">
<Container class="javax.swing.JTabbedPane" name="filterTabbedPane">
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="filterTabPaneMouseClicked"/>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="filterTabbedPaneMouseClicked"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
@ -40,10 +40,6 @@
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.communications.FiltersPanel" name="filtersPane">
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
@ -78,42 +74,6 @@
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.communications.AccountsBrowser" name="accountsBrowser">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.sleuthkit.autopsy.communications.AccountsBrowser(relationshipBrowser)"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
<JTabbedPaneConstraints tabName="Browse">
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="tabIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/communications/images/table.png"/>
</Property>
</JTabbedPaneConstraints>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.VisualizationPanel" name="vizPanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.sleuthkit.autopsy.communications.VisualizationPanel(relationshipBrowser)"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
<JTabbedPaneConstraints tabName="Visualize">
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="CVTTopComponent.vizPanel.TabConstraints.tabTitle_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="tabIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/communications/images/emblem-web.png"/>
</Property>
</JTabbedPaneConstraints>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>

View File

@ -55,15 +55,18 @@ public final class CVTTopComponent extends TopComponent {
private static final long serialVersionUID = 1L;
private boolean filtersVisible = true;
private final RelationshipBrowser relationshipBrowser = new RelationshipBrowser();
private final AccountsBrowser accountsBrowser = new AccountsBrowser(relationshipBrowser);
private CommunicationsFilter currentFilter;
private final VisualizationPanel vizPanel = new VisualizationPanel(relationshipBrowser);
private final FiltersPanel filtersPane = new FiltersPanel();
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public CVTTopComponent() {
initComponents();
splitPane.setRightComponent(relationshipBrowser);
splitPane.setDividerLocation(0.25);
setName(Bundle.CVTTopComponent_name());
/*
@ -76,15 +79,18 @@ public final class CVTTopComponent extends TopComponent {
// Make sure the Global Actions Context is proxying the selection of the active tab.
browseVisualizeTabPane.addChangeListener(changeEvent -> {
Component selectedComponent = browseVisualizeTabPane.getSelectedComponent();
if(selectedComponent instanceof Lookup.Provider) {
Lookup lookup = ((Lookup.Provider)selectedComponent).getLookup();
if (selectedComponent instanceof Lookup.Provider) {
Lookup lookup = ((Lookup.Provider) selectedComponent).getLookup();
proxyLookup.setNewLookups(lookup);
}
relationshipBrowser.setSelectionInfo(new SelectionInfo(new HashSet<>(), new HashSet<>(), currentFilter));
});
filterTabPanel.setLayout(new BorderLayout());
filterTabPanel.add(filtersPane, BorderLayout.CENTER);
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N
/*
* Connect the filtersPane to the accountsBrowser and visualizaionPanel
* via an Eventbus
@ -93,16 +99,17 @@ public final class CVTTopComponent extends TopComponent {
CVTEvents.getCVTEventBus().register(vizPanel);
CVTEvents.getCVTEventBus().register(accountsBrowser);
CVTEvents.getCVTEventBus().register(filtersPane);
filterTabbedPane.setIconAt(0, new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-left.png")));
filterTabbedPane.setTitleAt(0, "");
}
@Subscribe
void pinAccount(CVTEvents.PinAccountsEvent pinEvent) {
browseVisualizeTabPane.setSelectedIndex(1);
}
@Subscribe
void handle(final CVTEvents.FilterChangeEvent filterChangeEvent) {
currentFilter = filterChangeEvent.getNewFilter();
@ -118,11 +125,8 @@ public final class CVTTopComponent extends TopComponent {
filterTabbedPane = new JTabbedPane();
filterTabPanel = new JPanel();
filtersPane = new FiltersPanel();
splitPane = new JSplitPane();
browseVisualizeTabPane = new JTabbedPane();
accountsBrowser = new AccountsBrowser(relationshipBrowser);
vizPanel = new VisualizationPanel(relationshipBrowser);
setLayout(new BorderLayout());
@ -131,10 +135,6 @@ public final class CVTTopComponent extends TopComponent {
filterTabbedPaneMouseClicked(evt);
}
});
filterTabPanel.setLayout(new BorderLayout());
filterTabPanel.add(filtersPane, BorderLayout.CENTER);
filterTabbedPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.filterTabPanel.TabConstraints.tabTitle"), filterTabPanel); // NOI18N
add(filterTabbedPane, BorderLayout.WEST);
@ -143,9 +143,6 @@ public final class CVTTopComponent extends TopComponent {
splitPane.setResizeWeight(0.25);
browseVisualizeTabPane.setFont(new Font("Tahoma", 0, 18)); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N
splitPane.setLeftComponent(browseVisualizeTabPane);
browseVisualizeTabPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N
@ -154,8 +151,8 @@ public final class CVTTopComponent extends TopComponent {
private void filterTabbedPaneMouseClicked(MouseEvent evt) {//GEN-FIRST:event_filterTabPaneMouseClicked
int index = filterTabbedPane.indexAtLocation(evt.getX(), evt.getY());
if(index != -1) {
if(filtersVisible) {
if (index != -1) {
if (filtersVisible) {
filterTabbedPane.setIconAt(0, new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/arrow-right.png")));
filterTabPanel.removeAll();
filterTabPanel.revalidate();
@ -171,13 +168,10 @@ public final class CVTTopComponent extends TopComponent {
// Variables declaration - do not modify//GEN-BEGIN:variables
private AccountsBrowser accountsBrowser;
private JTabbedPane browseVisualizeTabPane;
private JTabbedPane filterTabbedPane;
private JPanel filterTabPanel;
private FiltersPanel filtersPane;
private JTabbedPane filterTabbedPane;
private JSplitPane splitPane;
private VisualizationPanel vizPanel;
// End of variables declaration//GEN-END:variables
@Override

View File

@ -65,7 +65,6 @@ import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.DeviceFilter;
import org.sleuthkit.datamodel.CommunicationsFilter.MostRecentFilter;
import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.DataSource;
import static org.sleuthkit.datamodel.Relationship.Type.CALL_LOG;
import static org.sleuthkit.datamodel.Relationship.Type.CONTACT;
@ -129,7 +128,7 @@ final public class FiltersPanel extends JPanel {
public FiltersPanel() {
initComponents();
initalizeDeviceAccountType();
initalizeDeviceAccountType();
deviceRequiredLabel.setVisible(false);
accountTypeRequiredLabel.setVisible(false);
@ -149,7 +148,6 @@ final public class FiltersPanel extends JPanel {
updateTimeZone();
validationListener = itemEvent -> validateFilters();
updateFilters(true);
UserPreferences.addChangeListener(preferenceChangeEvent -> {
if (preferenceChangeEvent.getKey().equals(UserPreferences.DISPLAY_TIMES_IN_LOCAL_TIME)
|| preferenceChangeEvent.getKey().equals(UserPreferences.TIME_ZONE_FOR_DISPLAYS)) {
@ -239,9 +237,15 @@ final public class FiltersPanel extends JPanel {
* Updates the filter widgets to reflect he data sources/types in the case.
*/
private boolean updateFilters(boolean initialState) {
boolean newAccountType = updateAccountTypeFilter(initialState);
boolean newDeviceFilter = updateDeviceFilter(initialState);
final SleuthkitCase sleuthkitCase;
try {
sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase();
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to perform filter update, update has been cancelled. Case is closed.", ex);
return false;
}
boolean newAccountType = updateAccountTypeFilter(initialState, sleuthkitCase);
boolean newDeviceFilter = updateDeviceFilter(initialState, sleuthkitCase);
// both or either are true, return true;
return newAccountType || newDeviceFilter;
}
@ -255,10 +259,10 @@ final public class FiltersPanel extends JPanel {
//clear the device filter widget when the case changes.
devicesMap.clear();
devicesListPane.removeAll();
accountTypeMap.clear();
accountTypeListPane.removeAll();
accountTypeMap.clear();
accountTypeListPane.removeAll();
initalizeDeviceAccountType();
});
}
@ -269,7 +273,7 @@ final public class FiltersPanel extends JPanel {
IngestManager.getInstance().removeIngestModuleEventListener(ingestListener);
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
}
private void initalizeDeviceAccountType() {
CheckBoxIconPanel panel = createAccoutTypeCheckBoxPanel(Account.Type.DEVICE, true);
accountTypeMap.put(Account.Type.DEVICE, panel.getCheckBox());
@ -277,17 +281,18 @@ final public class FiltersPanel extends JPanel {
}
/**
* Populate the Account Types filter widgets
* Populate the Account Types filter widgets.
*
* @param selected the initial value for the account type checkbox
* @param selected The initial value for the account type checkbox.
* @param sleuthkitCase The sleuthkit case for containing the account
* information.
*
* @return True, if a new accountType was found
*/
private boolean updateAccountTypeFilter(boolean selected) {
private boolean updateAccountTypeFilter(boolean selected, SleuthkitCase sleuthkitCase) {
boolean newOneFound = false;
try {
final CommunicationsManager communicationsManager = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager();
List<Account.Type> accountTypesInUse = communicationsManager.getAccountTypesInUse();
List<Account.Type> accountTypesInUse = sleuthkitCase.getCommunicationsManager().getAccountTypesInUse();
for (Account.Type type : accountTypesInUse) {
@ -302,10 +307,7 @@ final public class FiltersPanel extends JPanel {
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to update to update Account Types Filter", ex);
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "A case is required to update the account types filter.", ex);
}
if (newOneFound) {
accountTypeListPane.revalidate();
}
@ -333,17 +335,17 @@ final public class FiltersPanel extends JPanel {
}
/**
* Populate the devices filter widgets
* Populate the devices filter widgets.
*
* @param selected Sets the initial state of device check box
* @param selected Sets the initial state of device check box.
* @param sleuthkitCase The sleuthkit case for containing the data source
* information.
*
* @return true if a new device was found
*/
private boolean updateDeviceFilter(boolean selected) {
private boolean updateDeviceFilter(boolean selected, SleuthkitCase sleuthkitCase) {
boolean newOneFound = false;
try {
final SleuthkitCase sleuthkitCase = Case.getCurrentCaseThrows().getSleuthkitCase();
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
if (devicesMap.containsKey(dataSource.getDeviceId())) {
@ -358,8 +360,6 @@ final public class FiltersPanel extends JPanel {
newOneFound = true;
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.INFO, "Filter update cancelled. Case is closed.");
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "There was a error loading the datasources for the case.", tskCoreException);
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -111,7 +111,7 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory<CorrelationCase
private CorrelationAttributeInstance.Type getCorrelationType(Account.Type accountType) throws CentralRepoException {
if (correlationTypeMap == null) {
correlationTypeMap = new HashMap<>();
List<CorrelationAttributeInstance.Type> correcationTypeList = CorrelationAttributeInstance.getDefaultCorrelationTypes();
List<CorrelationAttributeInstance.Type> correcationTypeList = CentralRepository.getInstance().getCorrelationTypes();
correcationTypeList.forEach((type) -> {
correlationTypeMap.put(type.getId(), type);
});

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -198,7 +198,7 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
startSection(html, "Central Repository Comments");
List<CorrelationAttributeInstance> instancesList = new ArrayList<>();
if (artifact != null) {
instancesList.addAll(CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(artifact, false));
instancesList.addAll(CorrelationAttributeUtil.makeCorrAttrsFromArtifact(artifact));
}
try {
List<CorrelationAttributeInstance.Type> artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes();

View File

@ -2,3 +2,7 @@ ContextViewer.jSourceGoToResultButton.text=Go to Result
ContextViewer.jSourceTextLabel.text=jLabel2
ContextViewer.jSourceNameLabel.text=jSourceNameLabel
ContextViewer.jSourceLabel.text=Source
ContextViewer.jUsageGoToResultButton.text=Go to Result
ContextViewer.jUsageLabel.text=Usage
ContextViewer.jUsageNameLabel.text=jSourceNameLabel
ContextViewer.jUsageTextLabel.text=jLabel2

View File

@ -7,9 +7,16 @@ ContextViewer.jSourceGoToResultButton.text=Go to Result
ContextViewer.jSourceTextLabel.text=jLabel2
ContextViewer.jSourceNameLabel.text=jSourceNameLabel
ContextViewer.jSourceLabel.text=Source
ContextViewer.jUsageGoToResultButton.text=Go to Result
ContextViewer.jUsageLabel.text=Usage
ContextViewer.jUsageNameLabel.text=jSourceNameLabel
ContextViewer.jUsageTextLabel.text=jLabel2
ContextViewer.message=Message
ContextViewer.messageFrom=From
ContextViewer.messageOn=On
ContextViewer.messageTo=To
ContextViewer.on=Opened at
ContextViewer.recentDocs=Recent Documents:
ContextViewer.title=Context
ContextViewer.toolTip=Displays context for selected file.
ContextViewer.unknown=Opened at unknown time

View File

@ -24,23 +24,38 @@
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="jSourceLabel" min="-2" max="-2" attributes="0"/>
<Component id="jUsageLabel" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace min="6" pref="6" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jSourceLabel" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="jSourceNameLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jSourceTextLabel" pref="192" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="jUsageNameLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jUsageTextLabel" pref="192" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" pref="36" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="jSourceGoToResultButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" pref="36" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" pref="45" max="-2" attributes="0"/>
<Component id="jUsageGoToResultButton" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="46" max="-2" attributes="0"/>
<Component id="jSourceGoToResultButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -56,7 +71,16 @@
</Group>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="jSourceGoToResultButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="203" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jUsageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="jUsageNameLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="jUsageTextLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="jUsageGoToResultButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="62" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -96,5 +120,39 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="jUsageGoToResultButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties" key="ContextViewer.jUsageGoToResultButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jUsageGoToResultButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jUsageLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Dialog" size="14" style="1"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties" key="ContextViewer.jUsageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jUsageNameLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties" key="ContextViewer.jUsageNameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jUsageTextLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/contextviewer/Bundle.properties" key="ContextViewer.jUsageTextLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -83,6 +83,10 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
jSourceLabel = new javax.swing.JLabel();
jSourceNameLabel = new javax.swing.JLabel();
jSourceTextLabel = new javax.swing.JLabel();
jUsageGoToResultButton = new javax.swing.JButton();
jUsageLabel = new javax.swing.JLabel();
jUsageNameLabel = new javax.swing.JLabel();
jUsageTextLabel = new javax.swing.JLabel();
setBackground(new java.awt.Color(255, 255, 255));
@ -100,6 +104,20 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
org.openide.awt.Mnemonics.setLocalizedText(jSourceTextLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jSourceTextLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jUsageGoToResultButton, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageGoToResultButton.text")); // NOI18N
jUsageGoToResultButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jUsageGoToResultButtonActionPerformed(evt);
}
});
jUsageLabel.setFont(new java.awt.Font("Dialog", 1, 14)); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jUsageLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jUsageNameLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageNameLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jUsageTextLabel, org.openide.util.NbBundle.getMessage(ContextViewer.class, "ContextViewer.jUsageTextLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -107,18 +125,29 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jSourceLabel)
.addComponent(jUsageLabel)
.addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jSourceLabel)
.addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(jSourceNameLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE)))
.addGap(36, 36, 36))
.addComponent(jSourceTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(jUsageNameLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jUsageTextLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE)))))
.addGap(36, 36, 36))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jSourceGoToResultButton)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
.addGap(45, 45, 45)
.addComponent(jUsageGoToResultButton))
.addGroup(layout.createSequentialGroup()
.addGap(46, 46, 46)
.addComponent(jSourceGoToResultButton)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -131,7 +160,15 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
.addComponent(jSourceTextLabel))
.addGap(18, 18, 18)
.addComponent(jSourceGoToResultButton)
.addGap(0, 203, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jUsageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jUsageNameLabel)
.addComponent(jUsageTextLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jUsageGoToResultButton)
.addGap(0, 62, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
@ -146,6 +183,15 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
}//GEN-LAST:event_jSourceGoToResultButtonActionPerformed
private void jUsageGoToResultButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jUsageGoToResultButtonActionPerformed
final DirectoryTreeTopComponent dtc = DirectoryTreeTopComponent.findInstance();
// Navigate to the source context artifact.
if (sourceContextArtifact != null) {
dtc.viewArtifact(sourceContextArtifact);
}
}//GEN-LAST:event_jUsageGoToResultButtonActionPerformed
@Override
public void setNode(Node selectedNode) {
if ((selectedNode == null) || (!isSupported(selectedNode))) {
@ -246,9 +292,12 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
}
}
jSourceGoToResultButton.setVisible(true);
jUsageGoToResultButton.setVisible(true);
if (foundASource == false) {
setSourceName("Unknown");
showSourceText(false);
setUsageName("Unknown");
showUsageText(false);
}
}
@ -263,6 +312,11 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
* @throws TskCoreException
*/
private void addSourceEntry(BlackboardArtifact artifact) throws TskCoreException {
setSourceName("Unknown");
showSourceText(false);
setUsageName("Unknown");
showUsageText(false);
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT.getTypeID() == artifact.getArtifactTypeID()) {
BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
if (associatedArtifactAttribute != null) {
@ -287,7 +341,8 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
*/
@NbBundle.Messages({
"ContextViewer.attachmentSource=Attached to: ",
"ContextViewer.downloadSource=Downloaded from: "
"ContextViewer.downloadSource=Downloaded from: ",
"ContextViewer.recentDocs=Recent Documents: "
})
private void setSourceFields(BlackboardArtifact associatedArtifact) throws TskCoreException {
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID() == associatedArtifact.getArtifactTypeID()
@ -301,6 +356,10 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
setSourceName(Bundle.ContextViewer_downloadSource());
setSourceText(webDownloadArtifactToString(associatedArtifact));
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == associatedArtifact.getArtifactTypeID()) {
setUsageName(Bundle.ContextViewer_recentDocs());
setUsageText(recentDocArtifactToString(associatedArtifact));
}
}
@ -313,6 +372,15 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
jSourceNameLabel.setText(nameLabel);
}
/**
* Sets the usage label string.
*
* @param nameLabel String value for usage label.
*/
private void setUsageName(String nameLabel) {
jUsageNameLabel.setText(nameLabel);
}
/**
* Sets the source text string.
*
@ -327,6 +395,24 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
jSourceTextLabel.setVisible(show);
jSourceGoToResultButton.setEnabled(show);
jSourceLabel.setVisible(show);
jUsageLabel.setVisible(show);
}
/**
* Sets the Usage text string.
*
* @param text String value for Usage text.
*/
private void setUsageText(String text) {
jUsageTextLabel.setText(text);
showUsageText(!text.isEmpty());
}
private void showUsageText(boolean show) {
jUsageTextLabel.setVisible(show);
jUsageGoToResultButton.setEnabled(show);
jUsageLabel.setVisible(show);
jSourceLabel.setVisible(show);
}
/**
@ -355,6 +441,36 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
return sb.toString();
}
/**
* Returns a display string with recent Doc
* artifact.
*
* @param artifact artifact to get doc from.
*
* @return Display string with download URL and date/time.
*
* @throws TskCoreException
*/
@NbBundle.Messages({
"ContextViewer.on=Opened at",
"ContextViewer.unknown=Opened at unknown time"
})
private String recentDocArtifactToString(BlackboardArtifact artifact) throws TskCoreException {
StringBuilder sb = new StringBuilder(ARTIFACT_STR_MAX_LEN);
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributesMap = getAttributesMap(artifact);
BlackboardAttribute attribute = attributesMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
if (BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID() == artifact.getArtifactTypeID()) {
if (attribute.getValueLong() > 0) {
appendAttributeString(sb, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, attributesMap, Bundle.ContextViewer_on());
} else {
sb.append(Bundle.ContextViewer_unknown());
}
}
return sb.toString();
}
/**
* Returns a abbreviated display string for a message artifact.
*
@ -442,5 +558,9 @@ public final class ContextViewer extends javax.swing.JPanel implements DataConte
private javax.swing.JLabel jSourceLabel;
private javax.swing.JLabel jSourceNameLabel;
private javax.swing.JLabel jSourceTextLabel;
private javax.swing.JButton jUsageGoToResultButton;
private javax.swing.JLabel jUsageLabel;
private javax.swing.JLabel jUsageNameLabel;
private javax.swing.JLabel jUsageTextLabel;
// End of variables declaration//GEN-END:variables
}

View File

@ -82,6 +82,7 @@ public final class UserPreferences {
public static final String SHOW_ONLY_CURRENT_USER_TAGS = "ShowOnlyCurrentUserTags";
public static final String HIDE_SCO_COLUMNS = "HideCentralRepoCommentsAndOccurrences"; //The key for this setting pre-dates the settings current functionality //NON-NLS
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
private static final boolean DISPLAY_TRANSLATED_NAMES_DEFAULT = true;
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
@ -272,7 +273,7 @@ public final class UserPreferences {
}
public static boolean displayTranslatedFileNames() {
return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, false);
return preferences.getBoolean(DISPLAY_TRANSLATED_NAMES, DISPLAY_TRANSLATED_NAMES_DEFAULT);
}
/**

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.coreutils;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -288,15 +287,94 @@ public final class AppSQLiteDB {
}
/**
* Returns connection meta data.
*
* @return DatabaseMetaData
* @throws SQLException
* Checks if a column exists in a table.
*
* @param tableName name of the table
* @param columnName column name to check
*
* @return true if the column exists, false otherwise
* @throws TskCoreException
*/
public DatabaseMetaData getConnectionMetadata() throws SQLException {
return connection.getMetaData();
public boolean columnExists(String tableName, String columnName) throws TskCoreException {
boolean columnExists = false;
Statement colExistsStatement = null;
ResultSet resultSet = null;
try {
colExistsStatement = connection.createStatement();
String tableInfoQuery = "PRAGMA table_info(%s)"; //NON-NLS
resultSet = colExistsStatement.executeQuery(String.format(tableInfoQuery, tableName));
while (resultSet.next()) {
if (resultSet.getString("name").equalsIgnoreCase(columnName)) {
columnExists = true;
break;
}
}
} catch (SQLException ex) {
throw new TskCoreException("Error checking if column " + columnName + "exists ", ex);
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex2) {
logger.log(Level.WARNING, "Failed to close resultset after checking column", ex2);
}
}
if (colExistsStatement != null) {
try {
colExistsStatement.close();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Error closing Statement", ex2); //NON-NLS
}
}
}
return columnExists;
}
/**
* Checks if a table exists in the case database.
*
* @param tableName name of the table to check
*
* @return true if the table exists, false otherwise
* @throws TskCoreException
*/
public boolean tableExists(String tableName) throws TskCoreException {
boolean tableExists = false;
Statement tableExistsStatement = null;
ResultSet resultSet = null;
try {
tableExistsStatement = connection.createStatement();
resultSet = tableExistsStatement.executeQuery("SELECT name FROM sqlite_master WHERE type='table'"); //NON-NLS
while (resultSet.next()) {
if (resultSet.getString("name").equalsIgnoreCase(tableName)) { //NON-NLS
tableExists = true;
break;
}
}
} catch (SQLException ex) {
throw new TskCoreException("Error checking if table " + tableName + "exists ", ex);
} finally {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException ex2) {
logger.log(Level.WARNING, "Failed to close resultset after checking table", ex2);
}
}
if (tableExistsStatement != null) {
try {
tableExistsStatement.close();
} catch (SQLException ex2) {
logger.log(Level.SEVERE, "Error closing Statement", ex2); //NON-NLS
}
}
}
return tableExists;
}
/**
* Searches for a meta file associated with the give SQLite database. If
* found, it copies this file into the temp directory of the current case.

View File

@ -69,7 +69,7 @@ public class PlatformUtil {
* @return absolute path string to the install root dir
*/
public static String getInstallPath() {
File coreFolder = InstalledFileLocator.getDefault().locate("core", PlatformUtil.class.getPackage().getName(), false); //NON-NLS
File coreFolder = InstalledFileLocator.getDefault().locate("core", "org.sleuthkit.autopsy.core", false); //NON-NLS
File rootPath = coreFolder.getParentFile().getParentFile();
return rootPath.getAbsolutePath();
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -105,7 +105,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
this.content.getName(), this.content.getId()), ex);
}
if (UserPreferences.displayTranslatedFileNames()) {
if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) {
backgroundTasksPool.submit(new TranslationTask(
new WeakReference<>(this), weakPcl));
}
@ -331,7 +331,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
* background task that promises to update these values.
*/
if (UserPreferences.displayTranslatedFileNames()) {
if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) {
properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, ""));
}
@ -549,7 +549,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance attribute = null;
if (CentralRepository.isEnabled() && !UserPreferences.getHideSCOColumns()) {
attribute = CorrelationAttributeUtil.getInstanceFromContent(content);
attribute = CorrelationAttributeUtil.getCorrAttrForFile(content);
}
return attribute;
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -323,7 +323,8 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
if (displayName.isEmpty() && artifact != null) {
try {
displayName = Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(this.artifact.getObjectID()).getName();
Content content = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(this.artifact.getObjectID());
displayName = (content == null) ? artifact.getName() : content.getName();
} catch (TskCoreException | NoCurrentCaseException ex) {
displayName = artifact.getName();
}
@ -605,8 +606,8 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
@Override
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance correlationAttribute = null;
if (CentralRepository.isEnabled()) {
correlationAttribute = CorrelationAttributeUtil.getInstanceFromContent(associated);
if (CentralRepository.isEnabled() && associated instanceof AbstractFile) {
correlationAttribute = CorrelationAttributeUtil.getCorrAttrForFile((AbstractFile)associated);
}
return correlationAttribute;
}

View File

@ -97,7 +97,7 @@ class GetSCOTask implements Runnable {
logger.log(Level.WARNING, "Unable to get correlation type or value to determine value for O column for artifact", ex);
}
} else {
List<CorrelationAttributeInstance> listOfPossibleAttributes = CorrelationAttributeUtil.makeInstancesFromBlackboardArtifact(bbArtifact, false);
List<CorrelationAttributeInstance> listOfPossibleAttributes = CorrelationAttributeUtil.makeCorrAttrsFromArtifact(bbArtifact);
if (listOfPossibleAttributes.size() > 1) {
//Don't display anything if there is more than 1 correlation property for an artifact but let the user know
description = Bundle.GetSCOTask_occurrences_multipleProperties();

View File

@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Pool;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.Volume;
@ -73,12 +74,22 @@ public class VolumeNode extends AbstractContentNode<Volume> {
*/
public VolumeNode(Volume vol) {
super(vol);
// set name, display name, and icon
String volName = nameForVolume(vol);
long end = vol.getStart() + (vol.getLength() - 1);
String tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + "-" + end + ")";
// If this is a pool volume use a custom display name
try {
if (vol.getParent() != null &&
vol.getParent().getParent() instanceof Pool) {
// Pool volumes are not contiguous so printing a range of blocks is inaccurate
tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + ")";
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error looking up parent(s) of volume with obj ID = "+ vol.getId(), ex);
}
this.setDisplayName(tempVolName);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/vol-icon.png"); //NON-NLS

View File

@ -0,0 +1,140 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datamodel.utils;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.openide.nodes.AbstractNode;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.AbstractContentNode;
/**
* An abstract base class for background tasks needed to compute values for the
* property sheet of an AbstractNode.
*
* The results of the computation are returned by firing a PropertyChangeEvent
* and the run method has an exception firewall with logging. These features
* relieve the AbstractNode from having to create a thread to block on the get()
* method of the task Future.
*
* Only weak references to the AbstractNode and its PropertyChangeListener are
* held prior to task execution so that a queued task does not interfere with
* garbage collection if the node has been destroyed by the NetBeans framework.
*
* A thread pool with descriptively named threads (node-background-task-N) is
* provided for executing instances of the tasks.
*/
public abstract class AbstractNodePropertySheetTask<T extends AbstractNode> implements Runnable {
private static final Logger LOGGER = Logger.getLogger(AbstractContentNode.class.getName());
private static final Integer THREAD_POOL_SIZE = 10;
private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE, new ThreadFactoryBuilder().setNameFormat("node-background-task-%d").build());
private final WeakReference<T> weakNodeRef;
private final WeakReference<PropertyChangeListener> weakListenerRef;
/**
* Submits a task to compute values for the property sheet of an
* AbstractNode to a thread pool dedicated to such tasks with descriptively
* named threads (node-background-task-N).
*
* @param task The task.
*
* @return The Future of the task, may be used for task cancellation by
* calling Future.cancel(true).
*/
public static Future<?> submitTask(AbstractNodePropertySheetTask<?> task) {
return executor.submit(task);
}
/**
* Constructs an abstract base class for background tasks needed to compute
* values for the property sheet of an AbstractNode.
*
* The results of the computation are returned by firing a
* PropertyChangeEvent and the run method has an exception firewall with
* logging. These features relieve the AbstractNode from having to create a
* thread to block on the get() method of the task Future.
*
* Only weak references to the AbstractNode and its PropertyChangeListener
* are held prior to task execution so that a queued task does not interfere
* with garbage collection if the node has been destroyed by the NetBeans
* framework.
*
* A thread pool with descriptively named threads (node-background-task-N)
* is provided for executing instances of the tasks.
*
* @param node The node.
* @param listener A property change listener for the node.
*/
protected AbstractNodePropertySheetTask(T node, PropertyChangeListener listener) {
this.weakNodeRef = new WeakReference<>(node);
this.weakListenerRef = new WeakReference<>(listener);
}
/**
* Computes the values for the property sheet of an AbstractNode. The
* results of the computation are returned as a PropertyChangeEvent which is
* fired to the PropertyChangeEventListener of the node.
*
* IMPORTANT: Implementations of this method should check for cancellation
* by calling Thread.currentThread().isInterrupted() at appropriate
* intervals.
*
* @param node The AbstractNode.
*
* @return The result of the computation as a PropertyChangeEvent.
*/
protected abstract PropertyChangeEvent computePropertyValue(T node) throws Exception;
@Override
final public void run() {
try {
T node = this.weakNodeRef.get();
PropertyChangeListener listener = this.weakListenerRef.get();
if (node == null || listener == null) {
return;
}
if (Thread.currentThread().isInterrupted()) {
return;
}
PropertyChangeEvent changeEvent = computePropertyValue(node);
if (Thread.currentThread().isInterrupted()) {
return;
}
if (changeEvent != null) {
listener.propertyChange(changeEvent);
}
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error executing property sheet values computation background task", ex);
}
}
}

View File

@ -73,6 +73,15 @@ final public class FeatureAccessUtils {
return dataSourceDeletionAllowed;
}
/**
* Indicates whether or not a user can delete the current case.
*
* @return True or false.
*/
public static boolean canDeleteCurrentCase() {
return currentCaseIsSingleUserCase() || multiUserCaseRestrictionsFileAbsent();
}
/**
* Indicates whether or not the current case is a single-user case.
*
@ -83,12 +92,12 @@ final public class FeatureAccessUtils {
}
/**
* Indicates whether or not the current user is allowed to create or modify
* (add or delete data sources) multi-user cases.
* Indicates whether or not the multi-user case privileges restriction file
* is absent.
*
* @return True or false.
*/
public static boolean multiUserCaseRestrictionsFileAbsent() {
private static boolean multiUserCaseRestrictionsFileAbsent() {
File accessLimitingFile = new File(MULTIUSER_CASE_RESTRICTED_FILE_PATH);
return !accessLimitingFile.exists();
}

View File

@ -65,8 +65,6 @@ FileSearchPanel.stepTwoLabel.text=Step 2: Filter which images to show
FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
DiscoveryTopComponent.stepOneLabel.text=Step 1: Pick File Type
DiscoveryTopComponent.documentsButton.text=Documents
DocumentPanel.countLabel.toolTipText=
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.documentType.text=
DocumentPanel.isDeletedLabel.toolTipText=
ImageThumbnailPanel.isDeletedLabel.toolTipText=

View File

@ -14,8 +14,8 @@ DiscoveryUiUtility.megaBytes.text=MB
# {1} - units
DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}
DiscoveryUiUtility.terraBytes.text=TB
# {0} - extension
DocumentPanel.documentType.extension.text=Extension: {0}
# {0} - otherInstanceCount
DocumentPanel.nameLabel.more.text=\ and {0} more
DocumentWrapper.previewInitialValue=Preview not generated yet.
FileGroup.groupSortingAlgorithm.groupName.text=Group Name
FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
@ -24,6 +24,8 @@ FileGroup.groupSortingAlgorithm.groupSize.text=Group Size
FileSearch.DataSourceGroupKey.datasourceAndID={0}(ID: {1})
# {0} - Data source ID
FileSearch.DataSourceGroupKey.idOnly=Data source (ID: {0})
FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview.
FileSearch.documentSummary.noPreview=No preview available.
FileSearch.FileTagGroupKey.noSets=None
# {0} - file name
FileSearch.genVideoThumb.progress.text=extracting temporary file {0}
@ -173,9 +175,9 @@ FileSorter.SortingMethod.fullPath.displayName=Full Path
FileSorter.SortingMethod.keywordlist.displayName=Keyword List Names
GroupsListPanel.noResults.message.text=No results were found for the selected filters.
GroupsListPanel.noResults.title.text=No results found
# {0} - numberOfInstances
ImageThumbnailPanel.countLabel.text=Number of Instances: {0}
ImageThumbnailPanel.isDeleted.text=All instances of file are deleted.
# {0} - otherInstanceCount
ImageThumbnailPanel.nameLabel.more.text=\ and {0} more
OpenFileDiscoveryAction.resultsIncomplete.text=Results may be incomplete
ResultFile.score.interestingResult.description=At least one instance of the file has an interesting result associated with it.
ResultFile.score.notableFile.description=At least one instance of the file was recognized as notable.
@ -185,8 +187,7 @@ ResultFile.score.taggedFile.description=At least one instance of the file has be
# {1} - totalPages
ResultsPanel.currentPage.displayValue=Page: {0} of {1}
ResultsPanel.currentPageLabel.text=Page: -
ResultsPanel.documentPreviewWorker.noBytes=No bytes read for document, unable to display preview.
ResultsPanel.documentPreviewWorker.noPreview=No preview available.
ResultsPanel.documentPreview.text=Document preview creation cancelled.
# {0} - selectedPage
# {1} - maxPage
ResultsPanel.invalidPageNumber.message=The selected page number {0} does not exist. Please select a value from 1 to {1}.
@ -209,19 +210,18 @@ FileSearchPanel.stepTwoLabel.text=Step 2: Filter which images to show
FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings
DiscoveryTopComponent.stepOneLabel.text=Step 1: Pick File Type
DiscoveryTopComponent.documentsButton.text=Documents
DocumentPanel.countLabel.toolTipText=
DocumentPanel.fileSizeLabel.toolTipText=
DocumentPanel.documentType.text=
DocumentPanel.isDeletedLabel.toolTipText=
ImageThumbnailPanel.isDeletedLabel.toolTipText=
ResultsPanel.unableToCreate.text=Unable to create summary.
ResultsPanel.viewFileInDir.name=View File in Directory
VideoThumbnailPanel.bytes.text=bytes
# {0} - numberOfInstances
VideoThumbnailPanel.countLabel.text=Number of Instances: {0}
VideoThumbnailPanel.deleted.text=All instances of file are deleted.
VideoThumbnailPanel.gigaBytes.text=GB
VideoThumbnailPanel.kiloBytes.text=KB
VideoThumbnailPanel.megaBytes.text=MB
# {0} - otherInstanceCount
VideoThumbnailPanel.nameLabel.more.text=\ and {0} more
# {0} - fileSize
# {1} - units
VideoThumbnailPanel.sizeLabel.text=Size: {0} {1}

View File

@ -27,15 +27,14 @@
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="countLabel" min="-2" pref="530" max="-2" attributes="0"/>
<EmptySpace pref="81" max="32767" attributes="0"/>
<Component id="fileSizeLabel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="isDeletedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="fileSizeLabel" alignment="1" max="32767" attributes="0"/>
<Component id="previewScrollPane" max="32767" attributes="0"/>
<Component id="documentType" alignment="0" max="32767" attributes="0"/>
<Component id="previewScrollPane" pref="649" max="32767" attributes="0"/>
<Component id="nameLabel" alignment="0" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -45,16 +44,14 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="documentType" min="-2" pref="16" max="-2" attributes="0"/>
<Component id="nameLabel" min="-2" pref="16" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="previewScrollPane" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="16" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="scoreLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="isDeletedLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="countLabel" alignment="1" min="-2" max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="16" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -62,22 +59,6 @@
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="countLabel">
<Properties>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filequery/Bundle.properties" key="DocumentPanel.countLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[159, 12]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[159, 12]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[159, 12]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="isDeletedLabel">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
@ -121,12 +102,7 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="documentType">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/filequery/Bundle.properties" key="DocumentPanel.documentType.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Component class="javax.swing.JLabel" name="nameLabel">
</Component>
<Container class="javax.swing.JScrollPane" name="previewScrollPane">
<Properties>

View File

@ -52,21 +52,15 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
countLabel = new javax.swing.JLabel();
isDeletedLabel = new javax.swing.JLabel();
scoreLabel = new javax.swing.JLabel();
fileSizeLabel = new javax.swing.JLabel();
documentType = new javax.swing.JLabel();
nameLabel = new javax.swing.JLabel();
javax.swing.JScrollPane previewScrollPane = new javax.swing.JScrollPane();
previewTextArea = new javax.swing.JTextArea();
setBorder(javax.swing.BorderFactory.createEtchedBorder());
countLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.countLabel.toolTipText")); // NOI18N
countLabel.setMaximumSize(new java.awt.Dimension(159, 12));
countLabel.setMinimumSize(new java.awt.Dimension(159, 12));
countLabel.setPreferredSize(new java.awt.Dimension(159, 12));
isDeletedLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/file-icon-deleted.png"))); // NOI18N
isDeletedLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.isDeletedLabel.toolTipText")); // NOI18N
isDeletedLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
@ -81,8 +75,6 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere
fileSizeLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.fileSizeLabel.toolTipText")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(documentType, org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.documentType.text")); // NOI18N
previewScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
previewTextArea.setEditable(false);
@ -104,57 +96,55 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(countLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 530, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 81, Short.MAX_VALUE)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(isDeletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(fileSizeLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(previewScrollPane)
.addComponent(documentType, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(previewScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 649, Short.MAX_VALUE)
.addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addComponent(documentType, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(previewScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(isDeletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(countLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel countLabel;
private javax.swing.JLabel documentType;
private javax.swing.JLabel fileSizeLabel;
private javax.swing.JLabel isDeletedLabel;
private javax.swing.JLabel nameLabel;
private javax.swing.JTextArea previewTextArea;
private javax.swing.JLabel scoreLabel;
// End of variables declaration//GEN-END:variables
@Messages({"# {0} - extension",
"DocumentPanel.documentType.extension.text=Extension: {0}"})
@Messages({"# {0} - otherInstanceCount",
"DocumentPanel.nameLabel.more.text= and {0} more"})
@Override
public Component getListCellRendererComponent(JList<? extends DocumentWrapper> list, DocumentWrapper value, int index, boolean isSelected, boolean cellHasFocus) {
fileSizeLabel.setText(DiscoveryUiUtils.getFileSizeString(value.getResultFile().getFirstInstance().getSize()));
countLabel.setText(Bundle.ImageThumbnailPanel_countLabel_text(value.getResultFile().getAllInstances().size()));
documentType.setText(Bundle.DocumentPanel_documentType_extension_text(value.getResultFile().getFirstInstance().getNameExtension())); //WJS-TODO fill this in with a document type instead of just DOCUMENT
String nameText = value.getResultFile().getFirstInstance().getParentPath() + value.getResultFile().getFirstInstance().getName();
if (value.getResultFile().getAllInstances().size() > 1) {
nameText += Bundle.DocumentPanel_nameLabel_more_text(value.getResultFile().getAllInstances().size() - 1);
}
nameLabel.setText(nameText);
previewTextArea.setText(value.getPreview());
previewTextArea.setCaretPosition(0);
DiscoveryUiUtils.setDeletedIcon(value.getResultFile().isDeleted(), isDeletedLabel);
DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel);
setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
return this;
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -25,11 +25,13 @@ import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -43,9 +45,11 @@ import java.util.logging.Level;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.netbeans.api.progress.ProgressHandle;
import org.opencv.core.Mat;
import org.opencv.highgui.VideoCapture;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -71,6 +75,10 @@ import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.textextractors.TextExtractor;
import org.sleuthkit.autopsy.textextractors.TextExtractorFactory;
import org.sleuthkit.autopsy.textsummarizer.TextSummarizer;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
/**
* Main class to perform the file search.
@ -84,6 +92,8 @@ class FileSearch {
private static final Cache<SearchKey, Map<GroupKey, List<ResultFile>>> searchCache = CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build();
private static final int PREVIEW_SIZE = 256;
private static volatile TextSummarizer summarizerToUse = null;
/**
* Run the file search and returns the SearchResults object for debugging.
@ -239,6 +249,86 @@ class FileSearch {
return page;
}
/**
* Get a summary for the specified AbstractFile. If no TextSummarizers exist
* get the beginning of the file.
*
* @param file The AbstractFile to summarize.
*
* @return The summary or beginning of the specified file as a String.
*/
@NbBundle.Messages({"FileSearch.documentSummary.noPreview=No preview available.",
"FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview."})
static TextSummary summarize(AbstractFile file) {
TextSummary summary = null;
TextSummarizer localSummarizer = summarizerToUse;
if (localSummarizer == null) {
synchronized (searchCache) {
if (localSummarizer == null) {
localSummarizer = getLocalSummarizer();
}
}
}
if (localSummarizer != null) {
try {
//a summary of length 40 seems to fit without vertical scroll bars
summary = localSummarizer.summarize(file, 40);
} catch (IOException ex) {
return new TextSummary(Bundle.FileSearch_documentSummary_noPreview(), null, 0);
}
}
if (summary == null || StringUtils.isBlank(summary.getSummaryText())) {
//summary text was empty grab the beginning of the file
summary = new TextSummary(getFirstLines(file), null, 0);
}
return summary;
}
/**
* Get the beginning of text from the specified AbstractFile.
*
* @param file The AbstractFile to get text from.
*
* @return The beginning of text from the specified AbstractFile.
*/
private static String getFirstLines(AbstractFile file) {
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 (TextExtractor.InitReaderException ex) {
return Bundle.FileSearch_documentSummary_noPreview();
}
}
/**
* Get the first TextSummarizer found by a lookup of TextSummarizers.
*
* @return The first TextSummarizer found by a lookup of TextSummarizers.
*
* @throws IOException
*/
private static TextSummarizer getLocalSummarizer() {
Collection<? extends TextSummarizer> summarizers
= Lookup.getDefault().lookupAll(TextSummarizer.class
);
if (!summarizers.isEmpty()) {
summarizerToUse = summarizers.iterator().next();
return summarizerToUse;
}
return null;
}
/**
* Run the file search. Caching new results for access at later time.
*
@ -597,7 +687,6 @@ class FileSearch {
int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
framePositions[thumbnailNumber] = framePos;
thumbnailNumber++;
}
thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
}

View File

@ -20,36 +20,36 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="thumbnailPanel" pref="201" max="32767" attributes="0"/>
<Component id="fileSizeLabel" alignment="0" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="countLabel" max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="163" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="isDeletedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="nameLabel" alignment="0" max="32767" attributes="0"/>
<Component id="thumbnailPanel" alignment="0" pref="201" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="nameLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="thumbnailPanel" min="-2" pref="178" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="16" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="scoreLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="isDeletedLabel" min="-2" max="-2" attributes="0"/>
<Component id="countLabel" alignment="1" min="-2" max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="16" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -82,7 +82,7 @@
<Property name="toolTipText" type="java.lang.String" value=""/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="countLabel">
<Component class="javax.swing.JLabel" name="nameLabel">
<Properties>
<Property name="toolTipText" type="java.lang.String" value=""/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@ -133,4 +133,4 @@
</Properties>
</Component>
</SubComponents>
</Form>
</Form>

View File

@ -36,6 +36,7 @@ public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellR
private static final long serialVersionUID = 1L;
private static final Color SELECTION_COLOR = new Color(0, 120, 215);
private static final int MAX_NAME_STRING = 30;
/**
* Creates new form ImageThumbnailPanel
@ -56,7 +57,7 @@ public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellR
javax.swing.JPanel thumbnailPanel = new javax.swing.JPanel();
thumbnailLabel = new javax.swing.JLabel();
fileSizeLabel = new javax.swing.JLabel();
countLabel = new javax.swing.JLabel();
nameLabel = new javax.swing.JLabel();
isDeletedLabel = new javax.swing.JLabel();
scoreLabel = new javax.swing.JLabel();
@ -68,22 +69,22 @@ public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellR
fileSizeLabel.setToolTipText("");
countLabel.setToolTipText("");
countLabel.setMaximumSize(new java.awt.Dimension(159, 12));
countLabel.setMinimumSize(new java.awt.Dimension(159, 12));
countLabel.setPreferredSize(new java.awt.Dimension(159, 12));
nameLabel.setToolTipText("");
nameLabel.setMaximumSize(new java.awt.Dimension(159, 12));
nameLabel.setMinimumSize(new java.awt.Dimension(159, 12));
nameLabel.setPreferredSize(new java.awt.Dimension(159, 12));
isDeletedLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/file-icon-deleted.png"))); // NOI18N
isDeletedLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ImageThumbnailPanel.class, "ImageThumbnailPanel.isDeletedLabel.toolTipText")); // NOI18N
isDeletedLabel.setMaximumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMinimumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setPreferredSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
isDeletedLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"))); // NOI18N
scoreLabel.setToolTipText("");
scoreLabel.setMaximumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@ -92,53 +93,60 @@ public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellR
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(thumbnailPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(countLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 163, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(isDeletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(nameLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(thumbnailPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(thumbnailPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(isDeletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(countLabel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel countLabel;
private javax.swing.JLabel fileSizeLabel;
private javax.swing.JLabel isDeletedLabel;
private javax.swing.JLabel nameLabel;
private javax.swing.JLabel scoreLabel;
private javax.swing.JLabel thumbnailLabel;
// End of variables declaration//GEN-END:variables
@NbBundle.Messages({
"# {0} - numberOfInstances",
"ImageThumbnailPanel.countLabel.text=Number of Instances: {0}",
"# {0} - otherInstanceCount",
"ImageThumbnailPanel.nameLabel.more.text= and {0} more",
"ImageThumbnailPanel.isDeleted.text=All instances of file are deleted."})
@Override
public Component getListCellRendererComponent(JList<? extends ImageThumbnailWrapper> list, ImageThumbnailWrapper value, int index, boolean isSelected, boolean cellHasFocus) {
fileSizeLabel.setText(DiscoveryUiUtils.getFileSizeString(value.getResultFile().getFirstInstance().getSize()));
countLabel.setText(Bundle.ImageThumbnailPanel_countLabel_text(value.getResultFile().getAllInstances().size()));
String nameText = value.getResultFile().getFirstInstance().getParentPath() + value.getResultFile().getFirstInstance().getName();
if (value.getResultFile().getAllInstances().size() > 1) {
nameText += Bundle.ImageThumbnailPanel_nameLabel_more_text(value.getResultFile().getAllInstances().size() - 1);
}
if (nameText.length() > MAX_NAME_STRING) {
nameText = "..." + nameText.substring(nameText.length() - (MAX_NAME_STRING - 3));
}
nameLabel.setText(nameText);
thumbnailLabel.setIcon(new ImageIcon(value.getThumbnail()));
DiscoveryUiUtils.setDeletedIcon(value.getResultFile().isDeleted(), isDeletedLabel);
DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel);
DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel);
setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
return this;
@ -163,5 +171,4 @@ public class ImageThumbnailPanel extends javax.swing.JPanel implements ListCellR
return null;
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy
*
* Copyright 2019 Basis Technology Corp.
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -28,6 +28,8 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
@ -38,14 +40,12 @@ import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.StringExtract;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
@ -55,6 +55,7 @@ import org.sleuthkit.autopsy.modules.hashdatabase.AddContentToHashDbAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.textsummarizer.TextSummary;
/**
* Panel for displaying of file discovery results and handling the paging of
@ -702,6 +703,13 @@ public class ResultsPanel extends javax.swing.JPanel {
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "Video Worker Exception for file: " + thumbnailWrapper.getResultFile().getFirstInstance().getId(), ex);
} catch (CancellationException ignored) {
//we want to do nothing in response to this since we allow it to be cancelled
}
videoThumbnailViewer.repaint();
}
}
@ -736,6 +744,13 @@ public class ResultsPanel extends javax.swing.JPanel {
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.WARNING, "Image Worker Exception for file: " + thumbnailWrapper.getResultFile().getFirstInstance().getId(), ex);
} catch (CancellationException ignored) {
//we want to do nothing in response to this since we allow it to be cancelled
}
imageThumbnailViewer.repaint();
}
@ -748,7 +763,6 @@ public class ResultsPanel extends javax.swing.JPanel {
private class DocumentPreviewWorker extends SwingWorker<Void, Void> {
private final DocumentWrapper documentWrapper;
private static final int PREVIEW_SIZE = 256;
/**
* Construct a new DocumentPreviewWorker.
@ -761,55 +775,29 @@ public class ResultsPanel extends javax.swing.JPanel {
documentPreviewViewer.addDocument(documentWrapper);
}
@Messages({"ResultsPanel.unableToCreate.text=Unable to create summary."})
@Override
protected Void doInBackground() throws Exception {
String preview = createPreview(documentWrapper.getResultFile().getFirstInstance());
if (preview != null) {
documentWrapper.setPreview(preview);
TextSummary preview = FileSearch.summarize(documentWrapper.getResultFile().getFirstInstance());
if (preview == null) {
preview = new TextSummary(Bundle.ResultsPanel_unableToCreate_text(), null, 0);
}
documentWrapper.setPreview(preview.getSummaryText());
return null;
}
/**
* Create the string that will be used as the preview for the specified
* AbstractFile.
*
* @param file The AbstractFile to create the preview for.
*
* @return The String which is the preview for the specified
* AbstractFile.
*/
@Messages({"ResultsPanel.documentPreviewWorker.noPreview=No preview available.",
"ResultsPanel.documentPreviewWorker.noBytes=No bytes read for document, unable to display preview."})
private String createPreview(AbstractFile file) {
byte[] data = new byte[PREVIEW_SIZE];
int bytesRead = 0;
if (file.getSize() > 0) {
try {
int length = PREVIEW_SIZE > file.getSize() ? (int) file.getSize() : PREVIEW_SIZE; //if the size is less than the int it can be cast to an int
bytesRead = file.read(data, 0, length); // read the data
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error while trying to show the String content.", ex); //NON-NLS
}
}
String text;
if (bytesRead > 0) {
StringExtract stringExtract = new StringExtract();
final StringExtract.StringExtractUnicodeTable.SCRIPT selScript = StringExtract.StringExtractUnicodeTable.SCRIPT.LATIN_1;
stringExtract.setEnabledScript(selScript);
StringExtract.StringExtractResult res = stringExtract.extract(data, bytesRead, 0);
text = res.getText();
if (StringUtils.isBlank(text)) {
text = Bundle.ResultsPanel_documentPreviewWorker_noPreview();
}
} else {
text = Bundle.ResultsPanel_documentPreviewWorker_noBytes();
}
return text;
}
@Messages({"ResultsPanel.documentPreview.text=Document preview creation cancelled."})
@Override
protected void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
documentWrapper.setPreview(ex.getMessage());
logger.log(Level.WARNING, "Document Worker Exception", ex);
} catch (CancellationException ignored) {
documentWrapper.setPreview(Bundle.ResultsPanel_documentPreview_text());
//we want to do nothing in response to this since we allow it to be cancelled
}
documentPreviewViewer.repaint();
}

View File

@ -24,38 +24,36 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="imagePanel" alignment="0" pref="776" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="fileSizeLabel" min="-2" pref="248" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="countLabel" min="-2" pref="124" max="-2" attributes="0"/>
<Component id="fileSizeLabel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deletedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="nameLabel" alignment="0" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="nameLabel" min="-2" pref="14" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="imagePanel" min="-2" pref="140" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="fileSizeLabel" min="-2" pref="19" max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="deletedLabel" min="-2" max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
<Component id="countLabel" alignment="1" min="-2" pref="19" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="deletedLabel" min="-2" max="-2" attributes="0"/>
<Component id="fileSizeLabel" min="-2" pref="14" max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -67,7 +65,7 @@
</Container>
<Component class="javax.swing.JLabel" name="fileSizeLabel">
</Component>
<Component class="javax.swing.JLabel" name="countLabel">
<Component class="javax.swing.JLabel" name="nameLabel">
</Component>
<Component class="javax.swing.JLabel" name="scoreLabel">
<Properties>
@ -102,4 +100,4 @@
</Properties>
</Component>
</SubComponents>
</Form>
</Form>

View File

@ -100,7 +100,7 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe
imagePanel = new javax.swing.JPanel();
fileSizeLabel = new javax.swing.JLabel();
countLabel = new javax.swing.JLabel();
nameLabel = new javax.swing.JLabel();
scoreLabel = new javax.swing.JLabel();
deletedLabel = new javax.swing.JLabel();
@ -109,14 +109,14 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe
imagePanel.setLayout(new java.awt.GridBagLayout());
scoreLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"))); // NOI18N
scoreLabel.setMaximumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
scoreLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
scoreLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
deletedLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/file-icon-deleted.png"))); // NOI18N
deletedLabel.setMaximumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
deletedLabel.setMinimumSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
deletedLabel.setPreferredSize(new Dimension(DiscoveryUiUtils.getIconSize(),DiscoveryUiUtils.getIconSize()));
deletedLabel.setMaximumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
deletedLabel.setMinimumSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
deletedLabel.setPreferredSize(new Dimension(org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize(),org.sleuthkit.autopsy.filequery.DiscoveryUiUtils.getIconSize()));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@ -127,52 +127,55 @@ final class VideoThumbnailPanel extends javax.swing.JPanel implements ListCellRe
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(imagePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 776, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(countLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 124, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(imagePanel, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 19, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(deletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(countLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 19, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(deletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(fileSizeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel countLabel;
private javax.swing.JLabel deletedLabel;
private javax.swing.JLabel fileSizeLabel;
private javax.swing.JPanel imagePanel;
private javax.swing.JLabel nameLabel;
private javax.swing.JLabel scoreLabel;
// End of variables declaration//GEN-END:variables
@Messages({
"# {0} - numberOfInstances",
"VideoThumbnailPanel.countLabel.text=Number of Instances: {0}",
"# {0} - otherInstanceCount",
"VideoThumbnailPanel.nameLabel.more.text= and {0} more",
"VideoThumbnailPanel.deleted.text=All instances of file are deleted."})
@Override
public Component getListCellRendererComponent(JList<? extends VideoThumbnailsWrapper> list, VideoThumbnailsWrapper value, int index, boolean isSelected, boolean cellHasFocus) {
fileSizeLabel.setText(getFileSizeString(value.getResultFile().getFirstInstance().getSize()));
countLabel.setText(Bundle.VideoThumbnailPanel_countLabel_text(value.getResultFile().getAllInstances().size()));
String nameText = value.getResultFile().getFirstInstance().getParentPath() + value.getResultFile().getFirstInstance().getName();
if (value.getResultFile().getAllInstances().size() > 1) {
nameText += Bundle.VideoThumbnailPanel_nameLabel_more_text(value.getResultFile().getAllInstances().size() - 1);
}
nameLabel.setText(nameText);
addThumbnails(value);
imagePanel.setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
DiscoveryUiUtils.setDeletedIcon(value.getResultFile().isDeleted(), deletedLabel);
DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel);
DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel);
setBackground(isSelected ? SELECTION_COLOR : list.getBackground());
return this;
}

View File

@ -0,0 +1,251 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.geolocation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Track;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
/**
* The business logic for filtering waypoints.
*/
abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilterQueryCallBack {
private static final Logger logger = Logger.getLogger(AbstractWaypointFetcher.class.getName());
private final GeoFilterPanel.GeoFilter filters;
/**
* Constructs the Waypoint Runner
*
* @param filters
*/
AbstractWaypointFetcher(GeoFilterPanel.GeoFilter filters) {
this.filters = filters;
}
/**
* Gets the waypoints based in the current GeoFilter.
*
* This function kicks off a process that will send with
* handleFilteredWaypointSet being called. Subclasses must implement
* handleFitleredWayoiintSet to get the final results.
*
* @throws GeoLocationDataException
*/
void getWaypoints() throws GeoLocationDataException {
Case currentCase = Case.getCurrentCase();
WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(),
filters.getDataSources(),
filters.showAllWaypoints(),
filters.getMostRecentNumDays(),
filters.showWaypointsWithoutTimeStamp(),
this);
}
/**
* Called after all of the MapWaypoints are created from all of the
* TSK_GPS_XXX objects.
*
* @param mapWaypoints List of filtered MapWaypoints.
*/
abstract void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints);
@Override
public void process(List<Waypoint> waypoints) {
List<Track> tracks = null;
try {
tracks = Track.getTracks(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources());
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex);
}
List<Waypoint> completeList = createWaypointList(waypoints, tracks);
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(completeList);
handleFilteredWaypointSet(pointSet);
}
/**
* Returns a complete list of waypoints including the tracks. Takes into
* account the current filters and includes waypoints as approprate.
*
* @param waypoints List of waypoints
* @param tracks List of tracks
*
* @return A list of waypoints including the tracks based on the current
* filters.
*/
private List<Waypoint> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) {
final List<Waypoint> completeList = new ArrayList<>();
if (tracks != null) {
Long timeRangeEnd;
Long timeRangeStart;
if (!filters.showAllWaypoints()) {
// Figure out what the most recent time is given the filtered
// waypoints and the tracks.
timeRangeEnd = getMostRecent(waypoints, tracks);
timeRangeStart = timeRangeEnd - (86400 * filters.getMostRecentNumDays());
completeList.addAll(getWaypointsInRange(timeRangeStart, timeRangeEnd, waypoints));
completeList.addAll(getTracksInRange(timeRangeStart, timeRangeEnd, tracks));
} else {
completeList.addAll(waypoints);
for (Track track : tracks) {
completeList.addAll(track.getPath());
}
}
} else {
completeList.addAll(waypoints);
}
return completeList;
}
/**
* Return a list of waypoints that fall into the given time range.
*
* @param timeRangeStart start timestamp of range (seconds from java epoch)
* @param timeRangeEnd start timestamp of range (seconds from java epoch)
* @param waypoints List of waypoints to filter.
*
* @return A list of waypoints that fall into the time range.
*/
private List<Waypoint> getWaypointsInRange(Long timeRangeStart, Long timeRangeEnd, List<Waypoint> waypoints) {
List<Waypoint> completeList = new ArrayList<>();
// Add all of the waypoints that fix into the time range.
if (waypoints != null) {
for (Waypoint point : waypoints) {
Long time = point.getTimestamp();
if ((time == null && filters.showWaypointsWithoutTimeStamp())
|| (time != null && (time >= timeRangeStart && time <= timeRangeEnd))) {
completeList.add(point);
}
}
}
return completeList;
}
/**
* Return a list of waypoints from the given tracks that fall into for
* tracks that fall into the given time range. The track start time will
* used for determining if the whole track falls into the range.
*
* @param timeRangeStart start timestamp of range (seconds from java epoch)
* @param timeRangeEnd start timestamp of range (seconds from java epoch)
* @param tracks Track list.
*
* @return A list of waypoints that that belong to tracks that fall into the
* time range.
*/
private List<Waypoint> getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List<Track> tracks) {
List<Waypoint> completeList = new ArrayList<>();
if (tracks != null) {
for (Track track : tracks) {
Long trackTime = track.getStartTime();
if ((trackTime == null && filters.showWaypointsWithoutTimeStamp())
|| (trackTime != null && (trackTime >= timeRangeStart && trackTime <= timeRangeEnd))) {
completeList.addAll(track.getPath());
}
}
}
return completeList;
}
/**
* Find the latest time stamp in the given list of waypoints.
*
* @param points List of Waypoints, required.
*
* @return The latest time stamp (seconds from java epoch)
*/
private Long findMostRecentTimestamp(List<Waypoint> points) {
Long mostRecent = null;
for (Waypoint point : points) {
if (mostRecent == null) {
mostRecent = point.getTimestamp();
} else {
mostRecent = Math.max(mostRecent, point.getTimestamp());
}
}
return mostRecent;
}
/**
* Find the latest time stamp in the given list of tracks.
*
* @param tracks List of Waypoints, required.
*
* @return The latest time stamp (seconds from java epoch)
*/
private Long findMostRecentTracks(List<Track> tracks) {
Long mostRecent = null;
for (Track track : tracks) {
if (mostRecent == null) {
mostRecent = track.getStartTime();
} else {
mostRecent = Math.max(mostRecent, track.getStartTime());
}
}
return mostRecent;
}
/**
* Returns the "most recent" timestamp amount the list of waypoints and
* track points.
*
* @param points List of Waypoints
* @param tracks List of Tracks
*
* @return Latest time stamp (seconds from java epoch)
*/
private Long getMostRecent(List<Waypoint> points, List<Track> tracks) {
Long waypointMostRecent = findMostRecentTimestamp(points);
Long trackMostRecent = findMostRecentTracks(tracks);
if (waypointMostRecent != null && trackMostRecent != null) {
return Math.max(waypointMostRecent, trackMostRecent);
} else if (waypointMostRecent == null && trackMostRecent != null) {
return trackMostRecent;
} else if (waypointMostRecent != null && trackMostRecent == null) {
return waypointMostRecent;
}
return null;
}
}

View File

@ -30,6 +30,8 @@ GeoTopComponent_no_waypoints_returned_mgs=Applied filter failed to find waypoint
GeoTopComponent_no_waypoints_returned_Title=No Waypoints Found
GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete.
GLTopComponent_name=Geolocation
GLTopComponent_No_dataSource_message=There are no data sources with Geolocation artifacts found.
GLTopComponent_No_dataSource_Title=No Geolocation artifacts found
HidingPane_default_title=Filters
MapPanel_connection_failure_message=Failed to connect to new geolocation map tile source.
MapPanel_connection_failure_message_title=Connection Failure

View File

@ -67,6 +67,10 @@ final class CheckBoxListPanel<T> extends javax.swing.JPanel {
model.removeAllElements();
}
boolean isEmpty() {
return model.isEmpty();
}
@Override
public void setEnabled(boolean enabled) {
checkboxList.setEnabled(enabled);

View File

@ -20,14 +20,19 @@ package org.sleuthkit.autopsy.geolocation;
import java.awt.GridBagConstraints;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.util.Pair;
import javax.swing.ImageIcon;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@ -38,12 +43,25 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
class GeoFilterPanel extends javax.swing.JPanel {
final static String INITPROPERTY = "FilterPanelInitCompleted";
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(GeoFilterPanel.class.getName());
private final SpinnerNumberModel numberModel;
private final CheckBoxListPanel<DataSource> checkboxPanel;
// Make sure to update if
@SuppressWarnings("deprecation")
private static final BlackboardArtifact.ARTIFACT_TYPE[] GPS_ARTIFACT_TYPES = {
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK,
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION,
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE,
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH,
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK,
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT
};
/**
* Creates new GeoFilterPanel
*/
@ -73,7 +91,7 @@ class GeoFilterPanel extends javax.swing.JPanel {
gridBagConstraints.insets = new java.awt.Insets(0, 15, 0, 15);
add(checkboxPanel, gridBagConstraints);
}
@Override
public void setEnabled(boolean enabled) {
applyButton.setEnabled(enabled);
@ -84,18 +102,15 @@ class GeoFilterPanel extends javax.swing.JPanel {
daysLabel.setEnabled(enabled);
daysSpinner.setEnabled(enabled);
}
/**
* Update the data source list with the current data sources
*/
void updateDataSourceList() {
try {
initCheckboxList();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Failed to initialize the CheckboxListPane", ex); //NON-NLS
}
DataSourceUpdater updater = new DataSourceUpdater();
updater.execute();
}
/**
* Clears the data source list.
*/
@ -103,6 +118,10 @@ class GeoFilterPanel extends javax.swing.JPanel {
checkboxPanel.clearList();
}
boolean hasDataSources() {
return !checkboxPanel.isEmpty();
}
/**
* Adds an actionListener to listen for the filter apply action
*
@ -128,26 +147,12 @@ class GeoFilterPanel extends javax.swing.JPanel {
if (dataSources.isEmpty()) {
throw new GeoLocationUIException(Bundle.GeoFilterPanel_empty_dataSource());
}
return new GeoFilter(allButton.isSelected(),
showWaypointsWOTSCheckBox.isSelected(),
numberModel.getNumber().intValue(),
return new GeoFilter(allButton.isSelected(),
showWaypointsWOTSCheckBox.isSelected(),
numberModel.getNumber().intValue(),
dataSources);
}
/**
* Initialize the checkbox list panel
*
* @throws TskCoreException
*/
private void initCheckboxList() throws TskCoreException {
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
checkboxPanel.addElement(dsName, dataSource);
}
}
/**
* Based on the state of mostRecent radio button Change the state of the cnt
* spinner and the time stamp checkbox.
@ -377,4 +382,72 @@ class GeoFilterPanel extends javax.swing.JPanel {
}
}
/**
* SwingWorker for updating the list of valid data sources.
*
* doInBackground creates a list of Pair objects that contain the
* display name of the data source and the data source object.
*/
final private class DataSourceUpdater extends SwingWorker<List<Pair<String, DataSource>>, Void> {
@Override
protected List<Pair<String, DataSource>> doInBackground() throws Exception {
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
List<Pair<String, DataSource>> validSources = new ArrayList<>();
for (DataSource dataSource : sleuthkitCase.getDataSources()) {
if (isGPSDataSource(sleuthkitCase, dataSource)) {
String dsName = sleuthkitCase.getContentById(dataSource.getId()).getName();
Pair<String, DataSource> pair = new Pair<>(dsName, dataSource);
validSources.add(pair);
}
}
return validSources;
}
/**
* Returns whether or not the given data source has GPS artifacts.
*
* @param sleuthkitCase The current sleuthkitCase
* @param dataSource
*
* @return True if the data source as at least one TSK_GPS_XXXX
*
* @throws TskCoreException
*/
private boolean isGPSDataSource(SleuthkitCase sleuthkitCase, DataSource dataSource) throws TskCoreException {
for (BlackboardArtifact.ARTIFACT_TYPE type : GPS_ARTIFACT_TYPES) {
if (sleuthkitCase.getBlackboardArtifactsTypeCount(type.getTypeID(), dataSource.getId()) > 0) {
return true;
}
}
return false;
}
@Override
public void done() {
List<Pair<String, DataSource>> sources = null;
try {
sources = get();
} catch (InterruptedException | ExecutionException ex) {
Throwable cause = ex.getCause();
if (cause != null) {
logger.log(Level.SEVERE, cause.getMessage(), cause);
} else {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
if (sources != null) {
for (Pair<String, DataSource> source : sources) {
checkboxPanel.addElement(source.getKey(), source.getValue());
}
}
GeoFilterPanel.this.firePropertyChange(INITPROPERTY, false, true);
}
}
}

View File

@ -28,7 +28,6 @@ import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashSet;
@ -51,10 +50,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.geolocation.GeoFilterPanel.GeoFilter;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Track;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder.WaypointFilterQueryCallBack;
import org.sleuthkit.autopsy.ingest.IngestManager;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -93,7 +88,9 @@ public final class GeolocationTopComponent extends TopComponent {
@Messages({
"GLTopComponent_name=Geolocation",
"GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete."
"GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete.",
"GLTopComponent_No_dataSource_message=There are no data sources with Geolocation artifacts found.",
"GLTopComponent_No_dataSource_Title=No Geolocation artifacts found"
})
/**
@ -144,7 +141,6 @@ public final class GeolocationTopComponent extends TopComponent {
public void actionPerformed(ActionEvent e) {
geoFilterPanel.updateDataSourceList();
mapPanel.clearWaypoints();
updateWaypoints();
showRefreshPanel(false);
}
});
@ -158,6 +154,24 @@ public final class GeolocationTopComponent extends TopComponent {
}
});
geoFilterPanel.addPropertyChangeListener(GeoFilterPanel.INITPROPERTY, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (geoFilterPanel.hasDataSources()) {
updateWaypoints();
} else {
geoFilterPanel.setEnabled(false);
setWaypointLoading(false);
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
Bundle.GLTopComponent_No_dataSource_message(),
Bundle.GLTopComponent_No_dataSource_Title(),
JOptionPane.ERROR_MESSAGE);
}
}
});
mapPanel.addPropertyChangeListener(MapPanel.CURRENT_MOUSE_GEOPOSITION, new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
@ -201,9 +215,6 @@ public final class GeolocationTopComponent extends TopComponent {
@Override
public void open() {
super.open();
mapPanel.clearWaypoints();
geoFilterPanel.clearDataSourceList();
geoFilterPanel.updateDataSourceList();
// Let's make sure we only do this on the first open
if (!mapInitalized) {
@ -222,8 +233,12 @@ public final class GeolocationTopComponent extends TopComponent {
return; // Doen't set the waypoints.
}
}
mapPanel.clearWaypoints();
geoFilterPanel.clearDataSourceList();
geoFilterPanel.updateDataSourceList();
mapPanel.setWaypoints(new LinkedHashSet<>());
updateWaypoints();
}
/**
@ -237,8 +252,8 @@ public final class GeolocationTopComponent extends TopComponent {
public void run() {
boolean isShowing = false;
Component[] comps = mapPanel.getComponents();
for(Component comp: comps) {
if(comp.equals(refreshPanel)) {
for (Component comp : comps) {
if (comp.equals(refreshPanel)) {
isShowing = true;
break;
}
@ -246,10 +261,10 @@ public final class GeolocationTopComponent extends TopComponent {
if (show && !isShowing) {
mapPanel.add(refreshPanel, BorderLayout.NORTH);
mapPanel.revalidate();
} else if(!show && isShowing){
} else if (!show && isShowing) {
mapPanel.remove(refreshPanel);
mapPanel.revalidate();
}
}
}
});
@ -284,10 +299,61 @@ public final class GeolocationTopComponent extends TopComponent {
setWaypointLoading(true);
geoFilterPanel.setEnabled(false);
Thread thread = new Thread(new WaypointRunner(filters));
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
(new WaypointFetcher(filters)).getWaypoints();
} catch (GeoLocationDataException ex) {
logger.log(Level.SEVERE, "Failed to filter waypoints.", ex);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
Bundle.GeoTopComponent_filter_exception_Title(),
Bundle.GeoTopComponent_filter_exception_msg(),
JOptionPane.ERROR_MESSAGE);
setWaypointLoading(false);
}
});
}
}
});
thread.start();
}
/**
* Add the filtered set of waypoints to the map and set the various window
* components to their proper state.
*
* @param waypointList
*/
void addWaypointsToMap(Set<MapWaypoint> waypointList) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// If the list is empty, tell the user
if (waypointList == null || waypointList.isEmpty()) {
mapPanel.clearWaypoints();
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
Bundle.GeoTopComponent_no_waypoints_returned_Title(),
Bundle.GeoTopComponent_no_waypoints_returned_mgs(),
JOptionPane.INFORMATION_MESSAGE);
setWaypointLoading(false);
geoFilterPanel.setEnabled(true);
return;
}
mapPanel.clearWaypoints();
mapPanel.setWaypoints(waypointList);
setWaypointLoading(false);
geoFilterPanel.setEnabled(true);
}
});
}
/**
* Show or hide the waypoint loading progress bar.
*
@ -424,244 +490,18 @@ public final class GeolocationTopComponent extends TopComponent {
// End of variables declaration//GEN-END:variables
/**
* A runnable class for getting waypoints based on the current filters.
* Extends AbstractWaypointFetcher to handle the returning of
* the filters set of MapWaypoints.
*/
private class WaypointRunner implements Runnable, WaypointFilterQueryCallBack {
final private class WaypointFetcher extends AbstractWaypointFetcher {
private final GeoFilter filters;
/**
* Constructs the Waypoint Runner
*
* @param filters
*/
WaypointRunner(GeoFilter filters) {
this.filters = filters;
WaypointFetcher(GeoFilter filters) {
super(filters);
}
@Override
public void run() {
Case currentCase = Case.getCurrentCase();
try {
WaypointBuilder.getAllWaypoints(currentCase.getSleuthkitCase(),
filters.getDataSources(),
filters.showAllWaypoints(),
filters.getMostRecentNumDays(),
filters.showWaypointsWithoutTimeStamp(),
this);
} catch (GeoLocationDataException ex) {
logger.log(Level.SEVERE, "Failed to filter waypoints.", ex);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
Bundle.GeoTopComponent_filter_exception_Title(),
Bundle.GeoTopComponent_filter_exception_msg(),
JOptionPane.ERROR_MESSAGE);
setWaypointLoading(false);
}
});
}
}
@Override
public void process(List<Waypoint> waypoints) {
List<Track> tracks = null;
try {
tracks = Track.getTracks(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources());
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex);
}
List<Waypoint> completeList = createWaypointList(waypoints, tracks);
final Set<MapWaypoint> pointSet = MapWaypoint.getWaypoints(completeList);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// If the list is empty, tell the user and do not change
// the visible waypoints.
if (completeList == null || completeList.isEmpty()) {
mapPanel.clearWaypoints();
JOptionPane.showMessageDialog(GeolocationTopComponent.this,
Bundle.GeoTopComponent_no_waypoints_returned_Title(),
Bundle.GeoTopComponent_no_waypoints_returned_mgs(),
JOptionPane.INFORMATION_MESSAGE);
setWaypointLoading(false);
geoFilterPanel.setEnabled(true);
return;
}
mapPanel.clearWaypoints();
mapPanel.setWaypoints(pointSet);
setWaypointLoading(false);
geoFilterPanel.setEnabled(true);
}
});
}
/**
* Returns a complete list of waypoints including the tracks. Takes into
* account the current filters and includes waypoints as approprate.
*
* @param waypoints List of waypoints
* @param tracks List of tracks
*
* @return A list of waypoints including the tracks based on the current
* filters.
*/
private List<Waypoint> createWaypointList(List<Waypoint> waypoints, List<Track> tracks) {
final List<Waypoint> completeList = new ArrayList<>();
if (tracks != null) {
Long timeRangeEnd;
Long timeRangeStart;
if (!filters.showAllWaypoints()) {
// Figure out what the most recent time is given the filtered
// waypoints and the tracks.
timeRangeEnd = getMostRecent(waypoints, tracks);
timeRangeStart = timeRangeEnd - (86400 * filters.getMostRecentNumDays());
completeList.addAll(getWaypointsInRange(timeRangeStart, timeRangeEnd, waypoints));
completeList.addAll(getTracksInRange(timeRangeStart, timeRangeEnd, tracks));
} else {
completeList.addAll(waypoints);
for (Track track : tracks) {
completeList.addAll(track.getPath());
}
}
} else {
completeList.addAll(waypoints);
}
return completeList;
}
/**
* Return a list of waypoints that fall into the given time range.
*
* @param timeRangeStart start timestamp of range (seconds from java
* epoch)
* @param timeRangeEnd start timestamp of range (seconds from java
* epoch)
* @param waypoints List of waypoints to filter.
*
* @return A list of waypoints that fall into the time range.
*/
private List<Waypoint> getWaypointsInRange(Long timeRangeStart, Long timeRangeEnd, List<Waypoint> waypoints) {
List<Waypoint> completeList = new ArrayList<>();
// Add all of the waypoints that fix into the time range.
if (waypoints != null) {
for (Waypoint point : waypoints) {
Long time = point.getTimestamp();
if ((time == null && filters.showWaypointsWithoutTimeStamp())
|| (time != null && (time >= timeRangeStart && time <= timeRangeEnd))) {
completeList.add(point);
}
}
}
return completeList;
}
/**
* Return a list of waypoints from the given tracks that fall into for
* tracks that fall into the given time range. The track start time will
* used for determining if the whole track falls into the range.
*
* @param timeRangeStart start timestamp of range (seconds from java
* epoch)
* @param timeRangeEnd start timestamp of range (seconds from java
* epoch)
* @param tracks Track list.
*
* @return A list of waypoints that that belong to tracks that fall into
* the time range.
*/
private List<Waypoint> getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List<Track> tracks) {
List<Waypoint> completeList = new ArrayList<>();
if (tracks != null) {
for (Track track : tracks) {
Long trackTime = track.getStartTime();
if ((trackTime == null && filters.showWaypointsWithoutTimeStamp())
|| (trackTime != null && (trackTime >= timeRangeStart && trackTime <= timeRangeEnd))) {
completeList.addAll(track.getPath());
}
}
}
return completeList;
}
/**
* Find the latest time stamp in the given list of waypoints.
*
* @param points List of Waypoints, required.
*
* @return The latest time stamp (seconds from java epoch)
*/
private Long findMostRecentTimestamp(List<Waypoint> points) {
Long mostRecent = null;
for (Waypoint point : points) {
if (mostRecent == null) {
mostRecent = point.getTimestamp();
} else {
mostRecent = Math.max(mostRecent, point.getTimestamp());
}
}
return mostRecent;
}
/**
* Find the latest time stamp in the given list of tracks.
*
* @param tracks List of Waypoints, required.
*
* @return The latest time stamp (seconds from java epoch)
*/
private Long findMostRecentTracks(List<Track> tracks) {
Long mostRecent = null;
for (Track track : tracks) {
if (mostRecent == null) {
mostRecent = track.getStartTime();
} else {
mostRecent = Math.max(mostRecent, track.getStartTime());
}
}
return mostRecent;
}
/**
* Returns the "most recent" timestamp amount the list of waypoints and
* track points.
*
* @param points List of Waypoints
* @param tracks List of Tracks
*
* @return Latest time stamp (seconds from java epoch)
*/
private Long getMostRecent(List<Waypoint> points, List<Track> tracks) {
Long waypointMostRecent = findMostRecentTimestamp(points);
Long trackMostRecent = findMostRecentTracks(tracks);
if (waypointMostRecent != null && trackMostRecent != null) {
return Math.max(waypointMostRecent, trackMostRecent);
} else if (waypointMostRecent == null && trackMostRecent != null) {
return trackMostRecent;
} else if (waypointMostRecent != null && trackMostRecent == null) {
return waypointMostRecent;
}
return null;
void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints) {
addWaypointsToMap(mapWaypoints);
}
}
}

View File

@ -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

View File

@ -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<Waypoint.Property> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact,
label,
null,
latitude,
longitude,
altitude,
null,
attributeMap,
Route.this);
}
@Override
public Long getTimestamp() {
return ((Route) getParentGeoPath()).getTimestamp();
}
}
}

View File

@ -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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact, getTrackName(attributeMap));
List<GeoTrackPoint> 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<GeoTrackPoint> 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<GeoTrackPoint> getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
private GeoTrackPointList getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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<GeoTrackPoint> 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<GeoTrackPoint> 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<Waypoint.Property> createPropertyList(GeoTrackPoint point) {
List<Waypoint.Property> 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;

View File

@ -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<Waypoint.Property> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, GeoPath path) throws GeoLocationDataException {
Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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();

View File

@ -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<Route> getRoutes(List<Waypoint> waypoints) {
List<Route> 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<Track> getTracks(List<Waypoint> waypoints) {
List<Track> 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);
}

View File

@ -1,6 +1,6 @@
DATExtractor_process_message=Processing DJI DAT file: %s
DATFileExtractor_Extractor_Name=DAT File Extractor
DroneIngestModule_Description=Description
DroneIngestModule_Name=Drone
DroneIngestModule_Description=Analyzes files generated by drones.
DroneIngestModule_Name=Drone Analyzer
# {0} - AbstractFileName
DroneIngestModule_process_start=Started {0}

View File

@ -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<GeoTrackPoint> 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<GeoTrackPoint> processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException {
List<GeoTrackPoint> 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),

View File

@ -93,30 +93,22 @@
<Component id="jScrollPane1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="informationLabel" pref="0" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="356" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="indexButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="addHashesToDatabaseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
<Component id="informationScrollPanel" alignment="0" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="informationScrollPanel" alignment="0" pref="420" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="sendIngestMessagesCheckBox" min="-2" max="-2" attributes="0"/>
<Component id="ingestWarningLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="indexButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="addHashesToDatabaseButton" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="sendIngestMessagesCheckBox" min="-2" max="-2" attributes="0"/>
<Component id="ingestWarningLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="informationLabel" alignment="0" min="-2" pref="197" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
@ -298,11 +290,6 @@
</Component>
<Component class="javax.swing.JLabel" name="informationLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="informationLabel" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashLookupSettingsPanel.informationLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -418,11 +405,6 @@
<SubComponents>
<Component class="javax.swing.JLabel" name="nameLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="nameLabel" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashLookupSettingsPanel.nameLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -442,11 +424,6 @@
</Component>
<Component class="javax.swing.JLabel" name="typeLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="typeLabel" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashLookupSettingsPanel.typeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -466,11 +443,6 @@
</Component>
<Component class="javax.swing.JLabel" name="locationLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="locationLabel" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashLookupSettingsPanel.locationLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -616,11 +588,6 @@
</Component>
<Component class="javax.swing.JCheckBox" name="sendIngestMessagesCheckBox">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="sendIngestMessagesCheckBox" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashLookupSettingsPanel.sendIngestMessagesCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
@ -631,11 +598,6 @@
</Component>
<Component class="javax.swing.JLabel" name="ingestWarningLabel">
<Properties>
<Property name="font" type="java.awt.Font" editor="org.netbeans.modules.form.editors2.FontEditor">
<FontInfo relative="true">
<Font bold="false" component="ingestWarningLabel" property="font" relativeSize="false" size="11"/>
</FontInfo>
</Property>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/modules/hashdatabase/warning16.png"/>
</Property>

View File

@ -706,24 +706,20 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
}
});
informationLabel.setFont(informationLabel.getFont().deriveFont(informationLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(informationLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.informationLabel.text")); // NOI18N
informationScrollPanel.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
nameLabel.setFont(nameLabel.getFont().deriveFont(nameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.nameLabel.text")); // NOI18N
hashDbNameLabel.setFont(hashDbNameLabel.getFont().deriveFont(hashDbNameLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(hashDbNameLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.hashDbNameLabel.text")); // NOI18N
typeLabel.setFont(typeLabel.getFont().deriveFont(typeLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(typeLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.typeLabel.text")); // NOI18N
hashDbTypeLabel.setFont(hashDbTypeLabel.getFont().deriveFont(hashDbTypeLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(hashDbTypeLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.hashDbTypeLabel.text")); // NOI18N
locationLabel.setFont(locationLabel.getFont().deriveFont(locationLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(locationLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.locationLabel.text")); // NOI18N
hashDbLocationLabel.setFont(hashDbLocationLabel.getFont().deriveFont(hashDbLocationLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
@ -854,7 +850,6 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
}
});
sendIngestMessagesCheckBox.setFont(sendIngestMessagesCheckBox.getFont().deriveFont(sendIngestMessagesCheckBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
org.openide.awt.Mnemonics.setLocalizedText(sendIngestMessagesCheckBox, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.sendIngestMessagesCheckBox.text")); // NOI18N
sendIngestMessagesCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@ -862,7 +857,6 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
}
});
ingestWarningLabel.setFont(ingestWarningLabel.getFont().deriveFont(ingestWarningLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
ingestWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/hashdatabase/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(ingestWarningLabel, org.openide.util.NbBundle.getMessage(HashLookupSettingsPanel.class, "HashLookupSettingsPanel.ingestWarningLabel.text")); // NOI18N
@ -878,23 +872,18 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addComponent(informationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
.addGap(356, 356, 356))
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(indexButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(addHashesToDatabaseButton)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(informationScrollPanel)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(informationScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 420, Short.MAX_VALUE)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(sendIngestMessagesCheckBox)
.addComponent(ingestWarningLabel))
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())))
.addComponent(indexButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(addHashesToDatabaseButton))
.addComponent(sendIngestMessagesCheckBox)
.addComponent(ingestWarningLabel)
.addComponent(informationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 197, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(hashDatabasesLabel)

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -1,192 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<AbstractFile> 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<BlackboardAttribute> 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
}
}
}

View File

@ -1,251 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<AbstractFile> 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<BlackboardAttribute> 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);
}
}
}
}

View File

@ -1,198 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<AbstractFile> 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<BlackboardAttribute> 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
}
}
}

View File

@ -1,50 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Long, Long> 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;
}
}

View File

@ -1,59 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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();
}
}

View File

@ -324,7 +324,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return IngestModule.ProcessResult.ERROR;
} catch (IOException ex) {
totals.totalItemsWithErrors.incrementAndGet();
logger.log(Level.SEVERE, String.format("Error writing file '%s' (id=%d) to '%s' with the PhotoRec carver.", file.getName(), file.getId(), tempFilePath), ex); // NON-NLS
logger.log(Level.SEVERE, String.format("Error writing or processing file '%s' (id=%d) to '%s' with the PhotoRec carver.", file.getName(), file.getId(), tempFilePath), ex); // NON-NLS
MessageNotifyUtil.Notify.error(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.error.msg", file.getName()));
return IngestModule.ProcessResult.ERROR;
} finally {

View File

@ -269,7 +269,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
String architectureFolder = PlatformUtil.is64BitOS() ? PLASO64 : PLASO32;
String executableToFindName = Paths.get(PLASO, architectureFolder, executableName).toString();
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PlasoIngestModule.class.getPackage().getName(), false);
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, "org.sleuthkit.autopsy.core", false);
if (null == exeFile || exeFile.canExecute() == false) {
throw new FileNotFoundException(executableName + " executable not found.");
}

View File

@ -46,16 +46,6 @@ class ArtifactTextExtractor implements TextExtractor {
// "content" string to be indexed.
StringBuilder artifactContents = new StringBuilder();
Content dataSource = null;
try {
dataSource = artifact.getDataSource();
} catch (TskCoreException tskCoreException) {
throw new InitReaderException("Unable to get datasource for artifact: " + artifact.toString(), tskCoreException);
}
if (dataSource == null) {
throw new InitReaderException("Datasource was null for artifact: " + artifact.toString());
}
try {
for (BlackboardAttribute attribute : artifact.getAttributes()) {
artifactContents.append(attribute.getAttributeType().getDisplayName());
@ -67,7 +57,7 @@ class ArtifactTextExtractor implements TextExtractor {
// in the Autopsy datamodel.
switch (attribute.getValueType()) {
case DATETIME:
artifactContents.append(ContentUtils.getStringTime(attribute.getValueLong(), dataSource));
artifactContents.append(ContentUtils.getStringTime(attribute.getValueLong(), artifact));
break;
default:
artifactContents.append(attribute.getDisplayString());

View File

@ -423,7 +423,7 @@ final class TikaTextExtractor implements TextExtractor {
}
String executableToFindName = Paths.get(TESSERACT_DIR_NAME, TESSERACT_EXECUTABLE).toString();
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, TikaTextExtractor.class.getPackage().getName(), false);
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, "org.sleuthkit.autopsy.core", false);
if (null == exeFile) {
return null;
}

View File

@ -0,0 +1,48 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.textsummarizer;
import java.io.IOException;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Interface for implementation of summarizers for documents.
*/
public interface TextSummarizer {
/**
* Get the name of the TextSummarizer for identification purposes.
*
* @return The name of the TextSummarizer.
*/
String getName();
/**
* Summarize the provided abstract file into a summary with a size no
* greater than the size specified.
*
* @param file The AbstractFile to summarize.
* @param summarySize The size of the summary to create.
*
* @return The summary as a TextSummary object.
*
* @throws IOException
*/
TextSummary summarize(AbstractFile file, int summarySize) throws IOException;
}

View File

@ -0,0 +1,67 @@
/*
* Autopsy
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.textsummarizer;
import java.awt.Image;
/**
* Class to contain all information necessary to display a summary for a file.s
*/
public class TextSummary {
private final String summaryText;
private final Image sampleImage;
private final int numberOfImages;
/**
* Create a new TextSummary object.
*
* @param summary - The text portion of the summary.
* @param image - The Image portion of the summary
* @param countOfImages - The number of images including the one provided in
* the document.
*/
public TextSummary(String summary, Image image, int countOfImages) {
summaryText = summary;
sampleImage = image;
numberOfImages = countOfImages;
}
/**
* @return the summaryText
*/
public String getSummaryText() {
return summaryText;
}
/**
* @return the sampleImage
*/
public Image getSampleImage() {
return sampleImage;
}
/**
* @return the numberOfImages
*/
public int getNumberOfImages() {
return numberOfImages;
}
}

View File

@ -2,7 +2,7 @@
*
* Autopsy Forensic Browser
*
* Copyright 2018-2019 Basis Technology Corp.
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -62,6 +62,7 @@ import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFa
import org.sleuthkit.autopsy.modules.vmextractor.VMExtractorIngestModuleFactory;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory;
/**
* Utilities for testing intercase correlation feature.
@ -220,7 +221,7 @@ class InterCaseTestUtils {
this.kitchenShink = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.ALL_MODULES, kitchenSink);
try {
Collection<CorrelationAttributeInstance.Type> types = CorrelationAttributeInstance.getDefaultCorrelationTypes();
Collection<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getCorrelationTypes();
//TODO use ids instead of strings
FILE_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Files")).findAny().get();
@ -248,7 +249,7 @@ class InterCaseTestUtils {
CentralRepository.getInstance().shutdownConnections();
}
FileUtils.deleteDirectory(CENTRAL_REPO_DIRECTORY_PATH.toFile());
} catch (IOException | CentralRepoExceptionex) {
} catch (IOException | CentralRepoException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
@ -297,8 +298,10 @@ class InterCaseTestUtils {
crSettings.createDbDirectory();
}
crSettings.initializeDatabaseSchema();
crSettings.insertDefaultDatabaseContent();
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, crSettings);
centralRepoSchemaFactory.initializeDatabaseSchema();
centralRepoSchemaFactory.insertDefaultDatabaseContent();
crSettings.saveSettings();
CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.SQLITE.name());
CentralRepoPlatforms.saveSelectedPlatform();

View File

@ -1243,6 +1243,11 @@ public class SharedConfiguration {
HashDbManager hashDbManager = HashDbManager.getInstance();
hashDbManager.loadLastSavedConfiguration();
for (HashDbManager.HashDb hashDb : hashDbManager.getAllHashSets()) {
// Central Repository hash sets have no path and don't need to be copied
if (hashDb.getIndexPath().isEmpty() && hashDb.getDatabasePath().isEmpty()) {
continue;
}
if (hashDb.hasIndexOnly()) {
results.add(hashDb.getIndexPath());
} else {

View File

@ -37,8 +37,12 @@ from org.sleuthkit.datamodel import BlackboardArtifact
from org.sleuthkit.datamodel import BlackboardAttribute
from org.sleuthkit.datamodel import TskCoreException
from org.sleuthkit.datamodel.blackboardutils import GeoArtifactsHelper
from org.sleuthkit.datamodel.blackboardutils.attributes import GeoWaypoint
from org.sleuthkit.datamodel.blackboardutils.attributes import GeoTrackPoints
from org.sleuthkit.datamodel.blackboardutils.attributes import TskGeoWaypointsUtil
from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil import GeoWaypointList
from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList import GeoWaypoint
from org.sleuthkit.datamodel.blackboardutils.attributes import TskGeoTrackpointsUtil
from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil import GeoTrackPointList
from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList import GeoTrackPoint
from org.sleuthkit.autopsy.datamodel import ContentUtils
from org.sleuthkit.autopsy.ingest import IngestModule
from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException
@ -63,20 +67,15 @@ import gpxpy.parser
class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter):
moduleName = "GPX Parser"
# True - Verbose debugging messages sent to log file.
# False - Verbose debugging turned off.
debuglevel = False
def getModuleDisplayName(self):
return self.moduleName
# TODO: Give it a description
def getModuleDescription(self):
return "Module that extracts GEO data from GPX files."
def getModuleVersionNumber(self):
return "1.1"
return "1.2"
def isDataSourceIngestModuleFactory(self):
return True
@ -88,10 +87,11 @@ class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter):
# Data Source-level ingest module. One gets created per data source.
class GPXParserDataSourceIngestModule(DataSourceIngestModule):
_logger = Logger.getLogger(GPXParserDataSourceIngestModuleFactory.moduleName)
logger = Logger.getLogger(GPXParserDataSourceIngestModuleFactory.moduleName)
writeDebugMsgs = False
def log(self, level, msg):
self._logger.logp(level, self.__class__.__name__, inspect.stack()[1][3], msg)
self.logger.logp(level, self.__class__.__name__, inspect.stack()[1][3], msg)
def __init__(self):
self.context = None
@ -105,179 +105,130 @@ class GPXParserDataSourceIngestModule(DataSourceIngestModule):
# We don't know how much work there is yet.
progressBar.switchToIndeterminate()
# This will work in 4.0.1 and beyond.
# Use blackboard class to index blackboard artifacts for keyword search.
blackboard = Case.getCurrentCase().getServices().getBlackboard()
# Get the sleuthkitcase
# Get the case database and its blackboard.
skCase = Case.getCurrentCase().getSleuthkitCase()
blackboard = skCase.getBlackboard()
# In the name and then count and read them.
fileManager = Case.getCurrentCase().getServices().getFileManager()
# Get any files with a .gpx extension.
# It would perhaps be better to get these files by MIME type instead.
# RC: It would also be better if this were a file level ingest module so it could process files extracted from archives.
fileManager = Case.getCurrentCase().getServices().getFileManager()
files = fileManager.findFiles(dataSource, "%.gpx")
# TODO: Would like to change this to find files based on mimetype rather than extension.
#files = findFiles(dataSource, "text/xml")
#if (file.isMimeType('text/xml') == False):
# Update the progress bar now that we know how much work there is to do.
numFiles = len(files)
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "found " + str(numFiles) + " files")
if self.writeDebugMsgs: self.log(Level.INFO, "Found " + str(numFiles) + " GPX files")
progressBar.switchToDeterminate(numFiles)
fileCount = 0;
# Get module name for adding attributes
# Get the module name, it will be needed for adding attributes
moduleName = GPXParserDataSourceIngestModuleFactory.moduleName
# Check if a folder for this module is present in the case Temp directory.
# If not, create it.
dirName = os.path.join(Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module")
try:
os.stat(dirName)
except:
os.mkdir(dirName)
# Create a temp file name. It appears that we cannot close and delete
# this file, but we can overwrite it for each file we need to process.
fileName = os.path.join(dirName, "tmp.gpx")
fileCount = 0;
for file in files:
# Get the GeoArtifactsHelper
geoArtifactHelper = GeoArtifactsHelper(skCase, moduleName, file)
# Create a GeoArtifactsHelper for this file.
geoArtifactHelper = GeoArtifactsHelper(skCase, moduleName, None, file)
# Check if the user pressed cancel while we were busy.
if self.context.isJobCancelled():
return IngestModule.ProcessResult.OK
#self.log(Level.INFO, "GPX: Processing file: " + file.getName())
if self.writeDebugMsgs: self.log(Level.INFO, "Processing " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")")
fileCount += 1
# Check if module folder is present. If not, create it.
dirName = os.path.join(Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module")
try:
os.stat(dirName)
except:
os.mkdir(dirName)
fileName = os.path.join(dirName, "tmp.gpx")
# Check to see if temporary file exists. If it does, remove it.
if os.path.exists(fileName):
try:
os.remove(fileName)
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE DELETED " + fileName )
except:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE NOT DELETED " + fileName)
# This writes the file to the local file system.
# Write the file so that it can be parsed by gpxpy.
localFile = File(fileName)
ContentUtils.writeToFile(file, localFile)
# Send to gpxpy for parsing.
# Send the file to gpxpy for parsing.
gpxfile = open(fileName)
try:
gpx = gpxpy.parse(gpxfile)
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE PARSED")
except:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX:\t" + file.getName() + " - FILE NOT PARSED")
if self.writeDebugMsgs: self.log(Level.INFO, "Parsed " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")")
except Exception as e:
self.log(Level.WARNING, "Error parsing file " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e))
continue
if gpx:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: TRACKS")
if self.writeDebugMsgs: self.log(Level.INFO, "Processing tracks from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")")
for track in gpx.tracks:
for segment in track.segments:
geoPointList = ArrayList()
geoPointList = TskGeoTrackpointsUtil.GeoTrackPointList()
for point in segment.points:
elevation = 0
if point.elevation != None:
elevation = point.elevation
dateTime = 0
timeStamp = 0
try:
if (point.time != None):
datetime = long(time.mktime(point.time.timetuple()))
except:
pass
timeStamp = long(time.mktime(point.time.timetuple()))
except Exception as e:
self.log(Level.WARNING, "Error getting track timestamp from " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e))
geoPointList.add(GeoWaypoint.GeoTrackPoint(point.latitude, point.longitude, elevation, 0, 0, 0, dateTime))
geoPointList.addPoint(GeoTrackPoint(point.latitude, point.longitude, elevation, None, 0, 0, 0, timeStamp))
try:
# Add the trackpoint using the helper class
geoartifact = geoArtifactHelper.addTrack("Trackpoint", geoPointList)
geoArtifactHelper.addTrack("Track", geoPointList, None)
except Blackboard.BlackboardException as e:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard " )
self.log(Level.SEVERE, "Error posting GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
except TskCoreException as e:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper tskcoreexception" )
self.log(Level.SEVERE, "Error creating GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: WAYPOINTS")
if self.writeDebugMsgs: self.log(Level.INFO, "Processing waypoints from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")")
for waypoint in gpx.waypoints:
attributes = ArrayList()
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), moduleName, waypoint.latitude))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), moduleName, waypoint.longitude))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), moduleName, "Waypoint"))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), moduleName, waypoint.name))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), moduleName, "GPXParser"))
art.addAttributes(attributes)
try:
# Post the artifact to blackboard
skCase.getBlackboard().postArtifact(art, moduleName)
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)
attributes = ArrayList()
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), moduleName, waypoint.latitude))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), moduleName, waypoint.longitude))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), moduleName, "Waypoint"))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), moduleName, waypoint.name))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), moduleName, "GPXParser"))
art.addAttributes(attributes)
blackboard.postArtifact(art, moduleName)
except Blackboard.BlackboardException as e:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" )
self.log(Level.SEVERE, "Error posting GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
except TskCoreException as e:
self.log(Level.SEVERE, "Error creating GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX: ROUTES")
if self.writeDebugMsgs: self.log(Level.INFO, "Processing routes from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")")
for route in gpx.routes:
firstTimeThru = 0
startingPoint = list()
endingPoint = list()
geoWaypointList = TskGeoWaypointsUtil.GeoWaypointList()
for point in route.points:
# If first time in loop only populate starting point
if (firstTimeThru == 0):
startingPoint.append((point.latitude, point.longitude))
firstTimeThru = 1
else:
startingPoint.append((point.latitude, point.longitude))
endingPoint.append((point.latitude, point.longitude))
geoWaypointList.addPoint(point.latitude, point.longitude, elevation, point.name)
if (len(endingPoint) > 0):
# get length of ending point as this ensures that we have equal points to process.
for i in range(0,len(endingPoint) -1):
attributes = ArrayList()
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), moduleName, startingPoint[i][0]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID(), moduleName, startingPoint[i][1]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID(), moduleName, endingPoint[i][0]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID(), moduleName, endingPoint[i][1]))
art.addAttributes(attributes)
try:
# Post the artifact to blackboard
skCase.getBlackboard().postArtifact(art, moduleName)
except Blackboard.BlackboardException as e:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" )
else:
if (len(startingPoint) > 0):
attributes = ArrayList()
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(), moduleName, startingPoint[0][0]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID(), moduleName, startingPoint[0][1]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID(), moduleName, startingPoint[0][0]))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID(), moduleName, startingPoint[0][1]))
art.addAttributes(attributes)
try:
# Post the artifact to blackboard
skCase.getBlackboard().postArtifact(art, moduleName)
except Blackboard.BlackboardException as e:
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.SEVERE, "GPX: Error using geo artifact helper with blackboard for waypoints" )
try:
geoArtifactHelper.addRoute(None, None, geoWaypointList, None)
except Blackboard.BlackboardException as e:
self.log("Error posting GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
except TskCoreException as e:
self.log(Level.SEVERE, "Error creating GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage())
# Update the progress bar.
progressBar.progress(fileCount)
if os.path.exists(fileName):
try:
os.remove(fileName)
if GPXParserDataSourceIngestModuleFactory.debuglevel: self.log(Level.INFO, "GPX:\t" + "FILE DELETED")
except:
self.log(Level.SEVERE, "GPX:\t" + "FILE NOT DELETED")
# Post a message to the ingest messages inbox.
message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, "GPX Parser Data Source Ingest Module", "Found %d files" % fileCount)
message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, moduleName, "Processed %d files" % fileCount)
IngestServices.getInstance().postMessage(message)
return IngestModule.ProcessResult.OK;

View File

@ -92,39 +92,41 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer):
for tableName in CallLogAnalyzer._tableNames:
try:
resultSet = callLogDb.runQuery("SELECT number, date, duration, type, name FROM " + tableName + " ORDER BY date DESC;")
self._logger.log(Level.INFO, "Reading call log from table {0} in db {1}", [tableName, callLogDb.getDBFile().getName()])
if resultSet is not None:
while resultSet.next():
direction = ""
callerId = None
calleeId = None
timeStamp = resultSet.getLong("date") / 1000
number = resultSet.getString("number")
duration = resultSet.getLong("duration") # duration of call is in seconds
name = resultSet.getString("name") # name of person dialed or called. None if unregistered
calltype = resultSet.getInt("type")
if calltype == 1 or calltype == 3:
direction = CommunicationDirection.INCOMING
callerId = number
elif calltype == 2 or calltype == 5:
direction = CommunicationDirection.OUTGOING
calleeId = number
else:
direction = CommunicationDirection.UNKNOWN
tableFound = callLogDb.tableExists(tableName)
if tableFound:
resultSet = callLogDb.runQuery("SELECT number, date, duration, type, name FROM " + tableName + " ORDER BY date DESC;")
self._logger.log(Level.INFO, "Reading call log from table {0} in db {1}", [tableName, callLogDb.getDBFile().getName()])
if resultSet is not None:
while resultSet.next():
direction = ""
callerId = None
calleeId = None
timeStamp = resultSet.getLong("date") / 1000
number = resultSet.getString("number")
duration = resultSet.getLong("duration") # duration of call is in seconds
name = resultSet.getString("name") # name of person dialed or called. None if unregistered
## add a call log
if callerId is not None or calleeId is not None:
callLogArtifact = callLogDbHelper.addCalllog( direction,
callerId,
calleeId,
timeStamp, ## start time
timeStamp + duration * 1000, ## end time
CallMediaType.AUDIO)
calltype = resultSet.getInt("type")
if calltype == 1 or calltype == 3:
direction = CommunicationDirection.INCOMING
callerId = number
elif calltype == 2 or calltype == 5:
direction = CommunicationDirection.OUTGOING
calleeId = number
else:
direction = CommunicationDirection.UNKNOWN
## add a call log
if callerId is not None or calleeId is not None:
callLogArtifact = callLogDbHelper.addCalllog( direction,
callerId,
calleeId,
timeStamp, ## start time
timeStamp + duration * 1000, ## end time
CallMediaType.AUDIO)
except SQLException as ex:
self._logger.log(Level.WARNING, "Error processing query result for Android messages.", ex)

View File

@ -102,13 +102,7 @@ class ContactAnalyzer(general.AndroidComponentAnalyzer):
# 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.
# check if contacts.name_raw_contact_id exists. Modify the query accordingly.
columnFound = False
metadata = contactDb.getConnectionMetadata()
columnListResultSet = metadata.getColumns(None, None, "contacts", None)
while columnListResultSet.next():
if columnListResultSet.getString("COLUMN_NAME") == "name_raw_contact_id":
columnFound = True
break
columnFound = contactDb.columnExists("contacts", "name_raw_contact_id")
if columnFound:
resultSet = contactDb.runQuery(
"SELECT mimetype, data1, name_raw_contact.display_name AS display_name \n"

View File

@ -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())

View File

@ -229,6 +229,10 @@
<runtime-relative-path>ext/icu4j-3.8.jar</runtime-relative-path>
<binary-origin>release/modules/ext/icu4j-3.8.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/guava-19.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/guava-19.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/language-detector-0.6.jar</runtime-relative-path>
<binary-origin>release\modules\ext\language-detector-0.6.jar</binary-origin>

Some files were not shown because too many files have changed in this diff Show More