Skip to content

Commit 65c8195

Browse files
authored
refactor PII Logging behavior (#23698)
1 parent d50b687 commit 65c8195

10 files changed

+127
-42
lines changed

sdk/identity/Azure.Identity/src/AzureIdentityEventSource.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -162,25 +162,22 @@ private static string FormatException(Exception ex)
162162
}
163163

164164
[NonEvent]
165-
public void LogMsal(Microsoft.Identity.Client.LogLevel level, string message, bool containsPii)
165+
public void LogMsal(Microsoft.Identity.Client.LogLevel level, string message)
166166
{
167-
if (!containsPii)
167+
switch (level)
168168
{
169-
switch (level)
170-
{
171-
case Microsoft.Identity.Client.LogLevel.Error when IsEnabled(EventLevel.Error, EventKeywords.All):
172-
LogMsalError(message);
173-
break;
174-
case Microsoft.Identity.Client.LogLevel.Warning when IsEnabled(EventLevel.Warning, EventKeywords.All):
175-
LogMsalWarning(message);
176-
break;
177-
case Microsoft.Identity.Client.LogLevel.Info when IsEnabled(EventLevel.Informational, EventKeywords.All):
178-
LogMsalInformational(message);
179-
break;
180-
case Microsoft.Identity.Client.LogLevel.Verbose when IsEnabled(EventLevel.Verbose, EventKeywords.All):
181-
LogMsalVerbose(message);
182-
break;
183-
}
169+
case Microsoft.Identity.Client.LogLevel.Error when IsEnabled(EventLevel.Error, EventKeywords.All):
170+
LogMsalError(message);
171+
break;
172+
case Microsoft.Identity.Client.LogLevel.Warning when IsEnabled(EventLevel.Warning, EventKeywords.All):
173+
LogMsalWarning(message);
174+
break;
175+
case Microsoft.Identity.Client.LogLevel.Info when IsEnabled(EventLevel.Informational, EventKeywords.All):
176+
LogMsalInformational(message);
177+
break;
178+
case Microsoft.Identity.Client.LogLevel.Verbose when IsEnabled(EventLevel.Verbose, EventKeywords.All):
179+
LogMsalVerbose(message);
180+
break;
184181
}
185182
}
186183

sdk/identity/Azure.Identity/src/MsalClientBase.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal abstract class MsalClientBase<TClient>
1111
where TClient : IClientApplicationBase
1212
{
1313
private readonly AsyncLockWithValue<TClient> _clientAsyncLock;
14-
internal protected bool LogPII { get; protected set; }
14+
internal protected bool IsPiiLoggingEnabled { get; }
1515

1616
/// <summary>
1717
/// For mocking purposes only.
@@ -20,7 +20,7 @@ protected MsalClientBase()
2020
{
2121
}
2222

23-
protected MsalClientBase(CredentialPipeline pipeline, string tenantId, string clientId, ITokenCacheOptions cacheOptions)
23+
protected MsalClientBase(CredentialPipeline pipeline, string tenantId, string clientId, bool isPiiLoggingEnabled, ITokenCacheOptions cacheOptions)
2424
{
2525
// This validation is performed as a backstop. Validation in TokenCredentialOptions.AuthorityHost prevents users from explicitly
2626
// setting AuthorityHost to a non TLS endpoint. However, the AuthorityHost can also be set by the AZURE_AUTHORITY_HOST environment
@@ -29,14 +29,11 @@ protected MsalClientBase(CredentialPipeline pipeline, string tenantId, string cl
2929
// we validate here as all other credentials will create an MSAL client.
3030
Validations.ValidateAuthorityHost(pipeline.AuthorityHost);
3131

32+
IsPiiLoggingEnabled = isPiiLoggingEnabled;
3233
Pipeline = pipeline;
33-
3434
TenantId = tenantId;
35-
3635
ClientId = clientId;
37-
3836
TokenCache = cacheOptions?.TokenCachePersistenceOptions == null ? null : new TokenCache(cacheOptions?.TokenCachePersistenceOptions);
39-
4037
_clientAsyncLock = new AsyncLockWithValue<TClient>();
4138
}
4239

@@ -73,5 +70,13 @@ protected async ValueTask<TClient> GetClientAsync(bool async, CancellationToken
7370
asyncLock.SetValue(client);
7471
return client;
7572
}
73+
74+
protected void LogMsal(LogLevel level, string message, bool isPii)
75+
{
76+
if (!isPii || IsPiiLoggingEnabled)
77+
{
78+
AzureIdentityEventSource.Singleton.LogMsal(level, message);
79+
}
80+
}
7681
}
7782
}

sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,21 @@ internal class MsalConfidentialClient : MsalClientBase<IConfidentialClientApplic
1818
/// For mocking purposes only.
1919
/// </summary>
2020
protected MsalConfidentialClient()
21-
: base()
22-
{
23-
}
21+
{ }
2422

25-
public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, string clientSecret, ITokenCacheOptions cacheOptions, RegionalAuthority? regionalAuthority, bool logPii)
26-
: base(pipeline, tenantId, clientId, cacheOptions)
23+
public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, string clientSecret, ITokenCacheOptions cacheOptions, RegionalAuthority? regionalAuthority, bool isPiiLoggingEnabled)
24+
: base(pipeline, tenantId, clientId, isPiiLoggingEnabled, cacheOptions)
2725
{
2826
_clientSecret = clientSecret;
2927
RegionalAuthority = regionalAuthority;
30-
LogPII = logPii;
3128
}
3229

33-
public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, ITokenCacheOptions cacheOptions, RegionalAuthority? regionalAuthority, bool logPii)
34-
: base(pipeline, tenantId, clientId, cacheOptions)
30+
public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, ITokenCacheOptions cacheOptions, RegionalAuthority? regionalAuthority, bool isPiiLoggingEnabled)
31+
: base(pipeline, tenantId, clientId, isPiiLoggingEnabled, cacheOptions)
3532
{
3633
_includeX5CClaimHeader = includeX5CClaimHeader;
3734
_certificateProvider = certificateProvider;
3835
RegionalAuthority = regionalAuthority;
39-
LogPII = logPii;
4036
}
4137

4238
internal RegionalAuthority? RegionalAuthority { get; }
@@ -46,7 +42,7 @@ protected override async ValueTask<IConfidentialClientApplication> CreateClientA
4642
ConfidentialClientApplicationBuilder confClientBuilder = ConfidentialClientApplicationBuilder.Create(ClientId)
4743
.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, TenantId)
4844
.WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline))
49-
.WithLogging(AzureIdentityEventSource.Singleton.LogMsal, enablePiiLogging: LogPII);
45+
.WithLogging(LogMsal, enablePiiLogging: IsPiiLoggingEnabled);
5046

5147
if (_clientSecret != null)
5248
{

sdk/identity/Azure.Identity/src/MsalPublicClient.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ internal class MsalPublicClient : MsalClientBase<IPublicClientApplication>
1818
protected MsalPublicClient()
1919
{ }
2020

21-
public MsalPublicClient(CredentialPipeline pipeline, string tenantId, string clientId, string redirectUrl, ITokenCacheOptions cacheOptions, bool logPII)
22-
: base(pipeline, tenantId, clientId, cacheOptions)
21+
public MsalPublicClient(CredentialPipeline pipeline, string tenantId, string clientId, string redirectUrl, ITokenCacheOptions cacheOptions, bool isPiiLoggingEnabled)
22+
: base(pipeline, tenantId, clientId, isPiiLoggingEnabled, cacheOptions)
2323
{
2424
RedirectUrl = redirectUrl;
25-
LogPII = logPII;
2625
}
2726

2827
protected override ValueTask<IPublicClientApplication> CreateClientAsync(bool async, CancellationToken cancellationToken)
@@ -42,7 +41,7 @@ protected virtual ValueTask<IPublicClientApplication> CreateClientCoreAsync(stri
4241
.Create(ClientId)
4342
.WithAuthority(authorityUri)
4443
.WithHttpClientFactory(new HttpPipelineClientFactory(Pipeline.HttpPipeline))
45-
.WithLogging(AzureIdentityEventSource.Singleton.LogMsal, enablePiiLogging: LogPII);
44+
.WithLogging(LogMsal, enablePiiLogging: IsPiiLoggingEnabled);
4645

4746
if (!string.IsNullOrEmpty(RedirectUrl))
4847
{

sdk/identity/Azure.Identity/tests/DeviceCodeCredentialTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public void RespectsIsPIILoggingEnabled([Values(true, false)] bool isLoggingPIIE
127127
var credential = new DeviceCodeCredential(new DeviceCodeCredentialOptions { IsLoggingPIIEnabled = isLoggingPIIEnabled});
128128

129129
Assert.NotNull(credential.Client);
130-
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.LogPII);
130+
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.IsPiiLoggingEnabled);
131131
}
132132

133133
[Test]

sdk/identity/Azure.Identity/tests/InteractiveBrowserCredentialTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void RespectsIsPIILoggingEnabled([Values(true, false)] bool isLoggingPIIE
5555
var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions { IsLoggingPIIEnabled = isLoggingPIIEnabled});
5656

5757
Assert.NotNull(credential.Client);
58-
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.LogPII);
58+
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.IsPiiLoggingEnabled);
5959
}
6060

6161
[Test]
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.Tracing;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Azure.Core;
10+
using Azure.Core.Diagnostics;
11+
using Azure.Core.Pipeline;
12+
using Microsoft.Identity.Client;
13+
using NUnit.Framework;
14+
using Azure.Core.TestFramework;
15+
16+
namespace Azure.Identity.Tests
17+
{
18+
public class MsalClientBaseTests
19+
{
20+
[Test]
21+
[NonParallelizable]
22+
public void LogPiiIsEnforcedPerInstance([Values(true, false)] bool logPii)
23+
{
24+
string client1Message = "client1";
25+
string client2Message = "client2";
26+
List<string> messages = new();
27+
28+
using AzureEventSourceListener listener = new(
29+
(args, _) =>
30+
{
31+
if (args.EventName.StartsWith(nameof(AzureIdentityEventSource.LogMsal)))
32+
{
33+
messages.Add(args.GetProperty<string>("message"));
34+
}
35+
},
36+
EventLevel.Verbose);
37+
38+
var client_1 = new MockMsalClient(
39+
new CredentialPipeline(new Uri("https://w.com"), new HttpPipeline(new MockTransport()), new ClientDiagnostics(Moq.Mock.Of<ClientOptions>())),
40+
"tenant",
41+
"client",
42+
logPii,
43+
new InteractiveBrowserCredentialOptions());
44+
45+
var client_2 = new MockMsalClient(
46+
new CredentialPipeline(new Uri("https://w.com"), new HttpPipeline(new MockTransport()), new ClientDiagnostics(Moq.Mock.Of<ClientOptions>())),
47+
"tenant",
48+
"client",
49+
false, // never log PII
50+
new InteractiveBrowserCredentialOptions());
51+
52+
Assert.AreEqual(logPii, client_1.IsPiiLoggingEnabled);
53+
54+
client_1.Log(client1Message, true);
55+
client_2.Log(client2Message, true);
56+
57+
if (logPii)
58+
{
59+
Assert.Contains(client1Message, messages);
60+
}
61+
Assert.That(messages, Does.Not.Contain(client2Message));
62+
messages.Clear();
63+
64+
client_1.Log(client1Message, false);
65+
client_2.Log(client2Message, false);
66+
67+
Assert.That(messages, Contains.Item(client1Message));
68+
Assert.That(messages, Contains.Item(client2Message));
69+
}
70+
71+
private class MockMsalClient : MsalClientBase<IClientApplicationBase>
72+
{
73+
public MockMsalClient(CredentialPipeline pipeline, string tenantId, string clientId, bool isPiiLoggingEnabled, ITokenCacheOptions cacheOptions)
74+
: base(pipeline, tenantId, clientId, isPiiLoggingEnabled, cacheOptions)
75+
{ }
76+
77+
public ManualResetEventSlim Evt { get; set; }
78+
79+
protected override ValueTask<IClientApplicationBase> CreateClientAsync(bool async, CancellationToken cancellationToken)
80+
=> throw new NotImplementedException();
81+
82+
public void Log(string message, bool isPii)
83+
{
84+
LogMsal(LogLevel.Error, message, isPii);
85+
}
86+
}
87+
}
88+
}

sdk/identity/Azure.Identity/tests/SharedTokenCacheCredentialTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void RespectsIsPIILoggingEnabled([Values(true, false)] bool isLoggingPIIE
7878
var credential = new SharedTokenCacheCredential(new SharedTokenCacheCredentialOptions{ IsLoggingPIIEnabled = isLoggingPIIEnabled});
7979

8080
Assert.NotNull(credential.Client);
81-
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.LogPII);
81+
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.IsPiiLoggingEnabled);
8282
}
8383

8484
[Test]

sdk/identity/Azure.Identity/tests/UsernamePasswordCredentialTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void RespectsIsPIILoggingEnabled([Values(true, false)] bool isLoggingPIIE
7272
null);
7373

7474
Assert.NotNull(credential.Client);
75-
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.LogPII);
75+
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.IsPiiLoggingEnabled);
7676
}
7777

7878
[Test]

sdk/identity/Azure.Identity/tests/VisualStudioCodeCredentialTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void RespectsIsPIILoggingEnabled([Values(true, false)] bool isLoggingPIIE
8888
var credential = new VisualStudioCodeCredential(new VisualStudioCodeCredentialOptions{ IsLoggingPIIEnabled = isLoggingPIIEnabled});
8989

9090
Assert.NotNull(credential.Client);
91-
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.LogPII);
91+
Assert.AreEqual(isLoggingPIIEnabled, credential.Client.IsPiiLoggingEnabled);
9292
}
9393

9494
[Test]

0 commit comments

Comments
 (0)