diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp index 68c69a3c285211e99257edd132ed3776df133490..a31cb26acce6a2fc697519425f800a91bc5c438b 100644 --- a/bindings/java/jni/auto_db_java.cpp +++ b/bindings/java/jni/auto_db_java.cpp @@ -110,7 +110,7 @@ TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) { return TSK_ERR; } - m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;JJJ)J"); + m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;JJJLjava/lang/String;)J"); if (m_addFileMethodID == NULL) { return TSK_ERR; } @@ -631,6 +631,17 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file, } TSK_INUM_T par_meta_addr = fs_file->name->par_addr; + char *sid_str = NULL; + jstring sidj = NULL; // return null across JNI if sid is not available + + if (tsk_fs_file_get_owner_sid(fs_file, &sid_str) == 0) { + if (createJString(sid_str, sidj) != TSK_OK) { + free(sid_str); + return TSK_ERR; + } + free(sid_str); + } + // Add the file to the database jlong ret_val = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID, parObjId, fsObjId, @@ -643,7 +654,7 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file, (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime, meta_mode, gid, uid, pathj, extj, - (uint64_t)meta_seq, par_meta_addr, par_seqj); + (uint64_t)meta_seq, par_meta_addr, par_seqj, sidj); if (ret_val < 0) { free(name); @@ -690,7 +701,7 @@ TskAutoDbJava::addFile(TSK_FS_FILE* fs_file, (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime, meta_mode, gid, uid, // md5TextPtr, known, pathj, slackExtj, - (uint64_t)meta_seq, par_meta_addr, par_seqj); + (uint64_t)meta_seq, par_meta_addr, par_seqj, sidj); if (ret_val < 0) { free(name); diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java index 07d19a61921829eecbe226d1058688c98830b4bc..e277dc3156ff1239e437b6332e92b7cc713678ee 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java @@ -21,6 +21,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; @@ -29,6 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.SortedSet; @@ -96,6 +98,10 @@ public abstract class AbstractFile extends AbstractContent { private final List<Attribute> fileAttributesCache = new ArrayList<Attribute>(); private boolean loadedAttributesCacheFromDb = false; + private final String ownerUid; // string owner uid, for example a Windows SID. + // different from the numeric uid which is more commonly found + // on Unix based file systems. + private final Long osAccountObjId; // obj id of the owner's OS account, may be null /** * Initializes common fields used by AbstactFile implementations (objects in * tsk_files table) @@ -130,8 +136,11 @@ public abstract class AbstractFile extends AbstractContent { * unknown (default) * @param parentPath * @param mimeType The MIME type of the file, can be null. - * @param extension The extension part of the file name (not + * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid Owner uid/SID, can be null if not available. + * @param osAccountObjId Obj id of the owner OS account, may be null. + * */ AbstractFile(SleuthkitCase db, long objId, @@ -149,7 +158,9 @@ public abstract class AbstractFile extends AbstractContent { String md5Hash, String sha256Hash, FileKnown knownState, String parentPath, String mimeType, - String extension, + String extension, + String ownerUid, + Long osAccountObjId, List<Attribute> fileAttributes) { super(db, objId, name); this.dataSourceObjectId = dataSourceObjectId; @@ -182,6 +193,8 @@ public abstract class AbstractFile extends AbstractContent { this.mimeType = mimeType; this.extension = extension == null ? "" : extension; this.encodingType = TskData.EncodingType.NONE; + this.ownerUid = ownerUid; + this.osAccountObjId = osAccountObjId; if (Objects.nonNull(fileAttributes) && !fileAttributes.isEmpty()) { this.fileAttributesCache.addAll(fileAttributes); loadedAttributesCacheFromDb = true; @@ -1315,6 +1328,44 @@ public void save() throws TskCoreException { } } + /** + * Get the owner uid. + * + * Note this is a string uid, typically a Windows SID. + * This is different from the numeric uid commonly found + * on Unix based file systems. + * + * @return Optional with owner uid. + */ + public Optional<String> getOwnerUid() { + return Optional.ofNullable(ownerUid); + } + + /** + * Get the obj id of the owner account. + * + * @return Optional with Object id of the os account, or Optional.empty. + */ + Optional<Long> getOsAccountObjId() { + return Optional.of(osAccountObjId); + } + + /** + * Gets the owner account for the file. + * + * @return Optional with OsAccount, Optional.empty if there is no account. + * + * @throws TskCoreException If there is an error getting the account. + */ + public Optional<OsAccount> getOsAccount() throws TskCoreException { + + if (osAccountObjId == null) { + return Optional.empty(); + } + + return Optional.of(getSleuthkitCase().getOsAccountManager().getOsAccount(this.osAccountObjId)); + } + @Override public BlackboardArtifact newArtifact(int artifactTypeID) throws TskCoreException { // don't let them make more than 1 GEN_INFO @@ -1362,7 +1413,7 @@ protected AbstractFile(SleuthkitCase db, long objId, TskData.TSK_FS_ATTR_TYPE_EN TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath) { - this(db, objId, db.getDataSourceObjectId(objId), attrType, (int) attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, Collections.emptyList()); + this(db, objId, db.getDataSourceObjectId(objId), attrType, (int) attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList()); } /** @@ -1407,7 +1458,7 @@ protected AbstractFile(SleuthkitCase db, long objId, TskData.TSK_FS_ATTR_TYPE_EN String name, TskData.TSK_DB_FILES_TYPE_ENUM fileType, long metaAddr, int metaSeq, TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath, String mimeType) { - this(db, objId, dataSourceObjectId, attrType, (int) attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, Collections.emptyList()); + this(db, objId, dataSourceObjectId, attrType, (int) attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList()); } /** diff --git a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java index 784e5f95b42dd6f033bee9b1a6b4ecdc4b193a35..d546943744cc263dfce9ee3369f3a20c187a51df 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java +++ b/bindings/java/src/org/sleuthkit/datamodel/Blackboard.java @@ -200,7 +200,7 @@ public AnalysisResultAdded newAnalysisResult(BlackboardArtifact.Type artifactTyp + " results.conclusion AS conclusion, results.significance AS significance, results.confidence AS confidence, " + " results.configuration AS configuration, results.justification AS justification " + " FROM blackboard_artifacts AS arts, tsk_analysis_results AS results, blackboard_artifact_types AS types " //NON-NLS - + " WHERE arts.artifact_obj_id = results.obj_id " //NON-NLS + + " WHERE arts.artifact_obj_id = results.artifact_obj_id " //NON-NLS + " AND arts.artifact_type_id = types.artifact_type_id" + " AND arts.review_status_id !=" + BlackboardArtifact.ReviewStatus.REJECTED.getID(); diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties index 1cfc1a6234abd0b8426689d077987689de21c094..e510f6561c93bf2d2bd91341adc7bb93713f4ec4 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties +++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties @@ -368,4 +368,14 @@ IntersectionFilter.displayName.text=Intersection tagsFilter.displayName.text=Must be tagged TextFilter.displayName.text=Must include text: TypeFilter.displayName.text=Limit event types to -FileTypesFilter.displayName.text=Limit file types to \ No newline at end of file +FileTypesFilter.displayName.text=Limit file types to +OsAccountStatus.Unknown.text=Unknown +OsAccountStatus.Active.text=Active +OsAccountStatus.Disabled.text=Disabled +OsAccountStatus.Deleted.text=Deleted +OsAccountType.Unknown.text=Unknown +OsAccountType.Service.text=Service +OsAccountType.Interactive.text=Interactive +OsAccountInstanceType.PerformedActionOn.text=Account owner performed action on the host. +OsAccountInstanceType.ReferencedOn.text=Account was referenced on on the host. + diff --git a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED index 1cfc1a6234abd0b8426689d077987689de21c094..e510f6561c93bf2d2bd91341adc7bb93713f4ec4 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED +++ b/bindings/java/src/org/sleuthkit/datamodel/Bundle.properties-MERGED @@ -368,4 +368,14 @@ IntersectionFilter.displayName.text=Intersection tagsFilter.displayName.text=Must be tagged TextFilter.displayName.text=Must include text: TypeFilter.displayName.text=Limit event types to -FileTypesFilter.displayName.text=Limit file types to \ No newline at end of file +FileTypesFilter.displayName.text=Limit file types to +OsAccountStatus.Unknown.text=Unknown +OsAccountStatus.Active.text=Active +OsAccountStatus.Disabled.text=Disabled +OsAccountStatus.Deleted.text=Deleted +OsAccountType.Unknown.text=Unknown +OsAccountType.Service.text=Service +OsAccountType.Interactive.text=Interactive +OsAccountInstanceType.PerformedActionOn.text=Account owner performed action on the host. +OsAccountInstanceType.ReferencedOn.text=Account was referenced on on the host. + diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java index 8f37d21d0ca43b21a4e6fe1181881c490f91e226..d369e2f8f408630ae9ef747878d5ea74f607a845 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java +++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java @@ -212,9 +212,12 @@ private void createFileTables(Statement stmt) throws SQLException { + "mtime " + dbQueryHelper.getBigIntType() + ", mode INTEGER, uid INTEGER, gid INTEGER, md5 TEXT, sha256 TEXT, " + "known INTEGER, " + "parent_path TEXT, mime_type TEXT, extension TEXT, " + + "owner_uid TEXT DEFAULT NULL, " + + "os_account_obj_id " + dbQueryHelper.getBigIntType() + " DEFAULT NULL, " + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + "FOREIGN KEY(fs_obj_id) REFERENCES tsk_fs_info(obj_id) ON DELETE CASCADE, " - + "FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE)"); + + "FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE) " ); stmt.execute("CREATE TABLE file_encoding_types (encoding_type INTEGER PRIMARY KEY, name TEXT NOT NULL)"); @@ -274,20 +277,20 @@ private void createArtifactTables(Statement stmt) throws SQLException { + "value_text TEXT, value_int32 INTEGER, value_int64 " + dbQueryHelper.getBigIntType() + ", value_double NUMERIC(20, 10), " + "FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE, " + "FOREIGN KEY(artifact_type_id) REFERENCES blackboard_artifact_types(artifact_type_id), " - + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); + + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); } private void createAnalysisResultsTables(Statement stmt) throws SQLException { - stmt.execute("CREATE TABLE tsk_analysis_results (obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + stmt.execute("CREATE TABLE tsk_analysis_results (artifact_obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, " + "conclusion TEXT, " + "significance INTEGER NOT NULL, " + "confidence INTEGER NOT NULL, " + "configuration TEXT, justification TEXT, " + "ignore_score INTEGER DEFAULT 0, " // boolean - + "FOREIGN KEY(obj_id) REFERENCES blackboard_artifacts(artifact_obj_id) ON DELETE CASCADE" + + "FOREIGN KEY(artifact_obj_id) REFERENCES blackboard_artifacts(artifact_obj_id) ON DELETE CASCADE" + ")"); - stmt.execute("CREATE TABLE tsk_aggregate_score( obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + stmt.execute("CREATE TABLE tsk_aggregate_score( obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, " + "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + "significance INTEGER NOT NULL, " + "confidence INTEGER NOT NULL, " @@ -421,6 +424,59 @@ private void createAccountTables(Statement stmt) throws SQLException { + "FOREIGN KEY(account2_id) REFERENCES accounts(account_id), " + "FOREIGN KEY(relationship_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_os_account_realms (id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "name TEXT NOT NULL, " // realm name - host name or domain name + + "realm_addr TEXT DEFAULT NULL, " // a sid/uid or some some other identifier, may be null + + "host_id " + dbQueryHelper.getBigIntType() + " DEFAULT NULL, " // if the realm just comprises of a single host + + "name_type INTEGER, " // indicates if the realm name was was expressed or inferred + + "UNIQUE(name, host_id), " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id) )"); + + stmt.execute("CREATE TABLE tsk_os_accounts (os_account_obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, " + + "login_name TEXT DEFAULT NULL, " // login name, if available, may be null + + "full_name TEXT DEFAULT NULL, " // full name, if available, may be null + + "realm_id " + dbQueryHelper.getBigIntType() + ", " // row id for the realm, may be null if only SID is known + + "unique_id TEXT DEFAULT NULL, " // SID/UID, if available + + "signature TEXT NOT NULL, " // This exists only to prevent duplicates. It is either �realm_id/unique_id� if unique_id is defined or realm_id/login_name� if unique_id is not defined. + + "status INTEGER, " // enabled/disabled/deleted + + "admin INTEGER DEFAULT 0," // is admin account + + "type INTEGER, " // service/interactive + + "created_date " + dbQueryHelper.getBigIntType() + " DEFAULT NULL, " + + "UNIQUE(signature), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(realm_id) REFERENCES tsk_os_account_realms(id) )"); + + stmt.execute("CREATE TABLE tsk_os_account_attributes (id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "os_account_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "host_id " + dbQueryHelper.getBigIntType() + ", " + + "source_obj_id " + dbQueryHelper.getBigIntType() + ", " + + "attribute_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "value_type INTEGER NOT NULL, " + + "value_byte " + dbQueryHelper.getBlobType() + ", " + + "value_text TEXT, " + + "value_int32 INTEGER, value_int64 " + dbQueryHelper.getBigIntType() + ", " + + "value_double NUMERIC(20, 10), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id), " + + "FOREIGN KEY(source_obj_id) REFERENCES tsk_objects(obj_id), " + + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); + + stmt.execute("CREATE TABLE tsk_os_account_instances (id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "os_account_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "host_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "instance_type INTEGER NOT NULL, " // PerformedActionOn/ReferencedOn + + "UNIQUE(os_account_obj_id, data_source_obj_id, host_id), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id), " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id))"); + + stmt.execute("CREATE TABLE tsk_data_artifacts ( " + + "artifact_obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, " + + "os_account_obj_id " + dbQueryHelper.getBigIntType() + ", " + + "FOREIGN KEY(artifact_obj_id) REFERENCES blackboard_artifacts(artifact_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE) "); } private void createEventTables(Statement stmt) throws SQLException { diff --git a/bindings/java/src/org/sleuthkit/datamodel/DataArtifact.java b/bindings/java/src/org/sleuthkit/datamodel/DataArtifact.java new file mode 100644 index 0000000000000000000000000000000000000000..f9a0e898795e903ca815b4be2b44053e0e3aa048 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/DataArtifact.java @@ -0,0 +1,71 @@ +/* + * Sleuth Kit Data Model + * + * 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.datamodel; + +import java.util.Optional; + + +/** + * DataArtifact is a category of artifact types that are simply data directly + * extracted from a data source. + * + */ +public final class DataArtifact extends BlackboardArtifact { + + // data artifacts may have a OS Account associated with them. + private final OsAccount osAccount; + + + /** + * Constructs a DataArtifact. + * + * @param sleuthkitCase The SleuthKit case (case database) that contains + * the artifact data. + * @param artifactID The unique id for this artifact. + * @param sourceObjId The unique id of the content with which this + * artifact is associated. + * @param artifactObjId The object id of artifact, in tsk_objects. + * @param dataSourceObjId Object ID of the datasource where the artifact + * was found. + * @param artifactTypeID The type id of this artifact. + * @param artifactTypeName The type name of this artifact. + * @param displayName The display name of this artifact. + * @param reviewStatus The review status of this artifact. + * @param osAccount OsAccount associated with this artifact, may be + * null. + */ + DataArtifact(SleuthkitCase sleuthkitCase, long artifactID, long sourceObjId, long artifactObjId, long dataSourceObjId, int artifactTypeID, String artifactTypeName, String displayName, ReviewStatus reviewStatus, OsAccount osAccount) { + super(sleuthkitCase, artifactID, sourceObjId, artifactObjId, dataSourceObjId, artifactTypeID, artifactTypeName, displayName, reviewStatus); + this.osAccount = osAccount; + } + + + /** + * Gets the OS Account for this artifact. + * + * @return Optional with OsAccount, Optional.empty if there is no account. + * + * @throws TskCoreException If there is an error getting the account. + */ + public Optional<OsAccount> getOsAccount() throws TskCoreException { + return Optional.ofNullable(osAccount); + } + + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java b/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java index 0fa423bc2586592c43e6d7469fb560f0c2da138c..4879e39c21add3e8d5eb9e61af9cee7771c02951 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/DerivedFile.java @@ -83,6 +83,9 @@ public class DerivedFile extends AbstractFile { * @param encodingType The encoding type of the file. * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ DerivedFile(SleuthkitCase db, long objId, @@ -98,12 +101,14 @@ public class DerivedFile extends AbstractFile { long parentId, String mimeType, TskData.EncodingType encodingType, - String extension) { + String extension, + String ownerUid, + Long osAccountObjId) { // TODO (AUT-1904): The parent id should be passed to AbstractContent // through the class hierarchy contructors. super(db, objId, dataSourceObjectId, TskData.TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, name, TSK_DB_FILES_TYPE_ENUM.LOCAL, 0L, 0, dirType, metaType, dirFlag, - metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, Collections.emptyList()); + metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, Collections.emptyList()); setLocalFilePath(localPath); setEncodingType(encodingType); } @@ -307,7 +312,7 @@ protected DerivedFile(SleuthkitCase db, this(db, objId, db.getDataSourceObjectId(objId), name, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, md5Hash, null, knownState, - parentPath, localPath, parentId, null, TskData.EncodingType.NONE, null); + parentPath, localPath, parentId, null, TskData.EncodingType.NONE, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/Directory.java b/bindings/java/src/org/sleuthkit/datamodel/Directory.java index 784e025aa8f619a70aae667122c27e06199a25a2..f0d376b381093971ca36fde7624365117502bda0 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/Directory.java +++ b/bindings/java/src/org/sleuthkit/datamodel/Directory.java @@ -73,6 +73,9 @@ public class Directory extends FsContent { * @param knownState The known state of the file from a hash * database lookup, null if not yet looked up. * @param parentPath The path of the parent of the file. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ Directory(SleuthkitCase db, long objId, @@ -86,8 +89,9 @@ public class Directory extends FsContent { long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, - String md5Hash, String sha256Hash, FileKnown knownState, String parentPath) { - super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, null, null, Collections.emptyList()); + String md5Hash, String sha256Hash, FileKnown knownState, String parentPath, + String ownerUid, Long osAccountObjId ) { + super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, null, null, ownerUid, osAccountObjId, Collections.emptyList()); } /** @@ -248,6 +252,6 @@ protected Directory(SleuthkitCase db, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath) { - this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath); + this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/File.java b/bindings/java/src/org/sleuthkit/datamodel/File.java index 9b87670b9c44243ebb5a1c6cdd16556abaeb3123..659ed70acdee84c323b760f6a1a7f7915be6196b 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/File.java +++ b/bindings/java/src/org/sleuthkit/datamodel/File.java @@ -78,6 +78,9 @@ public class File extends FsContent { * yet been determined. * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ File(SleuthkitCase db, long objId, @@ -93,8 +96,10 @@ public class File extends FsContent { short modes, int uid, int gid, String md5Hash, String sha256Hash, FileKnown knownState, String parentPath, String mimeType, String extension, + String ownerUid, + Long osAccountObjId, List<Attribute> fileAttributes) { - super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, fileAttributes); + super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, fileAttributes); } /** @@ -248,6 +253,6 @@ protected File(SleuthkitCase db, String name, long metaAddr, int metaSeq, TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath, String mimeType) { - this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, mimeType, null, Collections.emptyList()); + this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, mimeType, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList()); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/FsContent.java b/bindings/java/src/org/sleuthkit/datamodel/FsContent.java index 56e872ecaa9c2be066f583412e9ae2c48e99a856..1e077d83c48f85232730f3e8dca5167d7fa447b2 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/FsContent.java +++ b/bindings/java/src/org/sleuthkit/datamodel/FsContent.java @@ -106,6 +106,9 @@ public abstract class FsContent extends AbstractFile { * yet been determined. * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ @SuppressWarnings("deprecation") FsContent(SleuthkitCase db, @@ -124,9 +127,11 @@ public abstract class FsContent extends AbstractFile { String md5Hash, String sha256Hash, FileKnown knownState, String parentPath, String mimeType, - String extension, + String extension, + String ownerUid, + Long osAccountObjId, List<Attribute> fileAttributes) { - super(db, objId, dataSourceObjectId, attrType, attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, fileAttributes); + super(db, objId, dataSourceObjectId, attrType, attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, fileAttributes); this.fsObjId = fsObjId; } @@ -387,7 +392,7 @@ public String toString(boolean preserveState) { String name, long metaAddr, int metaSeq, TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath) { - this(db, objId, db.getDataSourceObjectId(objId), fsObjId, attrType, (int) attrId, name, TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, Collections.emptyList()); + this(db, objId, db.getDataSourceObjectId(objId), fsObjId, attrType, (int) attrId, name, TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, null, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList() ); } /** @@ -446,6 +451,6 @@ public String toString(boolean preserveState) { String name, long metaAddr, int metaSeq, TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, FileKnown knownState, String parentPath, String mimeType) { - this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, mimeType, null, Collections.emptyList()); + this(db, objId, dataSourceObjectId, fsObjId, attrType, (int) attrId, name, TSK_DB_FILES_TYPE_ENUM.FS, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, null, knownState, parentPath, mimeType, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList()); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java index 5d9b4c8bb2fd6f290b7925392bfd8aba3bc209ab..fb0114fe9c366f656029b6c64beec18af60b09e6 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java +++ b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java @@ -72,6 +72,77 @@ public Host getOrCreateHost(String name) throws TskCoreException { } } + /** + * Get or create host with specified name. + * + * @param name Host name. + * @param transaction Database transaction to use. + * + * @return Host with the specified name. + * + * @throws TskCoreException + * + * @deprecated This method has been deprecated. + * Callers should use getHost() followed by createHost if needed. + */ + // RAMAN TBD: this method need to be deleted when the client code in Sleuthkit + // is refactroed to use the get/create methods instead of getOrCreate + @Deprecated + Host getOrCreateHost(String name, CaseDbTransaction transaction) throws TskCoreException { + + // must have a name + if (Strings.isNullOrEmpty(name) ) { + throw new IllegalArgumentException("Host name is required."); + } + + CaseDbConnection connection = transaction.getConnection(); + + // First search for host by name + Optional<Host> host = getHost(name, connection); + if (host.isPresent()) { + return host.get(); + } + + // couldn't find it, create a new host + return createHost(name, connection); + } + + /** + * Create a host with given name. + * + * @param name Host name. + * @param connection Database connection to use. + * @return Newly created host. + * @throws TskCoreException + */ + // RAMAN TBD: this method needs to be deleted when getOrCreateHost is deleted. + private Host createHost(String name, CaseDbConnection connection) throws TskCoreException { + db.acquireSingleUserCaseWriteLock(); + try { + String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS + PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS); + + preparedStatement.clearParameters(); + preparedStatement.setString(1, name); + + connection.executeUpdate(preparedStatement); + + // Read back the row id + try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) { + if (resultSet.next()) { + return new Host(resultSet.getLong(1), name); //last_insert_rowid() + } else { + throw new SQLException("Error executing " + hostInsertSQL); + } + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error adding host with name = %s", name), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + /** * Get all data sources associated with a given host. * @@ -100,32 +171,67 @@ public Set<DataSource> getDataSourcesForHost(Host host) throws TskCoreException } /** - * Get or create host with specified name. + * Create a host with specified name. If a host already exists with the given + * name, it returns the existing host. * - * @param name Host name. - * @param transaction Database transaction to use. + * @param name Host name. * * @return Host with the specified name. * * @throws TskCoreException */ - Host getOrCreateHost(String name, CaseDbTransaction transaction) throws TskCoreException { + public Host createHost(String name) throws TskCoreException { // must have a name if (Strings.isNullOrEmpty(name) ) { throw new IllegalArgumentException("Host name is required."); } - CaseDbConnection connection = transaction.getConnection(); + CaseDbConnection connection = this.db.getConnection(); + db.acquireSingleUserCaseWriteLock(); + try { + String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS + PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS); - // First search for host by name - Optional<Host> host = getHost(name, connection); - if (host.isPresent()) { - return host.get(); - } + preparedStatement.clearParameters(); + preparedStatement.setString(1, name); - // couldn't find it, create a new host - return createHost(name, connection); + connection.executeUpdate(preparedStatement); + + // Read back the row id + try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) { + if (resultSet.next()) { + return new Host(resultSet.getLong(1), name); //last_insert_rowid() + } else { + throw new SQLException("Error executing " + hostInsertSQL); + } + } + } catch (SQLException ex) { + // may have failed because it already exists. So try getting the host. + Optional<Host> host = this.getHost(name, connection); + if (host.isPresent()) { + return host.get(); + } else { + throw new TskCoreException(String.format("Error adding host with name = %s", name), ex); + } + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + /** + * Get host with given name. + * + * @param name Host name to look for. + * @param transaction Database transaction to use. + * + * @return Optional with host. Optional.empty if no matching host is found. + * + * @throws TskCoreException + */ + public Optional<Host> getHost(String name, CaseDbTransaction transaction ) throws TskCoreException { + + return getHost(name, transaction.getConnection()); } /** @@ -138,12 +244,12 @@ Host getOrCreateHost(String name, CaseDbTransaction transaction) throws TskCoreE * * @throws TskCoreException */ - Optional<Host> getHost(String name, CaseDbConnection connection ) throws TskCoreException { + private Optional<Host> getHost(String name, CaseDbConnection connection ) throws TskCoreException { String queryString = "SELECT * FROM tsk_hosts" + " WHERE LOWER(name) = LOWER('" + name + "')"; - - try (Statement s = connection.createStatement(); + try ( + Statement s = connection.createStatement(); ResultSet rs = connection.executeQuery(s, queryString)) { if (!rs.next()) { @@ -156,42 +262,6 @@ Optional<Host> getHost(String name, CaseDbConnection connection ) throws TskCore } } - - /** - * Create a host with given name. - * - * @param name Host name. - * @param connection Database connection to use. - * @return Newly created host. - * @throws TskCoreException - */ - private Host createHost(String name, CaseDbConnection connection) throws TskCoreException { - db.acquireSingleUserCaseWriteLock(); - try { - String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS - PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS); - - preparedStatement.clearParameters(); - preparedStatement.setString(1, name); - - connection.executeUpdate(preparedStatement); - - // Read back the row id - try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) { - if (resultSet.next()) { - return new Host(resultSet.getLong(1), name); //last_insert_rowid() - } else { - throw new SQLException("Error executing " + hostInsertSQL); - } - } - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, null, ex); - throw new TskCoreException(String.format("Error adding host with name = %s", name), ex); - } finally { - db.releaseSingleUserCaseWriteLock(); - } - } - /** * Get all hosts. * diff --git a/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java b/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java index d07412837d65d530e55a6b0e46018fd7eabd22a3..dea60c1329b396f7e1e817c3e84f387e6c1802ed 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/LayoutFile.java @@ -82,6 +82,9 @@ public class LayoutFile extends AbstractFile { * @param parentPath The path of the parent of the file. * @param mimeType The MIME type of the file, null if it has not * yet been determined. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ LayoutFile(SleuthkitCase db, long objId, @@ -93,8 +96,11 @@ public class LayoutFile extends AbstractFile { long size, long ctime, long crtime, long atime, long mtime, String md5Hash, String sha256Hash, FileKnown knownState, - String parentPath, String mimeType) { - super(db, objId, dataSourceObjectId, TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, name, fileType, 0L, 0, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, SleuthkitCase.extractExtension(name), Collections.emptyList()); + String parentPath, String mimeType, + String ownerUid, + Long osAccountObjId) { + + super(db, objId, dataSourceObjectId, TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, name, fileType, 0L, 0, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, SleuthkitCase.extractExtension(name), ownerUid, osAccountObjId, Collections.emptyList()); } /** @@ -281,6 +287,6 @@ protected LayoutFile(SleuthkitCase db, long objId, String name, TSK_FS_NAME_TYPE_ENUM dirType, TSK_FS_META_TYPE_ENUM metaType, TSK_FS_NAME_FLAG_ENUM dirFlag, short metaFlags, long size, String md5Hash, FileKnown knownState, String parentPath) { - this(db, objId, db.getDataSourceObjectId(objId), name, fileType, dirType, metaType, dirFlag, metaFlags, size, 0L, 0L, 0L, 0L, md5Hash, null, knownState, parentPath, null); + this(db, objId, db.getDataSourceObjectId(objId), name, fileType, dirType, metaType, dirFlag, metaFlags, size, 0L, 0L, 0L, 0L, md5Hash, null, knownState, parentPath, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java b/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java index 152c7c3831545d5a3725dbbae6694958724229f1..1b749b080ce91cc5624194e6920abdc4f5f2362e 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/LocalFile.java @@ -74,6 +74,9 @@ public class LocalFile extends AbstractFile { * @param encodingType The encoding type of the file. * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid String UID of the user as found in in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. */ LocalFile(SleuthkitCase db, long objId, @@ -88,10 +91,12 @@ public class LocalFile extends AbstractFile { long dataSourceObjectId, String localPath, TskData.EncodingType encodingType, - String extension) { + String extension, + String ownerUid, + Long osAccountObjId) { super(db, objId, dataSourceObjectId, TSK_FS_ATTR_TYPE_ENUM.TSK_FS_ATTR_TYPE_DEFAULT, 0, name, fileType, 0L, 0, dirType, metaType, dirFlag, - metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, Collections.emptyList()); + metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, Collections.emptyList()); // TODO (AUT-1904): The parent id should be passed to AbstractContent // through the class hierarchy contructors, using // AbstractContent.UNKNOWN_ID as needed. @@ -222,7 +227,7 @@ protected LocalFile(SleuthkitCase db, AbstractContent.UNKNOWN_ID, parentPath, db.getDataSourceObjectId(objId), localPath, - TskData.EncodingType.NONE, null); + TskData.EncodingType.NONE, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); } /** diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java new file mode 100644 index 0000000000000000000000000000000000000000..243c32c9083a0f63fb466d8b6cf497113504f138 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccount.java @@ -0,0 +1,446 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2020-2021 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.datamodel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * Abstracts an OS user account. + * + * An OS user account may own files and (some) artifacts. + * + */ +public final class OsAccount extends AbstractContent { + + private static final ResourceBundle bundle = ResourceBundle.getBundle("org.sleuthkit.datamodel.Bundle"); + + final static Long NO_ACCOUNT = null; + final static String NO_OWNER_ID = null; + + private final SleuthkitCase sleuthkitCase; + + private final long osAccountobjId; + private final OsAccountRealm realm; // realm where the username is unique - a domain or a host name. + private final String loginName; // user login name - may be null + private final String uniqueId; // a unique user sid/uid, may be null + + private String signature; // This exists only to prevent duplicates. + // It is either �realm_id/unique_id� if unique_id is defined, + // or realm_id/login_name� if login_name is defined. + + private String fullName; // full name + private boolean isAdmin = false; // is admin account. + private OsAccountType osAccountType = OsAccountType.UNKNOWN; + private OsAccountStatus osAccountStatus; + private Long creationTime = null; + + private final List<OsAccountAttribute> osAccountAttributes = new ArrayList<>(); + + /** + * Encapsulates status of an account - whether is it active or disabled or + * deleted. + */ + public enum OsAccountStatus { + UNKNOWN(0, bundle.getString("OsAccountStatus.Unknown.text")), + ACTIVE(1, bundle.getString("OsAccountStatus.Active.text")), + DISABLED(2, bundle.getString("OsAccountStatus.Disabled.text")), + DELETED(3, bundle.getString("OsAccountStatus.Deleted.text")); + + private final int id; + private final String name; + + OsAccountStatus(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Get account status id. + * + * @return Account status id. + */ + public int getId() { + return id; + } + + /** + * Get the account status enum name. + * + * @return + */ + public String getName() { + return name; + } + + /** + * Gets account status enum from id. + * + * @param statusId Id to look for. + * + * @return Account status enum. + */ + public static OsAccountStatus fromID(int statusId) { + for (OsAccountStatus statusType : OsAccountStatus.values()) { + if (statusType.ordinal() == statusId) { + return statusType; + } + } + return null; + } + } + + /** + * Encapsulates an account type - whether it's an interactive login account + * or a service account. + */ + public enum OsAccountType { + UNKNOWN(0, bundle.getString("OsAccountType.Unknown.text")), + SERVICE(1, bundle.getString("OsAccountType.Service.text")), + INTERACTIVE(2, bundle.getString("OsAccountType.Interactive.text")); + + private final int id; + private final String name; + + OsAccountType(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Get account type id. + * + * @return Account type id. + */ + public int getId() { + return id; + } + + /** + * Get account type name. + * + * @return Account type name. + */ + public String getName() { + return name; + } + + /** + * Gets account type enum from id. + * + * @param typeId Id to look for. + * + * @return Account type enum. + */ + public static OsAccountType fromID(int typeId) { + for (OsAccountType accountType : OsAccountType.values()) { + if (accountType.ordinal() == typeId) { + return accountType; + } + } + return null; + } + } + + /** + * Describes the relationship between an os account instance and the host + * where the instance was found. + * + * Whether an os account actually performed any action on the host or if + * just a reference to it was found on the host. + */ + public enum OsAccountInstanceType { + PERFORMED_ACTION_ON(0, bundle.getString("OsAccountInstanceType.PerformedActionOn.text")), // the user performed actions on a host + REFERENCED_ON(1, bundle.getString("OsAccountInstanceType.ReferencedOn.text") ); // user was simply referenced on a host + + private final int id; + private final String name; + + OsAccountInstanceType(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Get account instance type id. + * + * @return Account instance type id. + */ + public int getId() { + return id; + } + + /** + * Get account instance type name. + * + * @return Account instance type name. + */ + public String getName() { + return name; + } + + /** + * Gets account instance type enum from id. + * + * @param typeId Id to look for. + * + * @return Account instance type enum. + */ + public static OsAccountInstanceType fromID(int typeId) { + for (OsAccountInstanceType statusType : OsAccountInstanceType.values()) { + if (statusType.ordinal() == typeId) { + return statusType; + } + } + return null; + } + } + + /** + * Constructs an OsAccount with a realm/username and unique id, and + * signature. + * + * @param sleuthkitCase The SleuthKit case (case database) that contains + * the artifact data. + * @param osAccountobjId Obj id of the account in tsk_objects table. + * @param realm Realm - defines the scope of this account. + * @param loginName Login name for the account. May be null. + * @param uniqueId An id unique within the realm - a SID or uid. May + * be null, only if login name is not null. + * @param signature A unique signature constructed from realm id and + * loginName or uniqueId. + * @param accountStatus Account status. + */ + OsAccount(SleuthkitCase sleuthkitCase, long osAccountobjId, OsAccountRealm realm, String loginName, String uniqueId, String signature, OsAccountStatus accountStatus) { + + super(sleuthkitCase, osAccountobjId, signature); + + this.sleuthkitCase = sleuthkitCase; + this.osAccountobjId = osAccountobjId; + this.realm = realm; + this.loginName = loginName; + this.uniqueId = uniqueId; + this.signature = signature; + this.osAccountStatus = accountStatus; + } + + /** + * Sets the account user's full name. + * + * @param fullName Full name. + */ + public void setFullName(String fullName) { + this.fullName = fullName; + } + + /** + * Sets the admin flag for the account. + * + * @param isAdmin Flag to indicate if the account is an admin account. + */ + public void setIsAdmin(boolean isAdmin) { + this.isAdmin = isAdmin; + } + + /** + * Sets account type for the account. + * + * @param osAccountType Account type. + */ + public void setOsAccountType(OsAccountType osAccountType) { + this.osAccountType = osAccountType; + } + + /** + * Sets account status for the account. + * + * @param osAccountStatus Account status. + */ + public void setOsAccountStatus(OsAccountStatus osAccountStatus) { + this.osAccountStatus = osAccountStatus; + } + + /** + * Set account creation time. + * + * @param creationTime Creation time. + */ + public void setCreationTime(long creationTime) { + this.creationTime = creationTime; + } + + /** + * Set the signature. + * + * The signature may change if the login name or unique id is updated after + * creation. + * + * @param signature Signature. + */ + void setSignature(String signature) { + this.signature = signature; + } + + /** + * Adds account attributes to the account. + * + * @param osAccountAttributes Collection of attributes to add. + */ + void addAttributes(Set<OsAccountAttribute> osAccountAttributes) throws TskCoreException { + sleuthkitCase.getOsAccountManager().addOsAccountAttributes(this, osAccountAttributes); + osAccountAttributes.addAll(osAccountAttributes); + } + + /** + * Get the account id. + * + * @return Account id. + */ + public long getId() { + return osAccountobjId; + } + + /** + * Get the unique identifier for the account. + * The id is unique within the account realm. + * + * @return Optional unique identifier. + */ + public Optional<String> getUniqueIdWithinRealm() { + return Optional.ofNullable(uniqueId); + } + + /** + * Get the account realm. + * + * @return OsAccountRealm. + */ + public OsAccountRealm getRealm() { + return realm; + } + + /** + * Get account login name. + * + * @return Optional login name. + */ + public Optional<String> getLoginName() { + return Optional.ofNullable(loginName); + } + + /** + * Get account user full name. + * + * @return Optional with full name. + */ + public Optional<String> getFullName() { + return Optional.ofNullable(fullName); + } + + /** + * Check if account is an admin account. + * + * @return True if account is an admin account, false otherwise. + */ + public boolean isAdmin() { + return isAdmin; + } + + /** + * Get account creation time. + * + * @return Account creation time, returns 0 if creation time is not known. + */ + public Optional<Long> getCreationTime() { + return Optional.of(creationTime); + } + + /** + * Get account type. + * + * @return Account type. + */ + public OsAccountType getOsAccountType() { + return osAccountType; + } + + /** + * Get account status. + * + * @return Account status. + */ + public OsAccountStatus getOsAccountStatus() { + return osAccountStatus; + } + + /** + * Get additional account attributes. + * + * @return List of additional account attributes. May return an empty list. + */ + public List<OsAccountAttribute> getOsAccountAttributes() { + return Collections.unmodifiableList(osAccountAttributes); + } + + /** + * Gets the SleuthKit case database for this + * account. + * + * @return The SleuthKit case object. + */ + @Override + public SleuthkitCase getSleuthkitCase() { + return sleuthkitCase; + } + + @Override + public int read(byte[] buf, long offset, long len) throws TskCoreException { + // No data to read. + return 0; + } + + @Override + public void close() { + // nothing to close + } + + @Override + public long getSize() { + // No data. + return 0; + } + + @Override + public <T> T accept(ContentVisitor<T> v) { + // TODO: need to implement this when OS Accounts are part of the Autopsy tree. + + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public <T> T accept(SleuthkitItemVisitor<T> v) { + // TODO: need to implement this when OS Accounts are part of the Autopsy tree. + + throw new UnsupportedOperationException("Not supported yet."); + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccountAttribute.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccountAttribute.java new file mode 100644 index 0000000000000000000000000000000000000000..d1be2cd722af4152bc6c5b5fb90e3cede911b901 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccountAttribute.java @@ -0,0 +1,185 @@ +/* + * Sleuth Kit Data Model + * + * 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.datamodel; + +import java.util.Optional; +import org.sleuthkit.datamodel.BlackboardAttribute.Type; + +/** + * Abstracts host specific attributes of an OS account. As an example, last + * login on a specific host. + * + */ +public final class OsAccountAttribute extends AbstractAttribute { + + private final long osAccountObjId; // OS account to which this attribute belongs. + private final Long hostId; // Host to which this attribute applies, may be null + private final long sourceObjId; // Object id of the source where the attribute was discovered. + + /** + * Creates an os account attribute with int value. + * + * @param attributeType Attribute type. + * @param valueInt Int value. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + * @param sourceObjId Object id of the source where the attribute was + * found. + */ + public OsAccountAttribute(BlackboardAttribute.Type attributeType, int valueInt, long osAccountObjId, Long hostId, long sourceObjId) { + super(attributeType, valueInt); + + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Creates an os account attribute with long value. + * + * @param attributeType Attribute type. + * @param valueLong Long value. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + * @param sourceObjId Object id of the source where the attribute was + * found. + */ + public OsAccountAttribute(BlackboardAttribute.Type attributeType, long valueLong, long osAccountObjId, Long hostId, long sourceObjId) { + super(attributeType, valueLong); + + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Creates an os account attribute with double value. + * + * @param attributeType Attribute type. + * @param valueDouble Double value. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + * @param sourceObjId Object id of the source where the attribute was + * found. + */ + public OsAccountAttribute(BlackboardAttribute.Type attributeType, double valueDouble, long osAccountObjId, Long hostId, long sourceObjId) { + super(attributeType, valueDouble); + + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Creates an os account attribute with string value. + * + * @param attributeType Attribute type. + * @param valueString String value. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + * @param sourceObjId Object id of the source where the attribute was + * found. + */ + public OsAccountAttribute(BlackboardAttribute.Type attributeType, String valueString, long osAccountObjId, Long hostId, long sourceObjId) { + super(attributeType, valueString); + + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Creates an os account attribute with byte-array value. + * + * @param attributeType Attribute type. + * @param valueBytes Bytes value. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + * @param sourceObjId Object id of the source where the attribute was + * found. + */ + public OsAccountAttribute(Type attributeType, byte[] valueBytes, long osAccountObjId, Long hostId, long sourceObjId) { + super(attributeType, valueBytes); + + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Constructor to be used when creating an attribute after reading the data + * from the table. + * + * @param attributeType Attribute type. + * @param valueInt Int value. + * @param valueLong Long value. + * @param valueDouble Double value. + * @param valueString String value. + * @param valueBytes Bytes value. + * @param sleuthkitCase Sleuthkit case. + * @param osAccountObjId Obj id of account which the attribute pertains to. + * @param sourceObjId Object id of the source where the attribute was + * found. + * @param hostId Id of host on which the attribute applies to. Pass + * Null if it applies across hosts. + */ + OsAccountAttribute(BlackboardAttribute.Type attributeType, int valueInt, long valueLong, double valueDouble, String valueString, byte[] valueBytes, + SleuthkitCase sleuthkitCase, long osAccountObjId, Long hostId, long sourceObjId) { + + super( attributeType, + valueInt, valueLong, valueDouble, valueString, valueBytes, + sleuthkitCase); + this.osAccountObjId = osAccountObjId; + this.hostId = hostId; + this.sourceObjId = sourceObjId; + } + + /** + * Get the host id for the account attribute. + * + * @return Optional with Host id. + */ + Optional<Long> getHostId() { + return Optional.ofNullable(hostId); + } + + /** + * Get the object id of account to which this attribute applies. + * + * @return Account row id. + */ + public long getOsAccountObjId() { + return osAccountObjId; + } + + /** + * Get the object id of the source where the attribute was found. + * + * @return Object id of source. + */ + public long getSourceObjId() { + return sourceObjId; + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java new file mode 100644 index 0000000000000000000000000000000000000000..50e6929bac4231d93b202fae179456de950ec694 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccountManager.java @@ -0,0 +1,848 @@ +/* + * Sleuth Kit Data Model + * + * 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.datamodel; + +import com.google.common.base.Strings; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; + +/** + * Responsible for creating/updating/retrieving the OS accounts for files + * and artifacts. + * + */ +public final class OsAccountManager { + + private static final Logger LOGGER = Logger.getLogger(OsAccountManager.class.getName()); + + private final SleuthkitCase db; + + /** + * Construct a OsUserManager for the given SleuthkitCase. + * + * @param skCase The SleuthkitCase + * + */ + OsAccountManager(SleuthkitCase skCase) { + this.db = skCase; + } + + /** + * Creates an OS account with given unique id or given realm and login name. + * If an account already exists with the given id or realm/login, then the + * existing OS account is returned. + * + * @param uniqueAccountId Account sid/uid. + * @param loginName Login name. + * @param realmName Realm within which the accountId or login name is + * unique. + * @param host Host for the realm, may be null. + * + * @return OsAccount. + * + * @throws TskCoreException If there is an error in creating the OSAccount. + * + */ + public OsAccount createOsAccount(String uniqueAccountId, String loginName, String realmName, Host host) throws TskCoreException { + + // ensure at least one of the two is supplied - unique id or a login name + if (Strings.isNullOrEmpty(uniqueAccountId) && Strings.isNullOrEmpty(loginName)) { + throw new IllegalArgumentException("Cannot create OS account with both uniqueId and loginName as null."); + } + if (Strings.isNullOrEmpty(realmName)) { + throw new IllegalArgumentException("Realm name is required to create an OS account."); + } + + Optional<OsAccountRealm> realm = Optional.empty(); + + try (CaseDbConnection connection = this.db.getConnection();) { + + // get the realm with given name + realm = db.getOsAccountRealmManager().getRealmByName(realmName, host, connection); + if (!realm.isPresent()) { + // realm was not found, create it. + realm = Optional.of(db.getOsAccountRealmManager().createRealmByName(realmName, host)); + } + + // try to create account + try { + return createOsAccount(uniqueAccountId, loginName, realm.get(), OsAccount.OsAccountStatus.UNKNOWN, connection); + } catch (SQLException ex) { + + // Create may fail if an OsAccount already exists. + try (CaseDbConnection newConnection = this.db.getConnection()) { + + Optional<OsAccount> osAccount; + + // First search for account by uniqueId + if (!Strings.isNullOrEmpty(uniqueAccountId)) { + osAccount = getOsAccountByUniqueId(uniqueAccountId, host, newConnection); + if (osAccount.isPresent()) { + return osAccount.get(); + } + } + + // search by loginName + if (!Strings.isNullOrEmpty(loginName)) { + osAccount = getOsAccountByLoginName(loginName, realm.get(), newConnection); + if (osAccount.isPresent()) { + return osAccount.get(); + } + } + + // create failed for some other reason, throw an exception + throw new TskCoreException(String.format("Error creating OsAccount with loginName = %s", loginName), ex); + } + } + } + } + + /** + * Get the OS account with given unique id or given realm name and login name. + * + * @param uniqueAccountId Account sid/uid. + * @param loginName Login name. + * @param realmName Realm within which the accountId & login name is + * unique. + * @param host Host for the realm, may be null. + * @param transaction Transaction to use for database operation. + * + * @return Optional with OsAccount matching the given uniqueId, or + * realm/login name. Optional.empty if no match is found. + * + * @throws TskCoreException If there is an error getting the account. + */ + public Optional<OsAccount> getOsAccount(String uniqueAccountId, String loginName, String realmName, Host host, CaseDbTransaction transaction) throws TskCoreException { + + // ensure at least one of the two is supplied - unique id or a login name + if (Strings.isNullOrEmpty(uniqueAccountId) && Strings.isNullOrEmpty(loginName)) { + throw new IllegalArgumentException("Cannot create OS account with both uniqueId and loginName as null."); + } + + if (Strings.isNullOrEmpty(realmName)) { + throw new IllegalArgumentException("Realm name is required to create an OS account."); + } + + if (!Strings.isNullOrEmpty(uniqueAccountId)) { + Optional<OsAccount> osAccount = this.getOsAccountByUniqueId(uniqueAccountId, host, transaction.getConnection()); + if (osAccount.isPresent()) { + return osAccount; + } + } + + if (!Strings.isNullOrEmpty(loginName)) { + // first get the realm + Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getRealmByName(realmName, host, transaction); + if (!realm.isPresent()) { + throw new TskCoreException(String.format("No realm found with name %s", realmName)); + } + + return getOsAccountByLoginName(loginName, realm.get(), transaction.getConnection()); + } + + return Optional.empty(); + } + + /** + * Creates a OS account with the given uid, name, and realm. + * + * @param uniqueId Account sid/uid. May be null. + * @param loginName Login name. May be null only if SID is not null. + * @param realm Realm. + * @param accountStatus Account status. + * @param connection Database connection to use. + * + * @return OS account. + * + * @throws TskCoreException + */ + private OsAccount createOsAccount(String uniqueId, String loginName, OsAccountRealm realm, OsAccount.OsAccountStatus accountStatus, CaseDbConnection connection) throws TskCoreException, SQLException { + + if (Objects.isNull(realm)) { + throw new IllegalArgumentException("Cannot create an OS Account, realm is NULL."); + } + + String signature = makeAccountSignature(realm, uniqueId, loginName); + + db.acquireSingleUserCaseWriteLock(); + try { + + // first create a tsk_object for the OsAccount. + + // RAMAN TODO: need to get the correct parent obj id. + // Create an Object Directory parent and used its id. + long parentObjId = 0; + + // RAMAN TODO: what is the object type ?? + int objTypeId = TskData.ObjectType.ARTIFACT.getObjectType(); + + long osAccountObjId = db.addObject(parentObjId, objTypeId, connection); + + String accountInsertSQL = "INSERT INTO tsk_os_accounts(os_account_obj_id, login_name, realm_id, unique_id, signature, status)" + + " VALUES (?, ?, ?, ?, ?, ?)"; // NON-NLS + + PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.NO_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setLong(1, osAccountObjId); + + preparedStatement.setString(2, loginName); + if (!Objects.isNull(realm)) { + preparedStatement.setLong(3, realm.getId()); + } else { + preparedStatement.setNull(3, java.sql.Types.BIGINT); + } + + preparedStatement.setString(4, uniqueId); + preparedStatement.setString(5, signature); + preparedStatement.setInt(6, accountStatus.getId()); + + connection.executeUpdate(preparedStatement); + + return new OsAccount(db, osAccountObjId, realm, loginName, uniqueId, signature, accountStatus ); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + /** + * Get the OS account with the given unique id. + * + * @param uniqueId Account sid/uid. + * @param host Host for account realm, may be null. + * @param transaction Transaction to use for database connection. + * + * + * @return Optional with OsAccount, Optional.empty if no matching account is + * found. + * + * @throws TskCoreException If there is an error getting the account. + */ + public Optional<OsAccount> getOsAccount(String uniqueId, Host host, CaseDbTransaction transaction) throws TskCoreException { + + return getOsAccountByUniqueId(uniqueId, host, transaction.getConnection()); + } + + /** + * Gets the OS account for the given unique id. + * + * @param uniqueId Account SID/uid. + * @param host Host to match the realm, may be null. + * @param connection Database connection to use. + * + * @return Optional with OsAccount, Optional.empty if no account with matching uniqueId is found. + * + * @throws TskCoreException + */ + private Optional<OsAccount> getOsAccountByUniqueId(String uniqueId, Host host, CaseDbConnection connection) throws TskCoreException { + + String whereHostClause = (host == null) + ? " 1 = 1 " + : " ( realms.host_id = " + host.getId() + " OR realms.host_id IS NULL) "; + + String queryString = "SELECT accounts.os_account_obj_id as os_account_obj_id, accounts.login_name, accounts.full_name, " + + " accounts.realm_id, accounts.unique_id, accounts.signature, " + + " accounts.type, accounts.status, accounts.admin, accounts.created_date, " + + " realms.name as realm_name, realms.realm_addr as realm_addr, realms.host_id, realms.name_type " + + " FROM tsk_os_accounts as accounts" + + " LEFT JOIN tsk_os_account_realms as realms" + + " ON accounts.realm_id = realms.id" + + " WHERE " + whereHostClause + + " AND LOWER(accounts.unique_id) = LOWER('" + uniqueId + "')"; + + try (Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + if (!rs.next()) { + return Optional.empty(); // no match found + } else { + OsAccountRealm realm = null; + long realmId = rs.getLong("realm_id"); + if (!rs.wasNull()) { + realm = new OsAccountRealm(realmId, rs.getString("realm_name"), + OsAccountRealm.RealmNameType.fromID(rs.getInt("name_type")), + rs.getString("realm_addr"), host ); + } + + return Optional.of(osAccountFromResultSet(rs, realm)); + } + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting OS account for unique id = %s and host = %s", uniqueId, (host != null ? host.getName() : "null")), ex); + } + } + + /** + * Gets a OS Account by the realm and login name. + * + * @param loginName Login name. + * @param realm Account Realm. + * @param connection Database connection to use. + * + * @return Optional with OsAccount, Optional.empty, if no user is found with + * matching realm and login name. + * + * @throws TskCoreException + */ + private Optional<OsAccount> getOsAccountByLoginName(String loginName, OsAccountRealm realm, CaseDbConnection connection) throws TskCoreException { + + String queryString = "SELECT * FROM tsk_os_accounts" + + " WHERE LOWER(login_name) = LOWER('" + loginName + "')" + + " AND realm_id = " + realm.getId(); + + try (Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + if (!rs.next()) { + return Optional.empty(); // no match found + } else { + return Optional.of(osAccountFromResultSet(rs, realm)); + } + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting OS account for realm = %s and loginName = %s.", (realm != null) ? realm.getName() : "NULL", loginName), ex); + } + } + + /** + * Get the OS Account with the given object id. + * + * @param osAccountObjId Object id for the account. + * + * @return OsAccount. + * + * @throws TskCoreException If there is an error getting the account. + * @throws IllegalArgumentException If no matching object id is found. + */ + public OsAccount getOsAccount(long osAccountObjId) throws TskCoreException { + + try (CaseDbConnection connection = this.db.getConnection()) { + return getOsAccount(osAccountObjId, connection); + } + } + + /** + * Get the OsAccount with the given object id. + * + * @param osAccountObjId Object id for the account. + * @param connection Database connection to use. + * + * @return OsAccount. + * + * @throws TskCoreException If there is an error getting the account. + * @throws IllegalArgumentException If no matching object id is found. + */ + private OsAccount getOsAccount(long osAccountObjId, CaseDbConnection connection) throws TskCoreException { + + String queryString = "SELECT * FROM tsk_os_accounts" + + " WHERE os_account_obj_id = " + osAccountObjId; + + try ( Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + if (!rs.next()) { + throw new IllegalArgumentException(String.format("No account found with obj id = %d ", osAccountObjId)); + } else { + + OsAccountRealm realm = null; + long realmId = rs.getLong("realm_id"); + + if (!rs.wasNull()) { + realm = db.getOsAccountRealmManager().getRealm(realmId, connection); + } + + return osAccountFromResultSet(rs, realm); + } + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting account with obj id = %d ", osAccountObjId), ex); + } + } + + /** + * Adds a row to the tsk_os_account_instances table. + * + * @param osAccount Account for which an instance needs to be added. + * @param host Host on which the instance is found. + * @param dataSourceObjId Object id of the data source where the instance is found. + * @param instanceType Instance type. + * @param connection Database connection to use. + * + * @throws TskCoreException + */ + void addOsAccountInstance(OsAccount osAccount, Host host, long dataSourceObjId, OsAccount.OsAccountInstanceType instanceType, CaseDbConnection connection) throws TskCoreException { + + db.acquireSingleUserCaseWriteLock(); + try { + String accountInsertSQL = "INSERT INTO tsk_os_account_instances(os_account_obj_id, data_source_obj_id, host_id, instance_type)" + + " VALUES (?, ?, ?, ?)"; // NON-NLS + + PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.RETURN_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setLong(1, osAccount.getId()); + preparedStatement.setLong(2, dataSourceObjId); + preparedStatement.setLong(3, host.getId()); + preparedStatement.setInt(4, instanceType.getId()); + + connection.executeUpdate(preparedStatement); + + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error adding os account instance for account = %s, host name = %s, data source object id = %d", osAccount.getUniqueIdWithinRealm().orElse(osAccount.getLoginName().orElse("UNKNOWN")), host.getName(), dataSourceObjId ), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + /** + * Get all accounts that had an instance on the specified host. + * + * @param host Host for which to look accounts for. + * + * @return Set of OsAccounts, may be empty. + */ + Set<OsAccount> getAcccounts(Host host) throws TskCoreException { + + String queryString = "SELECT * FROM tsk_os_accounts as accounts " + + " JOIN tsk_os_account_instances as instances " + + " ON instances.os_account_obj_id = accounts.os_account_obj_id " + + " WHERE instances.host_id = " + host.getId(); + + try (CaseDbConnection connection = this.db.getConnection(); + Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + Set<OsAccount> accounts = new HashSet<>(); + while (rs.next()) { + OsAccountRealm realm = null; + long realmId = rs.getLong("realm_id"); + if (!rs.wasNull()) { + realm = db.getOsAccountRealmManager().getRealm(realmId, connection); + } + + accounts.add(osAccountFromResultSet(rs, realm)); + } + return accounts; + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting OS accounts for host id = %d", host.getId()), ex); + } + } + + + /** + * Gets or creates an OS account with the given SID. + * + * @param sid Account SID. + * @param host Host for the realm. + * + * @return OsAccount. + * @throws TskCoreException + */ + public OsAccount createOsAccountByWindowsSID(String sid, Host host) throws TskCoreException { + + // ensure SID is provided + if (Strings.isNullOrEmpty(sid)) { + throw new IllegalArgumentException("Cannot create OS account, sid is null."); + } + + + Optional<OsAccountRealm> realm = Optional.empty(); + try (CaseDbConnection connection = this.db.getConnection()) { + + // get the realm with given address + realm = db.getOsAccountRealmManager().getRealmByAddr(sid, host, connection); + if (!realm.isPresent()) { + // realm was not found, create it. + realm = Optional.of(db.getOsAccountRealmManager().createRealmByWindowsSid(sid, host)); + } + + return createOsAccount(sid, null, realm.get(), OsAccount.OsAccountStatus.UNKNOWN, connection); + } catch (SQLException ex) { + + // Create may fail if an OsAccount already exists. + try (CaseDbConnection connection = this.db.getConnection()) { + + // search by SID + Optional<OsAccount> osAccount = this.getOsAccountByUniqueId(sid, host, connection); + + if (osAccount.isPresent()) { + return osAccount.get(); + } + + // create failed for some other reason, throw an exception + throw new TskCoreException(String.format("Error creating OsAccount with SID = %s", sid), ex); + } + } + + } + + /** + * Gets an OS account with the given SID. + * + * @param sid Account SID. + * @param host Host for the realm. + * @param transaction Transaction to use. + * + * @return Optional with OsAccount, Optional.empty if no matching OsAccount is found. + * + * @throws TskCoreException + */ + public Optional<OsAccount> getOsAccountByWindowsSID(String sid, Host host, CaseDbTransaction transaction) throws TskCoreException { + + CaseDbConnection connection = transaction.getConnection(); + + // first get the realm for the given sid + Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getRealmByWindowsSid(sid, host, transaction); + if (!realm.isPresent()) { + throw new TskCoreException(String.format("No realm found for SID %s", sid)); + } + + // search by SID + return getOsAccountByUniqueId(sid, host, connection); + } + + /** + * Creates an OS account with the given login name and realm name. + * + * If an OS Account already exists with this realm-name/login-name, the + * existing OSAccount is returned. + * + * @param loginName Account SID. + * @param realmName Realm name. + * @param host Host for the realm. + * + * @return OsAccount with given realm-name/login-name. + * + * @throws TskCoreException + */ + public OsAccount createOsAccountByLogin(String loginName, String realmName, Host host) throws TskCoreException { + + // ensure login name is provided + if (Strings.isNullOrEmpty(loginName)) { + throw new IllegalArgumentException("Cannot create OS account, loginName is null."); + } + if (Strings.isNullOrEmpty(realmName)) { + throw new IllegalArgumentException("Cannot create OS account, realmName is null."); + } + + Optional<OsAccountRealm> realm = Optional.empty(); + try (CaseDbConnection connection = this.db.getConnection()) { + + // get the realm with given name + realm = db.getOsAccountRealmManager().getRealmByName(realmName, host, connection); + if (!realm.isPresent()) { + // RAMAN TBD: confirm this with Brian + realm = Optional.of(db.getOsAccountRealmManager().createRealmByName(realmName, host)); + } + + return createOsAccount(null, loginName, realm.get(), OsAccount.OsAccountStatus.UNKNOWN, connection); + } catch (SQLException ex) { + + // Create may fail if an OsAccount already exists. + try (CaseDbConnection connection = this.db.getConnection()) { + + // search by loginName + Optional<OsAccount> osAccount = getOsAccountByLoginName(loginName, realm.get(), connection); + if (osAccount.isPresent()) { + return osAccount.get(); + } + + // create failed for some other reason, throw an exception + throw new TskCoreException(String.format("Error creating OsAccount with loginName = %s", loginName), ex); + } + } + } + + /** + * Gets an OS account with the given login name and realm name. + * + * @param loginName Account SID. + * @param realmName Domain name. + * @param host Host for the realm. + * @param transaction Transaction to use. + * + * @return Optional with OsAccount, Optional.empty if no matching OS account + * is found. + * + * @throws TskCoreException + */ + public Optional<OsAccount> getOsAccountByLogin(String loginName, String realmName, Host host, CaseDbTransaction transaction) throws TskCoreException { + + CaseDbConnection connection = transaction.getConnection(); + + // first get the realm + Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getRealmByName(realmName, host, transaction); + if (!realm.isPresent()) { + throw new TskCoreException(String.format("No realm found with name %s", realmName)); + } + + return getOsAccountByLoginName(loginName, realm.get(), connection); + } + + /** + * Update the account login name. + * + * @param accountObjId Object id of the account to update. + * @param loginName Account login name. + * @param transaction Transaction + * + * + * @return OsAccount Updated account. + * + * @throws TskCoreException + */ + private OsAccount updateOsAccountLoginName(long accountObjId, String loginName, CaseDbTransaction transaction) throws TskCoreException { + + CaseDbConnection connection = transaction.getConnection(); + + db.acquireSingleUserCaseWriteLock(); + try { + String updateSQL = "UPDATE tsk_os_accounts SET login_name = ? WHERE os_account_obj_id = ?"; + + PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setString(1, loginName); + preparedStatement.setLong(2, accountObjId); + + connection.executeUpdate(preparedStatement); + + return getOsAccount(accountObjId, connection ); + + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error updating account with login name = %s, account id = %d", loginName, accountObjId), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + + /** + * Adds a rows to the tsk_os_account_attributes table for the given set of + * attribute. + * + * @param account Account for which the attributes is being added. + * @param accountAttribute Attribute to add. + * + * @throws TskCoreException, + */ + void addOsAccountAttributes(OsAccount account, Set<OsAccountAttribute> accountAttributes) throws TskCoreException { + + db.acquireSingleUserCaseWriteLock(); + + try (CaseDbConnection connection = db.getConnection()) { + for (OsAccountAttribute accountAttribute : accountAttributes) { + + String attributeInsertSQL = "INSERT INTO tsk_os_account_attributes(os_account_obj_id, host_id, source_obj_id, attribute_type_id, value_type, value_byte, value_text, value_int32, value_int64, value_double)" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS + + PreparedStatement preparedStatement = connection.getPreparedStatement(attributeInsertSQL, Statement.RETURN_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setLong(1, account.getId()); + if (accountAttribute.getHostId().isPresent()) { + preparedStatement.setLong(2, accountAttribute.getHostId().get()); + } else { + preparedStatement.setNull(2, java.sql.Types.BIGINT); + } + preparedStatement.setLong(3, accountAttribute.getSourceObjId()); + + preparedStatement.setLong(4, accountAttribute.getAttributeType().getTypeID()); + preparedStatement.setLong(5, accountAttribute.getAttributeType().getValueType().getType()); + + if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE) { + preparedStatement.setBytes(6, accountAttribute.getValueBytes()); + } else { + preparedStatement.setBytes(6, null); + } + + if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING + || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) { + preparedStatement.setString(7, accountAttribute.getValueString()); + } else { + preparedStatement.setString(7, null); + } + if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) { + preparedStatement.setInt(8, accountAttribute.getValueInt()); + } else { + preparedStatement.setNull(8, java.sql.Types.INTEGER); + } + + if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME + || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG) { + preparedStatement.setLong(9, accountAttribute.getValueLong()); + } else { + preparedStatement.setNull(9, java.sql.Types.BIGINT); + } + + if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) { + preparedStatement.setDouble(10, accountAttribute.getValueDouble()); + } else { + preparedStatement.setNull(10, java.sql.Types.DOUBLE); + } + + connection.executeUpdate(preparedStatement); + + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error adding OS Account attribute for account id = %d", account.getId()), ex); + } + + finally { + db.releaseSingleUserCaseWriteLock(); + } + + } + + /** + * Updates the database for the given OsAccount. + * + * @param osAccount OsAccount that needs to be updated. + * @param transaction Transaction to use. + * + * @return OsAccount Updated account. + * + * @throws TskCoreException + */ + OsAccount updateAccount(OsAccount osAccount, CaseDbTransaction transaction ) throws TskCoreException { + + CaseDbConnection connection = transaction.getConnection(); + db.acquireSingleUserCaseWriteLock(); + + String signature = makeAccountSignature(osAccount.getRealm(), osAccount.getUniqueIdWithinRealm().orElse(null), osAccount.getLoginName().orElse(null)); + + try { + String updateSQL = "UPDATE tsk_os_accounts SET " + + " login_name = ?, " // 1 + + " unique_id = ?, " // 2 + + " signature = ?, " // 3 + + " full_name = ?, " // 4 + + " status = ?, " // 5 + + " admin = ?, " // 6 + + " type = ?, " // 7 + + " created_date = ? " //8 + + " WHERE os_account_obj_id = ?"; //9 + + PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setString(1, osAccount.getLoginName().orElse(null)); + preparedStatement.setString(2, osAccount.getUniqueIdWithinRealm().orElse(null)); + + osAccount.setSignature(signature); + preparedStatement.setString(3, signature); + + preparedStatement.setString(4, osAccount.getFullName().orElse(null)); + + preparedStatement.setInt(5, osAccount.getOsAccountStatus().getId()); + preparedStatement.setInt(6, osAccount.isAdmin() ? 1 : 0); + preparedStatement.setInt(7, osAccount.getOsAccountType().getId()); + + preparedStatement.setLong(8, osAccount.getCreationTime().orElse(null)); + preparedStatement.setLong(9, osAccount.getId()); + + connection.executeUpdate(preparedStatement); + return osAccount; + } + catch (SQLException ex) { + Logger.getLogger(OsAccountManager.class.getName()).log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error updating account with unique id = %s, account id = %d", osAccount.getUniqueIdWithinRealm().orElse("Unknown"), osAccount.getId()), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + + } + /** + * Takes in a result with a row from tsk_os_accounts table and creates + * an OsAccount. + * + * @param rs ResultSet. + * @param realm Realm. + * @return OsAccount OS Account. + * + * @throws SQLException + */ + private OsAccount osAccountFromResultSet(ResultSet rs, OsAccountRealm realm) throws SQLException { + + OsAccount osAccount = new OsAccount(db, rs.getLong("os_account_obj_id"), realm, rs.getString("login_name"), rs.getString("unique_id"), rs.getString("signature"), OsAccount.OsAccountStatus.fromID(rs.getInt("status"))); + + // set other optional fields + + String fullName = rs.getString("full_name"); + if (!rs.wasNull()) { + osAccount.setFullName(fullName); + } + + int status = rs.getInt("status"); + if (!rs.wasNull()) { + osAccount.setOsAccountStatus(OsAccount.OsAccountStatus.fromID(status)); + } + + int admin = rs.getInt("admin"); + osAccount.setIsAdmin(admin != 0); + + int type = rs.getInt("type"); + if (!rs.wasNull()) { + osAccount.setOsAccountType(OsAccount.OsAccountType.fromID(type)); + } + + long creationTime = rs.getLong("created_date"); + if (!rs.wasNull()) { + osAccount.setCreationTime(creationTime); + } + + return osAccount; + } + + /** + * Created an account signature for an OS Account. This signature is simply + * to prevent duplicate accounts from being created. Signature is set to: + * realmId/uniqueId: if the account has a uniqueId, otherwise + * realmId/loginName: if the account has a login name. + * + * @param realm Account realm + * @param uniqueId Unique id. + * @param loginName Login name. + * + * @return Account signature. + */ + private String makeAccountSignature(OsAccountRealm realm, String uniqueId, String loginName) { + // Create a signature. + String signature; + if (Strings.isNullOrEmpty(uniqueId) == false) { + signature = String.format("%d/%s", realm.getId(), uniqueId); + } else if (Strings.isNullOrEmpty(loginName) == false) { + signature = String.format("%d/%s", realm.getId(), loginName); + } else { + throw new IllegalArgumentException("OS Account must have either a uniqueID or a login name."); + } + return signature; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealm.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealm.java new file mode 100644 index 0000000000000000000000000000000000000000..a257c123e41bc9bef6f5aa2b6a29eed99c8fdc03 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealm.java @@ -0,0 +1,146 @@ +/* + * Sleuth Kit Data Model + * + * 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.datamodel; + +import java.util.Optional; + +/** + * Encapsulates the scope of an OsAccount. An account is unique within a realm. + * + * The realm may be comprised of a single host, say for a local account, or a + * domain. + */ +public final class OsAccountRealm { + + private final long id; // row id + private final String name; + private final String realmAddr; + private final Host host; // if the realm consists of a single host, may be null + private final RealmNameType nameType; // if the realm is inferred + + + + OsAccountRealm(long id, String name, RealmNameType nameType, String realmAddr, Host host) { + this.id = id; + this.name = name; + this.realmAddr = realmAddr; + this.host = host; + this.nameType = nameType; + } + + /** + * Get the realm row id. + * + * @return Realm id. + */ + public long getId() { + return id; + } + + /** + * Get the realm name. + * + * @return Realm name. + */ + public String getName() { + return name; + } + + /** + * Get the realm address. + * + * @return Optional realm unique id.. + */ + public Optional<String> getRealmAddr() { + return Optional.ofNullable(realmAddr); + } + + /** + * Get the realm host, if it's a single host realm. + * + * @return Optional host. + */ + public Optional<Host> getHost() { + return Optional.ofNullable(host); + } + + /** + * Get realm name type. + * + * @return Realm name type. + */ + public RealmNameType getNameType() { + return nameType; + } + + + /** + * Enum to encapsulate realm name type. + * + * Realm name may be explicitly expressed, say in an event log. + * Occasionally, a name may be inferred (e.g. for stand alone machines) + */ + public enum RealmNameType { + EXPRESSED(0, "Expressed"), // realm name was explicitly expressed + INFERRED(1, "Inferred"); // name is inferred + + private final int id; + private final String name; + + RealmNameType(int id, String name) { + this.id = id; + this.name = name; + } + + /** + * Get the id of the realm name type. + * + * @return Realm name type id. + */ + public int getId() { + return id; + } + + /** + * Get the realm name type name. + * + * @return Realm name type name. + */ + String getName() { + return name; + } + + /** + * Gets a realm name type enum by id. + * + * @param typeId Realm name type id. + * + * @return RealmNameType enum. + */ + public static RealmNameType fromID(int typeId) { + for (RealmNameType statusType : RealmNameType.values()) { + if (statusType.ordinal() == typeId) { + return statusType; + } + } + return null; + } + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealmManager.java b/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealmManager.java new file mode 100644 index 0000000000000000000000000000000000000000..43d3b7bfe5f5dc8f88d69f74120880ba7bd2751e --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/OsAccountRealmManager.java @@ -0,0 +1,447 @@ +/* + * Sleuth Kit Data Model + * + * 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.datamodel; + +import com.google.common.base.Strings; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.datamodel.OsAccountRealm.RealmNameType; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; + + +/** + * Create/Retrieve/Update OS account realms. Realms represent either an individual + * host with local accounts or a domain. + */ +public final class OsAccountRealmManager { + + private static final Logger LOGGER = Logger.getLogger(OsAccountRealmManager.class.getName()); + + private final SleuthkitCase db; + + /** + * Construct a OsAccountRealmManager for the given SleuthkitCase. + * + * @param skCase The SleuthkitCase + * + */ + OsAccountRealmManager(SleuthkitCase skCase) { + this.db = skCase; + } + + /** + * Create the realm with the given name and scope for the given host. If a + * realm with same name already exists, then the existing realm is returned. + * + * @param realmName Realm name. + * @param host Host that realm reference was found on. May be null if + * the realm is a domain and not host-specific. + * + * @return OsAccountRealm Realm. + * + * @throws TskCoreException If there is an error creating the realm. + */ + public OsAccountRealm createRealmByName(String realmName, Host host) throws TskCoreException { + + if (Strings.isNullOrEmpty(realmName)) { + throw new IllegalArgumentException("A realm name is required."); + } + + try (CaseDbConnection connection = this.db.getConnection()) { + return createRealm(realmName, RealmNameType.EXPRESSED, null, host, connection); + } catch (SQLException ex) { + // Create may have failed if the realm already exists. try to get the realm by name. + try (CaseDbConnection connection = this.db.getConnection()) { + Optional<OsAccountRealm >accountRealm = this.getRealmByName(realmName, host, connection); + if (accountRealm.isPresent()) { + return accountRealm.get(); + } else { + throw new TskCoreException(String.format("Error creating realm with name = %s and host name = %s", realmName, (host != null ? host.getName() : "null")), ex); + } + } + } + } + + /** + * Get the realm with the given name and specified host. + * + * @param realmName Realm name. + * @param host Host for realm, may be null. + * @param transaction Transaction to use for database connection. + * + * @return OsAccountRealm, Optional.empty if no matching realm is found. + * + * @throws TskCoreException If there is an error creating the realm. + */ + public Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbTransaction transaction) throws TskCoreException { + + return this.getRealmByName(realmName, host, transaction.getConnection()); + } + + + /** + * Create realm for the given Windows SID. The input SID is a user/group + * SID. The domain SID is extracted from this incoming SID. If a realm + * already exists for the given user/group SID, the existing realm is + * returned. + * + * @param sid User/group SID. + * @param host Host for realm, may be null. + * + * @return OsAccountRealm. + * + * @throws TskCoreException If there is an error creating the realm. + */ + public OsAccountRealm createRealmByWindowsSid(String sid, Host host) throws TskCoreException { + + if (Strings.isNullOrEmpty(sid)) { + throw new IllegalArgumentException("A SID is required."); + } + + // get subAuthority sid + String subAuthorityId = getSubAuthorityId(sid); + + // RAMAN TBD: can the SID be parsed in some way to determine local vs domain ?? + + try (CaseDbConnection connection = this.db.getConnection()) { + return createRealm("Unknown Domain Name", OsAccountRealm.RealmNameType.INFERRED, subAuthorityId, host, connection); + } catch (SQLException ex) { + // Create may have failed if the realm already exists. try to get the realm by addr. + try (CaseDbConnection connection = this.db.getConnection()) { + Optional<OsAccountRealm >accountRealm = this.getRealmByAddr(subAuthorityId, host, connection); + if (accountRealm.isPresent()) { + return accountRealm.get(); + } else { + throw new TskCoreException(String.format("Error creating realm with address = %s", subAuthorityId), ex); + } + } + } + + } + + /** + * Get the realm for the given user/group SID. The input SID is a user/group + * SID. The domain SID is extracted from this incoming SID. + * + * @param sid User SID. + * @param host Host for realm, may be null. + * @param transaction Transaction to use for database connection. + * + * @return Optional with OsAccountRealm, Optional.empty if no realm found with matching real address. + * + * @throws TskCoreException + */ + public Optional<OsAccountRealm> getRealmByWindowsSid(String sid, Host host, CaseDbTransaction transaction) throws TskCoreException { + + // get subAuthority sid + String subAuthorityId = getSubAuthorityId(sid); + + CaseDbConnection connection = transaction.getConnection(); + return this.getRealmByAddr(subAuthorityId, host, connection); + } + + /** + * Updates the realm name and name type for the the specified realm id. + * + * @param realmId Row id of realm to update. + * @param realmName Realm name. + * @param nameType Name type. + * + * @return OsAccountRealm + * @throws TskCoreException + */ + OsAccountRealm updateRealmName(long realmId, String realmName, OsAccountRealm.RealmNameType nameType) throws TskCoreException { + + db.acquireSingleUserCaseWriteLock(); + try (CaseDbConnection connection = db.getConnection()) { + String updateSQL = "UPDATE tsk_os_account_realms SET name = ?, name_type = ? WHERE id = ?"; + PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setString(1, realmName); + preparedStatement.setInt(2, nameType.getId()); + preparedStatement.setLong(3, realmId); + + connection.executeUpdate(preparedStatement); + + return getRealm(realmId, connection ); + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error updating realm with name = %s, id = %d", realmName, realmId), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + private final static String REALM_QUERY_STRING = "SELECT realms.id as realm_id, realms.name as realm_name," + + " realms.realm_addr as realm_addr, realms.host_id, realms.name_type, " + + " hosts.id, hosts.name as host_name " + + " FROM tsk_os_account_realms as realms" + + " LEFT JOIN tsk_hosts as hosts" + + " ON realms.host_id = hosts.id"; + + /** + * Get the realm from the given row id. + * + * @param id Realm row id. + * @param connection Database connection to use. + * + * @return Realm. + * @throws TskCoreException + */ + OsAccountRealm getRealm(long id, CaseDbConnection connection) throws TskCoreException { + + String queryString = REALM_QUERY_STRING + + " WHERE realms.id = " + id; + + try ( Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + OsAccountRealm accountRealm = null; + if (rs.next()) { + accountRealm = resultSetToAccountRealm(rs); + } else { + throw new TskCoreException(String.format("No realm found with id = %d", id)); + } + + return accountRealm; + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex); + } + } + + /** + * Get the realm with the given realm address. + * + * @param realmAddr Realm address. + * @param host Host for realm, may be null. + * @param connection Database connection to use. + * + * @return Optional with OsAccountRealm, Optional.empty if no realm found with matching real address. + * + * @throws TskCoreException. + */ + Optional<OsAccountRealm> getRealmByAddr(String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException { + + // If a host is specified, we want to match the realm with matching name and specified host, or a realm with matching name and no host. + // If no host is specified, then we return the first realm with matching name. + String whereHostClause = (host == null) + ? " 1 = 1 " + : " ( realms.host_id = " + host.getId() + " OR realms.host_id IS NULL) "; + String queryString = REALM_QUERY_STRING + + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') " + + " AND " + whereHostClause + + " ORDER BY realms.host_id IS NOT NULL, realms.host_id"; // ensure that non null host_id is at the front + + try ( Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + OsAccountRealm accountRealm = null; + if (rs.next()) { + Host realmHost = null; + long hostId = rs.getLong("host_id"); + if (!rs.wasNull()) { + if (host != null ) { + realmHost = host; // exact match on given host + } else { + realmHost = new Host(hostId, rs.getString("host_name")); + } + } + + accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"), + RealmNameType.fromID(rs.getInt("name_type")), + rs.getString("realm_addr"), realmHost); + } + return Optional.ofNullable(accountRealm); + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error running the realms query = %s with realmaddr = %s and host name = %s", + queryString, realmAddr, (host != null ? host.getName() : "Null") ), ex); + } + } + + /** + * Get the realm with the given name and specified host. + * + * @param realmName Realm name. + * @param host Host for realm, may be null. + * @param connection Database connection to use. + * + * @return Optional with OsAccountRealm, Optional.empty if no matching realm is found. + * @throws TskCoreException. + */ + Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbConnection connection) throws TskCoreException { + + // If a host is specified, we want to match the realm with matching name and specified host, or a realm with matching name and no host. + // If no host is specified, then we return the first realm with matching name. + String whereHostClause = (host == null) + ? " 1 = 1 " + : " ( realms.host_id = " + host.getId() + " OR realms.host_id IS NULL ) "; + String queryString = REALM_QUERY_STRING + + " WHERE LOWER(realms.name) = LOWER('" + realmName + "')" + + " AND " + whereHostClause + + " ORDER BY realms.host_id IS NOT NULL, realms.host_id"; // ensure that non null host_id are at the front + + try (Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + OsAccountRealm accountRealm = null; + if (rs.next()) { + Host realmHost = null; + long hostId = rs.getLong("host_id"); + if (!rs.wasNull()) { + if (host != null ) { + realmHost = host; + } else { + realmHost = new Host(hostId, rs.getString("host_name")); + } + } + + accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"), + RealmNameType.fromID(rs.getInt("name_type")), + rs.getString("realm_addr"), realmHost); + + } + return Optional.ofNullable(accountRealm); + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting account realm for with name = %s", realmName), ex); + } + } + + + /** + * Creates a OsAccountRealm from the resultset of a REALM_QUERY_STRING query. + * + * @param rs ResultSet + * @return + * @throws SQLException + */ + private OsAccountRealm resultSetToAccountRealm(ResultSet rs) throws SQLException { + + long hostId = rs.getLong("host_id"); + Host realmhost = null; + if (!rs.wasNull()) { + realmhost = new Host(hostId, rs.getString("host_name")); + } + + return new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"), + RealmNameType.fromID(rs.getInt("name_type")), + rs.getString("realm_addr"), realmhost); + } + +// /** +// * Get all realms. +// * +// * @return Collection of OsAccountRealm +// */ +// Collection<OsAccountRealm> getRealms() throws TskCoreException { +// String queryString = "SELECT realms.id as realm_id, realms.name as realm_name, realms.realm_addr as realm_addr, realms.host_id, realms.name_type, " +// + " hosts.id, hosts.name as host_name " +// + " FROM tsk_os_account_realms as realms" +// + " LEFT JOIN tsk_hosts as hosts" +// + " ON realms.host_id = hosts.id"; +// +// try (CaseDbConnection connection = this.db.getConnection(); +// Statement s = connection.createStatement(); +// ResultSet rs = connection.executeQuery(s, queryString)) { +// +// ArrayList<OsAccountRealm> accountRealms = new ArrayList<>(); +// while (rs.next()) { +// long hostId = rs.getLong("host_id"); +// Host host = null; +// if (!rs.wasNull()) { +// host = new Host(hostId, rs.getString("host_name")); +// } +// +// accountRealms.add(new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"), +// RealmNameType.fromID(rs.getInt("name_type")), +// rs.getString("realm_addr"), host)); +// } +// +// return accountRealms; +// } catch (SQLException ex) { +// throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex); +// } +// } + + + /** + * Adds a row to the realms table. + * + * @param realmName Realm name. + * @param nameType Name type. + * @param realmAddr SID or some other identifier. May be null. + * @param host Host that realm reference was found on. Can be null if you know the realm is a domain and not host-specific. + * @param connection DB connection to use. + * + * @return OsAccountRealm Realm just created. + * + * @throws SQLException If there is an SQL error in creating realm. + * @throws TskCoreException If there is an internal error. + */ + private OsAccountRealm createRealm(String realmName, OsAccountRealm.RealmNameType nameType, String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException, SQLException { + + db.acquireSingleUserCaseWriteLock(); + try { + String realmInsertSQL = "INSERT INTO tsk_os_account_realms(name, realm_addr, host_id, name_type)" + + " VALUES (?, ?, ?, ?)"; // NON-NLS + + PreparedStatement preparedStatement = connection.getPreparedStatement(realmInsertSQL, Statement.RETURN_GENERATED_KEYS); + preparedStatement.clearParameters(); + + preparedStatement.setString(1, realmName); + preparedStatement.setString(2, realmAddr); + if (host != null) { + preparedStatement.setLong(3, host.getId()); + } else { + preparedStatement.setNull(3, java.sql.Types.BIGINT); + } + preparedStatement.setInt(4, nameType.getId()); + + connection.executeUpdate(preparedStatement); + + // Read back the row id + try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) { + long rowId = resultSet.getLong(1); // last_insert_rowid() + return new OsAccountRealm(rowId, realmName, nameType, realmAddr, host); + } + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + /** + * Gets the sub authority id from the given SID. + * + * @param sid SID + * + * @return Sub-authority id string. + */ + private String getSubAuthorityId(String sid) { + if (org.apache.commons.lang3.StringUtils.countMatches(sid, "-") < 5 ) { + throw new IllegalArgumentException(String.format("Invalid SID %s for a host/domain", sid)); + } + String subAuthorityId = sid.substring(0, sid.lastIndexOf('-')); + + return subAuthorityId; + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java index 161c21d65b8c2a42f4d1642324d50d10a95f6267..8cf9407e5c3209787c76fbf27684b59824041c33 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SlackFile.java @@ -77,6 +77,10 @@ public class SlackFile extends FsContent { * yet been determined. * @param extension The extension part of the file name (not * including the '.'), can be null. + * @param ownerUid UID of the file owner as found in the file + * system, can be null. + * @param osAccountObjId Obj id of the owner OS account, may be null. + * */ SlackFile(SleuthkitCase db, long objId, @@ -91,8 +95,10 @@ public class SlackFile extends FsContent { long ctime, long crtime, long atime, long mtime, short modes, int uid, int gid, String md5Hash, String sha256Hash, FileKnown knownState, String parentPath, String mimeType, - String extension) { - super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.SLACK, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, Collections.emptyList()); + String extension, + String ownerUid, + Long osAccountObjId) { + super(db, objId, dataSourceObjectId, fsObjId, attrType, attrId, name, TskData.TSK_DB_FILES_TYPE_ENUM.SLACK, metaAddr, metaSeq, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, extension, ownerUid, osAccountObjId, Collections.emptyList()); } /** diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 4aeb370831ac9da91484f0141996a3148854d809..61bd32ebed8dc6a1bf86d8f68ff77367b8985f72 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -212,7 +212,9 @@ public class SleuthkitCase { private CaseDbAccessManager dbAccessManager; private TaggingManager taggingMgr; private ScoringManager scoringManager; - private HostManager hostManager; + private OsAccountRealmManager osAccountRealmManager; + private OsAccountManager osAccountManager; + private HostManager hostManager; private final Map<String, Set<Long>> deviceIdToDatasourceObjIdMap = new HashMap<>(); @@ -391,6 +393,8 @@ private void init() throws Exception { dbAccessManager = new CaseDbAccessManager(this); taggingMgr = new TaggingManager(this); scoringManager = new ScoringManager(this); + osAccountRealmManager = new OsAccountRealmManager(this); + osAccountManager = new OsAccountManager(this); hostManager = new HostManager(this); } @@ -516,11 +520,33 @@ public ScoringManager getScoringManager() throws TskCoreException { } /** - * Gets the host manager for this case. + * Gets the OS account realm manager for this case. + * + * @return The per case OsAccountRealmManager object. + * + * @throws TskCoreException + */ + public OsAccountRealmManager getOsAccountRealmManager() throws TskCoreException { + return osAccountRealmManager; + } + + /** + * Gets the OS account manager for this case. + * + * @return The per case OsAccountManager object. + * + * @throws TskCoreException + */ + public OsAccountManager getOsAccountManager() throws TskCoreException { + return osAccountManager; + } + + /** + * Gets the Hosts manager for this case. * * @return The per case HostManager object. * - * @throws org.sleuthkit.datamodel.TskCoreException + * @throws TskCoreException */ public HostManager getHostManager() throws TskCoreException { return hostManager; @@ -2330,12 +2356,93 @@ private CaseDbSchemaVersionNumber updateFromSchema8dot6toSchema8dot7(CaseDbSchem + "FOREIGN KEY(obj_id) REFERENCES tsk_files(obj_id) ON DELETE CASCADE, " + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); + // create analysis results tables + statement.execute("CREATE TABLE tsk_analysis_results (artifact_obj_id " + bigIntDataType + " PRIMARY KEY, " + + "conclusion TEXT, " + + "significance INTEGER NOT NULL, " + + "confidence INTEGER NOT NULL, " + + "configuration TEXT, justification TEXT, " + + "ignore_score INTEGER DEFAULT 0, " // boolean + + "FOREIGN KEY(artifact_obj_id) REFERENCES blackboard_artifacts(artifact_obj_id) ON DELETE CASCADE" + + ")"); + + statement.execute("CREATE TABLE tsk_aggregate_score( obj_id " + bigIntDataType + " PRIMARY KEY, " + + "data_source_obj_id " + bigIntDataType + " NOT NULL, " + + "significance INTEGER NOT NULL, " + + "confidence INTEGER NOT NULL, " + + "UNIQUE (obj_id)," + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE " + + ")"); + + // RAMAN TBD: need to add UNIQUE (artifact_obj_id) constraint to blackboard_artifacts table for it to be FK + // create host table. statement.execute("CREATE TABLE tsk_hosts (id " + primaryKeyType + " PRIMARY KEY, " - + "name TEXT NOT NULL, " // host name - + "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted - + "UNIQUE(name)) "); + + "name TEXT NOT NULL, " // host name + + "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted + + "UNIQUE(name)) "); + + // Create OS Account and related tables + statement.execute("CREATE TABLE tsk_os_account_realms (id " + primaryKeyType + " PRIMARY KEY, " + + "name TEXT NOT NULL, " // realm name - host name or domain name + + "realm_addr TEXT DEFAULT NULL, " // a sid/uid or some some other unique identifier, may be null + + "host_id " + bigIntDataType + " DEFAULT NULL, " // if the realm just comprises of a single host + + "name_type INTEGER, " // indicates if the realm name was was expressed or inferred + + "UNIQUE(name, host_id), " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id) )"); + + statement.execute("CREATE TABLE tsk_os_accounts (os_account_obj_id " + bigIntDataType + " PRIMARY KEY, " + + "login_name TEXT DEFAULT NULL, " // login name, if available, may be null + + "full_name TEXT DEFAULT NULL, " // full name, if available, may be null + + "realm_id " + bigIntDataType + ", " // row id for the realm, may be null if only SID is known + + "unique_id TEXT DEFAULT NULL, " // SID/UID, if available + + "signature TEXT NOT NULL, " // realm/loginname or sid + + "status INTEGER, " // enabled/disabled/deleted + + "admin INTEGER DEFAULT 0," // is admin account + + "type INTEGER, " // service/interactive + + "created_date " + bigIntDataType + " DEFAULT NULL, " + + "UNIQUE(signature), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(realm_id) REFERENCES tsk_os_account_realms(id) )"); + + statement.execute("CREATE TABLE tsk_os_account_attributes (id " + primaryKeyType + " PRIMARY KEY, " + + "os_account_obj_id " + bigIntDataType + " NOT NULL, " + + "host_id " + bigIntDataType + ", " + + "source_obj_id " + bigIntDataType + ", " + + "attribute_type_id " + bigIntDataType + " NOT NULL, " + + "value_type INTEGER NOT NULL, " + + "value_byte " + bigIntDataType + ", " + + "value_text TEXT, " + + "value_int32 INTEGER, value_int64 " + bigIntDataType + ", " + + "value_double NUMERIC(20, 10), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id), " + + "FOREIGN KEY(source_obj_id) REFERENCES tsk_objects(obj_id), " + + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); + + statement.execute("CREATE TABLE tsk_os_account_instances (id " + primaryKeyType + " PRIMARY KEY, " + + "os_account_obj_id " + bigIntDataType + " NOT NULL, " + + "data_source_obj_id " + bigIntDataType + " NOT NULL, " + + "host_id " + bigIntDataType + " NOT NULL, " + + "instance_type INTEGER NOT NULL, " // PerformedActionOn/ReferencedOn + + "UNIQUE(os_account_obj_id, data_source_obj_id, host_id), " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id), " + + "FOREIGN KEY(host_id) REFERENCES tsk_hosts(id))"); + + statement.execute("CREATE TABLE tsk_data_artifacts ( " + + "artifact_obj_id " + bigIntDataType + " PRIMARY KEY, " + + "os_account_obj_id " + bigIntDataType + ", " + + "FOREIGN KEY(artifact_obj_id) REFERENCES blackboard_artifacts(artifact_obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(os_account_obj_id) REFERENCES tsk_os_accounts(os_account_obj_id) ON DELETE CASCADE) "); + + // add owner_uid & os_account_obj_id columns to tsk_files + statement.execute("ALTER TABLE tsk_files ADD COLUMN owner_uid TEXT DEFAULT NULL"); + statement.execute("ALTER TABLE tsk_files ADD COLUMN os_account_obj_id " + bigIntDataType + " DEFAULT NULL REFERENCES tsk_os_accounts(os_account_obj_id) DEFAULT NULL"); + + return new CaseDbSchemaVersionNumber(8, 7); } finally { @@ -5977,8 +6084,8 @@ public VirtualDirectory addVirtualDirectory(long parentId, String directoryName, // Insert a row for the virtual directory into the tsk_files table. // INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, - // dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + // dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id,extension,owner_uid, os_account_obj_id) + // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?,?) PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); statement.clearParameters(); statement.setLong(1, newObjId); @@ -6043,8 +6150,13 @@ public VirtualDirectory addVirtualDirectory(long parentId, String directoryName, //extension, since this is not really file we just set it to null statement.setString(21, null); + + statement.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + statement.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(statement); + return new VirtualDirectory(this, newObjId, dataSourceObjectId, directoryName, dirType, metaType, dirFlag, metaFlags, null, null, FileKnown.UNKNOWN, parentPath); @@ -6122,8 +6234,8 @@ public LocalDirectory addLocalDirectory(long parentId, String directoryName, Cas // Insert a row for the local directory into the tsk_files table. // INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, - // dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, sha256, known, mime_type, parent_path, data_source_obj_id) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + // dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, sha256, known, mime_type, parent_path, data_source_obj_id, extension, owner_uid, os_account_obj_id) + // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); statement.clearParameters(); statement.setLong(1, newObjId); @@ -6175,6 +6287,9 @@ public LocalDirectory addLocalDirectory(long parentId, String directoryName, Cas //extension, since this is a directory we just set it to null statement.setString(21, null); + statement.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + statement.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(statement); return new LocalDirectory(this, newObjId, dataSourceObjectId, directoryName, dirType, @@ -6256,8 +6371,8 @@ public LocalFilesDataSource addLocalFilesDataSource(String deviceId, String root // its own object id. // INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, // dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, - // atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id, extension) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + // atime, mtime, md5, known, mime_type, parent_path, data_source_obj_id, extension, owner_uid, os_account_obj_id) + // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?) PreparedStatement preparedStatement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); preparedStatement.clearParameters(); preparedStatement.setLong(1, newObjId); @@ -6287,6 +6402,8 @@ public LocalFilesDataSource addLocalFilesDataSource(String deviceId, String root preparedStatement.setString(19, parentPath); preparedStatement.setLong(20, newObjId); preparedStatement.setString(21, null); //extension, just set it to null + preparedStatement.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + preparedStatement.setNull(23, java.sql.Types.BIGINT); // osAccountObjId connection.executeUpdate(preparedStatement); return new LocalFilesDataSource(this, newObjId, newObjId, deviceId, rootDirectoryName, dirType, metaType, dirFlag, metaFlags, timeZone, null, null, FileKnown.UNKNOWN, parentPath); @@ -6735,7 +6852,7 @@ public FsContent addFileSystemFile(long dataSourceObjId, long fsObjId, } else { parentPath = "/"; } - + PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE_SYSTEM_FILE); statement.clearParameters(); statement.setLong(1, objectId); // obj_is @@ -6769,7 +6886,7 @@ public FsContent addFileSystemFile(long dataSourceObjId, long fsObjId, connection.executeUpdate(statement); DerivedFile derivedFile = new DerivedFile(this, objectId, dataSourceObjId, fileName, dirType, metaType, dirFlag, metaFlags, - size, ctime, crtime, atime, mtime, md5Hash, sha256Hash, null, parentPath, null, parent.getId(), mimeType, null, extension); + size, ctime, crtime, atime, mtime, md5Hash, sha256Hash, null, parentPath, null, parent.getId(), mimeType, null, extension, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); timelineManager.addEventsForNewFile(derivedFile, connection); @@ -6784,8 +6901,8 @@ public FsContent addFileSystemFile(long dataSourceObjId, long fsObjId, dirType, metaType, dirFlag, metaFlags, size, ctime, crtime, atime, mtime, (short) 0, 0, 0, md5Hash, sha256Hash, null, parentPath, mimeType, - extension, fileAttributes); - + extension, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, fileAttributes); + } catch (SQLException ex) { throw new TskCoreException(String.format("Failed to INSERT file system file %s (%s) with parent id %d in tsk_files table", fileName, parentPath, parent.getId()), ex); } finally { @@ -6872,8 +6989,8 @@ public final List<LayoutFile> addLayoutFiles(Content parent, List<TskFileRange> * INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, * has_path, dir_type, meta_type, dir_flags, meta_flags, size, * ctime, crtime, atime, mtime, md5, known, mime_type, - * parent_path, data_source_obj_id,extension) VALUES (?, ?, ?, - * ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + * parent_path, data_source_obj_id,extension, owner_uid, os_account_obj_id) VALUES (?, ?, ?, + * ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?) */ PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); prepStmt.clearParameters(); @@ -6900,6 +7017,10 @@ public final List<LayoutFile> addLayoutFiles(Content parent, List<TskFileRange> //extension, since this is not a FS file we just set it to null prepStmt.setString(21, null); + + prepStmt.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + prepStmt.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(prepStmt); /* @@ -6932,7 +7053,9 @@ public final List<LayoutFile> addLayoutFiles(Content parent, List<TskFileRange> null, null, FileKnown.UNKNOWN, parent.getUniquePath(), - null)); + null, + OsAccount.NO_OWNER_ID, + OsAccount.NO_ACCOUNT)); } transaction.commit(); @@ -7053,8 +7176,8 @@ public final List<LayoutFile> addCarvedFiles(CarvingResult carvingResult) throws * INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, * has_path, dir_type, meta_type, dir_flags, meta_flags, size, * ctime, crtime, atime, mtime, md5, known, mime_type, - * parent_path, data_source_obj_id,extenion) VALUES (?, ?, ?, ?, - * ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + * parent_path, data_source_obj_id,extenion, owner_uid, os_account_obj_id) + * VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) */ PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); prepStmt.clearParameters(); @@ -7083,6 +7206,10 @@ public final List<LayoutFile> addCarvedFiles(CarvingResult carvingResult) throws prepStmt.setString(19, parentPath); // parent path prepStmt.setLong(20, carvedFilesDir.getDataSourceObjectId()); // data_source_obj_id prepStmt.setString(21, extractExtension(carvedFile.getName())); //extension + + prepStmt.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + prepStmt.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(prepStmt); /* @@ -7117,7 +7244,9 @@ public final List<LayoutFile> addCarvedFiles(CarvingResult carvingResult) throws null, null, FileKnown.UNKNOWN, parentPath, - null)); + null, + OsAccount.NO_OWNER_ID, + OsAccount.NO_ACCOUNT)); } transaction.commit(); @@ -7259,6 +7388,9 @@ public DerivedFile addDerivedFile(String fileName, String localPath, final String extension = extractExtension(fileName); //extension statement.setString(21, extension); + + statement.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + statement.setNull(23, java.sql.Types.BIGINT); // osAccountObjId connection.executeUpdate(statement); @@ -7266,7 +7398,7 @@ public DerivedFile addDerivedFile(String fileName, String localPath, addFilePath(connection, newObjId, localPath, encodingType); DerivedFile derivedFile = new DerivedFile(this, newObjId, dataSourceObjId, fileName, dirType, metaType, dirFlag, metaFlags, - savedSize, ctime, crtime, atime, mtime, null, null, null, parentPath, localPath, parentId, null, encodingType, extension); + savedSize, ctime, crtime, atime, mtime, null, null, null, parentPath, localPath, parentId, null, encodingType, extension, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); timelineManager.addEventsForNewFile(derivedFile, connection); transaction.commit(); @@ -7376,7 +7508,7 @@ public DerivedFile updateDerivedFile(DerivedFile derivedFile, String localPath, long dataSourceObjId = getDataSourceObjectId(connection, parentId); final String extension = extractExtension(derivedFile.getName()); return new DerivedFile(this, derivedFile.getId(), dataSourceObjId, derivedFile.getName(), dirType, metaType, dirFlag, metaFlags, - savedSize, ctime, crtime, atime, mtime, null, null, null, parentPath, localPath, parentId, null, encodingType, extension); + savedSize, ctime, crtime, atime, mtime, null, null, null, parentPath, localPath, parentId, null, encodingType, extension, derivedFile.getOwnerUid().orElse(null), derivedFile.getOsAccountObjId().orElse(null)); } catch (SQLException ex) { connection.rollbackTransaction(); throw new TskCoreException("Failed to add derived file to case database", ex); @@ -7508,8 +7640,8 @@ public LocalFile addLocalFile(String fileName, String localPath, // Insert a row for the local/logical file into the tsk_files table. // INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, // dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, known, mime_type, - // parent_path, data_source_obj_id,extension) - // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) + // parent_path, data_source_obj_id,extension, uid_str, os_account_obj_id) + // VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?) PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); statement.clearParameters(); statement.setLong(1, objectId); @@ -7560,6 +7692,9 @@ public LocalFile addLocalFile(String fileName, String localPath, final String extension = extractExtension(fileName); statement.setString(21, extension); + statement.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + statement.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(statement); addFilePath(connection, objectId, localPath, encodingType); LocalFile localFile = new LocalFile(this, @@ -7576,7 +7711,8 @@ public LocalFile addLocalFile(String fileName, String localPath, parent.getId(), parentPath, dataSourceObjId, localPath, - encodingType, extension); + encodingType, extension, + OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT); getTimelineManager().addEventsForNewFile(localFile, connection); return localFile; @@ -7688,8 +7824,8 @@ public LayoutFile addLayoutFile(String fileName, * tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, * meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, * mtime, md5, known, mime_type, parent_path, - * data_source_obj_id,extenion) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, - * ?, ?, ?, ?, ?, ?, ?,?) + * data_source_obj_id,extenion, owner_uid, os_account_obj_id) + * VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) */ PreparedStatement prepStmt = connection.getPreparedStatement(PREPARED_STATEMENT.INSERT_FILE); prepStmt.clearParameters(); @@ -7728,6 +7864,10 @@ public LayoutFile addLayoutFile(String fileName, prepStmt.setLong(20, parent.getDataSource().getId()); // data_source_obj_id prepStmt.setString(21, extractExtension(fileName)); //extension + + prepStmt.setString(22, OsAccount.NO_OWNER_ID); // ownerUid + prepStmt.setNull(23, java.sql.Types.BIGINT); // osAccountObjId + connection.executeUpdate(prepStmt); /* @@ -7762,7 +7902,9 @@ public LayoutFile addLayoutFile(String fileName, null, null, FileKnown.UNKNOWN, parentPath, - null); + null, + OsAccount.NO_OWNER_ID, + OsAccount.NO_ACCOUNT); transaction.commit(); transaction = null; @@ -9070,6 +9212,9 @@ private List<AbstractFile> resultSetToAbstractFiles(ResultSet rs, CaseDbConnecti if (parentPath == null) { parentPath = "/"; //NON-NLS } + + Long osAccountObjId = rs.getLong("os_account_obj_id"); + LayoutFile lf = new LayoutFile(this, rs.getLong("obj_id"), //NON-NLS rs.getLong("data_source_obj_id"), @@ -9079,7 +9224,9 @@ private List<AbstractFile> resultSetToAbstractFiles(ResultSet rs, CaseDbConnecti TSK_FS_NAME_FLAG_ENUM.valueOf(rs.getShort("dir_flags")), rs.getShort("meta_flags"), //NON-NLS rs.getLong("size"), //NON-NLS rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS - rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), parentPath, rs.getString("mime_type")); //NON-NLS + rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), parentPath, + rs.getString("mime_type"), + rs.getString("owner_uid"), osAccountObjId ); //NON-NLS results.add(lf); } else if (type == TSK_DB_FILES_TYPE_ENUM.DERIVED.getFileType()) { final DerivedFile df; @@ -9114,6 +9261,8 @@ private List<AbstractFile> resultSetToAbstractFiles(ResultSet rs, CaseDbConnecti * @throws SQLException */ org.sleuthkit.datamodel.File file(ResultSet rs, FileSystem fs) throws SQLException { + Long osAccountObjId = rs.getLong("os_account_obj_id"); + org.sleuthkit.datamodel.File f = new org.sleuthkit.datamodel.File(this, rs.getLong("obj_id"), //NON-NLS rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS @@ -9125,7 +9274,7 @@ org.sleuthkit.datamodel.File file(ResultSet rs, FileSystem fs) throws SQLExcepti rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS (short) rs.getInt("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS - rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension"), Collections.emptyList()); //NON-NLS + rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension"), rs.getString("owner_uid"), osAccountObjId, Collections.emptyList()); //NON-NLS f.setFileSystem(fs); return f; } @@ -9142,6 +9291,8 @@ org.sleuthkit.datamodel.File file(ResultSet rs, FileSystem fs) throws SQLExcepti * @throws SQLException thrown if SQL error occurred */ Directory directory(ResultSet rs, FileSystem fs) throws SQLException { + Long osAccountObjId = rs.getLong("os_account_obj_id"); + Directory dir = new Directory(this, rs.getLong("obj_id"), rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS rs.getInt("attr_id"), rs.getString("name"), rs.getLong("meta_addr"), rs.getInt("meta_seq"), //NON-NLS @@ -9152,7 +9303,7 @@ Directory directory(ResultSet rs, FileSystem fs) throws SQLException { rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS rs.getShort("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS - rs.getString("parent_path")); //NON-NLS + rs.getString("parent_path"), rs.getString("owner_uid"), osAccountObjId); //NON-NLS dir.setFileSystem(fs); return dir; } @@ -9289,6 +9440,9 @@ private DerivedFile derivedFile(ResultSet rs, CaseDbConnection connection, long if (parentPath == null) { parentPath = ""; } + + Long osAccountObjId = rs.getLong("os_account_obj_id"); + final DerivedFile df = new DerivedFile(this, objId, rs.getLong("data_source_obj_id"), rs.getString("name"), //NON-NLS TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS @@ -9298,7 +9452,8 @@ private DerivedFile derivedFile(ResultSet rs, CaseDbConnection connection, long rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS parentPath, localPath, parentId, rs.getString("mime_type"), - encodingType, rs.getString("extension")); + encodingType, rs.getString("extension"), + rs.getString("owner_uid"), osAccountObjId); return df; } @@ -9342,6 +9497,8 @@ private LocalFile localFile(ResultSet rs, CaseDbConnection connection, long pare if (null == parentPath) { parentPath = ""; } + Long osAccountObjId = rs.getLong("os_account_obj_id"); + LocalFile file = new LocalFile(this, objId, rs.getString("name"), //NON-NLS TSK_DB_FILES_TYPE_ENUM.valueOf(rs.getShort("type")), //NON-NLS TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), //NON-NLS @@ -9351,7 +9508,8 @@ private LocalFile localFile(ResultSet rs, CaseDbConnection connection, long pare rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS rs.getString("mime_type"), rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS parentId, parentPath, rs.getLong("data_source_obj_id"), - localPath, encodingType, rs.getString("extension")); + localPath, encodingType, rs.getString("extension"), + rs.getString("owner_uid"), osAccountObjId); return file; } @@ -9367,6 +9525,7 @@ private LocalFile localFile(ResultSet rs, CaseDbConnection connection, long pare * @throws SQLException */ org.sleuthkit.datamodel.SlackFile slackFile(ResultSet rs, FileSystem fs) throws SQLException { + Long osAccountObjId = rs.getLong("os_account_obj_id"); org.sleuthkit.datamodel.SlackFile f = new org.sleuthkit.datamodel.SlackFile(this, rs.getLong("obj_id"), //NON-NLS rs.getLong("data_source_obj_id"), rs.getLong("fs_obj_id"), //NON-NLS TskData.TSK_FS_ATTR_TYPE_ENUM.valueOf(rs.getShort("attr_type")), //NON-NLS @@ -9378,7 +9537,8 @@ org.sleuthkit.datamodel.SlackFile slackFile(ResultSet rs, FileSystem fs) throws rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), //NON-NLS (short) rs.getInt("mode"), rs.getInt("uid"), rs.getInt("gid"), //NON-NLS rs.getString("md5"), rs.getString("sha256"), FileKnown.valueOf(rs.getByte("known")), //NON-NLS - rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension")); //NON-NLS + rs.getString("parent_path"), rs.getString("mime_type"), rs.getString("extension"), + rs.getString("owner_uid"), osAccountObjId); //NON-NLS f.setFileSystem(fs); return f; } @@ -9432,6 +9592,7 @@ List<Content> fileChildren(ResultSet rs, CaseDbConnection connection, long paren if (parentPath == null) { parentPath = ""; } + Long osAccountObjId = rs.getLong("os_account_obj_id"); final LayoutFile lf = new LayoutFile(this, rs.getLong("obj_id"), rs.getLong("data_source_obj_id"), rs.getString("name"), type, TSK_FS_NAME_TYPE_ENUM.valueOf(rs.getShort("dir_type")), @@ -9440,7 +9601,8 @@ List<Content> fileChildren(ResultSet rs, CaseDbConnection connection, long paren rs.getLong("size"), rs.getLong("ctime"), rs.getLong("crtime"), rs.getLong("atime"), rs.getLong("mtime"), rs.getString("md5"), rs.getString("sha256"), - FileKnown.valueOf(rs.getByte("known")), parentPath, rs.getString("mime_type")); + FileKnown.valueOf(rs.getByte("known")), parentPath, rs.getString("mime_type"), + rs.getString("owner_uid"), osAccountObjId); children.add(lf); break; } @@ -11813,7 +11975,7 @@ private enum PREPARED_STATEMENT { + "VALUES (?, ?, ?, ?, ?," + BlackboardArtifact.ReviewStatus.UNDECIDED.getID() + ")"), //NON-NLS POSTGRESQL_INSERT_ARTIFACT("INSERT INTO blackboard_artifacts (artifact_id, obj_id, artifact_obj_id, data_source_obj_id, artifact_type_id, review_status_id) " //NON-NLS + "VALUES (DEFAULT, ?, ?, ?, ?," + BlackboardArtifact.ReviewStatus.UNDECIDED.getID() + ")"), //NON-NLS - INSERT_ANALYSIS_RESULT("INSERT INTO tsk_analysis_results (obj_id, conclusion, significance, confidence, configuration, justification) " //NON-NLS + INSERT_ANALYSIS_RESULT("INSERT INTO tsk_analysis_results (artifact_obj_id, conclusion, significance, confidence, configuration, justification) " //NON-NLS + "VALUES (?, ?, ?, ?, ?, ?)"), //NON-NLS INSERT_STRING_ATTRIBUTE("INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_text) " //NON-NLS + "VALUES (?,?,?,?,?,?,?)"), //NON-NLS @@ -11849,8 +12011,8 @@ private enum PREPARED_STATEMENT { SELECT_FILE_DERIVATION_METHOD("SELECT tool_name, tool_version, other FROM tsk_files_derived_method WHERE derived_id = ?"), //NON-NLS SELECT_MAX_OBJECT_ID("SELECT MAX(obj_id) AS max_obj_id FROM tsk_objects"), //NON-NLS INSERT_OBJECT("INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?)"), //NON-NLS - INSERT_FILE("INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, sha256, known, mime_type, parent_path, data_source_obj_id,extension) " //NON-NLS - + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), //NON-NLS + INSERT_FILE("INSERT INTO tsk_files (obj_id, fs_obj_id, name, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, sha256, known, mime_type, parent_path, data_source_obj_id, extension, owner_uid, os_account_obj_id ) " //NON-NLS + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), //NON-NLS INSERT_FILE_SYSTEM_FILE("INSERT INTO tsk_files(obj_id, fs_obj_id, data_source_obj_id, attr_type, attr_id, name, meta_addr, meta_seq, type, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, md5, sha256, mime_type, parent_path, extension)" + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), // NON-NLS UPDATE_DERIVED_FILE("UPDATE tsk_files SET type = ?, dir_type = ?, meta_type = ?, dir_flags = ?, meta_flags = ?, size= ?, ctime= ?, crtime= ?, atime= ?, mtime= ?, mime_type = ? " diff --git a/bindings/java/src/org/sleuthkit/datamodel/SpecialDirectory.java b/bindings/java/src/org/sleuthkit/datamodel/SpecialDirectory.java index ba23c440898deedc491bf1f33e6eeb4bcaeb4931..031b8ace7600e4b68659d0b6d04904b1c05b7a06 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SpecialDirectory.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SpecialDirectory.java @@ -48,7 +48,7 @@ public abstract class SpecialDirectory extends AbstractFile { String mimeType) { super(db, objId, dataSourceObjectId, attrType, attrId, name, fileType, metaAddr, metaSeq, dirType, metaType, dirFlag, - metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, null, Collections.emptyList()); + metaFlags, size, ctime, crtime, atime, mtime, modes, uid, gid, md5Hash, sha256Hash, knownState, parentPath, mimeType, null, OsAccount.NO_OWNER_ID, OsAccount.NO_ACCOUNT, Collections.emptyList()); } /** diff --git a/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java b/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java index 36cc1d257c237663286722a7dadbacbba233ffa9..4528f5539c4c7c722137aa2eb5866d93d217363f 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TskCaseDbBridge.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.datamodel; +import com.google.common.base.Strings; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; @@ -26,9 +27,11 @@ import java.util.Arrays; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Queue; import java.util.logging.Level; import java.util.logging.Logger; @@ -55,6 +58,8 @@ class TskCaseDbBridge { private final Map<Long, TskData.TSK_FS_TYPE_ENUM> fsIdToFsType = new HashMap<>(); private final Map<ParentCacheKey, Long> parentDirCache = new HashMap<>(); + private final Map<String, OsAccount> ownerIdToAccountMap = new HashMap<>(); + private static final long BATCH_FILE_THRESHOLD = 500; private final Queue<FileInfo> batchedFiles = new LinkedList<>(); private final Queue<LayoutRangeInfo> batchedLayoutRanges = new LinkedList<>(); @@ -308,6 +313,7 @@ long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize, * @param seq The sequence number from fs_file->meta->seq. * @param parMetaAddr The metadata address of the parent * @param parSeq The parent sequence number if NTFS, -1 otherwise. + * @param ownerUid String uid of the file owner. May be an empty string. * * @return 0 if successful, -1 if not */ @@ -321,7 +327,7 @@ long addFile(long parentObjId, long crtime, long ctime, long atime, long mtime, int meta_mode, int gid, int uid, String escaped_path, String extension, - long seq, long parMetaAddr, long parSeq) { + long seq, long parMetaAddr, long parSeq, String ownerUid) { // Add the new file to the list batchedFiles.add(new FileInfo(parentObjId, @@ -334,7 +340,7 @@ long addFile(long parentObjId, crtime, ctime, atime, mtime, meta_mode, gid, uid, escaped_path, extension, - seq, parMetaAddr, parSeq)); + seq, parMetaAddr, parSeq, ownerUid)); // Add the current files to the database if we've exceeded the threshold or if we // have the root folder. @@ -353,6 +359,50 @@ long addFile(long parentObjId, private long addBatchedFilesToDb() { List<Long> newObjIds = new ArrayList<>(); try { + + // loop through the batch, and make sure owner accounts exist for all the files in the batch. + // If not, create accounts. + Iterator<FileInfo> it = batchedFiles.iterator(); + + beginTransaction(); + while (it.hasNext()) { + FileInfo fileInfo = it.next(); + String ownerUid = fileInfo.ownerUid; + if (Strings.isNullOrEmpty(fileInfo.ownerUid) == false) { + // first check the owner id is in the map, if found, then continue + if (this.ownerIdToAccountMap.containsKey(ownerUid)) { + continue; + } + + // RAMAN TBD: Need to get host by using the data source name and then use that host for creating the OS account below. + Host host = null; + + // query the DB to get the owner account + Optional<OsAccount> ownerAccount = caseDb.getOsAccountManager().getOsAccount(ownerUid, host, trans); + if (ownerAccount.isPresent()) { + // found account - add to map + ownerIdToAccountMap.put(ownerUid, ownerAccount.get()); + } else { + + // account not found in the database, create the account and add to map + commitTransaction(); + + // RAMAN TBD: what should this realm name be? + String realmName = "DUMMY"; + + // create the account + OsAccount newAccount = caseDb.getOsAccountManager().createOsAccount(ownerUid, null, realmName, host); + ownerIdToAccountMap.put(ownerUid, newAccount); + + beginTransaction(); + } + } + } + commitTransaction(); + + + + beginTransaction(); FileInfo fileInfo; while ((fileInfo = batchedFiles.poll()) != null) { @@ -363,6 +413,17 @@ private long addBatchedFilesToDb() { computedParentObjId = getParentObjId(fileInfo); } + Long ownerAccountObjId = OsAccount.NO_ACCOUNT; + if (Strings.isNullOrEmpty(fileInfo.ownerUid) == false) { + if (ownerIdToAccountMap.containsKey(fileInfo.ownerUid)) { + ownerAccountObjId = ownerIdToAccountMap.get(fileInfo.ownerUid).getId(); + } else { + // Error - owner should be in the map at this point!! + throw new TskCoreException(String.format("Failed to add file. Owner account not found for file with parent object ID: %d, name: %s, owner id: %s", fileInfo.parentObjId, fileInfo.name, fileInfo.ownerUid)); + } + } + + long objId = addFileToDb(computedParentObjId, fileInfo.fsObjId, fileInfo.dataSourceObjId, fileInfo.fsType, @@ -373,7 +434,7 @@ private long addBatchedFilesToDb() { fileInfo.crtime, fileInfo.ctime, fileInfo.atime, fileInfo.mtime, fileInfo.meta_mode, fileInfo.gid, fileInfo.uid, null, TskData.FileKnown.UNKNOWN, - fileInfo.escaped_path, fileInfo.extension, + fileInfo.escaped_path, fileInfo.extension, fileInfo.ownerUid, ownerAccountObjId, false, trans); if (fileInfo.fsObjId != fileInfo.parentObjId) { // Add new file ID to the list to send to ingest unless it is the root folder @@ -488,7 +549,7 @@ long addLayoutFile(long parentObjId, null, null, null, null, null, null, null, null, TskData.FileKnown.UNKNOWN, - null, null, + null, null, null, OsAccount.NO_ACCOUNT, true, trans); commitTransaction(); @@ -608,9 +669,9 @@ private class ParentCacheKey { ParentCacheKey(long fsObjId, long metaAddr, long seqNum, String path) { this.fsObjId = fsObjId; this.metaAddr = metaAddr; - if (fsIdToFsType.containsKey(fsObjId) - && (fsIdToFsType.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS) - || fsIdToFsType.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS_DETECT))) { + if (ownerIdToAccountMap.containsKey(fsObjId) + && (ownerIdToAccountMap.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS) + || ownerIdToAccountMap.get(fsObjId).equals(TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS_DETECT))) { this.seqNum = seqNum; } else { this.seqNum = 0; @@ -694,6 +755,7 @@ private class FileInfo { long seq; long parMetaAddr; long parSeq; + String ownerUid; FileInfo(long parentObjId, long fsObjId, long dataSourceObjId, @@ -705,7 +767,7 @@ private class FileInfo { long crtime, long ctime, long atime, long mtime, int meta_mode, int gid, int uid, String escaped_path, String extension, - long seq, long parMetaAddr, long parSeq) { + long seq, long parMetaAddr, long parSeq, String ownerUid) { this.parentObjId = parentObjId; this.fsObjId = fsObjId; @@ -733,6 +795,7 @@ private class FileInfo { this.seq = seq; this.parMetaAddr = parMetaAddr; this.parSeq = parSeq; + this.ownerUid = ownerUid; } } @@ -770,6 +833,8 @@ private class FileInfo { * @param known The file known status. * @param escaped_path The escaped path to the file. * @param extension The file extension. + * @param ownerUid Unique id of the file owner. + * @param ownerAcctObjId Object id of the owner account. * @param hasLayout True if this is a layout file, false otherwise. * @param transaction The open transaction. * @@ -787,8 +852,8 @@ private long addFileToDb(long parentObjId, Long crtime, Long ctime, Long atime, Long mtime, Integer meta_mode, Integer gid, Integer uid, String md5, TskData.FileKnown known, - String escaped_path, String extension, - boolean hasLayout, CaseDbTransaction transaction) throws TskCoreException { + String escaped_path, String extension, String ownerUid, Long ownerAcctObjId, + boolean hasLayout, CaseDbTransaction transaction) throws TskCoreException { try { SleuthkitCase.CaseDbConnection connection = transaction.getConnection(); @@ -796,9 +861,9 @@ private long addFileToDb(long parentObjId, // Insert a row for the local/logical file into the tsk_objects table. // INSERT INTO tsk_objects (par_obj_id, type) VALUES (?, ?) long objectId = caseDb.addObject(parentObjId, TskData.ObjectType.ABSTRACTFILE.getObjectType(), connection); - - String fileInsert = "INSERT INTO tsk_files (fs_obj_id, obj_id, data_source_obj_id, type, attr_type, attr_id, name, meta_addr, meta_seq, dir_type, meta_type, dir_flags, meta_flags, size, crtime, ctime, atime, mtime, mode, gid, uid, md5, known, parent_path, extension, has_layout)" - + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS + + String fileInsert = "INSERT INTO tsk_files (fs_obj_id, obj_id, data_source_obj_id, type, attr_type, attr_id, name, meta_addr, meta_seq, dir_type, meta_type, dir_flags, meta_flags, size, crtime, ctime, atime, mtime, mode, gid, uid, md5, known, parent_path, extension, has_layout, owner_uid, os_account_obj_id)" + + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS PreparedStatement preparedStatement = connection.getPreparedStatement(fileInsert, Statement.NO_GENERATED_KEYS); preparedStatement.clearParameters(); @@ -880,6 +945,15 @@ private long addFileToDb(long parentObjId, } else { preparedStatement.setNull(26, java.sql.Types.INTEGER); } + + preparedStatement.setString(27, ownerUid); // ownerUid + + if (ownerAcctObjId != OsAccount.NO_ACCOUNT) { + preparedStatement.setLong(28, ownerAcctObjId); // + } else { + preparedStatement.setNull(28, java.sql.Types.BIGINT); + } + connection.executeUpdate(preparedStatement); // If this is not a slack file create the timeline events @@ -892,7 +966,7 @@ private long addFileToDb(long parentObjId, TskData.TSK_FS_META_TYPE_ENUM.valueOf((short) metaType), TskData.TSK_FS_NAME_FLAG_ENUM.valueOf(dirFlags), (short) metaFlags, - size, ctime, crtime, atime, mtime, null, null, null, escaped_path, null, parentObjId, null, null, extension); + size, ctime, crtime, atime, mtime, null, null, null, escaped_path, null, parentObjId, null, null, extension, ownerUid, ownerAcctObjId); timelineManager.addEventsForNewFileQuiet(derivedFile, connection); } diff --git a/bindings/java/test/org/sleuthkit/datamodel/DataModelTestSuite.java b/bindings/java/test/org/sleuthkit/datamodel/DataModelTestSuite.java index 8a8826e7df5cb85c2edc7485151b8d20671e8ec6..affe3431e5b361415de4459423838bfe8ca7d803 100644 --- a/bindings/java/test/org/sleuthkit/datamodel/DataModelTestSuite.java +++ b/bindings/java/test/org/sleuthkit/datamodel/DataModelTestSuite.java @@ -46,6 +46,7 @@ CommunicationsManagerTest.class, CaseDbSchemaVersionNumberTest.class, AttributeTest.class, + OsAccountTest.class, // Note: these tests have dependencies on images being placed in the input folder: nps-2009-canon2-gen6, ntfs1-gen, and small2 // org.sleuthkit.datamodel.TopDownTraversal.class, diff --git a/bindings/java/test/org/sleuthkit/datamodel/OsAccountTest.java b/bindings/java/test/org/sleuthkit/datamodel/OsAccountTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b6a0f90fd73e280a473dd4a6791061cd6bfb89c3 --- /dev/null +++ b/bindings/java/test/org/sleuthkit/datamodel/OsAccountTest.java @@ -0,0 +1,293 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2021 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.datamodel; + +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.After; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; + +/** + * + * Tests OsAccount apis. + * + */ +public class OsAccountTest { + + private static final Logger LOGGER = Logger.getLogger(OsAccountTest.class.getName()); + + private static SleuthkitCase caseDB; + + private final static String TEST_DB = "OsAccountApiTest.db"; + + + private static String dbPath = null; + private static FileSystem fs = null; + + public OsAccountTest (){ + + } + + @BeforeClass + public static void setUpClass() { + String tempDirPath = System.getProperty("java.io.tmpdir"); + try { + dbPath = Paths.get(tempDirPath, TEST_DB).toString(); + + // Delete the DB file, in case + java.io.File dbFile = new java.io.File(dbPath); + dbFile.delete(); + if (dbFile.getParentFile() != null) { + dbFile.getParentFile().mkdirs(); + } + + // Create new case db + caseDB = SleuthkitCase.newCase(dbPath); + + SleuthkitCase.CaseDbTransaction trans = caseDB.beginTransaction(); + + Image img = caseDB.addImage(TskData.TSK_IMG_TYPE_ENUM.TSK_IMG_TYPE_DETECT, 512, 1024, "", Collections.emptyList(), "America/NewYork", null, null, null, "first", trans); + + fs = caseDB.addFileSystem(img.getId(), 0, TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_RAW, 0, 0, 0, 0, 0, "", trans); + + trans.commit(); + + + System.out.println("OsAccount Test DB created at: " + dbPath); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to create new case", ex); + } + } + + + @AfterClass + public static void tearDownClass() { + + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void hostTests() throws TskCoreException { + //SleuthkitCase.CaseDbTransaction transaction = caseDB.beginTransaction(); + try { + String HOSTNAME1 = "host1"; + + // Test: create a host + Host host1 = caseDB.getHostManager().createHost(HOSTNAME1); + assertEquals(host1.getName().equalsIgnoreCase(HOSTNAME1), true ); + + + // Test: get a host we just created. + CaseDbTransaction transaction = caseDB.beginTransaction(); + + + Optional<Host> optionalhost1 = caseDB.getHostManager().getHost(HOSTNAME1, transaction); + assertEquals(optionalhost1.isPresent(), true ); + + + String HOSTNAME2 = "host2"; + + // Get a host not yet created + Optional<Host> optionalhost2 = caseDB.getHostManager().getHost(HOSTNAME2, transaction); + assertEquals(optionalhost2.isPresent(), false ); + + transaction.commit(); + + // now create the second host + Host host2 = caseDB.getHostManager().createHost(HOSTNAME2); + assertEquals(host2.getName().equalsIgnoreCase(HOSTNAME2), true ); + + + // now get it again, should be found this time + transaction = caseDB.beginTransaction(); + optionalhost2 = caseDB.getHostManager().getHost(HOSTNAME2, transaction); + assertEquals(optionalhost2.isPresent(), true); + transaction.commit(); + + // create a host that already exists - should transperently return the existting host. + Host host2_2 = caseDB.getHostManager().createHost(HOSTNAME2); + assertEquals(host2_2.getName().equalsIgnoreCase(HOSTNAME2), true ); + + } + catch(Exception ex) { + //transaction.commit(); + } + + } + @Test + public void osAccountRealmTests() throws TskCoreException { + + + SleuthkitCase.CaseDbTransaction transaction = null; + + try { + // TEST: create a domain realm + String realmName1 = "basis"; + OsAccountRealm domainRealm1 = caseDB.getOsAccountRealmManager().createRealmByName(realmName1, null); + + assertEquals(domainRealm1.getName().equalsIgnoreCase(realmName1), true ); + assertEquals(domainRealm1.getNameType(), OsAccountRealm.RealmNameType.EXPRESSED); + assertEquals(domainRealm1.getRealmAddr().orElse(null), null); // verify there is no realm addr + + + + String realmName2 = "win-raman-abcd"; + + String realmAddr2SubAuth = "S-1-5-18-2033736216-1234567890"; + String realmAddr2 = "S-1-5-18-2033736216-1234567890-5432109876"; + + String hostName2 = "win-raman-abcd"; + + + //TEST: create a local realm with single host + // first create a host + Host host2 = caseDB.getHostManager().createHost(hostName2); + // verify host name + assertEquals(host2.getName().equalsIgnoreCase(hostName2), true); + + // create realm + OsAccountRealm localRealm2 = caseDB.getOsAccountRealmManager().createRealmByWindowsSid(realmAddr2, host2); + assertEquals(localRealm2.getRealmAddr().orElse("").equalsIgnoreCase(realmAddr2SubAuth), true ); + assertEquals(localRealm2.getHost().orElse(null).getName().equalsIgnoreCase(hostName2), true); + + + + // update the a realm name + OsAccountRealm updatedRealm2 = caseDB.getOsAccountRealmManager().updateRealmName(localRealm2.getId(), realmName2, OsAccountRealm.RealmNameType.EXPRESSED); + assertEquals(updatedRealm2.getRealmAddr().orElse("").equalsIgnoreCase(realmAddr2SubAuth), true ); + assertEquals(updatedRealm2.getName().equalsIgnoreCase(realmName2), true ); + + + + // get an existing realm - new SID but same sub authority as previously created realm. + String realmAddr3 = realmAddr2SubAuth + "-88888888"; + + transaction = caseDB.beginTransaction(); + Optional<OsAccountRealm> existingRealm3 = caseDB.getOsAccountRealmManager().getRealmByWindowsSid(realmAddr3, null, transaction); + assertEquals(existingRealm3.isPresent(), true); + assertEquals(existingRealm3.get().getRealmAddr().orElse("").equalsIgnoreCase(realmAddr2SubAuth), true ); + assertEquals(existingRealm3.get().getName().equalsIgnoreCase(realmName2), true ); + + transaction.commit(); // faux commit + + } + finally { +// if (transaction != null) { +// transaction.commit(); +// } + } + + + } + + @Test + public void basicOsAccountTests() throws TskCoreException { + + SleuthkitCase.CaseDbTransaction transaction = null; + + try { + String ownerUid1 = "S-1-5-32-544"; + String ownerUid2 = "S-1-5-21-725345543-854245398-1060284298-1003"; + String ownerUid3 = "S-1-5-21-725345543-854245398-1060284298-1004"; + + + String realmName1 = "Realm1"; + String realmName2 = "Realm2"; + + Host host = null; + + // create account realms + OsAccountRealm realm1 = caseDB.getOsAccountRealmManager().createRealmByName(realmName1, host); + OsAccountRealm realm2 = caseDB.getOsAccountRealmManager().createRealmByName(realmName2, host); + + + + // create accounts + OsAccount osAccount1 = caseDB.getOsAccountManager().createOsAccount(ownerUid1, null, realmName1, host); + OsAccount osAccount2 = caseDB.getOsAccountManager().createOsAccount(ownerUid2, null, realmName2, host); + OsAccount osAccount3 = caseDB.getOsAccountManager().createOsAccount(ownerUid3, null, realmName2, host); + + + assertEquals(osAccount1.isAdmin(), false); + assertEquals(osAccount1.getUniqueIdWithinRealm().orElse("").equalsIgnoreCase(ownerUid1), true); + assertEquals(osAccount1.getRealm().getName().equalsIgnoreCase(realmName1), true); + + + + + transaction = caseDB.beginTransaction(); // RAMAN TBD - does update need a transaction + + // Let's update osAccount1 + String fullName1 = "Johnny Depp"; + Long creationTime1 = 1611858618L; + osAccount1.setCreationTime(creationTime1); + osAccount1.setFullName(fullName1); + osAccount1.setIsAdmin(true); + + osAccount1 = caseDB.getOsAccountManager().updateAccount(osAccount1, transaction); + + assertEquals(osAccount1.getCreationTime().orElse(null), creationTime1); + + + transaction.commit(); + transaction = null; + + transaction = caseDB.beginTransaction(); // RAMAN TBD + + + + // now try and create osAccount1 again - it should return the existing account + OsAccount osAccount1_copy1 = caseDB.getOsAccountManager().createOsAccount(ownerUid1, null, realmName1, host); + + + assertEquals(osAccount1_copy1.getUniqueIdWithinRealm().orElse("").equalsIgnoreCase(ownerUid1), true); + assertEquals(osAccount1_copy1.getRealm().getName().equalsIgnoreCase(realmName1), true); + + + assertEquals(osAccount1_copy1.isAdmin(), true); // should be true now + assertEquals(osAccount1_copy1.getFullName().orElse("").equalsIgnoreCase(fullName1), true); + assertEquals(osAccount1.getCreationTime().orElse(null), creationTime1); + + } + + finally { + if (transaction != null) { + transaction.commit(); + } + } + + } +}