Sic.Framework-Nanjing-Baishi/MECF.Framework.Common/Aitex/Core/Account/CredentialManager.cs

497 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 登录凭据管理器。
/// </summary>
public class CredentialManager : Singleton<CredentialManager>
{
#region Variables
/// <summary>
/// 已登录的凭据激活超时时间。
/// </summary>
/// <remarks>
/// 如果凭据超过此时间没有被激活,则自动移除此凭据。
/// </remarks>
public const int LOGIN_CRED_KEEP_ALIVE_TIMEOUT_SEC = 60;
/// <summary>
/// 正在请求登录的凭据的生命时长。
/// </summary>
public const int REQ_LOGIN_CRED_LIFT_TIME_SEC = REQ_LOGIN_DIALOG_LIFT_TIME_SEC + 5;
/// <summary>
/// UI中的请求登录/授权登录对话框存活时间
/// </summary>
public const int REQ_LOGIN_DIALOG_LIFT_TIME_SEC = 30;
private readonly object _syncRoot = new();
/// <summary>
/// 已登录的凭据。
/// </summary>
private readonly Dictionary<Guid, Credential> _dictCredentialsLoggedIn = new ();
/// <summary>
/// 正在等在登录请求确认的凭据LoginName为字典Key。
/// </summary>
private readonly Dictionary<string, Credential> _dictCredentialsRequesting = new ();
private readonly PeriodicJob _threadMonitorCred;
private bool _isInitialized;
private int _maxCredentialAllowed;
#endregion
#region Constructors
/// <summary>
/// 登录凭据管理的构造函数。
/// </summary>
public CredentialManager()
{
_threadMonitorCred = new(1000, OnTimer, "CredentialAliveMonitorThread", true, true);
}
#endregion
#region Properties
/// <summary>
/// 返回当前凭据管理器是否支持多用户登录。
/// </summary>
public bool IsSupportMultiUserLogin => _maxCredentialAllowed > 1;
/// <summary>
/// 返回已登录的用户凭据的数量。
/// </summary>
public int LoggedInCount
{
get
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.Count;
}
}
}
/// <summary>
/// 返回正在请求登录的用户凭据的数量。
/// </summary>
public int LoginRequestingCount
{
get
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.Count;
}
}
}
#endregion
#region Methods
/// <summary>
/// 初始化登录凭据管理器。
/// </summary>
/// <param name="isSupportMultiUsersLogin">是否支持多用户同时登录。</param>
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<Guid>();
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<string>();
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
}
}
/// <summary>
/// 检查指定的用户名是否已经登录。
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
internal bool IsLoggedIn(string userName)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName) != null;
}
}
/// <summary>
/// 检查指定令牌的登录凭据是否存在。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
internal bool IsLoggedIn(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.ContainsKey(token);
}
}
/// <summary>
/// 检查指定的令牌是否已经过期。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
internal bool IsTokenExpired(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.ContainsKey(token);
}
}
/// <summary>
/// 报告客户端处于活动状态。
/// </summary>
/// <param name="myToken">客户端登录凭据</param>
/// <returns></returns>
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);
}
}
/// <summary>
/// 将凭据加入请求列表。
/// </summary>
/// <param name="cred"></param>
/// <exception cref="InvalidOperationException"></exception>
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;
}
}
/// <summary>
/// 将指定的凭据加入字典。
/// </summary>
/// <param name="cred">待授权的凭据</param>
/// <remarks>
/// 给凭据授权前必须调用<see cref="AddRequestingList"/>方法将凭据加入到等待列表中。
/// </remarks>
/// <exception cref="Exception"></exception>
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");
}
}
/// <summary>
/// 强制放行登录授权绕过Single-Session机制。
/// </summary>
/// <param name="cred"></param>
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");
}
}
/// <summary>
/// 接受指定用户名的登录请求。
/// </summary>
/// <param name="userName"></param>
public void Accept(string userName)
{
var cred = GetRequestingCredential(userName);
if (cred != null)
cred.State = CredentialState.Confirmed;
}
/// <summary>
/// 拒绝指定用户名的登录请求。
/// </summary>
/// <param name="userName"></param>
public void Reject(string userName)
{
lock (_syncRoot)
{
var cred = GetRequestingCredential(userName);
if (cred != null)
cred.State = CredentialState.Reject;
}
}
/// <summary>
/// 取消登录请求。
/// </summary>
/// <param name="userName"></param>
public void Cancel(string userName)
{
var cred = GetRequestingCredential(userName);
if (cred != null)
{
cred.LoginRequestCancellationTokenSource.Cancel();
cred.State = CredentialState.RequestCanceled;
}
}
/// <summary>
/// 移除指定令牌的凭据。
/// </summary>
/// <param name="token"></param>
public void Remove(Guid token)
{
lock (_syncRoot)
{
if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred))
{
WriteHistory(cred, "LOGOUT");
}
_dictCredentialsLoggedIn.Remove(token);
}
}
/// <summary>
/// 获取字典中第一个凭据。
/// </summary>
/// <returns></returns>
public Credential GetCredential()
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.Values.FirstOrDefault();
}
}
/// <summary>
/// 获取指定用户名的登录凭据。
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public Credential GetCredential(string userName)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName);
}
}
/// <summary>
/// 获取指定用户名的登录凭据。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public Credential GetCredential(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsLoggedIn.TryGetValue(token, out var cred) ? cred : null;
}
}
/// <summary>
/// 获取指定用户名的正在等在登录请求的凭据。
/// </summary>
/// <param name="userName">用户名</param>
/// <returns></returns>
public Credential GetRequestingCredential(string userName)
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.TryGetValue(userName, out var cred) ? cred : null;
}
}
/// <summary>
/// 校验指定令牌的凭据是否有效。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
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())");
}
/// <summary>
/// 创建一个令牌。
/// </summary>
/// <returns></returns>
public static Guid GenerateToken()
{
return Guid.NewGuid();
}
#endregion
}