Browse Source

Add HandlePeriodicallyChangePassword

pull/15955/head
liangshiwei 4 years ago
parent
commit
1e915a0c42
  1. 26
      modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs
  2. 2
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs
  3. 18
      modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs
  4. 53
      modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs
  5. 45
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs

26
modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs

@ -5,6 +5,9 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Identity.Settings;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
namespace Volo.Abp.Identity.AspNetCore;
@ -12,6 +15,10 @@ public class AbpSignInManager : SignInManager<IdentityUser>
{
protected AbpIdentityOptions AbpOptions { get; }
protected ISettingProvider SettingProvider { get; }
protected IClock Clock { get; }
public AbpSignInManager(
IdentityUserManager userManager,
IHttpContextAccessor contextAccessor,
@ -20,8 +27,9 @@ public class AbpSignInManager : SignInManager<IdentityUser>
ILogger<SignInManager<IdentityUser>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<IdentityUser> confirmation,
IOptions<AbpIdentityOptions> options
) : base(
IOptions<AbpIdentityOptions> options,
ISettingProvider settingProvider,
IClock clock) : base(
userManager,
contextAccessor,
claimsFactory,
@ -30,6 +38,8 @@ public class AbpSignInManager : SignInManager<IdentityUser>
schemes,
confirmation)
{
SettingProvider = settingProvider;
Clock = clock;
AbpOptions = options.Value;
}
@ -91,6 +101,18 @@ public class AbpSignInManager : SignInManager<IdentityUser>
return SignInResult.NotAllowed;
}
var forceUsersToPeriodicallyChangePassword = await SettingProvider.GetAsync<bool>(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword);
if (forceUsersToPeriodicallyChangePassword)
{
var passwordChangePeriodDays = await SettingProvider.GetAsync<int>(IdentitySettingNames.Password.PasswordChangePeriodDays);
var lastPasswordChangeTime = user.LastPasswordChangeTime ?? user.CreationTime;
if (passwordChangePeriodDays > 0 && lastPasswordChangeTime.AddDays(passwordChangePeriodDays) < Clock.Now)
{
Logger.LogWarning($"The user should change password! (username: \"{user.UserName}\", id:\"{user.Id}\")");
return SignInResult.NotAllowed;
}
}
return await base.PreSignInCheck(user);
}
}

2
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUser.cs

@ -383,7 +383,7 @@ public class IdentityUser : FullAuditedAggregateRoot<Guid>, IUser, IHasEntityVer
ShouldChangePasswordOnNextLogin = shouldChangePasswordOnNextLogin;
}
protected virtual void SetLastPasswordChangeTime(DateTime lastPasswordChangeTime)
public virtual void SetLastPasswordChangeTime(DateTime lastPasswordChangeTime)
{
LastPasswordChangeTime = lastPasswordChangeTime;
}

18
modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs

@ -13,6 +13,7 @@ using Volo.Abp.Domain.Services;
using Volo.Abp.Identity.Settings;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
namespace Volo.Abp.Identity;
@ -25,6 +26,8 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
protected ISettingProvider SettingProvider { get; }
protected ICancellationTokenProvider CancellationTokenProvider { get; }
protected IClock Clock { get; }
protected override CancellationToken CancellationToken => CancellationTokenProvider.Token;
public IdentityUserManager(
@ -41,7 +44,8 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider,
IOrganizationUnitRepository organizationUnitRepository,
ISettingProvider settingProvider)
ISettingProvider settingProvider,
IClock clock)
: base(
store,
optionsAccessor,
@ -55,6 +59,7 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
{
OrganizationUnitRepository = organizationUnitRepository;
SettingProvider = settingProvider;
Clock = clock;
RoleRepository = roleRepository;
UserRepository = userRepository;
CancellationTokenProvider = cancellationTokenProvider;
@ -253,4 +258,15 @@ public class IdentityUserManager : UserManager<IdentityUser>, IDomainService
return await UpdateUserAsync(user);
}
protected async override Task<IdentityResult> UpdatePasswordHash(IdentityUser user, string newPassword, bool validatePassword)
{
var result = await base.UpdatePasswordHash(user, newPassword, validatePassword);
if (result.Succeeded && user.Id != default)
{
user.SetLastPasswordChangeTime(Clock.Now);
}
return result;
}
}

53
modules/identityserver/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpResourceOwnerPasswordValidator.cs

@ -17,8 +17,11 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Settings;
using Volo.Abp.IdentityServer.Localization;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
@ -36,6 +39,10 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
protected AbpIdentityOptions AbpIdentityOptions { get; }
protected IOptions<IdentityOptions> IdentityOptions { get; }
protected ISettingProvider SettingProvider { get; }
protected IClock Clock { get; }
public AbpResourceOwnerPasswordValidator(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
@ -44,7 +51,9 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
IStringLocalizer<AbpIdentityServerResource> localizer,
IOptions<AbpIdentityOptions> abpIdentityOptions,
IServiceScopeFactory serviceScopeFactory,
IOptions<IdentityOptions> identityOptions)
IOptions<IdentityOptions> identityOptions,
ISettingProvider settingProvider,
IClock clock)
{
UserManager = userManager;
SignInManager = signInManager;
@ -54,6 +63,8 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
ServiceScopeFactory = serviceScopeFactory;
AbpIdentityOptions = abpIdentityOptions.Value;
IdentityOptions = identityOptions;
SettingProvider = settingProvider;
Clock = clock;
}
/// <summary>
@ -129,6 +140,18 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
return;
}
var forceUsersToPeriodicallyChangePassword = await SettingProvider.GetAsync<bool>(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword);
if (forceUsersToPeriodicallyChangePassword)
{
var passwordChangePeriodDays = await SettingProvider.GetAsync<int>(IdentitySettingNames.Password.PasswordChangePeriodDays);
var lastPasswordChangeTime = user.LastPasswordChangeTime ?? user.CreationTime;
if (passwordChangePeriodDays > 0 && lastPasswordChangeTime.AddDays(passwordChangePeriodDays) < Clock.Now)
{
await HandlePeriodicallyChangePasswordAsync(context, user, context.Password);
return;
}
}
errorDescription = Localizer["LoginIsNotAllowed"];
}
else
@ -201,12 +224,22 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
}
protected virtual async Task HandleShouldChangePasswordOnNextLoginAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword)
{
await HandlerChangePasswordAsync(context, user, currentPassword, ChangePasswordType.ShouldChangePasswordOnNextLogin);
}
protected virtual async Task HandlePeriodicallyChangePasswordAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword)
{
await HandlerChangePasswordAsync(context, user, currentPassword, ChangePasswordType.PeriodicallyChangePassword);
}
protected virtual async Task HandlerChangePasswordAsync(ResourceOwnerPasswordValidationContext context, IdentityUser user, string currentPassword, ChangePasswordType changePasswordType)
{
var changePasswordToken = context.Request?.Raw?["ChangePasswordToken"];
var newPassword = context.Request?.Raw?["NewPassword"];
if (!changePasswordToken.IsNullOrWhiteSpace() && !currentPassword.IsNullOrWhiteSpace() && !newPassword.IsNullOrWhiteSpace())
{
if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, nameof(IdentityUser.ShouldChangePasswordOnNextLogin), changePasswordToken))
if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString(), changePasswordToken))
{
var changePasswordResult = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword);
if (changePasswordResult.Succeeded)
@ -219,7 +252,11 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
ClientId = await FindClientIdAsync(context)
});
user.SetShouldChangePasswordOnNextLogin(false);
if (changePasswordType == ChangePasswordType.ShouldChangePasswordOnNextLogin)
{
user.SetShouldChangePasswordOnNextLogin(false);
}
await UserManager.UpdateAsync(user);
await SetSuccessResultAsync(context, user);
}
@ -237,12 +274,12 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
}
else
{
Logger.LogInformation("Authentication failed for username: {username}, reason: {ShouldChangePasswordOnNextLogin}", context.UserName, nameof(user.ShouldChangePasswordOnNextLogin));
Logger.LogInformation($"Authentication failed for username: {{{context.UserName}}}, reason: {{{changePasswordType.ToString()}}}");
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, nameof(user.ShouldChangePasswordOnNextLogin),
new Dictionary<string, object>()
{
{"userId", user.Id},
{"changePasswordToken", await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, nameof(IdentityUser.ShouldChangePasswordOnNextLogin))}
{"changePasswordToken", await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString())}
});
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
@ -328,4 +365,10 @@ public class AbpResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
return Task.CompletedTask;
}
public enum ChangePasswordType
{
ShouldChangePasswordOnNextLogin,
PeriodicallyChangePassword
}
}

45
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.Password.cs

@ -12,7 +12,9 @@ using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.Settings;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
@ -28,6 +30,8 @@ public partial class TokenController
protected IOptions<IdentityOptions> IdentityOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<IdentityOptions>>();
protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService<IdentitySecurityLogManager>();
protected ISettingProvider SettingProvider => LazyServiceProvider.LazyGetRequiredService<ISettingProvider>();
[UnitOfWork]
protected virtual async Task<IActionResult> HandlePasswordAsync(OpenIddictRequest request)
{
@ -107,6 +111,17 @@ public partial class TokenController
return await HandleShouldChangePasswordOnNextLoginAsync(request, user, request.Password);
}
var forceUsersToPeriodicallyChangePassword = await SettingProvider.GetAsync<bool>(IdentitySettingNames.Password.ForceUsersToPeriodicallyChangePassword);
if (forceUsersToPeriodicallyChangePassword)
{
var passwordChangePeriodDays = await SettingProvider.GetAsync<int>(IdentitySettingNames.Password.PasswordChangePeriodDays);
var lastPasswordChangeTime = user.LastPasswordChangeTime ?? user.CreationTime;
if (passwordChangePeriodDays > 0 && lastPasswordChangeTime.AddDays(passwordChangePeriodDays) < Clock.Now)
{
return await HandlePeriodicallyChangePasswordAsync(request, user, request.Password);
}
}
errorDescription = "You are not allowed to login! Your account is inactive or needs to confirm your email/phone number.";
}
else
@ -216,12 +231,22 @@ public partial class TokenController
}
protected virtual async Task<IActionResult> HandleShouldChangePasswordOnNextLoginAsync(OpenIddictRequest request, IdentityUser user, string currentPassword)
{
return await HandleChangePasswordAsync(request, user, currentPassword, ChangePasswordType.ShouldChangePasswordOnNextLogin);
}
protected virtual async Task<IActionResult> HandlePeriodicallyChangePasswordAsync(OpenIddictRequest request, IdentityUser user, string currentPassword)
{
return await HandleChangePasswordAsync(request, user, currentPassword, ChangePasswordType.PeriodicallyChangePassword);
}
protected virtual async Task<IActionResult> HandleChangePasswordAsync(OpenIddictRequest request, IdentityUser user, string currentPassword, ChangePasswordType changePasswordType)
{
var changePasswordToken = request.GetParameter("ChangePasswordToken")?.ToString();
var newPassword = request.GetParameter("NewPassword")?.ToString();
if (!changePasswordToken.IsNullOrWhiteSpace() && !currentPassword.IsNullOrWhiteSpace() && !newPassword.IsNullOrWhiteSpace())
{
if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, nameof(IdentityUser.ShouldChangePasswordOnNextLogin), changePasswordToken))
if (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString(), changePasswordToken))
{
var changePasswordResult = await UserManager.ChangePasswordAsync(user, currentPassword, newPassword);
if (changePasswordResult.Succeeded)
@ -234,7 +259,11 @@ public partial class TokenController
ClientId = request.ClientId
});
user.SetShouldChangePasswordOnNextLogin(false);
if (changePasswordType == ChangePasswordType.ShouldChangePasswordOnNextLogin)
{
user.SetShouldChangePasswordOnNextLogin(false);
}
await UserManager.UpdateAsync(user);
return await SetSuccessResultAsync(request, user);
}
@ -265,7 +294,7 @@ public partial class TokenController
}
else
{
Logger.LogInformation("Authentication failed for username: {username}, reason: {ShouldChangePasswordOnNextLogin}", request.Username, nameof(user.ShouldChangePasswordOnNextLogin));
Logger.LogInformation($"Authentication failed for username: {{{request.Username}}}, reason: {{{changePasswordType.ToString()}}}");
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext
{
@ -279,12 +308,12 @@ public partial class TokenController
items: new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = nameof(IdentityUser.ShouldChangePasswordOnNextLogin)
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = changePasswordType.ToString()
},
parameters: new Dictionary<string, object>
{
["userId"] = user.Id.ToString("N"),
["changePasswordToken"] = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, nameof(IdentityUser.ShouldChangePasswordOnNextLogin))
["changePasswordToken"] = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, changePasswordType.ToString())
});
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
@ -321,4 +350,10 @@ public partial class TokenController
await UserManager.GetTwoFactorEnabledAsync(user) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
}
public enum ChangePasswordType
{
ShouldChangePasswordOnNextLogin,
PeriodicallyChangePassword
}
}

Loading…
Cancel
Save