using System.Net; using System.Text.Json; using System.Text.RegularExpressions; using XFEExtension.NetCore.Exceptions; using XFEExtension.NetCore.ServerInteractive.Interfaces; using XFEExtension.NetCore.ServerInteractive.Models.ServerModels; using XFEExtension.NetCore.ServerInteractive.Models.UserModels; using XFEExtension.NetCore.StringExtension; namespace XFEExtension.NetCore.ServerInteractive.Utilities.Helpers; /// /// 用户帮助类 /// public static class UserHelper { private static readonly PasswordCredential s_dummyCredential = PasswordHasher.Hash("XFE-virtual-user-password"); /// /// 获取用户 /// /// /// /// public static IUserFaceInfo? GetUser(string id, IEnumerable userInfoList) => userInfoList.FirstOrDefault(user => user.Id == id); /// /// 获取用户(通过Session) /// /// /// /// /// /// /// /// public static UserOperateResult GetUser(string session, string deviceInfo, string ipAddress, IEnumerable encryptedUserLoginModels, IEnumerable userInfoList, out IUserInfo? user) { user = null; if (session.IsNullOrWhiteSpace()) return UserOperateResult.LoginExpired; lock (SessionStoreSynchronization.Gate) { var encryptedUserLoginModel = encryptedUserLoginModels.FirstOrDefault(model => !model.TokenHash.IsNullOrWhiteSpace() && SessionTokenHelper.Matches(session, model.TokenHash)); if (encryptedUserLoginModel is null) { Console.Write(" Session未找到、已过期或属于旧版格式"); return UserOperateResult.LoginExpired; } if (encryptedUserLoginModel.UserLoginModel.DeviceInfo != deviceInfo) { Console.Write($" 设备信息不匹配:session={encryptedUserLoginModel.SessionId}, expected={FormatForLog(encryptedUserLoginModel.UserLoginModel.DeviceInfo)}, actual={FormatForLog(deviceInfo)}"); return UserOperateResult.LoginExpired; } if (encryptedUserLoginModel.RevokedAtUtc is not null || encryptedUserLoginModel.ExpiresAtUtc <= DateTimeOffset.UtcNow) { Console.Write($" Session到期或已撤销:session={encryptedUserLoginModel.SessionId}, expire={encryptedUserLoginModel.ExpiresAtUtc:O}"); return UserOperateResult.LoginExpired; } if (!IsSameIPAddress(encryptedUserLoginModel.UserLoginModel.LastIPAddress, ipAddress)) { Console.Write($" IP地址不匹配:session={encryptedUserLoginModel.SessionId}, expected={encryptedUserLoginModel.UserLoginModel.LastIPAddress}, actual={ipAddress}"); return UserOperateResult.LoginExpired; } if (GetUser(encryptedUserLoginModel.UserLoginModel.Uid, userInfoList) is not IUserInfo userInfo) { Console.Write($" 用户ID未注册:uid={encryptedUserLoginModel.UserLoginModel.Uid}"); return UserOperateResult.UserNotFound; } if (!userInfo.Enable) { Console.Write($" 用户已禁用:uid={encryptedUserLoginModel.UserLoginModel.Uid}"); return UserOperateResult.UserDisabled; } user = userInfo; Console.Write($"({user.UserName})"); return UserOperateResult.Success; } } internal static bool IsSameIPAddress(string loginIPAddress, string requestIPAddress) => loginIPAddress == requestIPAddress || (loginIPAddress is "127.0.0.1" or "::1" && requestIPAddress is "127.0.0.1" or "::1"); private static string FormatForLog(string value) { if (value.IsNullOrEmpty()) return ""; return value.Length <= 16 ? value : $"{value[..16]}..."; } /// /// 获取用户 /// /// /// /// /// /// public static UserOperateResult GetUser(string userName, string password, IEnumerable userInfoList, out IUserInfo? user) => GetUser(userName, password, userInfoList, out user, out _); /// 验证用户名和密码,并指出是否完成了旧明文凭据升级。 public static UserOperateResult GetUser(string userName, string password, IEnumerable userInfoList, out IUserInfo? user, out bool passwordUpgraded) { user = null; passwordUpgraded = false; var userInfo = userInfoList.FirstOrDefault(candidate => candidate.UserName.Equals(userName, StringComparison.Ordinal)); if (userInfo is null) { _ = PasswordHasher.Verify(password, s_dummyCredential); return UserOperateResult.UserNotFound; } bool passwordMatches; if (userInfo.PasswordCredential is not null) passwordMatches = PasswordHasher.Verify(password, userInfo.PasswordCredential); else { passwordMatches = PasswordHasher.FixedTimePlainTextEquals(password, userInfo.Password); _ = PasswordHasher.Verify(password, s_dummyCredential); } if (!passwordMatches) return UserOperateResult.InvalidPassword; if (!userInfo.Enable) return UserOperateResult.UserDisabled; if (userInfo.PasswordCredential is null) { userInfo.PasswordCredential = PasswordHasher.Hash(password); userInfo.Password = string.Empty; passwordUpgraded = true; } user = userInfo; return UserOperateResult.Success; } internal static bool PreparePasswordCredential(IUserInfo user, IUserInfo? existing = null) { if (!user.Password.IsNullOrWhiteSpace()) { user.PasswordCredential = PasswordHasher.Hash(user.Password); user.Password = string.Empty; return true; } if (user.PasswordCredential is not null) { user.Password = string.Empty; return true; } if (existing?.PasswordCredential is not null) { user.PasswordCredential = existing.PasswordCredential; user.Password = string.Empty; return true; } if (existing is not null && !existing.Password.IsNullOrWhiteSpace()) { user.Password = existing.Password; return true; } return false; } /// /// 获取用户 /// /// /// /// /// /// /// public static IUserInfo GetUser(string userName, string password, IEnumerable userInfoList, ref HttpStatusCode statusCode) { var result = GetUser(userName, password, userInfoList, out var user); if (result == UserOperateResult.Success) return user!; statusCode = HttpStatusCode.Forbidden; throw new StopAction(() => { }, OutPutResult(result)); } /// /// 获取用户 /// /// /// /// /// /// /// public static IUserInfo GetUser(string userName, string password, IEnumerable userInfoList, ServerCoreReturnArgs r) { var result = GetUser(userName, password, userInfoList, out var user); return result != UserOperateResult.Success ? throw r.Error(OutPutResult(result), HttpStatusCode.Forbidden) : user!; } /// /// 校验用户权限 /// /// /// /// /// /// public static UserOperateResult ValidateUserPermission(string? userName, string? password, int requiredPermissionLevel, IEnumerable userInfoList) { if (userName.IsNullOrWhiteSpace()) return UserOperateResult.UserNotFound; if (password.IsNullOrWhiteSpace()) return UserOperateResult.InvalidPassword; var result = GetUser(userName, password, userInfoList, out var user); if (result != UserOperateResult.Success) return result; return user!.PermissionLevel < requiredPermissionLevel ? UserOperateResult.PermissionDenied : UserOperateResult.Success; } /// /// 校验用户权限(使用Session) /// /// /// /// /// /// /// /// public static UserOperateResult ValidateUserPermission(string? session, string? deviceInfo, string ipAddress, int requiredPermissionLevel, IEnumerable encryptedUserLoginModels, IEnumerable userInfoList) { if (session.IsNullOrWhiteSpace() || deviceInfo.IsNullOrWhiteSpace()) return UserOperateResult.UserNotFound; var result = GetUser(session, deviceInfo, ipAddress, encryptedUserLoginModels, userInfoList, out var user); if (result != UserOperateResult.Success) return result; return user!.PermissionLevel < requiredPermissionLevel ? UserOperateResult.PermissionDenied : UserOperateResult.Success; } /// /// 校验用户权限(直接传入用户对象) /// /// 用户信息对象 /// 所需权限等级 /// public static UserOperateResult ValidateUserPermission(IUserInfo userInfo, int requiredPermissionLevel) { if (!userInfo.Enable) return UserOperateResult.UserDisabled; return userInfo.PermissionLevel < requiredPermissionLevel ? UserOperateResult.PermissionDenied : UserOperateResult.Success; } /// /// 校验权限 /// /// /// /// /// /// /// public static void ValidatePermission(string? userName, string? password, int requiredPermissionLevel, IEnumerable userInfoList, ref HttpStatusCode statusCode) { var result = ValidateUserPermission(userName, password, requiredPermissionLevel, userInfoList); if (result == UserOperateResult.Success) return; statusCode = HttpStatusCode.Forbidden; throw new StopAction(() => { }, $"\n{OutPutResult(result)}"); } /// /// 校验权限 /// /// /// /// /// /// /// public static void ValidatePermission(string? userName, string? password, int requiredPermissionLevel, IEnumerable userInfoList, ServerCoreReturnArgs r) { var result = ValidateUserPermission(userName, password, requiredPermissionLevel, userInfoList); if (result == UserOperateResult.Success) return; r.StatusCode = HttpStatusCode.Forbidden; throw new StopAction(() => { }, $"\n{OutPutResult(result)}"); } /// /// 校验权限(使用Session) /// /// /// /// /// /// /// /// /// public static void ValidatePermission(string? session, string? deviceInfo, string ipAddress, int requiredPermissionLevel, IEnumerable encryptedUserLoginModels, IEnumerable userInfoList, ServerCoreReturnArgs r) { var result = ValidateUserPermission(session, deviceInfo, ipAddress, requiredPermissionLevel, encryptedUserLoginModels, userInfoList); if (result != UserOperateResult.Success) throw r.Error(OutPutResult(result), HttpStatusCode.Forbidden); } /// /// 校验权限(直接传入用户对象) /// /// 用户信息对象 /// 所需权限等级 /// /// public static void ValidatePermission(IUserInfo userInfo, int requiredPermissionLevel, ref HttpStatusCode statusCode) { var result = ValidateUserPermission(userInfo, requiredPermissionLevel); if (result == UserOperateResult.Success) return; statusCode = HttpStatusCode.Forbidden; throw new StopAction(() => { }, $"\n{OutPutResult(result)}"); } /// /// 校验权限(直接传入用户对象) /// /// 用户信息对象 /// 所需权限等级 /// public static void ValidatePermission(IUserInfo userInfo, int requiredPermissionLevel, ServerCoreReturnArgs r) { var result = ValidateUserPermission(userInfo, requiredPermissionLevel); if (result != UserOperateResult.Success) throw r.Error(OutPutResult(result), HttpStatusCode.Forbidden); } /// /// 输出结果 /// /// /// public static string OutPutResult(UserOperateResult userOperateResult) => userOperateResult switch { UserOperateResult.Success => "操作成功", UserOperateResult.UserNotFound => "用户不存在", UserOperateResult.InvalidPassword => "密码错误", UserOperateResult.UserDisabled => "用户被禁用", UserOperateResult.PermissionDenied => "权限不足", UserOperateResult.LoginExpired => "登录过期", _ => "未知错误" }; /// /// 加密用户登录模型 /// /// /// /// public static string Encrypt(string key, T model) where T : class => AesHelper.Encrypt(JsonSerializer.Serialize(model), key); /// /// 解密用户登录模型 /// /// /// /// public static T Decrypt(string key, string encryptedModel) where T : class, new() => JsonSerializer.Deserialize(AesHelper.Decrypt(encryptedModel, key)) ?? new(); }