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();
+			}
+		}
+
+	}
+}