Add some infrastructure for security logs.

pull/4675/head
maliming 5 years ago
parent f31c7ba36b
commit 65957cbb6e

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
@ -35,14 +36,15 @@ namespace Volo.Abp.AspNetCore.Auditing
context.AuditInfo.Url = BuildUrl(httpContext);
}
var clientInfoProvider = context.ServiceProvider.GetRequiredService<IWebClientInfoProvider>();
if (context.AuditInfo.ClientIpAddress == null)
{
context.AuditInfo.ClientIpAddress = GetClientIpAddress(httpContext);
context.AuditInfo.ClientIpAddress = clientInfoProvider.ClientIpAddress;
}
if (context.AuditInfo.BrowserInfo == null)
{
context.AuditInfo.BrowserInfo = GetBrowserInfo(httpContext);
context.AuditInfo.BrowserInfo = clientInfoProvider.BrowserInfo;
}
//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)
{
//TODO: Add options to include/exclude query, schema and host

@ -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;
}
}
}

@ -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;
}
}
}
}

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

@ -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;
}
}
}

@ -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);
}
}
}

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

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

@ -2,10 +2,10 @@
using System.Collections.Generic;
using Volo.Abp.Data;
namespace Volo.Abp.Users.SecurityLog
namespace Volo.Abp.SecurityLog
{
[Serializable]
public class UserSecurityLogInfo : IHasExtraProperties
public class SecurityLogInfo : IHasExtraProperties
{
/// <summary>
/// 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 UserSecurityLogInfo()
public SecurityLogInfo()
{
ExtraProperties = new Dictionary<string, object>();
}
public override string ToString()
{
return $"SECURITY LOG: [{ApplicationName} - {Identity} - {Action}]";
}
}
}

@ -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);
}
}
}

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

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

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

@ -1,12 +1,14 @@
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.Identity;
using Volo.Abp.SecurityLog;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
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 IdentityUserManager UserManager { get; set; }
public ISecurityLogManager SecurityLogManager { get; }
protected AccountPageModel()
{
@ -76,5 +79,13 @@ 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);
}
}
}

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

Loading…
Cancel
Save