using System; using System.Collections.Generic; using System.Linq; using Aitex.Core.RT.DBCore; using Aitex.Core.Util; using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel; namespace Aitex.Core.Account; /// /// 登录凭据管理器。 /// public class CredentialManager : Singleton { #region Variables /// /// 已登录的凭据激活超时时间。 /// /// /// 如果凭据超过此时间没有被激活,则自动移除此凭据。 /// public const int LOGIN_CRED_KEEP_ALIVE_TIMEOUT_SEC = 60; /// /// 正在请求登录的凭据的生命时长。 /// public const int REQ_LOGIN_CRED_LIFT_TIME_SEC = REQ_LOGIN_DIALOG_LIFT_TIME_SEC + 5; /// /// UI中的请求登录/授权登录对话框存活时间 /// public const int REQ_LOGIN_DIALOG_LIFT_TIME_SEC = 30; private readonly object _syncRoot = new(); /// /// 已登录的凭据。 /// private readonly Dictionary _dictCredentialsLoggedIn = new (); /// /// 正在等在登录请求确认的凭据,LoginName为字典Key。 /// private readonly Dictionary _dictCredentialsRequesting = new (); private readonly PeriodicJob _threadMonitorCred; private bool _isInitialized; private int _maxCredentialAllowed; #endregion #region Constructors /// /// 登录凭据管理的构造函数。 /// public CredentialManager() { _threadMonitorCred = new(1000, OnTimer, "CredentialAliveMonitorThread", true, true); } #endregion #region Properties /// /// 返回当前凭据管理器是否支持多用户登录。 /// public bool IsSupportMultiUserLogin => _maxCredentialAllowed > 1; /// /// 返回已登录的用户凭据的数量。 /// public int LoggedInCount { get { lock (_syncRoot) { return _dictCredentialsLoggedIn.Count; } } } /// /// 返回正在请求登录的用户凭据的数量。 /// public int LoginRequestingCount { get { lock (_syncRoot) { return _dictCredentialsRequesting.Count; } } } #endregion #region Methods /// /// 初始化登录凭据管理器。 /// /// 是否支持多用户同时登录。 public void Initialize(bool isSupportMultiUsersLogin) { if (_isInitialized) throw new InvalidOperationException($"{nameof(CredentialManager)} has been initialized."); _isInitialized = true; _maxCredentialAllowed = isSupportMultiUsersLogin ? int.MaxValue : 1; } private bool OnTimer() { lock (_syncRoot) { #region 已登录凭据存活检测 var loginRemovableList = new List(); foreach (var kvp in _dictCredentialsLoggedIn) { var cred = kvp.Value; if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > LOGIN_CRED_KEEP_ALIVE_TIMEOUT_SEC) loginRemovableList.Add(cred.Token); } if (loginRemovableList.Count > 0) { foreach (var token in loginRemovableList) { WriteHistory(_dictCredentialsLoggedIn[token], "EXPIRED"); _dictCredentialsLoggedIn.Remove(token); } } #endregion #region 请求登录凭据存活检测 var requestRemovableList = new List(); foreach (var kvp in _dictCredentialsRequesting) { var cred = kvp.Value; if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > REQ_LOGIN_CRED_LIFT_TIME_SEC) requestRemovableList.Add(kvp.Key); // 移除被取消或拒绝的凭据 if(cred.State is CredentialState.RequestCanceled or CredentialState.Reject) requestRemovableList.Add(kvp.Key); } if (requestRemovableList.Count > 0) { foreach (var loginName in requestRemovableList) { var reason = ""; switch (_dictCredentialsRequesting[loginName].State) { case CredentialState.RequestCanceled: reason = "REQ CANCELED"; break; case CredentialState.Reject: reason = "REQ REJECTED"; break; default: reason = "REQ EXPIRED"; break; } WriteHistory(_dictCredentialsRequesting[loginName], reason); _dictCredentialsRequesting.Remove(loginName); } } return true; #endregion } } /// /// 检查指定的用户名是否已经登录。 /// /// /// internal bool IsLoggedIn(string userName) { lock (_syncRoot) { return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName) != null; } } /// /// 检查指定令牌的登录凭据是否存在。 /// /// /// internal bool IsLoggedIn(Guid token) { lock (_syncRoot) { return _dictCredentialsLoggedIn.ContainsKey(token); } } /// /// 检查指定的令牌是否已经过期。 /// /// /// internal bool IsTokenExpired(Guid token) { lock (_syncRoot) { return _dictCredentialsLoggedIn.ContainsKey(token); } } /// /// 报告客户端处于活动状态。 /// /// 客户端登录凭据 /// public CredentialKeepAliveCheckResult KeepAlive(Guid myToken) { lock (_syncRoot) { if (_dictCredentialsLoggedIn.TryGetValue(myToken, out var loginCred)) { loginCred.LastAliveTime = DateTime.Now; // 刷新时间 if (IsSupportMultiUserLogin) { // 如果当前用户名在请求登录列表中,则返回CredentialKeepAliveResults.RequestingLogin,通知 // 已登录的客户端,当前用户正在请求异地登录。 if (_dictCredentialsRequesting.TryGetValue(loginCred.AccountInfo.LoginName, out var requestingLoginCred) && requestingLoginCred.State == CredentialState.Requesting) { return new(CredentialKeepAliveStates.RequestingLogin, requestingLoginCred); } } else { // 如果仅允许单一用户登录,并且登录请求列表中有等待确认的凭据,则通知当前凭据的Session弹出确认对话框 if (LoginRequestingCount > 0) { var requestingLoginCred = _dictCredentialsRequesting.Values.FirstOrDefault(); if (requestingLoginCred != null) return new(CredentialKeepAliveStates.RequestingLogin, requestingLoginCred); } } return new (CredentialKeepAliveStates.Alive); } return new (CredentialKeepAliveStates.NotFound); } } /// /// 将凭据加入请求列表。 /// /// /// public void AddRequestingList(Credential cred) { lock (_syncRoot) { if (IsSupportMultiUserLogin) { if (_dictCredentialsRequesting.ContainsKey(cred.AccountInfo.LoginName)) throw new InvalidOperationException("the credential has been existed in requesting list."); } else { if (LoginRequestingCount > 0) { var credRequesting = _dictCredentialsRequesting.Values.FirstOrDefault(); throw new InvalidOperationException($"User {credRequesting} are requesting to login, new request is not allowed."); } } WriteHistory(cred, "REQ LOGIN"); _dictCredentialsRequesting[cred.AccountInfo.LoginName] = cred; } } /// /// 将指定的凭据加入字典。 /// /// 待授权的凭据 /// /// 给凭据授权前必须调用方法将凭据加入到等待列表中。 /// /// public void Grant(Credential cred) { if (IsLoggedIn(cred.AccountInfo.LoginName)) throw new Exception($"user {cred.AccountInfo.LoginName} has been logged in."); lock (_syncRoot) { if (_dictCredentialsLoggedIn.Count >= _maxCredentialAllowed) throw new InvalidOperationException("maximum number of login credentials reached"); if (!_dictCredentialsRequesting.ContainsKey(cred.AccountInfo.LoginName)) throw new InvalidOperationException("the credential is not found in requesting list."); cred.State = CredentialState.Alive; _dictCredentialsLoggedIn[cred.Token] = cred; _dictCredentialsRequesting.Remove(cred.AccountInfo.LoginName); WriteHistory(cred, "LOGIN"); } } /// /// 强制放行登录授权,绕过Single-Session机制。 /// /// public void ForceGrant(Credential cred) { lock (_syncRoot) { if (_dictCredentialsLoggedIn.Count >= _maxCredentialAllowed) throw new InvalidOperationException("maximum number of login credentials reached"); cred.State = CredentialState.Alive; _dictCredentialsLoggedIn[cred.Token] = cred; WriteHistory(cred, "LOGIN"); } } /// /// 接受指定用户名的登录请求。 /// /// public void Accept(string userName) { var cred = GetRequestingCredential(userName); if (cred != null) cred.State = CredentialState.Confirmed; } /// /// 拒绝指定用户名的登录请求。 /// /// public void Reject(string userName) { lock (_syncRoot) { var cred = GetRequestingCredential(userName); if (cred != null) cred.State = CredentialState.Reject; } } /// /// 取消登录请求。 /// /// public void Cancel(string userName) { var cred = GetRequestingCredential(userName); if (cred != null) { cred.LoginRequestCancellationTokenSource.Cancel(); cred.State = CredentialState.RequestCanceled; } } /// /// 移除指定令牌的凭据。 /// /// public void Remove(Guid token) { lock (_syncRoot) { if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred)) { WriteHistory(cred, "LOGOUT"); } _dictCredentialsLoggedIn.Remove(token); } } /// /// 获取字典中第一个凭据。 /// /// public Credential GetCredential() { lock (_syncRoot) { return _dictCredentialsLoggedIn.Values.FirstOrDefault(); } } /// /// 获取指定用户名的登录凭据。 /// /// /// public Credential GetCredential(string userName) { lock (_syncRoot) { return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName); } } /// /// 获取指定用户名的登录凭据。 /// /// /// public Credential GetCredential(Guid token) { lock (_syncRoot) { return _dictCredentialsLoggedIn.TryGetValue(token, out var cred) ? cred : null; } } /// /// 获取指定用户名的正在等在登录请求的凭据。 /// /// 用户名 /// public Credential GetRequestingCredential(string userName) { lock (_syncRoot) { return _dictCredentialsRequesting.TryGetValue(userName, out var cred) ? cred : null; } } /// /// 校验指定令牌的凭据是否有效。 /// /// /// public bool ValidateCredential(Guid token) { lock (_syncRoot) { if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred)) { return cred.State == CredentialState.Alive; } return false; } } #endregion #region Static Methods private static void WriteHistory(Credential cred, string operation) { DB.InsertSql( "insert into credentials_history (\"login_name\", \"role_id\", \"host_name\", \"host_ip\", \"host_port\", " + "\"os_version\", \"computer_info\", \"cpu_info\", \"disk_info\", \"operation\", \"operation_time\") " + "values" + $"($sic${cred.AccountInfo.LoginName}$sic$, $sic${cred.RoleID}$sic$, $sic${cred.ClientInfo?.HostName??""}$sic$, " + $"$sic${cred.LoginIP}$sic$, $sic${cred.LoginPort}$sic$, $sic${cred.ClientInfo?.OSVersion??""}$sic$," + $"$sic${cred.ClientInfo?.ComputerSystem??""}$sic$, $sic${cred.ClientInfo?.CpuInfo??""}$sic$, $sic${cred.ClientInfo?.LogicalDisk??""}$sic$, " + $"$sic${operation}$sic$, NOW())"); } /// /// 创建一个令牌。 /// /// public static Guid GenerateToken() { return Guid.NewGuid(); } #endregion }