From c845a612b5471da8f4d4b07c14a998287be7335b Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Sun, 8 Jul 2018 21:32:56 +0300 Subject: [PATCH] #28 Migrated old Auditing infrastructure. --- .../Volo.Abp.Auditing.csproj | 1 + .../Volo/Abp/Auditing/AbpAuditingModule.cs | 5 + .../Volo/Abp/Auditing/AuditInfo.cs | 55 +++++ .../Volo/Abp/Auditing/AuditedAttribute.cs | 10 + .../Abp/Auditing/AuditingContractResolver.cs | 39 ++++ .../Volo/Abp/Auditing/AuditingHelper.cs | 188 ++++++++++++++++++ .../Volo/Abp/Auditing/AuditingInterceptor.cs | 90 +++++++++ .../Auditing/AuditingInterceptorRegistrar.cs | 42 ++++ .../Volo/Abp/Auditing/AuditingOptions.cs | 21 ++ .../Abp/Auditing/AuditingStoreExtensions.cs | 12 ++ .../Abp/Auditing/DefaultAuditInfoProvider.cs | 38 ++++ .../Abp/Auditing/DisableAuditingAttribute.cs | 10 + .../Volo/Abp/Auditing/IAuditInfoProvider.cs | 14 ++ .../Volo/Abp/Auditing/IAuditSerializer.cs | 7 + .../Volo/Abp/Auditing/IAudited.cs | 2 + .../Volo/Abp/Auditing/IAuditingEnabled.cs | 6 + .../Volo/Abp/Auditing/IAuditingHelper.cs | 20 ++ .../Volo/Abp/Auditing/IAuditingStore.cs | 13 ++ .../Volo/Abp/Auditing/IClientInfoProvider.cs | 11 + .../Abp/Auditing/JsonNetAuditSerializer.cs | 26 +++ .../Abp/Auditing/NullClientInfoProvider.cs | 14 ++ .../Abp/Auditing/SimpleLogAuditingStore.cs | 28 +++ .../Services/IApplicationService.cs | 11 +- .../Volo.Abp.Auditing.Tests.csproj | 1 + .../Abp/Auditing/AuditingInterceptor_Tests.cs | 86 ++++++++ 25 files changed, 748 insertions(+), 2 deletions(-) create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditInfo.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditedAttribute.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingOptions.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingStoreExtensions.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DefaultAuditInfoProvider.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DisableAuditingAttribute.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditInfoProvider.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditSerializer.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingEnabled.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IClientInfoProvider.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/NullClientInfoProvider.cs create mode 100644 framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs create mode 100644 framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs diff --git a/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj b/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj index a6ae064dbd..428d905f6b 100644 --- a/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj +++ b/framework/src/Volo.Abp.Auditing/Volo.Abp.Auditing.csproj @@ -15,6 +15,7 @@ + diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs index 8be1b456df..c475245ac3 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AbpAuditingModule.cs @@ -16,6 +16,11 @@ namespace Volo.Abp.Auditing )] public class AbpAuditingModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.OnRegistred(AuditingInterceptorRegistrar.RegisterIfNeeded); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAssemblyOf(); diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditInfo.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditInfo.cs new file mode 100644 index 0000000000..81e71e28ed --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Data; + +namespace Volo.Abp.Auditing +{ + public class AuditInfo : IHasExtraProperties + { + public Guid? TenantId { get; set; } + + public Guid? UserId { get; set; } + + public Guid? ImpersonatorUserId { get; set; } + + public Guid? ImpersonatorTenantId { get; set; } + + public string ServiceName { get; set; } + + public string MethodName { get; set; } + + public string Parameters { get; set; } + + public DateTime ExecutionTime { get; set; } + + public int ExecutionDuration { get; set; } + + public string ClientIpAddress { get; set; } + + public string ClientName { get; set; } + + public string BrowserInfo { get; set; } + + public Exception Exception { get; set; } + + public Dictionary ExtraProperties { get; } + + public AuditInfo() + { + ExtraProperties = new Dictionary(); + } + + public override string ToString() + { + var loggedUserId = UserId.HasValue + ? "user " + UserId.Value + : "an anonymous user"; + + var exceptionOrSuccessMessage = Exception != null + ? "exception: " + Exception.Message + : "succeed"; + + return $"AUDIT LOG: {ServiceName}.{MethodName} is executed by {loggedUserId} in {ExecutionDuration} ms from {ClientIpAddress} IP address with {exceptionOrSuccessMessage}."; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditedAttribute.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditedAttribute.cs new file mode 100644 index 0000000000..f6e61527a7 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditedAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Auditing +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)] + public class AuditedAttribute : Attribute + { + + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs new file mode 100644 index 0000000000..4607355a37 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingContractResolver.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Volo.Abp.Auditing +{ + public class AuditingContractResolver : CamelCasePropertyNamesContractResolver + { + private readonly List _ignoredTypes; + + public AuditingContractResolver(List ignoredTypes) + { + _ignoredTypes = ignoredTypes; + } + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + JsonProperty property = base.CreateProperty(member, memberSerialization); + + if (member.IsDefined(typeof(DisableAuditingAttribute)) || member.IsDefined(typeof(JsonIgnoreAttribute))) + { + property.ShouldSerialize = instance => false; + } + + foreach (var ignoredType in _ignoredTypes) + { + if (ignoredType.GetTypeInfo().IsAssignableFrom(property.PropertyType)) + { + property.ShouldSerialize = instance => false; + break; + } + } + + return property; + } + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs new file mode 100644 index 0000000000..089ec87d4c --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingHelper.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; +using Volo.Abp.Users; + +namespace Volo.Abp.Auditing +{ + public class AuditingHelper : IAuditingHelper, ITransientDependency + { + public ILogger Logger { get; set; } + + public IAuditingStore AuditingStore { get; set; } + protected ICurrentUser CurrentUser { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IClock Clock { get; } + protected IAuditInfoProvider AuditInfoProvider; + protected AuditingOptions Options; + protected IAuditSerializer AuditSerializer; + + public AuditingHelper( + IAuditInfoProvider auditInfoProvider, + IAuditSerializer auditSerializer, + IOptions options, + ICurrentUser currentUser, + ICurrentTenant currentTenant, + IClock clock) + { + AuditInfoProvider = auditInfoProvider; + Options = options.Value; + AuditSerializer = auditSerializer; + CurrentUser = currentUser; + CurrentTenant = currentTenant; + Clock = clock; + + Logger = NullLogger.Instance; + } + + public bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false) + { + if (!Options.IsEnabled) + { + return false; + } + + if (!Options.IsEnabledForAnonymousUsers && !CurrentUser.IsAuthenticated) + { + return false; + } + + if (methodInfo == null) + { + return false; + } + + if (!methodInfo.IsPublic) + { + return false; + } + + if (methodInfo.IsDefined(typeof(AuditedAttribute), true)) + { + return true; + } + + if (methodInfo.IsDefined(typeof(DisableAuditingAttribute), true)) + { + return false; + } + + var classType = methodInfo.DeclaringType; + if (classType != null) + { + if (classType.IsDefined(typeof(AuditedAttribute), true)) + { + return true; + } + + if (classType.IsDefined(typeof(DisableAuditingAttribute), true)) + { + return false; + } + + if (typeof(IAuditingEnabled).IsAssignableFrom(classType)) + { + return true; + } + } + + return defaultValue; + } + + public AuditInfo CreateAuditInfo(Type type, MethodInfo method, object[] arguments) + { + return CreateAuditInfo(type, method, CreateArgumentsDictionary(method, arguments)); + } + + public AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary arguments) + { + var auditInfo = new AuditInfo + { + TenantId = CurrentTenant.Id, + UserId = CurrentUser.Id, + //ImpersonatorUserId = AbpSession.ImpersonatorUserId, //TODO: Impersonation system is not available yet! + //ImpersonatorTenantId = AbpSession.ImpersonatorTenantId, + ServiceName = type != null + ? type.FullName + : "", + MethodName = method.Name, + Parameters = SerializeConvertArguments(arguments), + ExecutionTime = Clock.Now + }; + + try + { + AuditInfoProvider.Fill(auditInfo); + } + catch (Exception ex) + { + Logger.LogException(ex, LogLevel.Warning); + } + + return auditInfo; + } + + public void Save(AuditInfo auditInfo) + { + AuditingStore.Save(auditInfo); + } + + public async Task SaveAsync(AuditInfo auditInfo) + { + await AuditingStore.SaveAsync(auditInfo); + } + + private string SerializeConvertArguments(IDictionary arguments) + { + try + { + if (arguments.IsNullOrEmpty()) + { + return "{}"; + } + + var dictionary = new Dictionary(); + + foreach (var argument in arguments) + { + if (argument.Value != null && Options.IgnoredTypes.Any(t => t.IsInstanceOfType(argument.Value))) + { + dictionary[argument.Key] = null; + } + else + { + dictionary[argument.Key] = argument.Value; + } + } + + return AuditSerializer.Serialize(dictionary); + } + catch (Exception ex) + { + Logger.LogException(ex, LogLevel.Warning); + return "{}"; + } + } + + private static Dictionary CreateArgumentsDictionary(MethodInfo method, object[] arguments) + { + var parameters = method.GetParameters(); + var dictionary = new Dictionary(); + + for (var i = 0; i < parameters.Length; i++) + { + dictionary[parameters[i].Name] = arguments[i]; + } + + return dictionary; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs new file mode 100644 index 0000000000..94df027751 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptor.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Volo.Abp.Aspects; +using Volo.Abp.DependencyInjection; +using Volo.Abp.DynamicProxy; + +namespace Volo.Abp.Auditing +{ + public class AuditingInterceptor : AbpInterceptor, ITransientDependency + { + private readonly IAuditingHelper _auditingHelper; + + public AuditingInterceptor(IAuditingHelper auditingHelper) + { + _auditingHelper = auditingHelper; + } + + public override void Intercept(IAbpMethodInvocation invocation) + { + if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.Auditing)) + { + invocation.Proceed(); + return; + } + + if (!_auditingHelper.ShouldSaveAudit(invocation.Method)) + { + invocation.Proceed(); + return; + } + + var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetObject.GetType(), invocation.Method, invocation.Arguments); + + var stopwatch = Stopwatch.StartNew(); + + try + { + invocation.Proceed(); + } + catch (Exception ex) + { + auditInfo.Exception = ex; + throw; + } + finally + { + stopwatch.Stop(); + auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + _auditingHelper.Save(auditInfo); + } + } + + public override async Task InterceptAsync(IAbpMethodInvocation invocation) + { + //Try to reduce duplication with Intercept + + if (AbpCrossCuttingConcerns.IsApplied(invocation.TargetObject, AbpCrossCuttingConcerns.Auditing)) + { + await invocation.ProceedAsync(); + return; + } + + if (!_auditingHelper.ShouldSaveAudit(invocation.Method)) + { + await invocation.ProceedAsync(); + return; + } + + var auditInfo = _auditingHelper.CreateAuditInfo(invocation.TargetObject.GetType(), invocation.Method, invocation.Arguments); + + var stopwatch = Stopwatch.StartNew(); + + try + { + await invocation.ProceedAsync(); + } + catch (Exception ex) + { + auditInfo.Exception = ex; + } + finally + { + stopwatch.Stop(); + auditInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); + await _auditingHelper.SaveAsync(auditInfo); + } + } + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs new file mode 100644 index 0000000000..57a7f4b94e --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingInterceptorRegistrar.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Auditing +{ + public static class AuditingInterceptorRegistrar + { + public static void RegisterIfNeeded(IOnServiceRegistredContext context) + { + if (ShouldIntercept(context.ImplementationType)) + { + context.Interceptors.TryAdd(); + } + } + + private static bool ShouldIntercept(Type type) + { + if (type.IsDefined(typeof(AuditedAttribute), true)) + { + return true; + } + + if (type.IsDefined(typeof(DisableAuditingAttribute), true)) + { + return false; + } + + if (typeof(IAuditingEnabled).IsAssignableFrom(type)) + { + return true; + } + + if (type.GetMethods().Any(m => m.IsDefined(typeof(AuditedAttribute), true))) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingOptions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingOptions.cs new file mode 100644 index 0000000000..cea72e0f92 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingOptions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Auditing +{ + public class AuditingOptions + { + public bool IsEnabled { get; set; } + + public bool IsEnabledForAnonymousUsers { get; set; } + + public List IgnoredTypes { get; } + + public AuditingOptions() + { + IsEnabled = true; + IsEnabledForAnonymousUsers = true; + IgnoredTypes = new List(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingStoreExtensions.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingStoreExtensions.cs new file mode 100644 index 0000000000..3fdfc0e3ab --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/AuditingStoreExtensions.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Threading; + +namespace Volo.Abp.Auditing +{ + public static class AuditingStoreExtensions + { + public static void Save(this IAuditingStore auditingStore, AuditInfo auditInfo) + { + AsyncHelper.RunSync(() => auditingStore.SaveAsync(auditInfo)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DefaultAuditInfoProvider.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DefaultAuditInfoProvider.cs new file mode 100644 index 0000000000..ece89c98a5 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DefaultAuditInfoProvider.cs @@ -0,0 +1,38 @@ +using System; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Auditing +{ + //TODO: Implement as multiple providers can contribute! + + /// + /// Default implementation of . + /// + public class DefaultAuditInfoProvider : IAuditInfoProvider, ITransientDependency + { + protected IClientInfoProvider ClientInfoProvider { get; } + + public DefaultAuditInfoProvider(IClientInfoProvider clientInfoProvider) + { + ClientInfoProvider = clientInfoProvider; + } + + public virtual void Fill(AuditInfo auditInfo) + { + if (auditInfo.ClientIpAddress.IsNullOrEmpty()) + { + auditInfo.ClientIpAddress = ClientInfoProvider.ClientIpAddress; + } + + if (auditInfo.BrowserInfo.IsNullOrEmpty()) + { + auditInfo.BrowserInfo = ClientInfoProvider.BrowserInfo; + } + + if (auditInfo.ClientName.IsNullOrEmpty()) + { + auditInfo.ClientName = ClientInfoProvider.ComputerName; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DisableAuditingAttribute.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DisableAuditingAttribute.cs new file mode 100644 index 0000000000..06732f57ed --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/DisableAuditingAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Auditing +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)] + public class DisableAuditingAttribute : Attribute + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditInfoProvider.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditInfoProvider.cs new file mode 100644 index 0000000000..d3dcb2bf90 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditInfoProvider.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Auditing +{ + /// + /// Provides an interface to provide audit informations in the upper layers. + /// + public interface IAuditInfoProvider + { + /// + /// Called to fill needed properties. + /// + /// Audit info that is partially filled + void Fill(AuditInfo auditInfo); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditSerializer.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditSerializer.cs new file mode 100644 index 0000000000..28fd543cdc --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditSerializer.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Auditing +{ + public interface IAuditSerializer + { + string Serialize(object obj); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAudited.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAudited.cs index 2591441252..7936ba5b0a 100644 --- a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAudited.cs +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAudited.cs @@ -1,5 +1,7 @@ namespace Volo.Abp.Auditing { + //TODO: Rename IAudited to IAuditedObject (also for ICreationAudited, IDeletionAudited... and so on) + /// /// This interface can be implemented to add standard auditing properties to a class. /// diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingEnabled.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingEnabled.cs new file mode 100644 index 0000000000..d4de868817 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingEnabled.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Auditing +{ + public interface IAuditingEnabled + { + } +} diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs new file mode 100644 index 0000000000..0cdf850afc --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingHelper.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +namespace Volo.Abp.Auditing +{ + public interface IAuditingHelper + { + bool ShouldSaveAudit(MethodInfo methodInfo, bool defaultValue = false); + + AuditInfo CreateAuditInfo(Type type, MethodInfo method, object[] arguments); + + AuditInfo CreateAuditInfo(Type type, MethodInfo method, IDictionary arguments); + + void Save(AuditInfo auditInfo); + + Task SaveAsync(AuditInfo auditInfo); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs new file mode 100644 index 0000000000..92a456c197 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IAuditingStore.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Auditing +{ + public interface IAuditingStore + { + /// + /// Should save audits to a persistent store. + /// + /// Audit informations + Task SaveAsync(AuditInfo auditInfo); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IClientInfoProvider.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IClientInfoProvider.cs new file mode 100644 index 0000000000..1a94aa47eb --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/IClientInfoProvider.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Auditing +{ + public interface IClientInfoProvider + { + string BrowserInfo { get; } + + string ClientIpAddress { get; } + + string ComputerName { get; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs new file mode 100644 index 0000000000..a99d6a5019 --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/JsonNetAuditSerializer.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Auditing +{ + public class JsonNetAuditSerializer : IAuditSerializer, ITransientDependency + { + protected AuditingOptions Options; + + public JsonNetAuditSerializer(IOptions options) + { + Options = options.Value; + } + + public string Serialize(object obj) + { + var options = new JsonSerializerSettings + { + ContractResolver = new AuditingContractResolver(Options.IgnoredTypes) + }; + + return JsonConvert.SerializeObject(obj, options); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/NullClientInfoProvider.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/NullClientInfoProvider.cs new file mode 100644 index 0000000000..caadb3bf1a --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/NullClientInfoProvider.cs @@ -0,0 +1,14 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Auditing +{ + //TODO: Implement on aspnet core layer + public class NullClientInfoProvider : IClientInfoProvider, ISingletonDependency + { + public static NullClientInfoProvider Instance { get; } = new NullClientInfoProvider(); + + public string BrowserInfo => null; + public string ClientIpAddress => null; + public string ComputerName => null; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs new file mode 100644 index 0000000000..4737f5827c --- /dev/null +++ b/framework/src/Volo.Abp.Auditing/Volo/Abp/Auditing/SimpleLogAuditingStore.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Auditing +{ + [Dependency(TryRegister = true)] + public class SimpleLogAuditingStore : IAuditingStore, ISingletonDependency + { + public ILogger Logger { get; set; } + + public SimpleLogAuditingStore() + { + Logger = NullLogger.Instance; + } + + public Task SaveAsync(AuditInfo auditInfo) + { + Logger.LogWithLevel( + auditInfo.Exception == null ? LogLevel.Information : LogLevel.Warning, + auditInfo.ToString() + ); + + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs index 4c9a6ccedf..6bf851d693 100644 --- a/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs +++ b/framework/src/Volo.Abp.Ddd.Application/Volo/Abp/Application/Services/IApplicationService.cs @@ -1,4 +1,5 @@ -using Volo.Abp.Authorization; +using Volo.Abp.Auditing; +using Volo.Abp.Authorization; using Volo.Abp.DependencyInjection; using Volo.Abp.Uow; using Volo.Abp.Validation; @@ -8,7 +9,13 @@ namespace Volo.Abp.Application.Services /// /// This interface must be implemented by all application services to register and identify them by convention. /// - public interface IApplicationService : ITransientDependency, IRemoteService, IUnitOfWorkEnabled, IValidationEnabled, IAuthorizationEnabled + public interface IApplicationService : + ITransientDependency, + IRemoteService, + IUnitOfWorkEnabled, + IValidationEnabled, + IAuthorizationEnabled, + IAuditingEnabled { } diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj b/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj index ff875829d4..29c2979f4c 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo.Abp.Auditing.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs new file mode 100644 index 0000000000..7a6c8f6d18 --- /dev/null +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AuditingInterceptor_Tests.cs @@ -0,0 +1,86 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using NSubstitute; +using Volo.Abp.Autofac; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Modularity; +using Xunit; + +namespace Volo.Abp.Auditing +{ + public class AuditingInterceptor_Tests : AbpIntegratedTest + { + private IAuditingStore _auditingStore; + + public AuditingInterceptor_Tests() + { + + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + _auditingStore = Substitute.For(); + services.Replace(ServiceDescriptor.Singleton(_auditingStore)); + } + + [Fact] + public async Task Should_Write_AuditLog_For_Classes_That_Implement_IAuditingEnabled() + { + var myAuditedObject1 = GetRequiredService(); + await myAuditedObject1.DoItAsync(new InputObject {Value1 = "fourty-two", Value2 = 42}); + + #pragma warning disable 4014 + _auditingStore.Received().SaveAsync(Arg.Any()); + #pragma warning restore 4014 + } + + [DependsOn( + typeof(AbpAuditingModule), + typeof(AbpAutofacModule) + )] + public class TestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddType(); + } + } + + public interface IMyAuditedObject : ITransientDependency, IAuditingEnabled + { + + } + + public class MyAuditedObject1 : IMyAuditedObject + { + public async virtual Task DoItAsync(InputObject inputObject) + { + return new ResultObject + { + Value1 = inputObject.Value1 + "-result", + Value2 = inputObject.Value2 + 1 + }; + } + } + + public class ResultObject + { + public string Value1 { get; set; } + + public int Value2 { get; set; } + } + + public class InputObject + { + public string Value1 { get; set; } + + public int Value2 { get; set; } + } + } +}