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