Skip to content

Commit 51e063c

Browse files
committed
Add CustomerResourceId to enrich push metrics with tenant ID (#7936)
Progress on NuGet/Engineering#3106
1 parent 74cdb70 commit 51e063c

13 files changed

Lines changed: 236 additions & 7 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace NuGetGallery.Authentication
5+
{
6+
public static class MicrosoftClaims
7+
{
8+
private const string ClaimsDomain = "http://schemas.microsoft.com/identity/claims/";
9+
10+
/// <summary>
11+
/// The claim URL for the claim that stores the user's tenant ID, based on the external credential.
12+
/// </summary>
13+
public const string TenantId = ClaimsDomain + "tenantid";
14+
}
15+
}

src/NuGetGallery.Core/NuGetGallery.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
<Compile Include="Auditing\UserAuditRecord.cs" />
106106
<Compile Include="Authentication\AuthenticationExtensions.cs" />
107107
<Compile Include="Authentication\CredentialTypeInfo.cs" />
108+
<Compile Include="Authentication\MicrosoftClaims.cs" />
108109
<Compile Include="Certificates\CertificateFile.cs" />
109110
<Compile Include="Cookies\CookieComplianceServiceBase.cs" />
110111
<Compile Include="Cookies\CookieConsentMessage.cs" />

src/NuGetGallery.Services/Extensions/HttpContextBaseExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace NuGetGallery
1111
{
1212
public static class HttpContextBaseExtensions
1313
{
14+
public static HttpContextBase GetCurrent() => new HttpContextWrapper(HttpContext.Current);
15+
1416
public static User GetCurrentUser(this HttpContextBase httpContext)
1517
{
1618
return httpContext.GetOwinContext().GetCurrentUser();

src/NuGetGallery.Services/Extensions/IOwinContextExtensions.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Linq;
56
using System.Security.Claims;
6-
using System.Web;
77
using System.Web.Mvc;
88
using Microsoft.Owin;
99
using NuGet.Services.Entities;
10+
using NuGetGallery.Authentication;
1011

1112
namespace NuGetGallery
1213
{
@@ -64,9 +65,27 @@ private static User LoadUser(IOwinContext context)
6465

6566
if (!String.IsNullOrEmpty(userName))
6667
{
67-
return DependencyResolver.Current
68+
var user = DependencyResolver.Current
6869
.GetService<IUserService>()
6970
.FindByUsername(userName);
71+
72+
// Try to add the tenant ID information as an additional claim since we have the full user record
73+
// and the associated credentials.
74+
if (user != null && principal.Identity is ClaimsIdentity identity)
75+
{
76+
// From the schema, it is possible to have multiple credentials. Prefer the latest one.
77+
var externalCredential = user
78+
.Credentials
79+
.OrderByDescending(x => x.Created)
80+
.FirstOrDefault(c => c.IsExternal() && c.TenantId != null);
81+
82+
if (externalCredential != null)
83+
{
84+
identity.TryAddClaim(MicrosoftClaims.TenantId, externalCredential.TenantId);
85+
}
86+
}
87+
88+
return user;
7089
}
7190
}
7291
return null; // No user logged in, or credentials could not be resolved

src/NuGetGallery.Services/Extensions/PrincipalExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,17 @@ public static bool TryRemoveClaim(this IIdentity identity, string claimType)
213213

214214
return false;
215215
}
216+
217+
/// <summary>
218+
/// Get the tenant ID from the claims, if available. If no such claim exists, null is returned.
219+
/// </summary>
220+
/// <param name="identity">The identity to look for claims in.</param>
221+
/// <returns>The tenant ID or null.</returns>
222+
public static string GetTenantIdOrNull(this IIdentity identity)
223+
{
224+
var claimsIdentity = identity as ClaimsIdentity;
225+
return claimsIdentity?.FindFirst(MicrosoftClaims.TenantId)?.Value;
226+
}
216227

217228
/// <summary>
218229
/// Try to add a new default claim to the identity. It will not replace an existing claim.

src/NuGetGallery.Services/Telemetry/TelemetryService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace NuGetGallery
1818
{
1919
public class TelemetryService : ITelemetryService, IFeatureFlagTelemetryService
2020
{
21-
internal class Events
21+
public class Events
2222
{
2323
public const string ODataQueryFilter = "ODataQueryFilter";
2424
public const string ODataCustomQuery = "ODataCustomQuery";

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ internal static ApplicationInsightsConfiguration ConfigureApplicationInsights(
525525
telemetryConfiguration.TelemetryInitializers.Add(new ClientInformationTelemetryEnricher());
526526
telemetryConfiguration.TelemetryInitializers.Add(new KnownOperationNameEnricher());
527527
telemetryConfiguration.TelemetryInitializers.Add(new AzureWebAppTelemetryInitializer());
528+
telemetryConfiguration.TelemetryInitializers.Add(new CustomerResourceIdEnricher());
528529

529530
// Add processors
530531
telemetryConfiguration.TelemetryProcessorChainBuilder.Use(next =>

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@
587587
<Compile Include="Services\ValidationService.cs" />
588588
<Compile Include="Telemetry\ClientInformationTelemetryEnricher.cs" />
589589
<Compile Include="Telemetry\ClientTelemetryPIIProcessor.cs" />
590+
<Compile Include="Telemetry\CustomerResourceIdEnricher.cs" />
590591
<Compile Include="Telemetry\KnownOperationNameEnricher.cs" />
591592
<Compile Include="Telemetry\QuietExceptionLogger.cs" />
592593
<Compile Include="Infrastructure\PasswordValidationAttribute.cs" />

src/NuGetGallery/Telemetry/ClientInformationTelemetryEnricher.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ public void Initialize(ITelemetry telemetry)
4646
}
4747
}
4848

49-
protected virtual HttpContextBase GetHttpContext()
50-
{
51-
return new HttpContextWrapper(HttpContext.Current);
52-
}
49+
protected virtual HttpContextBase GetHttpContext() => HttpContextBaseExtensions.GetCurrent();
5350
}
5451
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Web;
7+
using Microsoft.ApplicationInsights.Channel;
8+
using Microsoft.ApplicationInsights.DataContracts;
9+
using Microsoft.ApplicationInsights.Extensibility;
10+
11+
namespace NuGetGallery
12+
{
13+
public class CustomerResourceIdEnricher : ITelemetryInitializer
14+
{
15+
private const string CustomerResourceId = "CustomerResourceId";
16+
private const string Prefix = "/tenants/";
17+
private static readonly string Empty = Prefix + Guid.Empty.ToString();
18+
19+
private static readonly HashSet<string> CustomMetricNames = new HashSet<string>
20+
{
21+
TelemetryService.Events.PackagePush,
22+
TelemetryService.Events.PackagePushFailure,
23+
};
24+
25+
public void Initialize(ITelemetry telemetry)
26+
{
27+
var metric = telemetry as MetricTelemetry;
28+
if (metric == null)
29+
{
30+
return;
31+
}
32+
33+
if (!CustomMetricNames.Contains(metric.Name))
34+
{
35+
return;
36+
}
37+
38+
var itemTelemetry = telemetry as ISupportProperties;
39+
if (itemTelemetry == null)
40+
{
41+
return;
42+
}
43+
44+
var httpContext = GetHttpContext();
45+
var tenantId = httpContext?.User?.Identity.GetTenantIdOrNull();
46+
var customerResourceId = tenantId != null ? Prefix + tenantId : Empty;
47+
itemTelemetry.Properties[CustomerResourceId] = customerResourceId;
48+
}
49+
50+
protected virtual HttpContextBase GetHttpContext() => HttpContextBaseExtensions.GetCurrent();
51+
}
52+
}

0 commit comments

Comments
 (0)