Skip to content

Commit

Permalink
Audit.WebApi / Audit.WebApi.Core: Allow configuring the `AuditDataPro…
Browse files Browse the repository at this point in the history
…vider` as a service in the `IServiceCollection` (#557)
  • Loading branch information
thepirat000 committed Dec 21, 2022
1 parent 1567f7f commit fd085ce
Show file tree
Hide file tree
Showing 22 changed files with 302 additions and 35 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ All notable changes to Audit.NET and its extensions will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [20.1.3] - 2022-12-20
- Audit.WebApi / Audit.WebApi.Core: Allow configuring the `AuditDataProvider` as a service in the `IServiceCollection` (#557)

## [20.1.2] - 2022-12-14
- Audit.EntityFramework.Core: Fixing issue with GetColumnName when a property is mapped to JSON using EF Core 7 (#555)

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>20.1.2</Version>
<Version>20.1.3</Version>
<PackageReleaseNotes>
</PackageReleaseNotes>
</PropertyGroup>
Expand Down
1 change: 0 additions & 1 deletion src/Audit.EntityFramework/DbContextHelper.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ private bool HasPropertyValue(IAuditDbContext context, EntityEntry entry, string
// property formatted
value = settings.FormatProperties[propName].Invoke(currentValue);
return true;

}
}
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/Audit.NET/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ static Configuration()
#endif
};
#endif
SystemClock = new DefaultSystemClock();
SystemClock = new DefaultSystemClock();
ResetCustomActions();
_auditScopeFactory = new AuditScopeFactory();
}
Expand Down
6 changes: 6 additions & 0 deletions src/Audit.NET/ConfigurationApi/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ public IConfigurator JsonAdapter<T>() where T : IJsonAdapter
return this;
}

public IConfigurator IncludeStackTrace(bool includeStackTrace = true)
{
Configuration.IncludeStackTrace = includeStackTrace;
return this;
}

public ICreationPolicyConfigurator UseNullProvider()
{
var dataProvider = new NullDataProvider();
Expand Down
4 changes: 4 additions & 0 deletions src/Audit.NET/ConfigurationApi/IConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public interface IConfigurator
/// </summary>
IConfigurator JsonAdapter<T>() where T : IJsonAdapter;
/// <summary>
/// Globally include the full stack trace in the audit events.
/// </summary>
IConfigurator IncludeStackTrace(bool includeStackTrace = true);
/// <summary>
/// Use a null provider. No audit events will be saved. Useful for testing purposes or to disable the audit logs.
/// </summary>
ICreationPolicyConfigurator UseNullProvider();
Expand Down
10 changes: 9 additions & 1 deletion src/Audit.WebApi/AuditApiAdapter.Core.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Microsoft.AspNetCore.Http.Extensions;
using System.Runtime.CompilerServices;
using Audit.Core.Providers;
using Microsoft.Extensions.DependencyInjection;

namespace Audit.WebApi
{
Expand Down Expand Up @@ -74,7 +75,14 @@ internal async Task BeforeExecutingAsync(ActionExecutingContext actionContext,
{
Action = auditAction
};
var auditScope = await AuditScope.CreateAsync(new AuditScopeOptions() { EventType = eventType, AuditEvent = auditEventAction, CallingMethod = actionDescriptor.MethodInfo });
var dataProvider = httpContext.RequestServices?.GetService<AuditDataProvider>();
var auditScope = await Core.Configuration.AuditScopeFactory.CreateAsync(new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = auditEventAction,
DataProvider = dataProvider,
CallingMethod = actionDescriptor?.MethodInfo
});
httpContext.Items[AuditApiHelper.AuditApiActionKey] = auditAction;
httpContext.Items[AuditApiHelper.AuditApiScopeKey] = auditScope;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Audit.WebApi/AuditApiAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ public async Task BeforeExecutingAsync(HttpActionContext actionContext, IContext
{
Action = auditAction
};

var dataProvider = contextWrapper.GetHttpContext()?.GetService(typeof(AuditDataProvider)) as AuditDataProvider;
var options = new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = auditEventAction,
DataProvider = dataProvider,
// the inner ActionDescriptor is of type ReflectedHttpActionDescriptor even when using api versioning:
CallingMethod = (actionContext.ActionDescriptor?.ActionBinding?.ActionDescriptor as ReflectedHttpActionDescriptor)?.MethodInfo
};
var auditScope = await AuditScope.CreateAsync(options);
var auditScope = await Configuration.AuditScopeFactory.CreateAsync(options);
contextWrapper.Set(AuditApiHelper.AuditApiActionKey, auditAction);
contextWrapper.Set(AuditApiHelper.AuditApiScopeKey, auditScope);
}
Expand Down
10 changes: 8 additions & 2 deletions src/Audit.WebApi/AuditMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using Microsoft.AspNetCore.Http.Extensions;
using Audit.Core.Extensions;
using Microsoft.Extensions.DependencyInjection;

namespace Audit.WebApi
{
Expand Down Expand Up @@ -114,7 +115,13 @@ private async Task BeforeInvoke(HttpContext context, bool includeHeaders, bool i
{
Action = auditAction
};
var auditScope = await AuditScope.CreateAsync(new AuditScopeOptions() { EventType = eventType, AuditEvent = auditEventAction });
var dataProvider = context.RequestServices?.GetService<AuditDataProvider>();
var auditScope = await Core.Configuration.AuditScopeFactory.CreateAsync(new AuditScopeOptions()
{
EventType = eventType,
AuditEvent = auditEventAction,
DataProvider = dataProvider
});
context.Items[AuditApiHelper.AuditApiActionKey] = auditAction;
context.Items[AuditApiHelper.AuditApiScopeKey] = auditScope;
}
Expand Down Expand Up @@ -158,7 +165,6 @@ private async Task AfterInvoke(HttpContext context, bool includeResponseBody, bo
await auditScope.DisposeAsync();
}
}

}
}
#endif
23 changes: 22 additions & 1 deletion src/Audit.WebApi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,28 @@ You can mix the **Audit Middleware** together with the **Global Action Filter**

### Output

The audit events are stored using a _Data Provider_. You can use one of the [available data providers](https://github.com/thepirat000/Audit.NET#data-providers-included) or implement your own. Please refer to the [data providers](https://github.com/thepirat000/Audit.NET#data-providers) section on Audit.NET documentation.
The audit events are stored using a **_Data Provider_**. You can use one of the [available data providers](https://github.com/thepirat000/Audit.NET#data-providers-included) or implement your own. Please refer to the [data providers](https://github.com/thepirat000/Audit.NET#data-providers) section on Audit.NET documentation.
You can setup the data provider to use by registering an instance of an `AuditDataProdiver`
to the `IServiceCollection` on your start-up code, for example:

```c#
var dataProvider = new FileDataProvider(cfg => cfg.Directory(@"C:\Logs"));
services.AddSingleton<AuditDataProvider>(dataProvider);
```

Or, alternatively, you can setup the data provider globally with the static configuration:

```c#
Audit.Core.Configuration.DataProvider = new FileDataProvider(cfg => cfg.Directory(@"C:\Logs"));
```

Or using the fluent API:

```c#
Audit.Core.Configuration.Setup()
.UseFileLogProvider(cfg => cfg.Directory(@"C:\Logs"));
```

### Settings (Action Filter)

Expand Down
6 changes: 5 additions & 1 deletion test/Audit.EntityFramework.UnitTest/EfTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ public void Test_FunctionMapping()
{
AuditEventEntityFramework auditEvent = null;
Audit.Core.Configuration.Setup()
.IncludeStackTrace(true)
.UseDynamicProvider(x => x.OnInsertAndReplace(ev =>
{
auditEvent = ev as AuditEventEntityFramework;
Expand All @@ -264,14 +265,17 @@ public void Test_FunctionMapping()
ctx.SaveChanges();
}

Audit.Core.Configuration.Setup()
.IncludeStackTrace(false);

Assert.AreEqual(1, auditEvent.EntityFrameworkEvent.Entries.Count);
// PK is zero because the insertion via SP
Assert.IsTrue((int)(auditEvent.EntityFrameworkEvent.Entries[0].PrimaryKey["Id"]) == 0);
Assert.IsTrue((int)(auditEvent.EntityFrameworkEvent.Entries[0].ColumnValues["Id"]) == 0);
Assert.AreEqual("Insert", auditEvent.EntityFrameworkEvent.Entries[0].Action);
Assert.AreEqual("Blogs", auditEvent.EntityFrameworkEvent.Entries[0].Table);
Assert.AreEqual(title, auditEvent.EntityFrameworkEvent.Entries[0].ColumnValues["Title"]);
Assert.IsTrue(auditEvent.Environment.CallingMethodName.Contains("Test_FunctionMapping"));
Assert.IsTrue(auditEvent.Environment.StackTrace.Contains("Test_FunctionMapping"));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<AssemblyOriginatorKeyFile>../../src/StrongName/Audit.NET.UnitTests.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign>true</PublicSign>
Expand Down Expand Up @@ -31,7 +31,9 @@
<PackageReference Include="System.Text.Json" Version="5.0.2" />
<PackageReference Include="NUnit" Version="3.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="*" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="*" />
</ItemGroup>

</Project>
10 changes: 8 additions & 2 deletions test/Audit.Integration.AspNetCore/Controllers/MyController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Audit.WebApi;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

Expand All @@ -24,12 +25,17 @@ public override void OnActionExecuting(ActionExecutingContext context)
{
scope.Event.CustomFields["ScopeExists"] = true;
}

base.OnActionExecuting(context);
}

public override void OnActionExecuted(ActionExecutedContext context)
// PATCH api/my/JsonPatch
[AuditApi(IncludeHeaders = true, IncludeResponseBody = true, IncludeRequestBody = true, IncludeModelState = true, IncludeResponseHeaders = true)]
[HttpPatch("JsonPatch")]
public IActionResult JsonPatch([FromBody] JsonPatchDocument<Customer> patchDoc)
{
base.OnActionExecuted(context);
return Ok();
}
}

}
8 changes: 8 additions & 0 deletions test/Audit.Integration.AspNetCore/Dtos.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Audit.Integration.AspNetCore
{
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,4 @@ public async Task<IActionResult> OnPutAsync([FromBody] Customer customer)
}
}

public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
}
4 changes: 4 additions & 0 deletions test/Audit.Integration.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ static async Task MainAsync(string[] args)
await webApiTests.TestInitialize();
Console.WriteLine("PASSED - TestInitialize");

Console.WriteLine("START - Test_WebApi_JsonPatch_Async");
await webApiTests.Test_WebApi_JsonPatch_Async();
Console.WriteLine("PASSED - Test_WebApi_JsonPatch_Async");

Console.WriteLine("START - Test_WebApi_GlobalFilter_SerializeParams_Async");
await webApiTests.Test_WebApi_GlobalFilter_SerializeParams_Async();
Console.WriteLine("PASSED - Test_WebApi_GlobalFilter_SerializeParams_Async");
Expand Down
29 changes: 26 additions & 3 deletions test/Audit.Integration.AspNetCore/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Audit.WebApi;
using System.Linq;
using Audit.Core;
using Audit.WebApi;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
Expand All @@ -7,6 +9,10 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Audit.Mvc;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Options;
using Microsoft.CodeAnalysis;

namespace Audit.Integration.AspNetCore
{
Expand All @@ -22,14 +28,15 @@ public Startup(IConfiguration configuration)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{

services.Configure<FormOptions>(options =>
{
options.ValueCountLimit = 2;
});

services.AddMvc(mvc =>
{
mvc.InputFormatters.Insert(0, GetJsonPatchInputFormatter());

mvc.EnableEndpointRouting = false;
mvc.Filters.Add(new AuditIgnoreActionFilter_ForTest());
mvc.Filters.Add(new AuditApiGlobalFilter(config => config
Expand All @@ -43,7 +50,7 @@ public void ConfigureServices(IServiceCollection services)
.IncludeResponseHeaders()
.IncludeResponseBody(ctx => ctx.HttpContext.Response.StatusCode == 200)
.IncludeRequestBody()));

mvc.Filters.Add(new AuditApiGlobalFilter(config => config
.LogActionIf(d => d.ControllerName == "Values" && d.ActionName == "TestSerializeParams")
.SerializeActionParameters(true)
Expand Down Expand Up @@ -101,5 +108,21 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseMvc();

}

private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter()
{
var builder = new ServiceCollection()
.AddLogging()
.AddMvc()
.AddNewtonsoftJson()
.Services.BuildServiceProvider();

return builder
.GetRequiredService<IOptions<MvcOptions>>()
.Value
.InputFormatters
.OfType<NewtonsoftJsonPatchInputFormatter>()
.First();
}
}
}
27 changes: 27 additions & 0 deletions test/Audit.Integration.AspNetCore/WebApiTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
Expand All @@ -8,6 +9,7 @@
using Audit.Core.Providers;
using Audit.Integration.AspNetCore.Pages.Test;
using Audit.WebApi;
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc.Controllers;
using Newtonsoft.Json;
using NUnit.Framework;
Expand Down Expand Up @@ -900,5 +902,30 @@ public async Task Test_WebApi_GlobalFilter_DoNotSerializeParams_Async()
Assert.AreEqual(1, events[0].Action.ActionParameters.Count);
Assert.AreEqual(-1, (events[0].Action.ActionParameters["customer"] as Customer)?.Id);
}

public async Task Test_WebApi_JsonPatch_Async()
{
Audit.Core.Configuration.Setup()
.UseInMemoryProvider()
.WithCreationPolicy(EventCreationPolicy.InsertOnEnd);

var client = new HttpClient();
var patchDoc = new JsonPatchDocument<Customer>();
patchDoc.Replace(e => e.Name, "NewValue");
var bodyJson = JsonConvert.SerializeObject(patchDoc);

var result = await client.PatchAsync($"http://localhost:{_port}/api/My/JsonPatch", new StringContent(bodyJson, Encoding.UTF8, "application/json-patch+json"));

var events = (Configuration.DataProvider as InMemoryDataProvider)?.GetAllEventsOfType<AuditEventWebApi>();
var eventJson = events?.FirstOrDefault()?.ToJson();
var op = (events?[0].Action.ActionParameters.First().Value as JsonPatchDocument<Customer>)?.Operations[0];

Assert.IsNotNull(events);
Assert.IsNotNull(eventJson);
Assert.IsNotNull(op);
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
Assert.AreEqual(1, events.Count);
Assert.AreEqual("NewValue", op.value);
}
}
}
Loading

0 comments on commit fd085ce

Please sign in to comment.