19 package org.sleuthkit.autopsy.centralrepository.datamodel;
21 import java.sql.Connection;
22 import java.sql.DriverManager;
23 import java.sql.PreparedStatement;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.List;
28 import java.util.Properties;
29 import java.util.logging.Level;
30 import java.util.regex.Pattern;
53 private final static String
JDBC_DRIVER =
"org.postgresql.Driver";
69 if (host == null || host.isEmpty()) {
75 if (portString == null || portString.isEmpty()) {
78 port = Integer.parseInt(portString);
79 if (port < 0 || port > 65535) {
83 }
catch (NumberFormatException ex) {
88 if (dbName == null || dbName.isEmpty()) {
94 if (bulkThresholdString == null || bulkThresholdString.isEmpty()) {
95 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
97 this.bulkThreshold = Integer.parseInt(bulkThresholdString);
98 if (getBulkThreshold() <= 0) {
99 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
102 }
catch (NumberFormatException ex) {
103 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
107 if (userName == null || userName.isEmpty()) {
112 if (password == null || password.isEmpty()) {
118 LOGGER.log(Level.WARNING,
"Failed to convert password from hex text to text.", ex);
133 LOGGER.log(Level.SEVERE,
"Failed to convert password from text to hex text.", ex);
145 String getConnectionURL(
boolean usePostgresDb) {
146 StringBuilder url =
new StringBuilder();
147 url.append(getJDBCBaseURI());
151 url.append(
"postgres");
156 return url.toString();
168 String url = getConnectionURL(usePostgresDb);
169 Properties props =
new Properties();
173 Class.forName(getDriver());
174 conn = DriverManager.getConnection(url, props);
175 }
catch (ClassNotFoundException | SQLException ex) {
178 LOGGER.log(Level.SEVERE,
"Failed to acquire ephemeral connection to postgresql.");
212 String sql =
"SELECT datname FROM pg_catalog.pg_database WHERE lower(datname) = lower(?) LIMIT 1";
213 PreparedStatement ps = null;
216 ps = conn.prepareStatement(sql);
218 rs = ps.executeQuery();
222 }
catch (SQLException ex) {
223 LOGGER.log(Level.SEVERE,
"Failed to execute database existance query.", ex);
257 String sql =
"CREATE DATABASE %s OWNER %s";
260 stmt = conn.createStatement();
262 }
catch (SQLException ex) {
263 LOGGER.log(Level.SEVERE,
"Failed to execute create database statement.", ex);
278 String sql =
"DROP DATABASE %s";
281 stmt = conn.createStatement();
282 stmt.execute(String.format(sql,
getDbName()));
283 }
catch (SQLException ex) {
284 LOGGER.log(Level.SEVERE,
"Failed to execute drop database statement.", ex);
310 StringBuilder createOrganizationsTable =
new StringBuilder();
311 createOrganizationsTable.append(
"CREATE TABLE IF NOT EXISTS organizations (");
312 createOrganizationsTable.append(
"id SERIAL PRIMARY KEY,");
313 createOrganizationsTable.append(
"org_name text NOT NULL,");
314 createOrganizationsTable.append(
"poc_name text NOT NULL,");
315 createOrganizationsTable.append(
"poc_email text NOT NULL,");
316 createOrganizationsTable.append(
"poc_phone text NOT NULL,");
317 createOrganizationsTable.append(
"CONSTRAINT org_name_unique UNIQUE (org_name)");
318 createOrganizationsTable.append(
")");
322 StringBuilder createCasesTable =
new StringBuilder();
323 createCasesTable.append(
"CREATE TABLE IF NOT EXISTS cases (");
324 createCasesTable.append(
"id SERIAL PRIMARY KEY,");
325 createCasesTable.append(
"case_uid text NOT NULL,");
326 createCasesTable.append(
"org_id integer,");
327 createCasesTable.append(
"case_name text NOT NULL,");
328 createCasesTable.append(
"creation_date text NOT NULL,");
329 createCasesTable.append(
"case_number text,");
330 createCasesTable.append(
"examiner_name text,");
331 createCasesTable.append(
"examiner_email text,");
332 createCasesTable.append(
"examiner_phone text,");
333 createCasesTable.append(
"notes text,");
334 createCasesTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
335 createCasesTable.append(
"CONSTRAINT case_uid_unique UNIQUE (case_uid)");
336 createCasesTable.append(
")");
339 String casesIdx1 =
"CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
340 String casesIdx2 =
"CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
342 StringBuilder createReferenceSetsTable =
new StringBuilder();
343 createReferenceSetsTable.append(
"CREATE TABLE IF NOT EXISTS reference_sets (");
344 createReferenceSetsTable.append(
"id SERIAL PRIMARY KEY,");
345 createReferenceSetsTable.append(
"org_id integer NOT NULL,");
346 createReferenceSetsTable.append(
"set_name text NOT NULL,");
347 createReferenceSetsTable.append(
"version text NOT NULL,");
348 createReferenceSetsTable.append(
"known_status integer NOT NULL,");
349 createReferenceSetsTable.append(
"read_only boolean NOT NULL,");
350 createReferenceSetsTable.append(
"type integer NOT NULL,");
351 createReferenceSetsTable.append(
"import_date text NOT NULL,");
352 createReferenceSetsTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
353 createReferenceSetsTable.append(
"CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
354 createReferenceSetsTable.append(
")");
356 String referenceSetsIdx1 =
"CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
359 StringBuilder createReferenceTypesTableTemplate =
new StringBuilder();
360 createReferenceTypesTableTemplate.append(
"CREATE TABLE IF NOT EXISTS %s (");
361 createReferenceTypesTableTemplate.append(
"id SERIAL PRIMARY KEY,");
362 createReferenceTypesTableTemplate.append(
"reference_set_id integer,");
363 createReferenceTypesTableTemplate.append(
"value text NOT NULL,");
364 createReferenceTypesTableTemplate.append(
"known_status integer NOT NULL,");
365 createReferenceTypesTableTemplate.append(
"comment text,");
366 createReferenceTypesTableTemplate.append(
"CONSTRAINT %s_multi_unique UNIQUE (reference_set_id, value),");
367 createReferenceTypesTableTemplate.append(
"foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
368 createReferenceTypesTableTemplate.append(
")");
371 String referenceTypesIdx1 =
"CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
372 String referenceTypesIdx2 =
"CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
374 StringBuilder createCorrelationTypesTable =
new StringBuilder();
375 createCorrelationTypesTable.append(
"CREATE TABLE IF NOT EXISTS correlation_types (");
376 createCorrelationTypesTable.append(
"id SERIAL PRIMARY KEY,");
377 createCorrelationTypesTable.append(
"display_name text NOT NULL,");
378 createCorrelationTypesTable.append(
"db_table_name text NOT NULL,");
379 createCorrelationTypesTable.append(
"supported integer NOT NULL,");
380 createCorrelationTypesTable.append(
"enabled integer NOT NULL,");
381 createCorrelationTypesTable.append(
"CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
382 createCorrelationTypesTable.append(
")");
384 String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
386 String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
387 String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
388 String instancesValueIdx = getAddValueIndexTemplate();
389 String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
390 String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
394 Connection conn = null;
400 Statement stmt = conn.createStatement();
402 stmt.execute(createOrganizationsTable.toString());
404 stmt.execute(createCasesTable.toString());
405 stmt.execute(casesIdx1);
406 stmt.execute(casesIdx2);
408 stmt.execute(getCreateDataSourcesTableStatement());
409 stmt.execute(getAddDataSourcesNameIndexStatement());
410 stmt.execute(getAddDataSourcesObjectIdIndexStatement());
412 stmt.execute(createReferenceSetsTable.toString());
413 stmt.execute(referenceSetsIdx1);
415 stmt.execute(createCorrelationTypesTable.toString());
422 stmt.execute(
"CREATE TABLE db_info (id SERIAL, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
423 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MAJOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() +
"')");
424 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MINOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() +
"')");
425 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MAJOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() +
"')");
426 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MINOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() +
"')");
431 String reference_type_dbname;
432 String instance_type_dbname;
437 stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
438 stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
439 stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
440 stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
441 stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
442 stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
446 stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
447 stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
448 stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
452 }
catch (SQLException ex) {
453 LOGGER.log(Level.SEVERE,
"Error initializing db schema.", ex);
456 LOGGER.log(Level.SEVERE,
"Error getting default correlation types. Likely due to one or more Type's with an invalid db table name.");
471 static String getCreateArtifactInstancesTableTemplate() {
473 return (
"CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY,case_id integer NOT NULL,"
474 +
"data_source_id integer NOT NULL,value text NOT NULL,file_path text NOT NULL,"
475 +
"known_status integer NOT NULL,comment text,file_obj_id BIGINT,"
476 +
"CONSTRAINT %s_multi_unique_ UNIQUE (data_source_id, value, file_path),"
477 +
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
478 +
"foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)");
488 static String getCreateDataSourcesTableStatement() {
489 return "CREATE TABLE IF NOT EXISTS data_sources "
490 +
"(id SERIAL PRIMARY KEY,case_id integer NOT NULL,device_id text NOT NULL,"
491 +
"name text NOT NULL,datasource_obj_id BIGINT,md5 text DEFAULT NULL,"
492 +
"sha1 text DEFAULT NULL,sha256 text DEFAULT NULL,"
493 +
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
494 +
"CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
504 static String getAddDataSourcesNameIndexStatement() {
505 return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
515 static String getAddDataSourcesObjectIdIndexStatement() {
516 return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
527 static String getAddCaseIdIndexTemplate() {
529 return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
540 static String getAddDataSourceIdIndexTemplate() {
542 return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
553 static String getAddValueIndexTemplate() {
555 return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
566 static String getAddKnownStatusIndexTemplate() {
568 return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
579 static String getAddObjectIdIndexTemplate() {
581 return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
596 boolean isChanged() {
604 return !host.equals(hostString) || !Integer.toString(port).equals(portString)
605 || !dbName.equals(dbNameString) || !Integer.toString(bulkThreshold).equals(bulkThresholdString)
606 || !userName.equals(userNameString) || !password.equals(userPasswordString);
620 if (null != host && !host.isEmpty()) {
638 if (port > 0 && port < 65535) {
641 throw new EamDbException(
"Invalid port. Must be a number greater than 0.");
652 return dbName.toLowerCase();
659 if (dbName == null || dbName.isEmpty()) {
660 throw new EamDbException(
"Invalid database name. Cannot be empty.");
661 }
else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
662 throw new EamDbException(
"Invalid database name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.");
665 this.dbName = dbName.toLowerCase();
671 int getBulkThreshold() {
679 if (bulkThreshold > 0) {
697 if (userName == null || userName.isEmpty()) {
699 }
else if (!Pattern.matches(DB_USER_NAMES_REGEX, userName)) {
700 throw new EamDbException(
"Invalid user name. Name must start with a letter and can only contain letters, numbers, and '_'.");
716 if (password == null || password.isEmpty()) {
717 throw new EamDbException(
"Invalid user password. Cannot be empty.");
725 String getValidationQuery() {
739 String getJDBCBaseURI() {
static final Logger LOGGER
void setHost(String host)
static String correlationTypeToInstanceTableName(CorrelationAttributeInstance.Type type)
Connection getEphemeralConnection(boolean usePostgresDb)
static final String JDBC_DRIVER
static boolean schemaVersionIsSet(Connection conn)
static final String VALIDATION_QUERY
static boolean executeValidationQuery(Connection conn, String validationQuery)
static final String DEFAULT_DBNAME
boolean insertDefaultDatabaseContent()
boolean verifyDatabaseSchema()
static final String JDBC_BASE_URI
static final String DEFAULT_USERNAME
static void closeResultSet(ResultSet resultSet)
static void closeStatement(Statement statement)
static List< CorrelationAttributeInstance.Type > getDefaultCorrelationTypes()
static final String DEFAULT_HOST
static final String DB_NAMES_REGEX
void setPassword(String password)
static final String DEFAULT_PASSWORD
static final int DEFAULT_PORT
static void closeConnection(Connection conn)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
boolean initializeDatabaseSchema()
boolean verifyDatabaseExists()
static boolean insertDefaultCorrelationTypes(Connection conn)
static String getConfigSetting(String moduleName, String settingName)
boolean verifyConnection()
void setUserName(String userName)
void setDbName(String dbName)
synchronized static Logger getLogger(String name)
void setBulkThreshold(int bulkThreshold)
static String correlationTypeToReferenceTableName(CorrelationAttributeInstance.Type type)
static String convertTextToHexText(String property)
static final String DB_USER_NAMES_REGEX
static final int FILES_TYPE_ID
static String convertHexTextToText(String property)