Skip to content

Commit 36f7d92

Browse files
pmaytakjmprieur
andauthored
Update audience exceptions (#3195)
* Update audience exceptions. * Add switch tests * Apply suggestions from code review * Update API files. --------- Co-authored-by: Jean-Marc Prieur <[email protected]>
1 parent 333dc69 commit 36f7d92

File tree

9 files changed

+141
-53
lines changed

9 files changed

+141
-53
lines changed

src/Microsoft.IdentityModel.Tokens/AppContextSwitches.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ internal static class AppContextSwitches
8484
/// </summary>
8585
internal static bool UpdateConfigAsBlocking => _updateConfigAsBlockingCall ??= (AppContext.TryGetSwitch(UpdateConfigAsBlockingSwitch, out bool blockingCall) && blockingCall);
8686

87+
/// <summary>
88+
/// When enabled, some exceptions and log messages will contain additional details. Enable temporarily only for debugging purposes.
89+
/// </summary>
90+
internal const string DoNotScrubExceptionsSwitch = "Switch.Microsoft.IdentityModel.DoNotScrubExceptions";
91+
private static bool? _doNotScrubExceptions;
92+
internal static bool DoNotScrubExceptions => _doNotScrubExceptions ??= (AppContext.TryGetSwitch(DoNotScrubExceptionsSwitch, out bool doNotScrubExceptions) && doNotScrubExceptions);
93+
8794
/// <summary>
8895
/// Used for testing to reset all switches to its default value.
8996
/// </summary>
@@ -103,6 +110,9 @@ internal static void ResetAllSwitches()
103110

104111
_updateConfigAsBlockingCall = null;
105112
AppContext.SetSwitch(UpdateConfigAsBlockingSwitch, false);
113+
114+
_doNotScrubExceptions = null;
115+
AppContext.SetSwitch(DoNotScrubExceptionsSwitch, false);
106116
}
107117
}
108118
}

src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10214S = "IDX10214: Audience validation failed. See https://aka.ms/identitymodel/app-context-switches" -> string
2+
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10215S = "IDX10215: Audience validation failed. See https://aka.ms/identitymodel/app-context-switches" -> string
13
Microsoft.IdentityModel.Tokens.Cnf.Jku.set -> void
24
Microsoft.IdentityModel.Tokens.Cnf.JsonWebKey.set -> void
35
Microsoft.IdentityModel.Tokens.Cnf.Jwe.set -> void

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ internal static class LogMessages
4141
public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace.";
4242
public const string IDX10212 = "IDX10212: Issuer validation failed. Issuer: '{0}'. Did not match any: validationParameters.ValidIssuers: '{1}' or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer: '{2}'. For more details, see https://aka.ms/IdentityModel/issuer-validation. ";
4343
public const string IDX10214 = "IDX10214: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudience: '{1}' or validationParameters.ValidAudiences: '{2}'.";
44+
public const string IDX10214S = "IDX10214: Audience validation failed. See https://aka.ms/identitymodel/app-context-switches";
4445
public const string IDX10215 = "IDX10215: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudiences: '{1}'.";
46+
public const string IDX10215S = "IDX10215: Audience validation failed. See https://aka.ms/identitymodel/app-context-switches";
4547
public const string IDX10222 = "IDX10222: Lifetime validation failed. The token is not yet valid. ValidFrom (UTC): '{0}', Current time (UTC): '{1}'.";
4648
public const string IDX10223 = "IDX10223: Lifetime validation failed. The token is expired. ValidTo (UTC): '{0}', Current time (UTC): '{1}'.";
4749
public const string IDX10224 = "IDX10224: Lifetime validation failed. The NotBefore (UTC): '{0}' is after Expires (UTC): '{1}'.";

src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AudienceValidationError.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ protected override Exception CreateException()
4747
{
4848
if (ExceptionType == typeof(SecurityTokenInvalidAudienceException))
4949
{
50-
var exception = new SecurityTokenInvalidAudienceException(MessageDetail.Message, InnerException) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(TokenAudiences) };
50+
var exception = TokenAudiences != null ?
51+
new SecurityTokenInvalidAudienceException(MessageDetail.Message, InnerException) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(TokenAudiences) } :
52+
new SecurityTokenInvalidAudienceException(MessageDetail.Message, InnerException);
5153
exception.SetValidationError(this);
5254

5355
return exception;

src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,27 @@ internal static ValidationResult<string> ValidateAudience(
8686
return validAudience;
8787

8888
// TODO we shouldn't be serializing here.
89-
return new AudienceValidationError(
90-
new MessageDetail(
91-
LogMessages.IDX10215,
92-
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences)),
93-
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))),
94-
ValidationFailureType.AudienceValidationFailed,
95-
typeof(SecurityTokenInvalidAudienceException),
96-
ValidationError.GetCurrentStackFrame(),
97-
tokenAudiences,
98-
validationParameters.ValidAudiences);
89+
90+
if (AppContextSwitches.DoNotScrubExceptions)
91+
return new AudienceValidationError(
92+
new MessageDetail(
93+
LogMessages.IDX10215,
94+
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences)),
95+
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))),
96+
ValidationFailureType.AudienceValidationFailed,
97+
typeof(SecurityTokenInvalidAudienceException),
98+
ValidationError.GetCurrentStackFrame(),
99+
tokenAudiences,
100+
validationParameters.ValidAudiences);
101+
else
102+
return new AudienceValidationError(
103+
new MessageDetail(
104+
LogMessages.IDX10215S),
105+
ValidationFailureType.AudienceValidationFailed,
106+
typeof(SecurityTokenInvalidAudienceException),
107+
ValidationError.GetCurrentStackFrame(),
108+
null,
109+
null);
99110
}
100111

101112
private static string? ValidTokenAudience(IList<string> tokenAudiences, IList<string> validAudiences, bool ignoreTrailingSlashWhenValidatingAudience)

src/Microsoft.IdentityModel.Tokens/Validators.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,18 @@ public static void ValidateAudience(IEnumerable<string> audiences, SecurityToken
117117
if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences))
118118
return;
119119

120-
SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException(
121-
LogHelper.FormatInvariant(LogMessages.IDX10214,
122-
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)),
123-
LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
124-
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))))
125-
{ InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) };
120+
SecurityTokenInvalidAudienceException ex;
121+
122+
if (AppContextSwitches.DoNotScrubExceptions)
123+
ex = new SecurityTokenInvalidAudienceException(
124+
LogHelper.FormatInvariant(LogMessages.IDX10214,
125+
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)),
126+
LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
127+
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))))
128+
{ InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) };
129+
else
130+
ex = new SecurityTokenInvalidAudienceException(
131+
LogHelper.FormatInvariant(LogMessages.IDX10214S));
126132

127133
if (!validationParameters.LogValidationExceptions)
128134
throw ex;

test/Microsoft.IdentityModel.Tokens.Tests/Validation/AudienceValidationResultTests.cs

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,56 @@ public class AudienceValidationResultTests
1616
[Theory, MemberData(nameof(ValidateAudienceParameterTestCases), DisableDiscoveryEnumeration = true)]
1717
public void ValidateAudienceParameters(AudienceValidationTheoryData theoryData)
1818
{
19-
CompareContext context = TestUtilities.WriteHeader($"{this}.ValidateAudienceParameters", theoryData);
19+
AppContext.SetSwitch(AppContextSwitches.DoNotScrubExceptionsSwitch, theoryData.DoNotScrubErrorMessages);
2020

21-
if (theoryData.ValidAudiences != null)
21+
try
2222
{
23-
foreach (string audience in theoryData.ValidAudiences)
24-
theoryData.ValidationParameters.ValidAudiences.Add(audience);
25-
}
23+
CompareContext context = TestUtilities.WriteHeader($"{this}.ValidateAudienceParameters", theoryData);
2624

27-
ValidationResult<string> result = Validators.ValidateAudience(
28-
theoryData.TokenAudiences,
29-
theoryData.SecurityToken,
30-
theoryData.ValidationParameters,
31-
theoryData.CallContext);
25+
if (theoryData.ValidAudiences != null)
26+
{
27+
foreach (string audience in theoryData.ValidAudiences)
28+
theoryData.ValidationParameters.ValidAudiences.Add(audience);
29+
}
3230

33-
if (result.IsValid)
34-
{
35-
IdentityComparer.AreStringsEqual(
36-
result.UnwrapResult(),
37-
theoryData.Result.UnwrapResult(),
38-
context);
31+
ValidationResult<string> result = Validators.ValidateAudience(
32+
theoryData.TokenAudiences,
33+
theoryData.SecurityToken,
34+
theoryData.ValidationParameters,
35+
theoryData.CallContext);
3936

40-
theoryData.ExpectedException.ProcessNoException(context);
41-
}
42-
else
43-
{
44-
ValidationError validationError = result.UnwrapError();
45-
IdentityComparer.AreStringsEqual(
46-
validationError.FailureType.Name,
47-
theoryData.Result.UnwrapError().FailureType.Name,
48-
context);
37+
if (result.IsValid)
38+
{
39+
IdentityComparer.AreStringsEqual(
40+
result.UnwrapResult(),
41+
theoryData.Result.UnwrapResult(),
42+
context);
4943

50-
IdentityComparer.AreStringsEqual(
51-
validationError.MessageDetail.Message,
52-
theoryData.Result.UnwrapError().MessageDetail.Message,
53-
context);
44+
theoryData.ExpectedException.ProcessNoException(context);
45+
}
46+
else
47+
{
48+
ValidationError validationError = result.UnwrapError();
49+
IdentityComparer.AreStringsEqual(
50+
validationError.FailureType.Name,
51+
theoryData.Result.UnwrapError().FailureType.Name,
52+
context);
5453

55-
Exception exception = validationError.GetException();
56-
theoryData.ExpectedException.ProcessException(exception, context);
57-
}
54+
IdentityComparer.AreStringsEqual(
55+
validationError.MessageDetail.Message,
56+
theoryData.Result.UnwrapError().MessageDetail.Message,
57+
context);
5858

59-
TestUtilities.AssertFailIfErrors(context);
59+
Exception exception = validationError.GetException();
60+
theoryData.ExpectedException.ProcessException(exception, context);
61+
}
62+
63+
TestUtilities.AssertFailIfErrors(context);
64+
}
65+
finally
66+
{
67+
AppContextSwitches.ResetAllSwitches();
68+
}
6069
}
6170

6271
public static TheoryData<AudienceValidationTheoryData> ValidateAudienceParameterTestCases
@@ -99,6 +108,33 @@ public static TheoryData<AudienceValidationTheoryData> ValidateAudienceParameter
99108
typeof(SecurityTokenInvalidAudienceException),
100109
null)
101110
},
111+
new AudienceValidationTheoryData("AudiencesEmptyString_ScrubbedMessage")
112+
{
113+
TokenAudiences = new List<string> { string.Empty },
114+
ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10215:"),
115+
ValidationParameters = new ValidationParameters(),
116+
ValidAudiences = ["audience1"],
117+
Result = new ValidationError(
118+
new MessageDetail(
119+
LogMessages.IDX10215S),
120+
ValidationFailureType.AudienceValidationFailed,
121+
typeof(SecurityTokenInvalidAudienceException),
122+
null)
123+
},
124+
new AudienceValidationTheoryData("AudiencesWhiteSpace_ScrubbedMessage")
125+
{
126+
TokenAudiences = new List<string> { " " },
127+
ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10215:"),
128+
ValidationParameters = new ValidationParameters(),
129+
ValidAudiences = ["audience1"],
130+
Result = new ValidationError(
131+
new MessageDetail(
132+
LogMessages.IDX10215S),
133+
ValidationFailureType.AudienceValidationFailed,
134+
typeof(SecurityTokenInvalidAudienceException),
135+
null)
136+
},
137+
102138
new AudienceValidationTheoryData("AudiencesEmptyString")
103139
{
104140
TokenAudiences = new List<string> { string.Empty },
@@ -112,7 +148,8 @@ public static TheoryData<AudienceValidationTheoryData> ValidateAudienceParameter
112148
LogHelper.MarkAsNonPII("audience1")),
113149
ValidationFailureType.AudienceValidationFailed,
114150
typeof(SecurityTokenInvalidAudienceException),
115-
null)
151+
null),
152+
DoNotScrubErrorMessages = true
116153
},
117154
new AudienceValidationTheoryData("AudiencesWhiteSpace")
118155
{
@@ -127,9 +164,9 @@ public static TheoryData<AudienceValidationTheoryData> ValidateAudienceParameter
127164
LogHelper.MarkAsNonPII("audience1")),
128165
ValidationFailureType.AudienceValidationFailed,
129166
typeof(SecurityTokenInvalidAudienceException),
130-
null)
167+
null),
168+
DoNotScrubErrorMessages = true
131169
},
132-
133170
};
134171
}
135172
}
@@ -448,6 +485,8 @@ public AudienceValidationTheoryData(string testId) : base(testId) { }
448485
public List<string> ValidAudiences { get; set; }
449486

450487
internal ValidationResult<string> Result { get; set; }
488+
489+
internal bool DoNotScrubErrorMessages { get; set; }
451490
}
452491
}
453492
}

test/System.IdentityModel.Tokens.Jwt.Tests/JwtSecurityTokenHandlerTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,8 @@ public static TheoryData<JwtTheoryData> ValidateAlgorithmTheoryData
17951795
[Theory, MemberData(nameof(ValidateAudienceTheoryData), DisableDiscoveryEnumeration = true)]
17961796
public void ValidateAudience(JwtTheoryData theoryData)
17971797
{
1798+
AppContext.SetSwitch(AppContextSwitches.DoNotScrubExceptionsSwitch, theoryData.DoNotScrubErrorMessages);
1799+
17981800
TestUtilities.WriteHeader($"{this}.ValidateAudience", theoryData);
17991801

18001802
try
@@ -1807,6 +1809,10 @@ public void ValidateAudience(JwtTheoryData theoryData)
18071809
{
18081810
theoryData.ExpectedException.ProcessException(ex);
18091811
}
1812+
finally
1813+
{
1814+
AppContextSwitches.ResetAllSwitches();
1815+
}
18101816
}
18111817

18121818
public static TheoryData<JwtTheoryData> ValidateAudienceTheoryData
@@ -1845,11 +1851,19 @@ public static TheoryData<JwtTheoryData> ValidateAudienceTheoryData
18451851
ValidationParameters = ValidateAudienceValidationParameters(null, null, null, true),
18461852
},
18471853
new JwtTheoryData
1854+
{
1855+
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidAudienceException), substringExpected: "IDX10214", propertiesExpected: new Dictionary<string, object>{ { "InvalidAudience", null } }),
1856+
TestId = "'Audience == NotDefault.Audience' ScrubbedMessage",
1857+
SecurityToken = tokenHandler.CreateJwtSecurityToken(issuer: Default.Issuer, audience: Default.Audience),
1858+
ValidationParameters = ValidateAudienceValidationParameters(NotDefault.Audience, null, null, true),
1859+
},
1860+
new JwtTheoryData
18481861
{
18491862
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidAudienceException), substringExpected: "IDX10214", propertiesExpected: new Dictionary<string, object>{ { "InvalidAudience", Default.Audience } }),
18501863
TestId = "'Audience == NotDefault.Audience'",
18511864
SecurityToken = tokenHandler.CreateJwtSecurityToken(issuer: Default.Issuer, audience: Default.Audience),
18521865
ValidationParameters = ValidateAudienceValidationParameters(NotDefault.Audience, null, null, true),
1866+
DoNotScrubErrorMessages = true,
18531867
},
18541868
new JwtTheoryData
18551869
{

test/System.IdentityModel.Tokens.Jwt.Tests/JwtTheoryData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@ public JwtTheoryData() { }
3838
public bool SetupIssuerLkg { get; set; }
3939

4040
public BaseConfigurationManager SetupIssuerLkgConfigurationManager { get; set; }
41+
42+
internal bool DoNotScrubErrorMessages { get; set; }
4143
}
4244
}

0 commit comments

Comments
 (0)