Browse Source

Add some infrastructure for security logs.

pull/4675/head
maliming 6 years ago
parent
commit
65957cbb6e
  1. 24
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs
  2. 74
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs
  3. 43
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs
  4. 9
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs
  5. 21
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs
  6. 34
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs
  7. 11
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs
  8. 9
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs
  9. 11
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs
  10. 22
      framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs
  11. 9
      framework/src/Volo.Abp.Security/Volo/Abp/Users/SecurityLog/IUserSecurityLogStore.cs
  12. 3
      modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs
  13. 25
      modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs
  14. 11
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs
  15. 8
      modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

24
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Auditing/AspNetCoreAuditLogContributor.cs

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -35,14 +36,15 @@ namespace Volo.Abp.AspNetCore.Auditing
context.AuditInfo.Url = BuildUrl(httpContext); context.AuditInfo.Url = BuildUrl(httpContext);
} }
var clientInfoProvider = context.ServiceProvider.GetRequiredService<IWebClientInfoProvider>();
if (context.AuditInfo.ClientIpAddress == null) if (context.AuditInfo.ClientIpAddress == null)
{ {
context.AuditInfo.ClientIpAddress = GetClientIpAddress(httpContext); context.AuditInfo.ClientIpAddress = clientInfoProvider.ClientIpAddress;
} }
if (context.AuditInfo.BrowserInfo == null) if (context.AuditInfo.BrowserInfo == null)
{ {
context.AuditInfo.BrowserInfo = GetBrowserInfo(httpContext); context.AuditInfo.BrowserInfo = clientInfoProvider.BrowserInfo;
} }
//TODO: context.AuditInfo.ClientName //TODO: context.AuditInfo.ClientName
@ -62,24 +64,6 @@ namespace Volo.Abp.AspNetCore.Auditing
} }
} }
protected virtual string GetBrowserInfo(HttpContext httpContext)
{
return httpContext.Request?.Headers?["User-Agent"];
}
protected virtual string GetClientIpAddress(HttpContext httpContext)
{
try
{
return httpContext.Connection?.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
}
protected virtual string BuildUrl(HttpContext httpContext) protected virtual string BuildUrl(HttpContext httpContext)
{ {
//TODO: Add options to include/exclude query, schema and host //TODO: Add options to include/exclude query, schema and host

74
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/SecurityLog/AspNetCoreSecurityLogManager.cs

@ -0,0 +1,74 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.Clients;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.SecurityLog;
using Volo.Abp.Timing;
using Volo.Abp.Tracing;
using Volo.Abp.Users;
namespace Volo.Abp.AspNetCore.SecurityLog
{
[Dependency(ReplaceServices = true)]
public class AspNetCoreSecurityLogManager : DefaultSecurityLogManager
{
protected ILogger<AspNetCoreSecurityLogManager> Logger { get; }
protected IClock Clock { get; }
protected ICurrentUser CurrentUser { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ICurrentClient CurrentClient { get; }
protected IHttpContextAccessor HttpContextAccessor { get; }
protected ICorrelationIdProvider CorrelationIdProvider { get; }
protected IWebClientInfoProvider WebClientInfoProvider { get; }
public AspNetCoreSecurityLogManager(
IOptions<AbpSecurityLogOptions> securityLogOptions,
ISecurityLogStore securityLogStore,
ILogger<AspNetCoreSecurityLogManager> logger,
IClock clock,
ICurrentUser currentUser,
ICurrentTenant currentTenant,
ICurrentClient currentClient,
IHttpContextAccessor httpContextAccessor,
ICorrelationIdProvider correlationIdProvider,
IWebClientInfoProvider webClientInfoProvider)
: base(securityLogOptions, securityLogStore)
{
Logger = logger;
Clock = clock;
CurrentUser = currentUser;
CurrentTenant = currentTenant;
CurrentClient = currentClient;
HttpContextAccessor = httpContextAccessor;
CorrelationIdProvider = correlationIdProvider;
WebClientInfoProvider = webClientInfoProvider;
}
public override async Task<SecurityLogInfo> CreateAsync()
{
var securityLogInfo = await base.CreateAsync();
securityLogInfo.CreationTime = Clock.Now;
securityLogInfo.TenantId = CurrentTenant.Id;
securityLogInfo.TenantName = CurrentTenant.Name;
securityLogInfo.UserId = CurrentUser.Id;
securityLogInfo.UserName = CurrentUser.UserName;
securityLogInfo.ClientId = CurrentClient.Id;
securityLogInfo.CorrelationId = CorrelationIdProvider.Get();
securityLogInfo.ClientIpAddress = WebClientInfoProvider.ClientIpAddress;
securityLogInfo.BrowserInfo = WebClientInfoProvider.BrowserInfo;
return securityLogInfo;
}
}
}

43
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs

@ -0,0 +1,43 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.WebClientInfo
{
public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransientDependency
{
protected ILogger<HttpContextWebClientInfoProvider> Logger { get; }
protected IHttpContextAccessor HttpContextAccessor { get; }
public HttpContextWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor)
{
Logger = logger;
HttpContextAccessor = httpContextAccessor;
}
public string BrowserInfo => GetBrowserInfo();
public string ClientIpAddress => GetClientIpAddress();
protected virtual string GetBrowserInfo()
{
return HttpContextAccessor.HttpContext?.Request?.Headers?["User-Agent"];
}
protected virtual string GetClientIpAddress()
{
try
{
return HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
}
}
}

9
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/IWebClientInfoProvider.cs

@ -0,0 +1,9 @@
namespace Volo.Abp.AspNetCore.WebClientInfo
{
public interface IWebClientInfoProvider
{
string BrowserInfo { get; }
string ClientIpAddress { get; }
}
}

21
framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/AbpSecurityLogOptions.cs

@ -0,0 +1,21 @@
namespace Volo.Abp.SecurityLog
{
public class AbpSecurityLogOptions
{
/// <summary>
/// Default: true.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The name of the application or service writing security log.
/// Default: null.
/// </summary>
public string ApplicationName { get; set; }
public AbpSecurityLogOptions()
{
IsEnabled = true;
}
}
}

34
framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/DefaultSecurityLogManager.cs

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.SecurityLog
{
public class DefaultSecurityLogManager : ISecurityLogManager, ITransientDependency
{
protected AbpSecurityLogOptions SecurityLogOptions { get; }
protected ISecurityLogStore SecurityLogStore { get; }
public DefaultSecurityLogManager(
IOptions<AbpSecurityLogOptions> securityLogOptions,
ISecurityLogStore securityLogStore)
{
SecurityLogStore = securityLogStore;
SecurityLogOptions = securityLogOptions.Value;
}
public virtual Task<SecurityLogInfo> CreateAsync()
{
return Task.FromResult(new SecurityLogInfo
{
ApplicationName = SecurityLogOptions.ApplicationName
});
}
public async Task SaveAsync(SecurityLogInfo securityLogInfo)
{
await SecurityLogStore.SaveAsync(securityLogInfo);
}
}
}

11
framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogManager.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Volo.Abp.SecurityLog
{
public interface ISecurityLogManager
{
Task<SecurityLogInfo> CreateAsync();
Task SaveAsync(SecurityLogInfo securityLogInfo);
}
}

9
framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/ISecurityLogStore.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Volo.Abp.SecurityLog
{
public interface ISecurityLogStore
{
Task SaveAsync(SecurityLogInfo securityLogInfo);
}
}

11
framework/src/Volo.Abp.Security/Volo/Abp/Users/SecurityLog/UserSecurityLogInfo.cs → framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SecurityLogInfo.cs

@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using Volo.Abp.Data; using Volo.Abp.Data;
namespace Volo.Abp.Users.SecurityLog namespace Volo.Abp.SecurityLog
{ {
[Serializable] [Serializable]
public class UserSecurityLogInfo : IHasExtraProperties public class SecurityLogInfo : IHasExtraProperties
{ {
/// <summary> /// <summary>
/// The name of the application or service writing user security logs. /// The name of the application or service writing user security logs.
@ -45,9 +45,14 @@ namespace Volo.Abp.Users.SecurityLog
public DateTime CreationTime { get; set; } public DateTime CreationTime { get; set; }
public UserSecurityLogInfo() public SecurityLogInfo()
{ {
ExtraProperties = new Dictionary<string, object>(); ExtraProperties = new Dictionary<string, object>();
} }
public override string ToString()
{
return $"SECURITY LOG: [{ApplicationName} - {Identity} - {Action}]";
}
} }
} }

22
framework/src/Volo.Abp.Security/Volo/Abp/SecurityLog/SimpleSecurityLogStore.cs

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.SecurityLog
{
public class SimpleSecurityLogStore : ISecurityLogStore, ITransientDependency
{
public ILogger<SimpleSecurityLogStore> Logger { get; set; }
public SimpleSecurityLogStore(ILogger<SimpleSecurityLogStore> logger)
{
Logger = logger;
}
public Task SaveAsync(SecurityLogInfo securityLogInfo)
{
Logger.LogInformation(securityLogInfo.ToString());
return Task.FromResult(0);
}
}
}

9
framework/src/Volo.Abp.Security/Volo/Abp/Users/SecurityLog/IUserSecurityLogStore.cs

@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace Volo.Abp.Users.SecurityLog
{
public interface IUserSecurityLogStore
{
Task SaveAsync(UserSecurityLogInfo userSecurityLogInfo);
}
}

3
modules/account/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerSupportedLoginModel.cs

@ -15,6 +15,7 @@ using System.Threading.Tasks;
using Volo.Abp.Account.Settings; using Volo.Abp.Account.Settings;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.SecurityLog;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@ -127,6 +128,8 @@ namespace Volo.Abp.Account.Web.Pages.Account
true true
); );
await CreateSecurityLog("Login_" + result);
if (result.RequiresTwoFactor) if (result.RequiresTwoFactor)
{ {
return RedirectToPage("./SendSecurityCode", new return RedirectToPage("./SendSecurityCode", new

25
modules/account/src/Volo.Abp.Account.Web/Areas/Account/Controllers/AccountController.cs

@ -7,6 +7,7 @@ using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web.Areas.Account.Controllers.Models; using Volo.Abp.Account.Web.Areas.Account.Controllers.Models;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.SecurityLog;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
@ -25,14 +26,20 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
protected SignInManager<IdentityUser> SignInManager { get; } protected SignInManager<IdentityUser> SignInManager { get; }
protected IdentityUserManager UserManager { get; } protected IdentityUserManager UserManager { get; }
protected ISettingProvider SettingProvider { get; } protected ISettingProvider SettingProvider { get; }
protected ISecurityLogManager SecurityLogManager { get; }
public AccountController(SignInManager<IdentityUser> signInManager, IdentityUserManager userManager, ISettingProvider settingProvider) public AccountController(
SignInManager<IdentityUser> signInManager,
IdentityUserManager userManager,
ISettingProvider settingProvider,
ISecurityLogManager securityLogManager)
{ {
LocalizationResource = typeof(AccountResource); LocalizationResource = typeof(AccountResource);
SignInManager = signInManager; SignInManager = signInManager;
UserManager = userManager; UserManager = userManager;
SettingProvider = settingProvider; SettingProvider = settingProvider;
SecurityLogManager = securityLogManager;
} }
[HttpPost] [HttpPost]
@ -44,19 +51,23 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
ValidateLoginInfo(login); ValidateLoginInfo(login);
await ReplaceEmailToUsernameOfInputIfNeeds(login); await ReplaceEmailToUsernameOfInputIfNeeds(login);
var loginResult = GetAbpLoginResult(await SignInManager.PasswordSignInAsync(
return GetAbpLoginResult(await SignInManager.PasswordSignInAsync(
login.UserNameOrEmailAddress, login.UserNameOrEmailAddress,
login.Password, login.Password,
login.RememberMe, login.RememberMe,
true true
)); ));
await CreateSecurityLog("Login_" + loginResult.Result);
return loginResult;
} }
[HttpGet] [HttpGet]
[Route("logout")] [Route("logout")]
public virtual async Task Logout() public virtual async Task Logout()
{ {
await CreateSecurityLog("Logout");
await SignInManager.SignOutAsync(); await SignInManager.SignOutAsync();
} }
@ -150,5 +161,13 @@ namespace Volo.Abp.Account.Web.Areas.Account.Controllers
throw new UserFriendlyException(L["LocalLoginDisabledMessage"]); 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);
}
} }
} }

11
modules/account/src/Volo.Abp.Account.Web/Pages/Account/AccountPageModel.cs

@ -1,12 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Account.Localization; using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.SecurityLog;
using IdentityUser = Volo.Abp.Identity.IdentityUser; using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace Volo.Abp.Account.Web.Pages.Account namespace Volo.Abp.Account.Web.Pages.Account
@ -15,6 +17,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
{ {
public SignInManager<IdentityUser> SignInManager { get; set; } public SignInManager<IdentityUser> SignInManager { get; set; }
public IdentityUserManager UserManager { get; set; } public IdentityUserManager UserManager { get; set; }
public ISecurityLogManager SecurityLogManager { get; }
protected AccountPageModel() protected AccountPageModel()
{ {
@ -76,5 +79,13 @@ namespace Volo.Abp.Account.Web.Pages.Account
{ {
return "~/"; //TODO: ??? return "~/"; //TODO: ???
} }
protected virtual async Task CreateSecurityLog(string action)
{
var securityLog = await SecurityLogManager.CreateAsync();
securityLog.Identity = "Web";
securityLog.Action = action;
await SecurityLogManager.SaveAsync(securityLog);
}
} }
} }

8
modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -14,8 +14,8 @@ using Volo.Abp.Account.Settings;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.Security.Claims; using Volo.Abp.Security.Claims;
using Volo.Abp.SecurityLog;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using IdentityUser = Volo.Abp.Identity.IdentityUser; using IdentityUser = Volo.Abp.Identity.IdentityUser;
@ -83,7 +83,7 @@ namespace Volo.Abp.Account.Web.Pages.Account
ValidateModel(); ValidateModel();
ExternalProviders = await GetExternalProviders(); ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
await ReplaceEmailToUsernameOfInputIfNeeds(); await ReplaceEmailToUsernameOfInputIfNeeds();
@ -95,6 +95,8 @@ namespace Volo.Abp.Account.Web.Pages.Account
true true
); );
await CreateSecurityLog(result.ToString());
if (result.RequiresTwoFactor) if (result.RequiresTwoFactor)
{ {
return RedirectToPage("./SendSecurityCode", new return RedirectToPage("./SendSecurityCode", new
@ -182,6 +184,8 @@ namespace Volo.Abp.Account.Web.Pages.Account
bypassTwoFactor: true bypassTwoFactor: true
); );
await CreateSecurityLog(result.ToString());
if (result.IsLockedOut) if (result.IsLockedOut)
{ {
throw new UserFriendlyException("Cannot proceed because user is locked out!"); throw new UserFriendlyException("Cannot proceed because user is locked out!");

Loading…
Cancel
Save