mirror of https://github.com/abpframework/abp.git
committed by
GitHub
50 changed files with 1936 additions and 185 deletions
@ -0,0 +1,73 @@ |
|||||
|
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; |
||||
|
} |
||||
|
|
||||
|
protected 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,42 @@ |
|||||
|
using System; |
||||
|
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 async Task SaveAsync(Action<SecurityLogInfo> saveAction = null) |
||||
|
{ |
||||
|
if (!SecurityLogOptions.IsEnabled) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var securityLogInfo = await CreateAsync(); |
||||
|
saveAction?.Invoke(securityLogInfo); |
||||
|
await SecurityLogStore.SaveAsync(securityLogInfo); |
||||
|
} |
||||
|
|
||||
|
protected virtual Task<SecurityLogInfo> CreateAsync() |
||||
|
{ |
||||
|
return Task.FromResult(new SecurityLogInfo |
||||
|
{ |
||||
|
ApplicationName = SecurityLogOptions.ApplicationName |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.SecurityLog |
||||
|
{ |
||||
|
public interface ISecurityLogManager |
||||
|
{ |
||||
|
Task SaveAsync(Action<SecurityLogInfo> saveAction = null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.SecurityLog |
||||
|
{ |
||||
|
public interface ISecurityLogStore |
||||
|
{ |
||||
|
Task SaveAsync(SecurityLogInfo securityLogInfo); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.SecurityLog |
||||
|
{ |
||||
|
[Serializable] |
||||
|
public class SecurityLogInfo |
||||
|
{ |
||||
|
public string ApplicationName { get; set; } |
||||
|
|
||||
|
public string Identity { get; set; } |
||||
|
|
||||
|
public string Action { get; set; } |
||||
|
|
||||
|
public Dictionary<string, object> ExtraProperties { get; } |
||||
|
|
||||
|
public Guid? UserId { get; set; } |
||||
|
|
||||
|
public string UserName { get; set; } |
||||
|
|
||||
|
public Guid? TenantId { get; set; } |
||||
|
|
||||
|
public string TenantName { get; set; } |
||||
|
|
||||
|
public string ClientId { get; set; } |
||||
|
|
||||
|
public string CorrelationId { get; set; } |
||||
|
|
||||
|
public string ClientIpAddress { get; set; } |
||||
|
|
||||
|
public string BrowserInfo { get; set; } |
||||
|
|
||||
|
public DateTime CreationTime { get; set; } |
||||
|
|
||||
|
public SecurityLogInfo() |
||||
|
{ |
||||
|
ExtraProperties = new Dictionary<string, object>(); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"SECURITY LOG: [{ApplicationName} - {Identity} - {Action}]"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.SecurityLog |
||||
|
{ |
||||
|
public class SimpleSecurityLogStore : ISecurityLogStore, ITransientDependency |
||||
|
{ |
||||
|
public ILogger<SimpleSecurityLogStore> Logger { get; set; } |
||||
|
protected AbpSecurityLogOptions SecurityLogOptions { get; } |
||||
|
|
||||
|
public SimpleSecurityLogStore(ILogger<SimpleSecurityLogStore> logger, IOptions<AbpSecurityLogOptions> securityLogOptions) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
SecurityLogOptions = securityLogOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public Task SaveAsync(SecurityLogInfo securityLogInfo) |
||||
|
{ |
||||
|
if (!SecurityLogOptions.IsEnabled) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
Logger.LogInformation(securityLogInfo.ToString()); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using NSubstitute; |
||||
|
using Volo.Abp.SecurityLog; |
||||
|
using Volo.Abp.Testing; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Security.SecurityLog |
||||
|
{ |
||||
|
|
||||
|
public class SecurityLogManager_Tests : AbpIntegratedTest<AbpSecurityTestModule> |
||||
|
{ |
||||
|
private readonly ISecurityLogManager _securityLogManager; |
||||
|
|
||||
|
private ISecurityLogStore _auditingStore; |
||||
|
|
||||
|
public SecurityLogManager_Tests() |
||||
|
{ |
||||
|
_securityLogManager = GetRequiredService<ISecurityLogManager>(); |
||||
|
} |
||||
|
|
||||
|
protected override void AfterAddApplication(IServiceCollection services) |
||||
|
{ |
||||
|
_auditingStore = Substitute.For<ISecurityLogStore>(); |
||||
|
services.AddSingleton(_auditingStore); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task SaveAsync() |
||||
|
{ |
||||
|
await _securityLogManager.SaveAsync(securityLog => |
||||
|
{ |
||||
|
securityLog.Identity = "Test"; |
||||
|
securityLog.Action = "Test-Action"; |
||||
|
securityLog.UserName = "Test-User"; |
||||
|
}); |
||||
|
|
||||
|
await _auditingStore.Received().SaveAsync(Arg.Is<SecurityLogInfo>(log => |
||||
|
log.ApplicationName == "AbpSecurityTest" && |
||||
|
log.Identity == "Test" && |
||||
|
log.Action == "Test-Action" && |
||||
|
log.UserName == "Test-User")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public class IdentitySecurityLogConsts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default value: 96
|
||||
|
/// </summary>
|
||||
|
public static int MaxApplicationNameLength { get; set; } = 96; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 96
|
||||
|
/// </summary>
|
||||
|
public static int MaxIdentityLength { get; set; } = 96; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 96
|
||||
|
/// </summary>
|
||||
|
public static int MaxActionLength { get; set; } = 96; |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 256
|
||||
|
/// </summary>
|
||||
|
public static int MaxUserNameLength { get; set; } = 256; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 64
|
||||
|
/// </summary>
|
||||
|
public static int MaxTenantNameLength { get; set; } = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 64
|
||||
|
/// </summary>
|
||||
|
public static int MaxClientIpAddressLength { get; set; } = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 64
|
||||
|
/// </summary>
|
||||
|
public static int MaxClientIdLength { get; set; } = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 64
|
||||
|
/// </summary>
|
||||
|
public static int MaxCorrelationIdLength { get; set; } = 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Default value: 512
|
||||
|
/// </summary>
|
||||
|
public static int MaxBrowserInfoLength { get; set; } = 512; |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public interface IIdentitySecurityLogRepository : IBasicRepository<IdentitySecurityLog, Guid> |
||||
|
{ |
||||
|
Task<List<IdentitySecurityLog>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = 50, |
||||
|
int skipCount = 0, |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<long> GetCountAsync( |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<IdentitySecurityLog> GetByUserIdAsync( |
||||
|
Guid id, |
||||
|
Guid userId, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
using Volo.Abp.SecurityLog; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public class IdentitySecurityLog : AggregateRoot<Guid>, IMultiTenant |
||||
|
{ |
||||
|
public Guid? TenantId { get; protected set; } |
||||
|
|
||||
|
public string ApplicationName { get; protected set; } |
||||
|
|
||||
|
public string Identity { get; protected set; } |
||||
|
|
||||
|
public string Action { get; protected set; } |
||||
|
|
||||
|
public Guid? UserId { get; protected set; } |
||||
|
|
||||
|
public string UserName { get; protected set; } |
||||
|
|
||||
|
public string TenantName { get; protected set; } |
||||
|
|
||||
|
public string ClientId { get; protected set; } |
||||
|
|
||||
|
public string CorrelationId { get; protected set; } |
||||
|
|
||||
|
public string ClientIpAddress { get; protected set; } |
||||
|
|
||||
|
public string BrowserInfo { get; protected set; } |
||||
|
|
||||
|
public DateTime CreationTime { get; protected set; } |
||||
|
|
||||
|
protected IdentitySecurityLog() |
||||
|
{ |
||||
|
ExtraProperties = new Dictionary<string, object>(); |
||||
|
} |
||||
|
|
||||
|
public IdentitySecurityLog(IGuidGenerator guidGenerator, SecurityLogInfo securityLogInfo) |
||||
|
{ |
||||
|
Id = guidGenerator.Create(); |
||||
|
TenantId = securityLogInfo.TenantId; |
||||
|
TenantName = securityLogInfo.TenantName.Truncate(IdentitySecurityLogConsts.MaxTenantNameLength); |
||||
|
|
||||
|
ApplicationName = securityLogInfo.ApplicationName.Truncate(IdentitySecurityLogConsts.MaxApplicationNameLength); |
||||
|
Identity = securityLogInfo.Identity.Truncate(IdentitySecurityLogConsts.MaxIdentityLength); |
||||
|
Action = securityLogInfo.Action.Truncate(IdentitySecurityLogConsts.MaxActionLength); |
||||
|
|
||||
|
UserId = securityLogInfo.UserId; |
||||
|
UserName = securityLogInfo.UserName.Truncate(IdentitySecurityLogConsts.MaxUserNameLength); |
||||
|
|
||||
|
CreationTime = securityLogInfo.CreationTime; |
||||
|
|
||||
|
ClientIpAddress = securityLogInfo.ClientIpAddress.Truncate(IdentitySecurityLogConsts.MaxClientIpAddressLength); |
||||
|
ClientId = securityLogInfo.ClientId.Truncate(IdentitySecurityLogConsts.MaxClientIdLength); |
||||
|
CorrelationId = securityLogInfo.CorrelationId.Truncate(IdentitySecurityLogConsts.MaxCorrelationIdLength); |
||||
|
BrowserInfo = securityLogInfo.BrowserInfo.Truncate(IdentitySecurityLogConsts.MaxBrowserInfoLength); |
||||
|
|
||||
|
ExtraProperties = securityLogInfo.ExtraProperties; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public class IdentitySecurityLogEvent : 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; } |
||||
|
|
||||
|
public Dictionary<string, object> ExtraProperties { get; } |
||||
|
|
||||
|
public IdentitySecurityLogEvent() |
||||
|
{ |
||||
|
ExtraProperties = new Dictionary<string, object>(); |
||||
|
} |
||||
|
|
||||
|
public virtual IdentitySecurityLogEvent WithProperty(string key, object value) |
||||
|
{ |
||||
|
ExtraProperties[key] = value; |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,86 @@ |
|||||
|
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 IdentitySecurityLogHandler : ILocalEventHandler<IdentitySecurityLogEvent>, ITransientDependency |
||||
|
{ |
||||
|
protected ISecurityLogManager SecurityLogManager { get; } |
||||
|
protected IdentityUserManager UserManager { get; } |
||||
|
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor { get; } |
||||
|
protected IUserClaimsPrincipalFactory<IdentityUser> UserClaimsPrincipalFactory { get; } |
||||
|
protected ICurrentUser CurrentUser { get; } |
||||
|
|
||||
|
public IdentitySecurityLogHandler( |
||||
|
ISecurityLogManager securityLogManager, |
||||
|
IdentityUserManager userManager, |
||||
|
ICurrentPrincipalAccessor currentPrincipalAccessor, |
||||
|
IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory, |
||||
|
ICurrentUser currentUser) |
||||
|
{ |
||||
|
SecurityLogManager = securityLogManager; |
||||
|
UserManager = userManager; |
||||
|
CurrentPrincipalAccessor = currentPrincipalAccessor; |
||||
|
UserClaimsPrincipalFactory = userClaimsPrincipalFactory; |
||||
|
CurrentUser = currentUser; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(IdentitySecurityLogEvent eventData) |
||||
|
{ |
||||
|
Action<SecurityLogInfo> 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; |
||||
|
} |
||||
|
|
||||
|
foreach (var property in eventData.ExtraProperties) |
||||
|
{ |
||||
|
securityLog.ExtraProperties[property.Key] = property.Value; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
await SecurityLogManager.SaveAsync(securityLogAction); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Guids; |
||||
|
using Volo.Abp.SecurityLog; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class IdentitySecurityLogStore : ISecurityLogStore, ITransientDependency |
||||
|
{ |
||||
|
public ILogger<IdentitySecurityLogStore> Logger { get; set; } |
||||
|
|
||||
|
protected AbpSecurityLogOptions SecurityLogOptions { get; } |
||||
|
protected IIdentitySecurityLogRepository IdentitySecurityLogRepository { get; } |
||||
|
protected IGuidGenerator GuidGenerator { get; } |
||||
|
protected IUnitOfWorkManager UnitOfWorkManager { get; } |
||||
|
|
||||
|
public IdentitySecurityLogStore( |
||||
|
ILogger<IdentitySecurityLogStore> logger, |
||||
|
IOptions<AbpSecurityLogOptions> securityLogOptions, |
||||
|
IIdentitySecurityLogRepository identitySecurityLogRepository, |
||||
|
IGuidGenerator guidGenerator, |
||||
|
IUnitOfWorkManager unitOfWorkManager) |
||||
|
{ |
||||
|
Logger = logger; |
||||
|
SecurityLogOptions = securityLogOptions.Value; |
||||
|
IdentitySecurityLogRepository = identitySecurityLogRepository; |
||||
|
GuidGenerator = guidGenerator; |
||||
|
UnitOfWorkManager = unitOfWorkManager; |
||||
|
} |
||||
|
|
||||
|
public async Task SaveAsync(SecurityLogInfo securityLogInfo) |
||||
|
{ |
||||
|
if (!SecurityLogOptions.IsEnabled) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) |
||||
|
{ |
||||
|
await IdentitySecurityLogRepository.InsertAsync(new IdentitySecurityLog(GuidGenerator, securityLogInfo)); |
||||
|
await uow.CompleteAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.EntityFrameworkCore |
||||
|
{ |
||||
|
public class EFCoreIdentitySecurityLogRepository : EfCoreRepository<IIdentityDbContext, IdentitySecurityLog, Guid>, IIdentitySecurityLogRepository |
||||
|
{ |
||||
|
public EFCoreIdentitySecurityLogRepository(IDbContextProvider<IIdentityDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public async Task<List<IdentitySecurityLog>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = 50, |
||||
|
int skipCount = 0, |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var query = GetListQuery( |
||||
|
startTime, |
||||
|
endTime, |
||||
|
applicationName, |
||||
|
identity, |
||||
|
action, |
||||
|
userId, |
||||
|
userName, |
||||
|
clientId, |
||||
|
correlationId |
||||
|
); |
||||
|
|
||||
|
return await query.OrderBy(sorting ?? nameof(IdentitySecurityLog.CreationTime) + " desc") |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async Task<long> GetCountAsync( |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var query = GetListQuery( |
||||
|
startTime, |
||||
|
endTime, |
||||
|
applicationName, |
||||
|
identity, |
||||
|
action, |
||||
|
userId, |
||||
|
userName, |
||||
|
clientId, |
||||
|
correlationId |
||||
|
); |
||||
|
|
||||
|
return await query.LongCountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async Task<IdentitySecurityLog> GetByUserIdAsync(Guid id, Guid userId, bool includeDetails = false, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await DbSet.FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
protected virtual IQueryable<IdentitySecurityLog> GetListQuery( |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null) |
||||
|
{ |
||||
|
return DbSet.AsNoTracking() |
||||
|
.WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime) |
||||
|
.WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime >= endTime) |
||||
|
.WhereIf(!applicationName.IsNullOrWhiteSpace(), securityLog => securityLog.ApplicationName == applicationName) |
||||
|
.WhereIf(!identity.IsNullOrWhiteSpace(), securityLog => securityLog.Identity == identity) |
||||
|
.WhereIf(!action.IsNullOrWhiteSpace(), securityLog => securityLog.Action == action) |
||||
|
.WhereIf(userId.HasValue, securityLog => securityLog.UserId == userId) |
||||
|
.WhereIf(!userName.IsNullOrWhiteSpace(), securityLog => securityLog.UserName == userName) |
||||
|
.WhereIf(!clientId.IsNullOrWhiteSpace(), securityLog => securityLog.ClientId == clientId) |
||||
|
.WhereIf(!correlationId.IsNullOrWhiteSpace(), securityLog => securityLog.CorrelationId == correlationId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
using MongoDB.Driver.Linq; |
||||
|
using Volo.Abp.Domain.Repositories.MongoDB; |
||||
|
using Volo.Abp.MongoDB; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.MongoDB |
||||
|
{ |
||||
|
public class MongoIdentitySecurityLogRepository : |
||||
|
MongoDbRepository<IAbpIdentityMongoDbContext, IdentitySecurityLog, Guid>, IIdentitySecurityLogRepository |
||||
|
{ |
||||
|
public MongoIdentitySecurityLogRepository(IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider) |
||||
|
: base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async Task<List<IdentitySecurityLog>> GetListAsync( |
||||
|
string sorting = null, |
||||
|
int maxResultCount = 50, |
||||
|
int skipCount = 0, |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var query = GetListQuery( |
||||
|
startTime, |
||||
|
endTime, |
||||
|
applicationName, |
||||
|
identity, |
||||
|
action, |
||||
|
userId, |
||||
|
userName, |
||||
|
clientId, |
||||
|
correlationId |
||||
|
); |
||||
|
|
||||
|
return await query.OrderBy(sorting ?? nameof(IdentitySecurityLog.CreationTime) + " desc") |
||||
|
.As<IMongoQueryable<IdentitySecurityLog>>() |
||||
|
.PageBy<IdentitySecurityLog, IMongoQueryable<IdentitySecurityLog>>(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async Task<long> GetCountAsync( |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var query = GetListQuery( |
||||
|
startTime, |
||||
|
endTime, |
||||
|
applicationName, |
||||
|
identity, |
||||
|
action, |
||||
|
userId, |
||||
|
userName, |
||||
|
clientId, |
||||
|
correlationId |
||||
|
); |
||||
|
|
||||
|
return await query.As<IMongoQueryable<IdentitySecurityLog>>() |
||||
|
.LongCountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public async Task<IdentitySecurityLog> GetByUserIdAsync(Guid id, Guid userId, bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await GetMongoQueryable().FirstOrDefaultAsync(x => x.Id == id && x.UserId == userId, |
||||
|
GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
protected virtual IQueryable<IdentitySecurityLog> GetListQuery( |
||||
|
DateTime? startTime = null, |
||||
|
DateTime? endTime = null, |
||||
|
string applicationName = null, |
||||
|
string identity = null, |
||||
|
string action = null, |
||||
|
Guid? userId = null, |
||||
|
string userName = null, |
||||
|
string clientId = null, |
||||
|
string correlationId = null) |
||||
|
{ |
||||
|
return GetMongoQueryable() |
||||
|
.WhereIf(startTime.HasValue, securityLog => securityLog.CreationTime >= startTime) |
||||
|
.WhereIf(endTime.HasValue, securityLog => securityLog.CreationTime >= endTime) |
||||
|
.WhereIf(!applicationName.IsNullOrWhiteSpace(), |
||||
|
securityLog => securityLog.ApplicationName == applicationName) |
||||
|
.WhereIf(!identity.IsNullOrWhiteSpace(), securityLog => securityLog.Identity == identity) |
||||
|
.WhereIf(!action.IsNullOrWhiteSpace(), securityLog => securityLog.Action == action) |
||||
|
.WhereIf(userId.HasValue, securityLog => securityLog.UserId == userId) |
||||
|
.WhereIf(!userName.IsNullOrWhiteSpace(), securityLog => securityLog.UserName == userName) |
||||
|
.WhereIf(!clientId.IsNullOrWhiteSpace(), securityLog => securityLog.ClientId == clientId) |
||||
|
.WhereIf(!correlationId.IsNullOrWhiteSpace(), |
||||
|
securityLog => securityLog.CorrelationId == correlationId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Abp.Identity.EntityFrameworkCore |
||||
|
{ |
||||
|
public class IdentitySecurityLogRepository_Tests : IdentitySecurityLogRepository_Tests<AbpIdentityEntityFrameworkCoreTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Identity.MongoDB |
||||
|
{ |
||||
|
[Collection(MongoTestCollection.Name)] |
||||
|
public class IdentitySecurityLogRepository_Tests : IdentitySecurityLogRepository_Tests<AbpIdentityMongoDbTestModule> |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.Identity |
||||
|
{ |
||||
|
public abstract class IdentitySecurityLogRepository_Tests<TStartupModule> : AbpIdentityTestBase<TStartupModule> |
||||
|
where TStartupModule : IAbpModule |
||||
|
{ |
||||
|
protected IIdentitySecurityLogRepository RoleRepository { get; } |
||||
|
protected IdentityTestData TestData { get; } |
||||
|
|
||||
|
protected IdentitySecurityLogRepository_Tests() |
||||
|
{ |
||||
|
RoleRepository = GetRequiredService<IIdentitySecurityLogRepository>(); |
||||
|
TestData = GetRequiredService<IdentityTestData>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetListAsync() |
||||
|
{ |
||||
|
var logs = await RoleRepository.GetListAsync(); |
||||
|
logs.ShouldNotBeEmpty(); |
||||
|
logs.ShouldContain(x => x.ApplicationName == "Test-ApplicationName" && x.UserId == TestData.UserJohnId); |
||||
|
logs.ShouldContain(x => x.ApplicationName == "Test-ApplicationName" && x.UserId == TestData.UserDavidId); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetCountAsync() |
||||
|
{ |
||||
|
var count = await RoleRepository.GetCountAsync(); |
||||
|
count.ShouldBe(2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue