Skip to content

Commit

Permalink
AspNetUserClaimLayoutRenderer - Extended with Azure Claims ObjectId +…
Browse files Browse the repository at this point in the history
… TenantId + AppId (#1069)
  • Loading branch information
snakefoot authored Nov 23, 2024
1 parent 5756772 commit a63b270
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 3 deletions.
61 changes: 58 additions & 3 deletions src/Shared/LayoutRenderers/AspNetUserClaimLayoutRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,44 @@ namespace NLog.Web.LayoutRenderers
public class AspNetUserClaimLayoutRenderer : AspNetLayoutMultiValueRendererBase
{
/// <summary>
/// Key to lookup using <see cref="ClaimsIdentity.FindFirst(string)"/> with fallback to <see cref="ClaimsPrincipal.FindFirst(string)"/>
/// New ObjectId claim: "oid".
/// </summary>
#pragma warning disable S3962 // Replace this 'static readonly' declaration with 'const'
private static readonly string Oid = "oid";
#pragma warning restore S3962 // Replace this 'static readonly' declaration with 'const'

/// <summary>
/// Old ObjectId claim: "http://schemas.microsoft.com/identity/claims/objectidentifier".
/// </summary>
private const string ObjectId = "http://schemas.microsoft.com/identity/claims/objectidentifier";

/// <summary>
/// New TenantId claim: "tid".
/// </summary>
#pragma warning disable S3962 // Replace this 'static readonly' declaration with 'const'
private static readonly string Tid = "tid";
#pragma warning restore S3962 // Replace this 'static readonly' declaration with 'const'

/// <summary>
/// Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid".
/// </summary>
private const string TenantId = "http://schemas.microsoft.com/identity/claims/tenantid";

#pragma warning disable S3962 // Replace this 'static readonly' declaration with 'const'
private static readonly string AppId = "appid";
#pragma warning restore S3962 // Replace this 'static readonly' declaration with 'const'

private const string Azp = "azp";

/// <summary>
/// Key to lookup using primary <see cref="ClaimsIdentity.FindFirst(string)"/> with fallback to <see cref="ClaimsPrincipal.FindFirst(string)"/>
/// </summary>
/// <remarks>
/// When value is prefixed with "ClaimTypes." (Remember dot) then ít will lookup in well-known claim types from <see cref="ClaimTypes"/>. Ex. ClaimsTypes.Name
/// If this is null or empty then all claim types are rendered
/// When value is prefixed with "ClaimTypes." (Remember dot) then ít will lookup in well-known claim types from <see cref="ClaimTypes"/>. Ex. ClaimsTypes.Name .
///
/// Additional Azure Claims are also recognized: ClaimTypes.ObjectId + ClaimTypes.TenantId + ClaimTypes.AppId .
///
/// If using value null or empty then all claim types are rendered.
/// </remarks>
[DefaultParameter]
public string ClaimType { get; set; }
Expand All @@ -45,6 +78,18 @@ protected override void InitializeLayoutRenderer()
{
ClaimType = claimTypesField.GetValue(null) as string ?? ClaimType.Trim();
}
else if (nameof(ObjectId).Equals(fieldName, StringComparison.OrdinalIgnoreCase) || nameof(Oid).Equals(fieldName, StringComparison.OrdinalIgnoreCase))
{
ClaimType = Oid;
}
else if (nameof(TenantId).Equals(fieldName, StringComparison.OrdinalIgnoreCase) || nameof(Tid).Equals(fieldName, StringComparison.OrdinalIgnoreCase))
{
ClaimType = Tid;
}
else if (nameof(AppId).Equals(fieldName, StringComparison.OrdinalIgnoreCase) || nameof(Azp).Equals(fieldName, StringComparison.OrdinalIgnoreCase))
{
ClaimType = AppId;
}
}

base.InitializeLayoutRenderer();
Expand All @@ -70,6 +115,16 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
else
{
var claim = GetClaim(claimsPrincipal, ClaimType);
if (claim is null)
{
if (ReferenceEquals(ClaimType, Oid))
claim = GetClaim(claimsPrincipal, ObjectId); // Fallback to old style
else if (ReferenceEquals(ClaimType, Tid))
claim = GetClaim(claimsPrincipal, TenantId); // Fallback to old style
else if (ReferenceEquals(ClaimType, AppId))
claim = GetClaim(claimsPrincipal, Azp); // Fallback to Authorized Parties
}

builder.Append(claim?.Value);
}
}
Expand Down
21 changes: 21 additions & 0 deletions tests/Shared/LayoutRenderers/AspNetUserClaimLayoutRendererTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,27 @@ public void UserClaimTypeNameRendersValue()
Assert.Equal(expectedResult, result);
}

[Theory]
[InlineData("ClaimType.ObjectId", "oid")]
[InlineData("ClaimType.TenantId", "tid")]
[InlineData("ClaimType.AppId", "azp")]
public void UserClaimTypeNameRendersAzureClaims(string claimType, string claimId)
{
// Arrange
var (renderer, httpContext) = CreateWithHttpContext();
renderer.ClaimType = claimType;

var identity = Substitute.For<System.Security.Claims.ClaimsIdentity>();
identity.FindFirst(claimId).Returns(new System.Security.Claims.Claim(claimId, claimType));
httpContext.User.Identity.Returns(identity);

// Act
string result = renderer.Render(new LogEventInfo());

// Assert
Assert.Equal(claimType, result);
}

[Fact]
public void AllRendersAllValue()
{
Expand Down

0 comments on commit a63b270

Please sign in to comment.