diff --git a/src/Shared/LayoutRenderers/AspNetUserClaimLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetUserClaimLayoutRenderer.cs
index b895111a..e0d2ee2b 100644
--- a/src/Shared/LayoutRenderers/AspNetUserClaimLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetUserClaimLayoutRenderer.cs
@@ -9,6 +9,8 @@
using NLog.Common;
using NLog.Config;
using NLog.LayoutRenderers;
+using NLog.Layouts;
+using NLog.Web.Enums;
namespace NLog.Web.LayoutRenderers
{
@@ -28,11 +30,16 @@ public class AspNetUserClaimLayoutRenderer : AspNetLayoutMultiValueRendererBase
///
///
/// When value is prefixed with "ClaimTypes." (Remember dot) then ít will lookup in well-known claim types from . Ex. ClaimsTypes.Name
- /// If this is null or empty then all claim types are rendered
+ /// If this is null or empty then the Type and Value properties of all claim types are rendered
///
[DefaultParameter]
public string ClaimType { get; set; }
+ ///
+ /// If this is true, then all string properties of the are rendered as well the values in its Properties property.
+ ///
+ public bool Verbose { get; set; }
+
///
protected override void InitializeLayoutRenderer()
{
@@ -64,15 +71,34 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
if (string.IsNullOrEmpty(ClaimType))
{
- var allClaims = GetAllClaims(claimsPrincipal);
- SerializePairs(allClaims, builder, logEvent);
+ if (Verbose)
+ {
+#if NET46
+ SerializeVerbose((claimsPrincipal as ClaimsPrincipal)?.Claims, builder, logEvent);
+#else
+ SerializeVerbose(claimsPrincipal.Claims, builder, logEvent);
+#endif
+ }
+ else
+ {
+ var allClaims = GetAllClaims(claimsPrincipal);
+ SerializePairs(allClaims, builder, logEvent);
+ }
}
else
{
var claim = GetClaim(claimsPrincipal, ClaimType);
if (claim != null)
{
- builder.Append(claim?.Value);
+ if (Verbose)
+ {
+ SerializeVerbose(new List {claim}, builder, logEvent);
+ }
+ else
+ {
+ builder.Append(claim.Value);
+
+ }
}
}
}
@@ -82,6 +108,174 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
}
}
+ private void SerializeVerbose(IEnumerable claims, StringBuilder builder, LogEventInfo logEvent)
+ {
+ if (claims == null)
+ {
+ return;
+ }
+
+ switch (OutputFormat)
+ {
+ case AspNetRequestLayoutOutputFormat.Flat:
+ SerializeVerboseFlat(claims, builder, logEvent);
+ break;
+ case AspNetRequestLayoutOutputFormat.JsonArray:
+ case AspNetRequestLayoutOutputFormat.JsonDictionary:
+ SerializeVerboseJson(claims, builder, logEvent);
+ break;
+ }
+ }
+
+ private void SerializeVerboseJson(IEnumerable claims, StringBuilder builder, LogEventInfo logEvent)
+ {
+ var firstItem = true;
+ var includeSeparator = false;
+
+ foreach (var claim in claims)
+ {
+ if (firstItem)
+ {
+ if (OutputFormat == AspNetRequestLayoutOutputFormat.JsonDictionary)
+ {
+ builder.Append('{');
+ }
+ else
+ {
+ builder.Append('[');
+ }
+ }
+ else
+ {
+ builder.Append(',');
+ }
+
+ builder.Append('{');
+
+ includeSeparator |= AppendJsonProperty(builder, nameof(claim.Type), claim.Type, false);
+ includeSeparator |= AppendJsonProperty(builder, nameof(claim.Value), claim.Value, includeSeparator);
+ includeSeparator |= AppendJsonProperty(builder, nameof(claim.ValueType), claim.ValueType, includeSeparator);
+
+ includeSeparator |= AppendJsonProperty(builder, nameof(claim.Issuer), claim.Issuer, includeSeparator);
+ includeSeparator |= AppendJsonProperty(builder, nameof(claim.OriginalIssuer), claim.OriginalIssuer, includeSeparator);
+
+ if (claim.Properties != null && claim.Properties.Count > 0)
+ {
+ builder.Append(",\"");
+ builder.Append(nameof(claim.Properties));
+ builder.Append("\":");
+ SerializePairs(claim.Properties.OrderBy(entry => entry.Key).ToList(), builder, logEvent);
+ }
+
+ builder.Append('}');
+
+ firstItem = false;
+ }
+
+ if (!firstItem)
+ {
+ if (OutputFormat == AspNetRequestLayoutOutputFormat.JsonDictionary)
+ {
+ builder.Append('}');
+ }
+ else
+ {
+ builder.Append(']');
+ }
+ }
+ }
+
+ private void SerializeVerboseFlat(IEnumerable claims, StringBuilder builder, LogEventInfo logEvent)
+ {
+ var propertySeparator = GetRenderedItemSeparator(logEvent);
+ var valueSeparator = GetRenderedValueSeparator(logEvent);
+ var objectSeparator = GetRenderedObjectSeparator(logEvent);
+
+ var firstObject = true;
+ var includeSeparator = false;
+
+ foreach (var claim in claims)
+ {
+ if (!firstObject)
+ {
+ builder.Append(objectSeparator);
+ }
+
+ firstObject = false;
+
+ includeSeparator |= AppendFlatProperty(builder, nameof(claim.Type), claim.Type, valueSeparator, "");
+ includeSeparator |= AppendFlatProperty(builder, nameof(claim.Value), claim.Value, valueSeparator, includeSeparator ? propertySeparator : "");
+ includeSeparator |= AppendFlatProperty(builder, nameof(claim.ValueType), claim.ValueType, valueSeparator, includeSeparator ? propertySeparator : "");
+
+ includeSeparator |= AppendFlatProperty(builder, nameof(claim.Issuer), claim.Issuer, valueSeparator, includeSeparator ? propertySeparator : "");
+ includeSeparator |= AppendFlatProperty(builder, nameof(claim.OriginalIssuer), claim.OriginalIssuer, valueSeparator, includeSeparator ? propertySeparator : "");
+
+ if (claim.Properties != null && claim.Properties.Count > 0)
+ {
+ builder.Append(propertySeparator);
+ builder.Append("Properties[");
+ SerializePairs(claim.Properties.OrderBy(entry => entry.Key).ToList(), builder, logEvent);
+ builder.Append(']');
+ }
+ }
+ }
+
+ ///
+ /// Separator between objects, like cookies. Only used for
+ ///
+ /// Render with
+ public string ObjectSeparator { get => _objectSeparatorLayout?.OriginalText; set => _objectSeparatorLayout = new SimpleLayout(value ?? ""); }
+ private SimpleLayout _objectSeparatorLayout = new SimpleLayout(";");
+
+ ///
+ /// Get the rendered
+ ///
+ private string GetRenderedObjectSeparator(LogEventInfo logEvent)
+ {
+ return logEvent != null ? _objectSeparatorLayout.Render(logEvent) : ObjectSeparator;
+ }
+
+ ///
+ /// Append the quoted name and value separated by a colon
+ ///
+ private static bool AppendJsonProperty(StringBuilder builder, string name, string value, bool includePropertySeparator)
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ if (includePropertySeparator)
+ {
+ builder.Append(',');
+ }
+ AppendQuoted(builder, name);
+ builder.Append(':');
+ AppendQuoted(builder, value);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Append the quoted name and value separated by a value separator
+ /// and ended by item separator
+ ///
+ private static bool AppendFlatProperty(
+ StringBuilder builder,
+ string name,
+ string value,
+ string valueSeparator,
+ string itemSeparator)
+ {
+ if (!string.IsNullOrEmpty(value))
+ {
+ builder.Append(itemSeparator);
+ builder.Append(name);
+ builder.Append(valueSeparator);
+ builder.Append(value);
+ return true;
+ }
+ return false;
+ }
+
#if NET46
private static IEnumerable> GetAllClaims(System.Security.Principal.IPrincipal claimsPrincipal)
{
diff --git a/tests/Shared/LayoutRenderers/AspNetUserClaimLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetUserClaimLayoutRendererTests.cs
index becd3abd..612391a2 100644
--- a/tests/Shared/LayoutRenderers/AspNetUserClaimLayoutRendererTests.cs
+++ b/tests/Shared/LayoutRenderers/AspNetUserClaimLayoutRendererTests.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
+using NLog.Web.Enums;
#if ASP_NET_CORE
using Microsoft.Extensions.Primitives;
using HttpContextBase = Microsoft.AspNetCore.Http.HttpContext;
@@ -11,6 +12,7 @@
using NLog.Web.LayoutRenderers;
using NSubstitute;
using Xunit;
+using static System.Net.WebRequestMethods;
namespace NLog.Web.Tests.LayoutRenderers
{
@@ -96,6 +98,111 @@ public void AllRendersAllValue()
// Assert
Assert.Equal(expectedResult, result);
}
+
+ [Fact]
+ public void VerboseMultipleFlatTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ renderer.OutputFormat = AspNetRequestLayoutOutputFormat.Flat;
+ renderer.Verbose = true;
+
+ var expectedResult =
+ "Type=http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor,Value=Actorvalue,ValueType=Actorstring,Issuer=Actorissuer,OriginalIssuer=ActororiginalIssuer,Properties[claim1property1=claim1value1,claim1property2=claim1value2];Type=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/anonymous,Value=Anonymousvalue,ValueType=Anonymousstring,Issuer=Anonymousissuer,OriginalIssuer=AnonymousoriginalIssuer;Type=http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication,Value=Authenticationvalue,ValueType=Authenticationstring,Issuer=Authenticationissuer,OriginalIssuer=AuthenticationoriginalIssuer";
+
+ var principal = Substitute.For();
+
+ var claim1 = new Claim(ClaimTypes.Actor, "Actorvalue", "Actorstring", "Actorissuer", "ActororiginalIssuer");
+ var claim2 = new Claim(ClaimTypes.Anonymous, "Anonymousvalue", "Anonymousstring", "Anonymousissuer", "AnonymousoriginalIssuer");
+ var claim3 = new Claim(ClaimTypes.Authentication, "Authenticationvalue", "Authenticationstring", "Authenticationissuer", "AuthenticationoriginalIssuer");
+
+ claim1.Properties.Add("claim1property1","claim1value1");
+ claim1.Properties.Add("claim1property2", "claim1value2");
+
+ principal.Claims.Returns(new List()
+ {
+ claim1, claim2, claim3
+ }
+ );
+
+ httpContext.User.Returns(principal);
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Fact]
+ public void VerboseMultipleJsonArrayTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonArray;
+ renderer.Verbose = true;
+
+ var expectedResult =
+ "[{\"Type\":\"http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor\",\"Value\":\"Actorvalue\",\"ValueType\":\"Actorstring\",\"Issuer\":\"Actorissuer\",\"OriginalIssuer\":\"ActororiginalIssuer\",\"Properties\":[{\"claim1property1\":\"claim1value1\"},{\"claim1property2\":\"claim1value2\"}]},{\"Type\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/anonymous\",\"Value\":\"Anonymousvalue\",\"ValueType\":\"Anonymousstring\",\"Issuer\":\"Anonymousissuer\",\"OriginalIssuer\":\"AnonymousoriginalIssuer\"},{\"Type\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication\",\"Value\":\"Authenticationvalue\",\"ValueType\":\"Authenticationstring\",\"Issuer\":\"Authenticationissuer\",\"OriginalIssuer\":\"AuthenticationoriginalIssuer\"}]";
+
+ var principal = Substitute.For();
+
+ var claim1 = new Claim(ClaimTypes.Actor, "Actorvalue", "Actorstring", "Actorissuer", "ActororiginalIssuer");
+ var claim2 = new Claim(ClaimTypes.Anonymous, "Anonymousvalue", "Anonymousstring", "Anonymousissuer", "AnonymousoriginalIssuer");
+ var claim3 = new Claim(ClaimTypes.Authentication, "Authenticationvalue", "Authenticationstring", "Authenticationissuer", "AuthenticationoriginalIssuer");
+
+ claim1.Properties.Add("claim1property1", "claim1value1");
+ claim1.Properties.Add("claim1property2", "claim1value2");
+
+ principal.Claims.Returns(new List()
+ {
+ claim1, claim2, claim3
+ }
+ );
+
+ httpContext.User.Returns(principal);
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Fact]
+ public void VerboseMultipleJsonDictionaryTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ renderer.OutputFormat = AspNetRequestLayoutOutputFormat.JsonDictionary;
+ renderer.Verbose = true;
+
+ var expectedResult =
+ "{{\"Type\":\"http://schemas.xmlsoap.org/ws/2009/09/identity/claims/actor\",\"Value\":\"Actorvalue\",\"ValueType\":\"Actorstring\",\"Issuer\":\"Actorissuer\",\"OriginalIssuer\":\"ActororiginalIssuer\",\"Properties\":{\"claim1property1\":\"claim1value1\",\"claim1property2\":\"claim1value2\"}},{\"Type\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/anonymous\",\"Value\":\"Anonymousvalue\",\"ValueType\":\"Anonymousstring\",\"Issuer\":\"Anonymousissuer\",\"OriginalIssuer\":\"AnonymousoriginalIssuer\"},{\"Type\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/authentication\",\"Value\":\"Authenticationvalue\",\"ValueType\":\"Authenticationstring\",\"Issuer\":\"Authenticationissuer\",\"OriginalIssuer\":\"AuthenticationoriginalIssuer\"}}";
+
+ var principal = Substitute.For();
+
+ var claim1 = new Claim(ClaimTypes.Actor, "Actorvalue", "Actorstring", "Actorissuer", "ActororiginalIssuer");
+ var claim2 = new Claim(ClaimTypes.Anonymous, "Anonymousvalue", "Anonymousstring", "Anonymousissuer", "AnonymousoriginalIssuer");
+ var claim3 = new Claim(ClaimTypes.Authentication, "Authenticationvalue", "Authenticationstring", "Authenticationissuer", "AuthenticationoriginalIssuer");
+
+ claim1.Properties.Add("claim1property1", "claim1value1");
+ claim1.Properties.Add("claim1property2", "claim1value2");
+
+ principal.Claims.Returns(new List()
+ {
+ claim1, claim2, claim3
+ }
+ );
+
+ httpContext.User.Returns(principal);
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
}
}