diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/Dtos/NeedChangePasswordOutput.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/Dtos/NeedChangePasswordOutput.cs new file mode 100644 index 00000000..d1912907 --- /dev/null +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/Dtos/NeedChangePasswordOutput.cs @@ -0,0 +1,8 @@ +namespace Lion.AbpPro.BasicManagement.Users.Dtos; + +public class NeedChangePasswordOutput +{ + public bool NeedChangePassword { get; set; } + + public string Message { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/IUserAppService.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/IUserAppService.cs index 67ef5f6a..e0a35d33 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/IUserAppService.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application.Contracts/Users/IUserAppService.cs @@ -67,5 +67,10 @@ namespace Lion.AbpPro.BasicManagement.Users /// 获取个人信息 /// Task MyProfileAsync(); + + /// + /// 是否需要修改密码 + /// + Task NeedChangePasswordAsync(); } } \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs index 8942a69c..f741099d 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Application/Users/UserAppService.cs @@ -1,3 +1,4 @@ +using System.Security.Claims; using Lion.AbpPro.BasicManagement.Users.Dtos; using Magicodes.ExporterAndImporter.Excel; using Magicodes.ExporterAndImporter.Excel.AspNetCore; @@ -7,6 +8,7 @@ using Volo.Abp.Account; using Volo.Abp.Auditing; using Volo.Abp.Uow; using IdentityRole = Volo.Abp.Identity.IdentityRole; +using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Lion.AbpPro.BasicManagement.Users { @@ -18,19 +20,21 @@ namespace Lion.AbpPro.BasicManagement.Users private readonly IIdentityUserRepository _identityUserRepository; private readonly IExcelExporter _excelExporter; private readonly IOptions _options; + private readonly ISettingProvider _settingProvider; public UserAppService( IIdentityUserAppService identityUserAppService, IdentityUserManager userManager, IIdentityUserRepository userRepository, IExcelExporter excelExporter, - IOptions options) + IOptions options, ISettingProvider settingProvider) { _identityUserAppService = identityUserAppService; _userManager = userManager; _identityUserRepository = userRepository; _excelExporter = excelExporter; _options = options; + _settingProvider = settingProvider; } /// @@ -151,9 +155,35 @@ namespace Lion.AbpPro.BasicManagement.Users result.CheckErrors(); } + await UpdateChangePasswordTimeAsync(identityUser); return result.Succeeded; } + private async Task UpdateChangePasswordTimeAsync(IdentityUser identityUser) + { + var now = Clock.Now.ToString("u"); + var firstChangePasswordTime = identityUser.GetFirstChangePasswordTime(); + if (firstChangePasswordTime == null) + { + // 用户是否第一次修改密码 + await _userManager.AddClaimAsync(identityUser, new Claim(BasicManagementConsts.FirstChangePasswordTime, now)); + await _userManager.AddClaimAsync(identityUser, new Claim(BasicManagementConsts.LastChangePasswordTime, now)); + } + else + { + // 更新最后一次登录时间 + var lastChangePasswordTimeClaim = identityUser.Claims.FirstOrDefault(e => e.ClaimType == BasicManagementConsts.LastChangePasswordTime); + if (lastChangePasswordTimeClaim != null) + { + await _userManager.ReplaceClaimAsync(identityUser, new Claim(lastChangePasswordTimeClaim.ClaimType, lastChangePasswordTimeClaim.ClaimValue), new Claim(BasicManagementConsts.LastChangePasswordTime, now)); + } + else + { + await _userManager.AddClaimAsync(identityUser, new Claim(BasicManagementConsts.LastChangePasswordTime, now)); + } + } + } + /// /// 重置 /// @@ -165,6 +195,7 @@ namespace Lion.AbpPro.BasicManagement.Users await _userManager.RemovePasswordAsync(identityUser); var result = await _userManager.AddPasswordAsync(identityUser, input.Password); result.CheckErrors(); + await UpdateChangePasswordTimeAsync(identityUser); return result.Succeeded; } @@ -205,5 +236,44 @@ namespace Lion.AbpPro.BasicManagement.Users return ObjectMapper.Map(user); } + + public virtual async Task NeedChangePasswordAsync() + { + var result = new NeedChangePasswordOutput(); + // 获取设置 + var value = await _settingProvider.GetOrNullAsync(BasicManagementConsts.EnableNewAccountRequiredChangePassword); + bool.TryParse(value, out var convertValue); + if (!convertValue) return result; + var user = await _userManager.FindByIdAsync(CurrentUser.GetId().ToString()); + if (user == null) + return result; + + var firstChangePasswordTime = user.GetFirstChangePasswordTime(); + if (firstChangePasswordTime == null) + { + result.NeedChangePassword = true; + result.Message = L[BasicManagementErrorCodes.NewPasswordExpire]; + return result; + } + + var lastChangePasswordTime = user.GetLastChangePasswordTime(); + if (!lastChangePasswordTime.HasValue) return result; + + // 获取过期天数 + var expireDays = await _settingProvider.GetOrNullAsync(BasicManagementConsts.PasswordExpireDay); + int.TryParse(expireDays, out var expireDay); + + var remindDays = await _settingProvider.GetOrNullAsync(BasicManagementConsts.PasswordRemindDay); + int.TryParse(remindDays, out var remindDay); + + // 提前7天提示用户需要修改密码 + var day = expireDay - remindDay; + if (lastChangePasswordTime.Value > Clock.Now.AddDays(-day)) return result; + + result.NeedChangePassword = true; + result.Message = L[BasicManagementErrorCodes.OldPasswordExpire]; + return result; + + } } } \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementConsts.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementConsts.cs index 828a8822..ffb74211 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementConsts.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementConsts.cs @@ -6,4 +6,35 @@ public static class BasicManagementConsts public const string NameSpace = "Lion.AbpPro.BasicManagement"; /// 默认语言 public const string DefaultCultureName = "zh-Hans"; + + + /// + /// 新用户首次登录是否强制修改密码 + /// + public const string EnableNewAccountRequiredChangePassword = "EnableNewAccountRequiredChangePassword"; + + /// + /// 启用密码过期是否强制修改密码 + /// + public const string EnableExpireRequiredChangePassword = "EnableExpireRequiredChangePassword"; + + /// + /// 密码过期天数 + /// + public const string PasswordExpireDay = "PasswordExpireDay"; + + /// + /// 密码过期,提前通知天数 + /// + public const string PasswordRemindDay = "PasswordRemindDay"; + + /// + /// 首次修改密码时间 + /// + public const string FirstChangePasswordTime = "FirstChangePasswordTime"; + + /// + /// 最后修改密码时间 + /// + public const string LastChangePasswordTime = "LastChangePasswordTime"; } diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementErrorCodes.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementErrorCodes.cs index 6061fcf1..8624e59a 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementErrorCodes.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/BasicManagementErrorCodes.cs @@ -9,4 +9,7 @@ public static class BasicManagementErrorCodes public const string TenantNotExist = BasicManagementConsts.NameSpace + ":100005"; public const string NotSupportSetConnectionString = BasicManagementConsts.NameSpace + ":100006"; public const string UserNotExist = BasicManagementConsts.NameSpace + ":100007"; + public const string PasswordExpire = BasicManagementConsts.NameSpace + ":100008"; + public const string NewPasswordExpire = BasicManagementConsts.NameSpace + ":100009"; + public const string OldPasswordExpire = BasicManagementConsts.NameSpace + ":100010"; } \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/en.json b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/en.json index b725f9e2..040e298d 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/en.json +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/en.json @@ -1,7 +1,7 @@ { "culture": "en", "texts": { - "MyAccount": "My account", + "MyAccount": "My account", "SamplePageMessage": "A sample page for the BasicManagement module", "Permission:Query": "Query", "Permission:Create": "Create", @@ -18,12 +18,23 @@ "Permission:OrganizationUnitManagement": "OrganizationUnitManagement", "Permission:FeatureManagement": "FeatureManagement", "Setting.Group.System": "System", + "EnableNewAccountRequiredChangePassword": "EnableNewAccountRequiredChangePassword", + "Description:EnableNewAccountRequiredChangePassword": "EnableNewAccountRequiredChangePassword", + "EnableExpireRequiredChangePassword": "EnableExpireRequiredChangePassword", + "Description:EnableExpireRequiredChangePassword": "EnableExpireRequiredChangePassword", + "PasswordExpireDay": "PasswordExpireDay", + "Description:PasswordExpireDay": "PasswordExpireDay", + "PasswordRemindDay": "PasswordRemindDay", + "Description:PasswordRemindDay": "PasswordRemindDay", "Lion.AbpPro.BasicManagement:100001": "OrganizationUnit Not Exist", "Lion.AbpPro.BasicManagement:100002": "UserLockedOut", "Lion.AbpPro.BasicManagement:100003": "UserOrPasswordMismatch", "Lion.AbpPro.BasicManagement:100004": "UserDisabled", "Lion.AbpPro.BasicManagement:100005": "Tenant Not Exist", "Lion.AbpPro.BasicManagement:100006": "Not Support Set ConnectionString", - "Lion.AbpPro.BasicManagement:100007": "User Not Exist" + "Lion.AbpPro.BasicManagement:100007": "User Not Exist", + "Lion.AbpPro.BasicManagement:100008": "Password Has Expire", + "Lion.AbpPro.BasicManagement:100009": "A new user must change the password upon the first login.", + "Lion.AbpPro.BasicManagement:100010": "Your password is about to expire. Please change your password." } } \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/zh-Hans.json b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/zh-Hans.json index a9d214db..a71ddca3 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/zh-Hans.json +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain.Shared/Localization/BasicManagement/zh-Hans.json @@ -18,12 +18,23 @@ "Permission:OrganizationUnitManagement": "组织结构管理", "Permission:FeatureManagement": "功能管理", "Setting.Group.System": "系统", + "EnableNewAccountRequiredChangePassword": "首次登录必须修改密码", + "Description:EnableNewAccountRequiredChangePassword": "首次登录必须修改密码", + "EnableExpireRequiredChangePassword": "定期修改密码", + "Description:EnableExpireRequiredChangePassword": "定期修改密码", + "PasswordExpireDay": "密码过期天数", + "Description:PasswordExpireDay": "密码过期天数", + "PasswordRemindDay": "密码过期提醒天数", + "Description:PasswordRemindDay": "密码过期提醒天数", "Lion.AbpPro.BasicManagement:100001": "组织机构不存在", "Lion.AbpPro.BasicManagement:100002": "用户被锁定", "Lion.AbpPro.BasicManagement:100003": "用户名或者密码错误", "Lion.AbpPro.BasicManagement:100004": "用户已禁用", "Lion.AbpPro.BasicManagement:100005": "租户不存在", "Lion.AbpPro.BasicManagement:100006": "当前模块不支持设置数据库连接字符串", - "Lion.AbpPro.BasicManagement:100007": "用户不存在" + "Lion.AbpPro.BasicManagement:100007": "用户不存在", + "Lion.AbpPro.BasicManagement:100008": "密码已过期,请联系管理员重置密码。", + "Lion.AbpPro.BasicManagement:100009": "新用户首次登录需要修改密码.", + "Lion.AbpPro.BasicManagement:100010": "您的密码即将过期,请修改密码." } } \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/IdentityUserClaimExtensions.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/IdentityUserClaimExtensions.cs new file mode 100644 index 00000000..16900464 --- /dev/null +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/IdentityUserClaimExtensions.cs @@ -0,0 +1,26 @@ +namespace Lion.AbpPro.BasicManagement; + +public static class IdentityUserClaimExtensions +{ + /// + /// 获取用户首次修改密码时间 + /// + public static DateTime? GetFirstChangePasswordTime(this IdentityUser user) + { + var claim = user.Claims.FirstOrDefault(e => e.ClaimType == BasicManagementConsts.FirstChangePasswordTime); + if (claim == null) return null; + DateTime.TryParse(claim.ClaimValue, out var result); + return result; + } + + /// + /// 获取用户最后一次登录时间 + /// + public static DateTime? GetLastChangePasswordTime(this IdentityUser user) + { + var claim = user.Claims.FirstOrDefault(e => e.ClaimType == BasicManagementConsts.LastChangePasswordTime); + if (claim == null) return null; + DateTime.TryParse(claim.ClaimValue, out var result); + return result; + } +} \ No newline at end of file diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettingDefinitionProvider.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettingDefinitionProvider.cs index 457affd6..273baf9a 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettingDefinitionProvider.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettingDefinitionProvider.cs @@ -73,6 +73,45 @@ public class BasicManagementSettingDefinitionProvider : SettingDefinitionProvide BasicManagementSettings.Group.SystemManagement) .WithProperty(AbpProSettingConsts.ControlType.Default, AbpProSettingConsts.ControlType.Number); + + context.Add( + new SettingDefinition(BasicManagementSettings.EnableNewAccountRequiredChangePassword, + "false", + L(BasicManagementConsts.EnableNewAccountRequiredChangePassword), + L($"Description:{BasicManagementConsts.EnableNewAccountRequiredChangePassword}")) + .WithProperty(BasicManagementSettings.Group.Default, + BasicManagementSettings.Group.SystemManagement) + .WithProperty(AbpProSettingConsts.ControlType.Default, + AbpProSettingConsts.ControlType.TypeCheckBox)); + context.Add( + new SettingDefinition(BasicManagementSettings.EnableExpireRequiredChangePassword, + "false", + L(BasicManagementConsts.EnableExpireRequiredChangePassword), + L($"Description:{BasicManagementConsts.EnableExpireRequiredChangePassword}")) + .WithProperty(BasicManagementSettings.Group.Default, + BasicManagementSettings.Group.SystemManagement) + .WithProperty(AbpProSettingConsts.ControlType.Default, + AbpProSettingConsts.ControlType.TypeCheckBox)); + + context.Add( + new SettingDefinition(BasicManagementSettings.PasswordExpireDay, + "90", + L(BasicManagementConsts.PasswordExpireDay), + L($"Description:{BasicManagementConsts.PasswordExpireDay}")) + .WithProperty(BasicManagementSettings.Group.Default, + BasicManagementSettings.Group.SystemManagement) + .WithProperty(AbpProSettingConsts.ControlType.Default, + AbpProSettingConsts.ControlType.Number)); + + context.Add( + new SettingDefinition(BasicManagementSettings.PasswordRemindDay, + "7", + L(BasicManagementConsts.PasswordRemindDay), + L($"Description:{BasicManagementConsts.PasswordRemindDay}")) + .WithProperty(BasicManagementSettings.Group.Default, + BasicManagementSettings.Group.SystemManagement) + .WithProperty(AbpProSettingConsts.ControlType.Default, + AbpProSettingConsts.ControlType.Number)); } diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettings.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettings.cs index 7e2707e0..9ae035a4 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettings.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.Domain/Settings/BasicManagementSettings.cs @@ -3,6 +3,26 @@ public static class BasicManagementSettings { + /// + /// 新用户首次登录是否强制修改密码 + /// + public const string EnableNewAccountRequiredChangePassword = BasicManagementConsts.EnableNewAccountRequiredChangePassword; + + /// + /// 启用密码过期是否强制修改密码 + /// + public const string EnableExpireRequiredChangePassword = BasicManagementConsts.EnableExpireRequiredChangePassword; + + /// + /// 定期修改密码天数默认90天 + /// + public const string PasswordExpireDay = BasicManagementConsts.PasswordExpireDay; + + /// + /// 密码过期,提前通知天数 + /// + public const string PasswordRemindDay = BasicManagementConsts.PasswordRemindDay; + /// /// 系统控制分组 /// diff --git a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.HttpApi/Systems/UserController.cs b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.HttpApi/Systems/UserController.cs index 778f7204..a95a3935 100644 --- a/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.HttpApi/Systems/UserController.cs +++ b/aspnet-core/modules/BasicManagement/src/Lion.AbpPro.BasicManagement.HttpApi/Systems/UserController.cs @@ -98,5 +98,12 @@ namespace Lion.AbpPro.BasicManagement.Systems { return _userAppService.MyProfileAsync(); } + + [HttpPost("needChangePassword")] + [SwaggerOperation(summary: "是否需要修改密码", Tags = new[] { "Users" })] + public Task NeedChangePasswordAsync() + { + return _userAppService.NeedChangePasswordAsync(); + } } } \ No newline at end of file