Files
DP44/DataPRO/Users/User.cs
2026-04-17 14:55:32 -04:00

804 lines
32 KiB
C#

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
{
/// <summary>
/// this class encapsulates a single user
/// </summary>
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
/// <summary>
/// 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
/// </summary>
public bool IsADefaultUser => UserName == GetDefaultUserName(Role);
/// <summary>
/// a default user id is 1, 2, 3, or 4 based on the default name
/// </summary>
public bool IsADefaultID => Id == GetDefaultUserId(Role);
/// <summary>
/// considered a duplicate if the same default name exists in the db
/// </summary>
public bool IsADuplicate => DuplicateFound(Role);
/// <summary>
/// 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
/// </summary>
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());
}
/// <summary>
/// the user friendly name for a user, a display name
/// </summary>
private string _name;
public string Name
{
get => _name;
set => SetProperty(ref _name, value, Tags.Name.ToString());
}
/// <summary>
/// the user name for the user, a short unique name for the user
/// </summary>
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());
}
/// <summary>
/// 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
/// </summary>
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());
}
}
/// <summary>
/// right now the version field is updated whenever the user is updated, so it keeps
/// track of how many times it has been modified
/// </summary>
private int _version;
public int Version
{
get => _version;
set => SetProperty(ref _version, value, Tags.Version.ToString());
}
/// <summary>
/// the time the user was last modified or commited to the db
/// </summary>
private DateTime _lastModified = DateTime.MinValue;
public DateTime LastModified
{
get => _lastModified;
set => SetProperty(ref _lastModified, value, Tags.LastModified.ToString());
}
/// <summary>
/// the user that last modified the user
/// </summary>
private string _lastModifiedBy = DefaultLastModifiedBy;
public string LastModifiedBy
{
get => _lastModifiedBy;
set => SetProperty(ref _lastModifiedBy, value, Tags.LastModifiedBy.ToString());
}
/// <summary>
/// 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
/// </summary>
private bool _bLocalOnly;
public bool LocalOnly
{
get => _bLocalOnly;
set => SetProperty(ref _bLocalOnly, value, Tags.LocalOnly.ToString());
}
/// <summary>
/// returns true if the user's role is Administrator
/// </summary>
public bool IsAdmin => Role == DefaultRoles.Administrator;
/// <summary>
/// returns true if the user's role is guest
/// </summary>
public bool IsGuest => Role == DefaultRoles.Guest;
/// <summary>
/// lookup of permissions by UI item
/// </summary>
private readonly Dictionary<IUIItems, UserPermissionLevels> _tabPermissions = new Dictionary<IUIItems, UserPermissionLevels>();
/// <summary>
/// lookup of visibility by UI item
/// </summary>
private readonly Dictionary<IUIItems, bool> _showTabs = new Dictionary<IUIItems, bool>();
/// <summary>
/// lock for accessing tab permissions or visibility
/// </summary>
private static readonly object TAB_PERMISSIONS_LOCK = new object();
#endregion properties
#region Constructors and Initializers
/// <summary>
/// used by Add user page to create a new user
/// </summary>
/// <param name="items"></param>
public User(IEnumerable<IUIItems> items)
{
Role = DefaultRoles.Guest;
foreach (var item in items)
{
SetPermission(item, item.GetDefaultRolePermission(Role));
SetShowTabFlag(item, item.GetDefaultRoleVisibility(Role));
}
}
/// <summary>
/// deep copy constructor
/// </summary>
/// <param name="user"></param>
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;
}
/// <summary>
/// reserved for when creating a default user
/// </summary>
/// <param name="username"></param>
public User(string username)
{
_userName = username;
_name = username;
}
/// <summary>
/// creates a user given a role and a list of UI items
/// </summary>
/// <param name="role"></param>
/// <param name="uiItems"></param>
/// <returns></returns>
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;
}
/// <summary>
/// constructor using db row
/// </summary>
/// <param name="row"></param>
/// <param name="items"></param>
/// <param name="permissionLookup"></param>
/// <param name="tagLookup"></param>
public User(DataRow row, IEnumerable<IUIItems> items, IReadOnlyDictionary<int, Dictionary<long, UIItemHelper>> permissionLookup, IReadOnlyDictionary<int, List<int>> tagLookup)
{
var nameToUiItem = new Dictionary<string, IUIItems>();
foreach (var item in items)
{
nameToUiItem[item.GetName()] = item;
}
var fields = Enum.GetValues(typeof(DbOperations.Users.UserFields)).Cast<DbOperations.Users.UserFields>().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<string, List<IUIItems>> 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<string, List<IUIItems>> 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]);
}
}
}
}
}
/// <summary>
/// True if more than one user with this name exists in the Users table
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
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<string>();
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<string> 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<XmlFields>().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<string, List<IUIItems>>();
Parallel.ForEach(items, item =>
{
lock (lookup)
{
if (!lookup.ContainsKey(item.GetName())) { lookup.Add(item.GetName(), new List<IUIItems>()); }
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<string, List<IUIItems>> 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
}
}