Skip to content

Commit

Permalink
Audit.WebApi.Core: Adding SkipResponseBodyContent configuration (#690)
Browse files Browse the repository at this point in the history
  • Loading branch information
thepirat000 committed Aug 22, 2024
1 parent 65e1193 commit 8fd3105
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 42 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/).

## [26.0.1] - 2024-08-22:
- Audit.WebApi.Core: Adding new SkipResponseBodyContent() configuration to allow deciding whether to skip or include the response body content **after** the action is executed (#690)

## [26.0.0] - 2024-07-19:
- Audit.NET.Elasticsearch: Upgrading the elasticsearch data provider to use the new client Elastic.Clients.Elasticsearch instead of the deprecated NEST client. [Migration guide](https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/migration-guide.html) (#682)

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>26.0.0</Version>
<Version>26.0.1</Version>
<PackageReleaseNotes></PackageReleaseNotes>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
</PropertyGroup>
Expand Down
3 changes: 2 additions & 1 deletion src/Audit.WebApi/AuditMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,12 @@ private async Task AfterInvoke(HttpContext context, bool includeResponseBody, bo
auditAction.ResponseStatus = AuditApiHelper.GetStatusCodeString(statusCode);
if (includeResponseBody && auditAction.ResponseBody == null)
{
var skipResponse = _config._skipRequestBodyBuilder != null && _config._skipRequestBodyBuilder.Invoke(context);
auditAction.ResponseBody = new BodyContent
{
Type = context.Response.ContentType,
Length = context.Response.ContentLength,
Value = await AuditApiHelper.GetResponseBody(context, default)
Value = skipResponse ? null : await AuditApiHelper.GetResponseBody(context, default)
};
}
}
Expand Down
25 changes: 23 additions & 2 deletions src/Audit.WebApi/ConfigurationApi/AuditMiddlewareConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class AuditMiddlewareConfigurator : IAuditMiddlewareConfigurator
internal Func<HttpContext, bool> _includeRequestBodyBuilder;
internal Func<HttpContext, bool> _includeResponseBodyBuilder;
internal Func<HttpContext, string> _eventTypeNameBuilder;
internal Func<HttpContext, bool> _skipRequestBodyBuilder;

public IAuditMiddlewareConfigurator IncludeHeaders(bool include = true)
{
Expand Down Expand Up @@ -61,6 +62,26 @@ public IAuditMiddlewareConfigurator IncludeResponseBody(Func<HttpContext, bool>
return this;
}

public IAuditMiddlewareConfigurator SkipResponseBodyContent(Func<HttpContext, bool> skipPredicate)
{
if (_includeResponseBodyBuilder == null)
{
_includeResponseBodyBuilder = _ => true;
}
_skipRequestBodyBuilder = skipPredicate;
return this;
}

public IAuditMiddlewareConfigurator SkipResponseBodyContent(bool skip)
{
if (_includeResponseBodyBuilder == null)
{
_includeResponseBodyBuilder = _ => true;
}
_skipRequestBodyBuilder = _ => skip;
return this;
}

public IAuditMiddlewareConfigurator FilterByRequest(Func<HttpRequest, bool> requestPredicate)
{
_requestFilter = requestPredicate;
Expand All @@ -73,9 +94,9 @@ public IAuditMiddlewareConfigurator WithEventType(string eventTypeName)
return this;
}

public IAuditMiddlewareConfigurator WithEventType(Func<HttpContext, string> eventTypeNamePredicate)
public IAuditMiddlewareConfigurator WithEventType(Func<HttpContext, string> eventTypeNameBuilder)
{
_eventTypeNameBuilder = eventTypeNamePredicate;
_eventTypeNameBuilder = eventTypeNameBuilder;
return this;
}
}
Expand Down
23 changes: 20 additions & 3 deletions src/Audit.WebApi/ConfigurationApi/IAuditMiddlewareConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,38 @@ public interface IAuditMiddlewareConfigurator
/// <summary>
/// Specifies a predicate to determine the event type name on the audit output.
/// </summary>
/// <param name="eventTypeNamePredicate">A function of the executing context to determine the event type name. The following placeholders can be used as part of the string:
/// <param name="eventTypeNameBuilder">A function of the executing context to determine the event type name. The following placeholders can be used as part of the string:
/// - {url}: replaced with the requst URL.
/// - {verb}: replaced with the HTTP verb used (GET, POST, etc).
/// </param>
IAuditMiddlewareConfigurator WithEventType(Func<HttpContext, string> eventTypeNamePredicate);
IAuditMiddlewareConfigurator WithEventType(Func<HttpContext, string> eventTypeNameBuilder);
/// <summary>
/// Specifies whether the response body should be included on the audit output.
/// </summary>
/// <param name="include">True to include the response body, false otherwise</param>
IAuditMiddlewareConfigurator IncludeResponseBody(bool include = true);

/// <summary>
/// Specifies a predicate to determine whether the response body should be included on the audit output.
/// The predicate is evaluated before request execution.
/// </summary>
/// <param name="includePredicate">A function of the executed context to determine whether the response body should be included on the audit output</param>
/// <param name="includePredicate">A function of the executed context to determine whether the response body should be included on the audit output.
/// This predicate is evaluated before request execution.</param>
IAuditMiddlewareConfigurator IncludeResponseBody(Func<HttpContext, bool> includePredicate);

/// <summary>
/// Specifies a predicate to determine whether the response body content should be skipped or included in the audit output.
/// The predicate is evaluated after request execution.
/// </summary>
/// <param name="skipPredicate">A function of the executed context to determine whether the response body content should be skipped or included in the audit output.
/// This predicate is evaluated after request execution.</param>
IAuditMiddlewareConfigurator SkipResponseBodyContent(Func<HttpContext, bool> skipPredicate);

/// <summary>
/// Specifies whether the response body content should be skipped or included in the audit output.
/// </summary>
/// <param name="skip">A boolean to determine whether the response body should be skipped or included in the audit output.</param>
IAuditMiddlewareConfigurator SkipResponseBodyContent(bool skip);
}
}
#endif
5 changes: 4 additions & 1 deletion src/Audit.WebApi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,10 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- **IncludeHeaders()**: Boolean (or function of the HTTP context that returns a boolean) to indicate whether to include the Http Request Headers or not. Default is false.
- **IncludeResponseHeaders()**: Boolean (or function of the HTTP context that returns a boolean) to indicate whether to include the Http Response Headers or not. Default is false.
- **IncludeRequestBody()**: Boolean (or function of the HTTP context that returns a boolean) to indicate whether to include or exclude the request body from the logs. Default is false. (Check the following note)
- **IncludeResponseBody()**: Boolean (or function of the HTTP context that returns a boolean) to indicate whether to include response body or not. Default is false.
- **IncludeResponseBody()**: Boolean (or a predicate of the HTTP context before execution) to indicate whether to include response body.
The predicate is evaluated before action execution. Default is false.
- **SkipResponseBodyContent()**: Boolean (or a predicate of the HTTP context after execution) to indicate whether to skip or include the response body content.
The predicate is evaluated after action execution. Default is false.
- **WithEventType()**: A string (or a function of the HTTP context that returns a string) that identifies the event type. Can contain the following placeholders (default is "{verb} {url}"):
- \{verb}: replaced with the HTTP verb used (GET, POST, etc).
- \{url}: replaced with the request URL.
Expand Down
2 changes: 2 additions & 0 deletions test/Audit.UnitTest/EventLogDataProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public void SetUp()
Core.Configuration.Reset();
}

#if !NETCOREAPP3_1
[Test]
public void TestEventLogDataProvider_InsertReplaceEvent()
{
Expand Down Expand Up @@ -45,6 +46,7 @@ public void TestEventLogDataProvider_InsertReplaceEvent()
// Assert
Assert.That(eventId1, Is.Null);
}
#endif

[Test]
public void Test_EventLogDataProvider_FluentApi()
Expand Down
38 changes: 4 additions & 34 deletions test/Audit.WebApi.UnitTest/IsolationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,9 @@
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Audit.Core;
using Audit.Core.Providers;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Audit.WebApi.UnitTest
{
Expand All @@ -33,31 +26,6 @@ public IActionResult Action_Middleware([FromQuery] string q)
}
}

public class TestHelper
{
public static TestServer GetTestServer(AuditDataProvider dataProvider)
{
return new TestServer(WebHost.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddSingleton(dataProvider);
services.AddControllers();
})
.Configure((ctx, app) =>
{
app.UseAuditMiddleware(cfg => cfg
.FilterByRequest(r => r.Path.Value?.Contains("Action_Middleware") == true));
app.UseRouting();
app.UseEndpoints(e =>
{
e.MapControllers();
});
})
.ConfigureLogging(log => log.SetMinimumLevel(LogLevel.Warning))
);
}
}

[Parallelizable]
public class IsolationTests
{
Expand All @@ -72,7 +40,8 @@ private async Task Test_Isolation_InjectDataProvider_AuditApiAttribute_TestOne()
{
var guid = Guid.NewGuid();
var dataProvider = new InMemoryDataProvider();
using var app = TestHelper.GetTestServer(dataProvider);
using var app = TestHelper.GetTestServer(dataProvider,
cfg => cfg.FilterByRequest(r => r.Path.Value?.Contains("Action_Middleware") == true));
using var client = app.CreateClient();

var response = await client.GetAsync($"/TestIsolation/Action_AuditApiAttribute?q={guid}");
Expand All @@ -93,7 +62,8 @@ private async Task Test_Isolation_InjectDataProvider_Middleware_TestOne()
{
var guid = Guid.NewGuid();
var dataProvider = new InMemoryDataProvider();
using var app = TestHelper.GetTestServer(dataProvider);
using var app = TestHelper.GetTestServer(dataProvider, cfg => cfg
.FilterByRequest(r => r.Path.Value?.Contains("Action_Middleware") == true));
using var client = app.CreateClient();

var response = await client.GetAsync($"/TestIsolation/Action_Middleware?q={guid}");
Expand Down
72 changes: 72 additions & 0 deletions test/Audit.WebApi.UnitTest/MiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#if NETCOREAPP3_1 || NET6_0
using System.Net;
using System.Threading.Tasks;
using Audit.Core.Providers;
using Microsoft.AspNetCore.Mvc;
using NUnit.Framework;

namespace Audit.WebApi.UnitTest
{
[Route("[controller]")]
[ApiController]
public class TestMiddlewareController : ControllerBase
{
[Route("")]
[HttpGet]
public IActionResult Get([FromQuery] int length)
{
return Ok(new string('#', length));
}
}

[Parallelizable]
public class MiddlewareTests
{
[TestCase(null, null, true, true)]
[TestCase(null, false, false, false)]
[TestCase(null, true, false, true)]
[TestCase(true, null, false, false)]
[TestCase(true, false, false, false)]
[TestCase(true, true, false, true)]
[TestCase(false, null, true, true)]
[TestCase(false, false, true, true)]
[TestCase(false, true, true, true)]
public async Task Test_IncludeResponseBody(bool? includeResponseBody, bool? skipResponseBody, bool expectNullResponseBody, bool expectNullResponseBodyContent)
{
var dataProvider = new InMemoryDataProvider();

using var app = TestHelper.GetTestServer(dataProvider, cfg =>
{
cfg.FilterByRequest(r => r.Path.Value?.Contains("TestMiddleware") == true);
if (includeResponseBody.HasValue)
{
cfg.IncludeResponseBody(includeResponseBody!.Value);
}
if (skipResponseBody.HasValue)
{
if (skipResponseBody.GetValueOrDefault())
{
cfg.SkipResponseBodyContent(_ => skipResponseBody!.Value);
}
else
{
cfg.SkipResponseBodyContent(skipResponseBody!.Value);
}
}
});

using var client = app.CreateClient();

var response = await client.GetAsync($"/TestMiddleware?length=10");

var events = dataProvider.GetAllEvents();

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));

Assert.That(events, Has.Count.EqualTo(1));
Assert.That(events[0].GetWebApiAuditAction().ResponseBody, expectNullResponseBody ? Is.Null : Is.Not.Null);
Assert.That(events[0].GetWebApiAuditAction().ResponseBody?.Value, expectNullResponseBodyContent ? Is.Null : Is.Not.Null);
}
}
}
#endif
37 changes: 37 additions & 0 deletions test/Audit.WebApi.UnitTest/TestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#if NETCOREAPP3_1 || NET6_0
using System;
using Audit.Core;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Audit.WebApi.UnitTest
{
public class TestHelper
{
public static TestServer GetTestServer(AuditDataProvider dataProvider, Action<ConfigurationApi.IAuditMiddlewareConfigurator> middlewareConfig)
{
return new TestServer(WebHost.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddSingleton(dataProvider);
services.AddControllers();
})
.Configure((ctx, app) =>
{
app.UseAuditMiddleware(middlewareConfig);
app.UseRouting();
app.UseEndpoints(e =>
{
e.MapControllers();
});
})
.ConfigureLogging(log => log.SetMinimumLevel(LogLevel.Warning))
);
}
}
}
#endif

0 comments on commit 8fd3105

Please sign in to comment.