using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Globalization; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Xml; using DTS.Common.Classes; using DTS.Common.Interface.Tags; using DTS.Common.Storage; // ReSharper disable GenericEnumeratorNotDisposed // ReSharper disable InconsistentNaming // ReSharper disable once CheckNamespace namespace DTS.Slice.Users { /// /// this class encapsulates a single user /// public class User : TagAwareBase { public override TagTypes TagType => TagTypes.User; #region constants and enums public enum DefaultRoles { Administrator = 0, PowerUser = 1, User = 2, Guest = 3 } public enum UserPermissionLevels { Deny = 0, Read = 1, ReadAndExecute = 2, Edit = 3, Admin = 4 } public enum Tags { User, Role, Version, LastModified, LastModifiedBy, Name, UserName, Password, LocalOnly, Permissions, Visibility, Id } public enum XmlFields { UserName, DisplayName, Password, // ReSharper disable once InconsistentNaming IUIItemPermissions, // ReSharper disable once InconsistentNaming IUIItemVisibility, Role, LastModified, LastModifiedBy, Version, LocalOnly, UserTags } private const string DefaultLastModifiedBy = "---"; private const string DefaultAdminPwd = "DTSAdmin"; public const string DefaultPoweruserPwd = "PowerUser123"; private const string DefaultUserPwd = "UserNormal"; private const string DefaultAdminUsername = "Admin"; private const string DefaultGuestUsername = "Guest"; private const string DefaultPoweruserUsername = "PowerUser"; private const string DefaultUserUsername = "User"; private const int InvalidId = -1; public const string DefaultAerouserPwd = "AeroUser123"; public const string DefaultCrashuserPwd = "CrashUser123"; public const string DefaultTSRAIRuserPwd = "TSRAIRUser123"; public const string DefaultAeroUsername = "AeroUser"; public const string DefaultCrashUsername = "CrashUser"; public const string DefaultTSRAIRUsername = "TSRAIRUser"; #endregion #region properties /// /// a default user is a user with one of the default roles and names, like Admin or Guest /// return true if is a default user /// public bool IsADefaultUser => UserName == GetDefaultUserName(Role); /// /// a default user id is 1, 2, 3, or 4 based on the default name /// public bool IsADefaultID => Id == GetDefaultUserId(Role); /// /// considered a duplicate if the same default name exists in the db /// public bool IsADuplicate => DuplicateFound(Role); /// /// hash of password is seeded and stored in xml /// hash is seeded with computable information known about the user /// hash is base64encoding for xml niceness /// xml is encrypted prior to writing to file /// private string _password = ""; public void SetPassword(string s) { var b = Encoding.UTF8.GetBytes($"{s}_{UserName}"); var sha2 = SHA256.Create(); var hashed = sha2.ComputeHash(b); s = Convert.ToBase64String(hashed); _password = s; OnPropertyChanged(Tags.Password.ToString()); } /// /// the user friendly name for a user, a display name /// private string _name; public string Name { get => _name; set => SetProperty(ref _name, value, Tags.Name.ToString()); } /// /// the user name for the user, a short unique name for the user /// private string _userName = string.Empty; public string UserName { get => _userName; set => SetProperty(ref _userName, value, Tags.UserName.ToString()); } private int _id = InvalidId; public int Id { get => _id; set => SetProperty(ref _id, value, Tags.Id.ToString()); } /// /// the default role for the user, if a permission is not explicitly set for the user /// it will use this default role to determine the effect permission /// private DefaultRoles _myRole = DefaultRoles.Guest; public DefaultRoles Role { get => _myRole; set { if (IsADefaultUser && _myRole != value) { throw new NotSupportedException(); } SetProperty(ref _myRole, value, Tags.Role.ToString()); } } /// /// right now the version field is updated whenever the user is updated, so it keeps /// track of how many times it has been modified /// private int _version; public int Version { get => _version; set => SetProperty(ref _version, value, Tags.Version.ToString()); } /// /// the time the user was last modified or commited to the db /// private DateTime _lastModified = DateTime.MinValue; public DateTime LastModified { get => _lastModified; set => SetProperty(ref _lastModified, value, Tags.LastModified.ToString()); } /// /// the user that last modified the user /// private string _lastModifiedBy = DefaultLastModifiedBy; public string LastModifiedBy { get => _lastModifiedBy; set => SetProperty(ref _lastModifiedBy, value, Tags.LastModifiedBy.ToString()); } /// /// a remnant of the multiple database era, this was to mark a user as being local to this db and should never /// be pushed to the central db /// private bool _bLocalOnly; public bool LocalOnly { get => _bLocalOnly; set => SetProperty(ref _bLocalOnly, value, Tags.LocalOnly.ToString()); } /// /// returns true if the user's role is Administrator /// public bool IsAdmin => Role == DefaultRoles.Administrator; /// /// returns true if the user's role is guest /// public bool IsGuest => Role == DefaultRoles.Guest; /// /// lookup of permissions by UI item /// private readonly Dictionary _tabPermissions = new Dictionary(); /// /// lookup of visibility by UI item /// private readonly Dictionary _showTabs = new Dictionary(); /// /// lock for accessing tab permissions or visibility /// private static readonly object TAB_PERMISSIONS_LOCK = new object(); #endregion properties #region Constructors and Initializers /// /// used by Add user page to create a new user /// /// public User(IEnumerable items) { Role = DefaultRoles.Guest; foreach (var item in items) { SetPermission(item, item.GetDefaultRolePermission(Role)); SetShowTabFlag(item, item.GetDefaultRoleVisibility(Role)); } } /// /// deep copy constructor /// /// public User(User user) { Name = user.Name; UserName = user.UserName; Version = user.Version; _password = user._password; Role = user.Role; LastModified = user.LastModified; LastModifiedBy = user.LastModifiedBy; LocalOnly = user.LocalOnly; var e = user._tabPermissions.GetEnumerator(); while (e.MoveNext()) { SetPermission(e.Current.Key, e.Current.Value); } var e2 = user._showTabs.GetEnumerator(); while (e2.MoveNext()) { SetShowTabFlag(e2.Current.Key, e2.Current.Value); } TagIDs = user.TagIDs; } /// /// reserved for when creating a default user /// /// public User(string username) { _userName = username; _name = username; } /// /// creates a user given a role and a list of UI items /// /// /// /// public static User CreateDefault(DefaultRoles role, IUIItems[] uiItems) { var uName = GetDefaultUserName(role); var user = new User(uName) { _version = 1, _myRole = role }; switch (role) { case DefaultRoles.Administrator: user.SetPassword(DefaultAdminPwd); break; case DefaultRoles.Guest: user.SetPassword(""); break; case DefaultRoles.PowerUser: user.SetPassword(DefaultPoweruserPwd); break; case DefaultRoles.User: user.SetPassword(DefaultUserPwd); break; default: throw new NotSupportedException("Unknown role " + role);// TODO: handle exception properly } user._lastModified = DateTime.Now; bool bShow; UserPermissionLevels permission; Parallel.ForEach(uiItems, uiItem => { permission = uiItem.GetDefaultRolePermission(role); bShow = uiItem.GetDefaultRoleVisibility(role); lock (user) { user.SetShowTabFlag(uiItem, bShow); user.SetPermission(uiItem, permission); } }); return user; } public static User CreateNonDefaultPowerUser(string username, IUIItems[] uiItems) { var user = new User(username) { _version = 1, _myRole = DefaultRoles.PowerUser }; switch (username) { case DefaultAeroUsername: user.SetPassword(DefaultAerouserPwd); break; case DefaultCrashUsername: user.SetPassword(DefaultCrashuserPwd); break; case DefaultTSRAIRUsername: user.SetPassword(DefaultTSRAIRuserPwd); break; } user._lastModified = DateTime.Now; bool bShow; UserPermissionLevels permission; Parallel.ForEach(uiItems, uiItem => { permission = uiItem.GetDefaultRolePermission(user.Role); bShow = uiItem.GetDefaultRoleVisibility(user.Role); lock (user) { user.SetShowTabFlag(uiItem, bShow); user.SetPermission(uiItem, permission); } }); return user; } /// /// constructor using db row /// /// /// /// /// public User(DataRow row, IEnumerable items, IReadOnlyDictionary> permissionLookup, IReadOnlyDictionary> tagLookup) { var nameToUiItem = new Dictionary(); foreach (var item in items) { nameToUiItem[item.GetName()] = item; } var fields = Enum.GetValues(typeof(DbOperations.Users.UserFields)).Cast().ToArray(); foreach (var field in fields) { if (DBNull.Value.Equals(row[field.ToString()])) { continue; } switch (field) { case DbOperations.Users.UserFields.ID: Id = Convert.ToInt32(row[field.ToString()]); break; case DbOperations.Users.UserFields.UserName: UserName = (string)row[field.ToString()]; break; case DbOperations.Users.UserFields.Role: _myRole = (DefaultRoles)Convert.ToInt32(row[field.ToString()]); break; case DbOperations.Users.UserFields.Password: _password = (string)row[field.ToString()]; break; case DbOperations.Users.UserFields.LocalOnly: LocalOnly = Convert.ToBoolean(row[field.ToString()]); break; case DbOperations.Users.UserFields.LastModifiedBy: LastModifiedBy = (string)row[field.ToString()]; break; case DbOperations.Users.UserFields.LastModified: LastModified = (DateTime)row[field.ToString()]; break; case DbOperations.Users.UserFields.DisplayName: Name = (string)row[field.ToString()]; break; default: throw new NotSupportedException("unknown field: " + field);// TODO: handle exception properly } } if (permissionLookup.ContainsKey(Id)) { var e = permissionLookup[Id].GetEnumerator(); while (e.MoveNext()) { if (!nameToUiItem.ContainsKey(e.Current.Value.UIItemName)) continue; SetPermission(nameToUiItem[e.Current.Value.UIItemName], (UserPermissionLevels)e.Current.Value.Permission); SetShowTabFlag(nameToUiItem[e.Current.Value.UIItemName], e.Current.Value.IsVisible); } } if (tagLookup.ContainsKey(Id)) { TagIDs = tagLookup[Id].ToArray(); } } private User() { } #endregion #region Methods public void MarkChanged() { OnPropertyChanged(Tags.User.ToString()); } public string GetPasswordHash() { return _password; } public void SetPasswordHash(string value) { _password = value; OnPropertyChanged(Tags.Password.ToString()); } public bool CheckPasswordHash(string hash) { if (null == _password && string.IsNullOrEmpty(hash)) { return true; } if (null == _password) { return false; } return _password == hash; } public bool CheckPassword(string password) { try { if (null == _password && string.IsNullOrEmpty(password)) { return true; } if (null == _password) { return false; } var b = Encoding.UTF8.GetBytes(string.Format("{0}_{1}", password, UserName)); var sha2 = SHA256.Create(); var hashed = sha2.ComputeHash(b); password = Convert.ToBase64String(hashed); return _password == password; } catch (Exception) { return false; }// TODO: handle exception properly } public UserPermissionLevels GetPermission(IUIItems tab, IUIItems parent = null) { if (IsAdmin) { return UserPermissionLevels.Admin; } lock (TAB_PERMISSIONS_LOCK) { if (!_tabPermissions.ContainsKey(tab)) { return null != parent ? GetPermission(parent) : tab.GetDefaultRolePermission(Role); } return _tabPermissions[tab]; } } public string GetPermissionSerialized() { var sb = new StringBuilder(); lock (TAB_PERMISSIONS_LOCK) { var i = _tabPermissions.GetEnumerator(); while (i.MoveNext()) { if (sb.Length > 0) { sb.Append(","); } sb.Append(i.Current.Key.GetName()); sb.Append("="); sb.Append(((int)i.Current.Value).ToString()); } } return sb.ToString(); } public void SetPermission(IUIItems tab, UserPermissionLevels permission) { lock (TAB_PERMISSIONS_LOCK) { if (!_tabPermissions.ContainsKey(tab)) { _tabPermissions.Add(tab, permission); } else { _tabPermissions[tab] = permission; } OnPropertyChanged(Tags.Permissions.ToString()); } } public bool IsShowTabFlagSet(IUIItems tab, IUIItems parent = null) { //15985 I do not see a way to allow the User account to have visibility of system settings tile but not manage users //if current view is named admin, he has visibility //otherwise, if he has admin privileges, he has visibility if (IsAdmin && IsADefaultUser) { return true; } if (GetPermission(tab, parent) == UserPermissionLevels.Admin) { return true; } if (_showTabs.ContainsKey(tab)) { return _showTabs[tab]; } var name = tab.GetName(); if (name.EndsWith("_Page")) { name = name.Substring(0, name.Length - 5); } var matches = from key in _showTabs.Keys where key.GetName() == name select key; if (matches.Any()) { return _showTabs[matches.First()]; } if (null != parent) { return IsShowTabFlagSet(parent); } return tab.GetDefaultRoleVisibility(Role); } public void SetShowTabFlag(IUIItems tab, bool bFlag) { if (_showTabs.ContainsKey(tab)) { _showTabs[tab] = bFlag; } else { _showTabs.Add(tab, bFlag); OnPropertyChanged(Tags.Visibility.ToString()); } } public static string GetDefaultUserName(DefaultRoles role) { switch (role) { case DefaultRoles.Administrator: return DefaultAdminUsername; case DefaultRoles.Guest: return DefaultGuestUsername; case DefaultRoles.PowerUser: return DefaultPoweruserUsername; case DefaultRoles.User: return DefaultUserUsername; default: throw new NotSupportedException("Unknown role " + role); // TODO: handle exception properly } } private static int GetDefaultUserId(DefaultRoles role) { return (int)role + 1; } public string GetVisibilitySerialized() { var sb = new StringBuilder(); var i2 = _showTabs.GetEnumerator(); while (i2.MoveNext()) { if (sb.Length > 0) { sb.Append(","); } sb.Append(i2.Current.Key.GetName()); sb.Append("="); sb.Append(i2.Current.Value ? "1" : "0"); } return sb.ToString(); } protected void SetVisibilitiesFromCSV(string sVisibiles, Dictionary> lookup) { var tokens = sVisibiles.Split(','); foreach (var token in tokens) { var subtokens = token.Split('='); if (2 != subtokens.Length) { continue; } if (!lookup.ContainsKey(subtokens[0])) { continue; } foreach (var item in lookup[subtokens[0]]) { _showTabs[item] = Convert.ToBoolean(Convert.ToInt32(subtokens[1])); } } } protected void SetPermissionsFromCSV(string sPermissions, Dictionary> lookup) { var tokens = sPermissions.Split(','); foreach (var token in tokens) { var subtokens = token.Split('='); if (2 != subtokens.Length) { continue; } if (!lookup.ContainsKey(subtokens[0])) { continue; } foreach (var item in lookup[subtokens[0]]) { if (_tabPermissions != null) { lock (TAB_PERMISSIONS_LOCK) { _tabPermissions[item] = (UserPermissionLevels)Convert.ToInt32(subtokens[1]); } } } } } /// /// True if more than one user with this name exists in the Users table /// /// /// public static bool DuplicateFound(DefaultRoles role) { var userName = role.ToString(); using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.Text; cmd.CommandText = $"SELECT ID FROM [dbo].[Users] WHERE UserName = '{userName}'"; using (var ds = DbOperations.Connection.QueryDataSet(cmd)) { if (ds.Tables.Count > 0) { return ds.Tables[0].Rows.Count > 1; } } } finally { cmd.Connection.Dispose(); } } return false; } public string[] GetLastUsedHardware() { var results = new List(); using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LastUsedHardwareGet.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = Id }); #endregion //cmd.ExecuteNonQuery(); using (var ds = DbOperations.Connection.QueryDataSet(cmd)) { foreach (DataRow dr in ds.Tables[0].Rows) { var hardwareId = Convert.ToString(dr["HardwareID"]); if (!results.Contains(hardwareId)) { results.Add(hardwareId); } } } } finally { cmd.Connection.Dispose(); } } return results.ToArray(); } public void SetLastUsedHardware(List hardware) { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LastUsedHardwareDelete.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = Id }); var errorNumberParam = new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorNumberParam); var errorMessageParam = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessageParam); #endregion params cmd.ExecuteNonQuery(); if (int.Parse(errorNumberParam.Value.ToString()) != 0) { //errorMessageParam.Value } } finally { cmd.Connection.Dispose(); } } if (!hardware.Any()) return; foreach (var h in hardware) { using (var cmd = DbOperations.GetSQLCommand(true)) { try { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = DbOperationsEnum.StoredProcedure.sp_LastUsedHardwareInsert.ToString(); #region params cmd.Parameters.Add(new SqlParameter("@UserId", SqlDbType.Int) { Value = Id }); cmd.Parameters.Add(new SqlParameter("@HardwareID", SqlDbType.NVarChar, 50) { Value = h }); cmd.Parameters.Add(new SqlParameter("@SerialNumber", SqlDbType.NVarChar, 50) { Value = h }); //Not sure why this field is needed, but it was added by Alex var errorNumberParam = new SqlParameter("@errorNumber", SqlDbType.Int) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorNumberParam); var errorMessageParam = new SqlParameter("@errorMessage", SqlDbType.NVarChar, 250) { Direction = ParameterDirection.Output }; cmd.Parameters.Add(errorMessageParam); #endregion params cmd.ExecuteNonQuery(); if (int.Parse(errorNumberParam.Value.ToString()) != 0) { //errorMessageParam.Value } } finally { cmd.Connection.Dispose(); } } } } #endregion #region XML serialization public void WriteXML(ref XmlWriter writer) { writer.WriteStartElement("User"); var fields = Enum.GetValues(typeof(XmlFields)).Cast().ToArray(); foreach (var field in fields) { writer.WriteStartElement(field.ToString()); switch (field) { case XmlFields.DisplayName: writer.WriteString(Name); break; case XmlFields.IUIItemPermissions: writer.WriteString(GetPermissionSerialized()); break; case XmlFields.IUIItemVisibility: writer.WriteString(GetVisibilitySerialized()); break; case XmlFields.LastModified: writer.WriteString(LastModified.ToString(CultureInfo.InvariantCulture)); break; case XmlFields.LastModifiedBy: writer.WriteString(LastModifiedBy); break; case XmlFields.LocalOnly: writer.WriteString(LocalOnly.ToString(CultureInfo.InvariantCulture)); break; case XmlFields.Password: writer.WriteString(_password); break; case XmlFields.Role: writer.WriteString(Role.ToString()); break; case XmlFields.UserName: writer.WriteString(UserName); break; case XmlFields.UserTags: writer.WriteString(GetTagsAsCommaSeparatedString(DbOperations.TagsGet)); break; case XmlFields.Version: writer.WriteString(Version.ToString(CultureInfo.InvariantCulture)); break; default: throw new NotSupportedException(field.ToString()); // TODO: handle exception properly } writer.WriteEndElement(); } writer.WriteEndElement(); } public static User ReadXML(XmlElement root, IUIItems[] items) { var lookup = new Dictionary>(); Parallel.ForEach(items, item => { lock (lookup) { if (!lookup.ContainsKey(item.GetName())) { lookup.Add(item.GetName(), new List()); } if (!lookup[item.GetName()].Contains(item)) { lookup[item.GetName()].Add(item); } } }); var user = new User(); foreach (var node in root.ChildNodes) { if (node is XmlElement element) { ProcessXMLElement(element, ref user, lookup); } } return user; } private static void ProcessXMLElement(XmlElement node, ref User user, Dictionary> lookup) { XmlFields field; if (Enum.TryParse(node.Name, out field)) { switch (field) { case XmlFields.DisplayName: user.Name = node.InnerText; break; case XmlFields.LastModified: user.LastModified = DateTime.Parse(node.InnerText, CultureInfo.InvariantCulture); break; case XmlFields.LastModifiedBy: user.LastModifiedBy = node.InnerText; break; case XmlFields.LocalOnly: user.LocalOnly = Boolean.Parse(node.InnerText); break; case XmlFields.Password: user.SetPasswordHash(node.InnerText); break; case XmlFields.Role: DefaultRoles role; if (Enum.TryParse(node.InnerText, out role)) { user.Role = role; } break; case XmlFields.UserName: user.UserName = node.InnerText; break; case XmlFields.UserTags: user.SetTagsFromCommaSeparatedString(node.InnerText, DbOperations.GetSQLCommand, DbOperations.TagsGet, DbOperations.TagsGetId, DbOperations.TagsInsert); break; case XmlFields.Version: int iTemp; if (int.TryParse(node.InnerText, NumberStyles.Any, CultureInfo.InvariantCulture, out iTemp)) { user.Version = iTemp; } break; case XmlFields.IUIItemPermissions: user.SetPermissionsFromCSV(node.InnerText, lookup); break; case XmlFields.IUIItemVisibility: user.SetVisibilitiesFromCSV(node.InnerText, lookup); break; } } } #endregion XML Serialization } }