diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs index 4c60a6ce37..55ece86be2 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs @@ -49,7 +49,7 @@ namespace Volo.Abp.AspNetCore.SecurityLog WebClientInfoProvider = webClientInfoProvider; } - public override async Task CreateAsync() + protected override async Task CreateAsync() { var securityLogInfo = await base.CreateAsync(); diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs index 0f5d40567c..fcc03965f2 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -18,17 +19,19 @@ namespace Volo.Abp.SecurityLog SecurityLogOptions = securityLogOptions.Value; } - public virtual Task CreateAsync() + public async Task SaveAsync(Action saveAction) + { + var securityLogInfo = await CreateAsync(); + saveAction?.Invoke(securityLogInfo); + await SecurityLogStore.SaveAsync(securityLogInfo); + } + + protected virtual Task CreateAsync() { return Task.FromResult(new SecurityLogInfo { ApplicationName = SecurityLogOptions.ApplicationName }); } - - public async Task SaveAsync(SecurityLogInfo securityLogInfo) - { - await SecurityLogStore.SaveAsync(securityLogInfo); - } } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs index f762522509..0bedfd0411 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs @@ -1,11 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Volo.Abp.SecurityLog { public interface ISecurityLogManager { - Task CreateAsync(); - - Task SaveAsync(SecurityLogInfo securityLogInfo); + Task SaveAsync(Action saveAction); } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs index 11f46c7527..6185777b97 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs @@ -35,8 +35,6 @@ namespace Volo.Abp.SecurityLog public string ClientId { get; set; } - public string ClientName { get; set; } - public string CorrelationId { get; set; } public string ClientIpAddress { get; set; } diff --git a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs index af157ed629..0d90972792 100644 --- a/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs @@ -14,6 +14,8 @@ using System.Security.Principal; using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.MultiTenancy; using Volo.Abp.SecurityLog; using Volo.Abp.Settings; @@ -128,7 +130,12 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); - await CreateSecurityLog("Login_" + result); + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); if (result.RequiresTwoFactor) { diff --git a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs index 38e8e7e7c2..69b57a42f4 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs @@ -6,7 +6,9 @@ using Volo.Abp.Account.Localization; using Volo.Abp.Account.Settings; using Volo.Abp.Account.Web.Areas.Account.Controllers.Models; using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.EventBus.Local; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.SecurityLog; using Volo.Abp.Settings; using Volo.Abp.Validation; @@ -26,20 +28,22 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers protected SignInManager SignInManager { get; } protected IdentityUserManager UserManager { get; } protected ISettingProvider SettingProvider { get; } - protected ISecurityLogManager SecurityLogManager { get; } + + protected ILocalEventBus LocalEventBus { get; } public AccountController( SignInManager signInManager, IdentityUserManager userManager, ISettingProvider settingProvider, - ISecurityLogManager securityLogManager) + ISecurityLogManager securityLogManager, + ILocalEventBus localEventBus) { LocalizationResource = typeof(AccountResource); SignInManager = signInManager; UserManager = userManager; SettingProvider = settingProvider; - SecurityLogManager = securityLogManager; + LocalEventBus = localEventBus; } [HttpPost] @@ -51,23 +55,33 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers ValidateLoginInfo(login); await ReplaceEmailToUsernameOfInputIfNeeds(login); - var loginResult = GetAbpLoginResult(await SignInManager.PasswordSignInAsync( + var signInResult = await SignInManager.PasswordSignInAsync( login.UserNameOrEmailAddress, login.Password, login.RememberMe, true - )); + ); - await CreateSecurityLog("Login_" + loginResult.Result); + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = signInResult.ToIdentitySecurityLogAction(), + UserName = login.UserNameOrEmailAddress + }); - return loginResult; + return GetAbpLoginResult(signInResult); } [HttpGet] [Route("logout")] public virtual async Task Logout() { - await CreateSecurityLog("Logout"); + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); } @@ -133,7 +147,7 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers return new AbpLoginResult(LoginResultType.InvalidUserNameOrPassword); } - return new AbpLoginResult(LoginResultType.Success); + return new AbpLoginResult(LoginResultType.Succeeded); } protected virtual void ValidateLoginInfo(UserLoginInfo login) @@ -161,13 +175,5 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers throw new UserFriendlyException(L["LocalLoginDisabledMessage"]); } } - - protected virtual async Task CreateSecurityLog(string action) - { - var securityLog = await SecurityLogManager.CreateAsync(); - securityLog.Identity = "Web"; - securityLog.Action = action; - await SecurityLogManager.SaveAsync(securityLog); - } } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs index d7fd0f8239..e645c9c9cf 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.Account.Localization; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; +using Volo.Abp.EventBus.Local; using Volo.Abp.Identity; -using Volo.Abp.SecurityLog; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace Volo.Abp.Account.Web.Pages.Account @@ -17,7 +15,7 @@ namespace Volo.Abp.Account.Web.Pages.Account { public SignInManager SignInManager { get; set; } public IdentityUserManager UserManager { get; set; } - public ISecurityLogManager SecurityLogManager { get; } + public ILocalEventBus LocalEventBus { get; set; } protected AccountPageModel() { @@ -79,13 +77,5 @@ namespace Volo.Abp.Account.Web.Pages.Account { return "~/"; //TODO: ??? } - - protected virtual async Task CreateSecurityLog(string action) - { - var securityLog = await SecurityLogManager.CreateAsync(); - securityLog.Identity = "Web"; - securityLog.Action = action; - await SecurityLogManager.SaveAsync(securityLog); - } } } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 2916e60b4f..06903ad44f 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -13,11 +13,12 @@ using System.Threading.Tasks; using Volo.Abp.Account.Settings; using Volo.Abp.Auditing; using Volo.Abp.Identity; +using Volo.Abp.Identity.AspNetCore; using Volo.Abp.Security.Claims; -using Volo.Abp.SecurityLog; using Volo.Abp.Settings; using Volo.Abp.Validation; using IdentityUser = Volo.Abp.Identity.IdentityUser; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace Volo.Abp.Account.Web.Pages.Account { @@ -95,7 +96,12 @@ namespace Volo.Abp.Account.Web.Pages.Account true ); - await CreateSecurityLog(result.ToString()); + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = result.ToIdentitySecurityLogAction(), + UserName = LoginInput.UserNameOrEmailAddress + }); if (result.RequiresTwoFactor) { @@ -184,7 +190,14 @@ namespace Volo.Abp.Account.Web.Pages.Account bypassTwoFactor: true ); - await CreateSecurityLog(result.ToString()); + if (!result.Succeeded) + { + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = "Login" + result + }); + } if (result.IsLockedOut) { @@ -208,6 +221,15 @@ namespace Volo.Abp.Account.Web.Pages.Account var user = await CreateExternalUserAsync(info); await SignInManager.SignInAsync(user, false); + + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = result.ToIdentitySecurityLogAction(), + UserName = user.Name, + TenantId = user.TenantId + }); + return RedirectSafely(returnUrl, returnUrlHash); } diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs index 8cb2c2bc1e..a9d4ed9a53 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Logout.cshtml.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Identity; namespace Volo.Abp.Account.Web.Pages.Account { @@ -15,6 +16,12 @@ namespace Volo.Abp.Account.Web.Pages.Account public virtual async Task OnGetAsync() { + await LocalEventBus.PublishAsync(new SecurityLogEvent + { + Identity = IdentitySecurityLogIdentityConsts.Identity, + Action = IdentitySecurityLogActionConsts.Logout + }); + await SignInManager.SignOutAsync(); if (ReturnUrl != null) { diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs new file mode 100644 index 0000000000..39e3ae92fc --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/SignInResultExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Identity; + +namespace Volo.Abp.Identity.AspNetCore +{ + public static class SignInResultExtensions + { + public static string ToIdentitySecurityLogAction(this SignInResult result) + { + if (result.Succeeded) + { + return IdentitySecurityLogActionConsts.LoginSucceeded; + } + + if (result.IsLockedOut) + { + return IdentitySecurityLogActionConsts.LoginLockedout; + } + + if (result.RequiresTwoFactor) + { + return IdentitySecurityLogActionConsts.LoginRequiresTwoFactor; + } + + if (result.IsNotAllowed) + { + return IdentitySecurityLogActionConsts.LoginNotAllowed; + } + + if (!result.Succeeded) + { + return IdentitySecurityLogActionConsts.LoginFailed; + } + + return IdentitySecurityLogActionConsts.LoginFailed; + } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs new file mode 100644 index 0000000000..ea5276e673 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogActionConsts.cs @@ -0,0 +1,33 @@ +namespace Volo.Abp.Identity +{ + public class IdentitySecurityLogActionConsts + { + public static string LoginSucceeded { get; set; } = "LoginSucceeded"; + + public static string LoginLockedout { get; set; } = "LoginLockedout"; + + public static string LoginNotAllowed { get; set; } = "LoginNotAllowed"; + + public static string LoginRequiresTwoFactor { get; set; } = "LoginRequiresTwoFactor"; + + public static string LoginFailed { get; set; } = "LoginFailed"; + + public static string LoginInvalidUserName { get; set; } = "LoginInvalidUserName"; + + public static string LoginInvalidUserNameOrPassword { get; set; } = "LoginInvalidUserNameOrPassword"; + + public static string Logout { get; set; } = "Logout"; + + public static string ChangeUserName { get; set; } = "ChangeUserName"; + + public static string ChangeEmail { get; set; } = "ChangeEmail"; + + public static string ChangePhoneNumber { get; set; } = "ChangePhoneNumber"; + + public static string ChangePassword { get; set; } = "ChangePassword"; + + public static string TwoFactorEnabled { get; set; } = "TwoFactorEnabled"; + + public static string TwoFactorDisabled { get; set; } = "TwoFactorDisabled"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs new file mode 100644 index 0000000000..5616dfb4e6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentitySecurityLogIdentityConsts.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Identity +{ + public static class IdentitySecurityLogIdentityConsts + { + public static string Identity { get; set; } = "Identity"; + + public static string IdentityExternal { get; set; } = "IdentityExternal"; + + public static string IdentityTwoFactor { get; set; } = "IdentityTwoFactor"; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs index 58349e8b69..0ef3582af1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Microsoft/AspNetCore/Identity/AbpIdentityResultExtensions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Localization; using Volo.Abp.Identity; +using Volo.Abp.Localization; using Volo.Abp.Text.Formatting; namespace Microsoft.AspNetCore.Identity @@ -48,12 +49,15 @@ namespace Microsoft.AspNetCore.Identity if (!localizedString.ResourceNotFound) { - var englishLocalizedString = localizer.WithCulture(CultureInfo.GetCultureInfo("en"))[key]; - if (!englishLocalizedString.ResourceNotFound) + using (CultureHelper.Use(CultureInfo.GetCultureInfo("en"))) { - if (FormattedStringValueExtracter.IsMatch(error.Description, englishLocalizedString.Value, out var values)) + var englishLocalizedString = localizer[key]; + if (!englishLocalizedString.ResourceNotFound) { - return string.Format(localizedString.Value, values.Cast().ToArray()); + if (FormattedStringValueExtracter.IsMatch(error.Description, englishLocalizedString.Value, out var values)) + { + return string.Format(localizedString.Value, values.Cast().ToArray()); + } } } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogEvent.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogEvent.cs new file mode 100644 index 0000000000..d8857d02d9 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogEvent.cs @@ -0,0 +1,18 @@ +using System; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity +{ + public class SecurityLogEvent : IMultiTenant + { + public Guid? TenantId { get; set; } + + public string Identity { get; set; } + + public string Action { get; set; } + + public string UserName { get; set; } + + public string ClientId { get; set; } + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogHandler.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogHandler.cs new file mode 100644 index 0000000000..43321668b1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/SecurityLogHandler.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus; +using Volo.Abp.Security.Claims; +using Volo.Abp.SecurityLog; +using Volo.Abp.Uow; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity +{ + public class SecurityLogHandler : ILocalEventHandler, ITransientDependency + { + protected ISecurityLogManager SecurityLogManager { get; } + protected IdentityUserManager UserManager { get; } + protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; } + protected IUserClaimsPrincipalFactory UserClaimsPrincipalFactory { get; } + protected ICurrentUser CurrentUser { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + + public SecurityLogHandler( + ISecurityLogManager securityLogManager, + IdentityUserManager userManager, + ICurrentPrincipalAccessor currentPrincipalAccessor, + IUserClaimsPrincipalFactory userClaimsPrincipalFactory, + ICurrentUser currentUser, + IUnitOfWorkManager unitOfWorkManager) + { + SecurityLogManager = securityLogManager; + UserManager = userManager; + CurrentPrincipalAccessor = currentPrincipalAccessor; + UserClaimsPrincipalFactory = userClaimsPrincipalFactory; + CurrentUser = currentUser; + UnitOfWorkManager = unitOfWorkManager; + } + + public async Task HandleEventAsync(SecurityLogEvent eventData) + { + Action securityLogAction = securityLog => + { + securityLog.Identity = eventData.Identity; + securityLog.Action = eventData.Action; + + if (securityLog.UserName.IsNullOrWhiteSpace()) + { + securityLog.UserName = eventData.UserName; + } + + if (securityLog.ClientId.IsNullOrWhiteSpace()) + { + securityLog.ClientId = eventData.ClientId; + } + }; + + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + if (CurrentUser.IsAuthenticated) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + else + { + if (eventData.UserName.IsNullOrWhiteSpace()) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + else + { + var user = await UserManager.FindByNameAsync(eventData.UserName); + if (user != null) + { + using (CurrentPrincipalAccessor.Change(await UserClaimsPrincipalFactory.CreateAsync(user))) + { + await SecurityLogManager.SaveAsync(securityLogAction); + } + } + } + } + + await uow.CompleteAsync(); + } + } + } +}