diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs index f7b084d250..7269690d1f 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs @@ -13,6 +13,7 @@ using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; +using static Microsoft.IdentityModel.Validators.AadIssuerValidator; namespace Microsoft.IdentityModel.Validators { @@ -23,8 +24,12 @@ public class AadIssuerValidator { private static readonly TimeSpan LastKnownGoodConfigurationLifetime = new TimeSpan(0, 24, 0, 0); + internal const string V11EndpointSuffix = "/v1.1"; + internal const string V11EndpointSuffixWithTrailingSlash = $"{V11EndpointSuffix}/"; + internal const string V2EndpointSuffix = "/v2.0"; internal const string V2EndpointSuffixWithTrailingSlash = $"{V2EndpointSuffix}/"; + internal const string TenantIdTemplate = "{tenantid}"; private Func _configurationManagerProvider; @@ -35,7 +40,8 @@ internal AadIssuerValidator( { HttpClient = httpClient; AadAuthority = aadAuthority.TrimEnd('/'); - IsV2Authority = aadAuthority.Contains(V2EndpointSuffix); + AadAuthorityVersion = GetProtocolVersion(AadAuthority); + SetupAuthorities(AadAuthority, AadAuthorityVersion); } internal AadIssuerValidator( @@ -51,11 +57,12 @@ internal AadIssuerValidator( } private HttpClient HttpClient { get; } - private string _aadAuthorityV1; - private string _aadAuthorityV2; + private BaseConfigurationManager _configurationManagerV1; + private BaseConfigurationManager _configurationManagerV11; private BaseConfigurationManager _configurationManagerV2; private IssuerLastKnownGood _issuerLKGV1; + private IssuerLastKnownGood _issuerLKGV11; private IssuerLastKnownGood _issuerLKGV2; internal BaseConfigurationManager ConfigurationManagerV1 @@ -74,6 +81,22 @@ internal BaseConfigurationManager ConfigurationManagerV1 } } + internal BaseConfigurationManager ConfigurationManagerV11 + { + get + { + if (_configurationManagerV11 == null) + _configurationManagerV11 = CreateConfigManager(AadAuthorityV11); + + return _configurationManagerV11; + } + + set + { + _configurationManagerV11 = value; + } + } + internal BaseConfigurationManager ConfigurationManagerV2 { get @@ -92,30 +115,54 @@ internal BaseConfigurationManager ConfigurationManagerV2 internal string AadAuthorityV1 { - get - { - if (_aadAuthorityV1 == null) - _aadAuthorityV1 = IsV2Authority ? CreateV1Authority(AadAuthority) : AadAuthority; + get; + private set; + } - return _aadAuthorityV1; - } + internal string AadAuthorityV11 + { + get; + private set; } internal string AadAuthorityV2 { - get - { - if (_aadAuthorityV2 == null) - _aadAuthorityV2 = IsV2Authority ? AadAuthority : AadAuthority + V2EndpointSuffix; + get; + private set; + } - return _aadAuthorityV2; + private void SetupAuthorities(string aadAuthority, ProtocolVersion version) + { + switch (version) + { + case ProtocolVersion.V1: + AadAuthorityV1 = aadAuthority; + AadAuthorityV11 = AadAuthorityV1 + V11EndpointSuffix; + AadAuthorityV2 = AadAuthorityV1 + V2EndpointSuffix; + break; + + case ProtocolVersion.V11: + AadAuthorityV1 = CreateV1Authority(AadAuthority, V11EndpointSuffix); + AadAuthorityV11 = aadAuthority; + AadAuthorityV2 = AadAuthorityV1 + V2EndpointSuffix; + break; + + case ProtocolVersion.V2: + AadAuthorityV1 = CreateV1Authority(AadAuthority); + AadAuthorityV11 = AadAuthorityV1 + V11EndpointSuffix; + AadAuthorityV2 = aadAuthority; + break; + + default: + throw new InvalidOperationException("Unsupported protocol version."); } } internal string AadIssuerV1 { get; set; } internal string AadIssuerV2 { get; set; } internal string AadAuthority { get; set; } - internal bool IsV2Authority { get; set; } + internal ProtocolVersion AadAuthorityVersion { get; set; } + internal static readonly IDictionary s_issuerValidators = new ConcurrentDictionary(); /// @@ -196,14 +243,14 @@ internal async ValueTask ValidateAsync( try { - var isV2Issuer = IsV2Issuer(securityToken); - var effectiveConfigurationManager = GetEffectiveConfigurationManager(isV2Issuer); + var issuerVersion = GetTokenIssuerVersion(securityToken); + var effectiveConfigurationManager = GetEffectiveConfigurationManager(issuerVersion); string aadIssuer = null; if (validationParameters.ValidateWithLKG) { // returns null if LKG issuer expired - aadIssuer = GetEffectiveLKGIssuer(isV2Issuer); + aadIssuer = GetEffectiveLKGIssuer(issuerVersion); } else { @@ -217,7 +264,7 @@ internal async ValueTask ValidateAsync( // The original LKG assignment behavior for previous self-state management. if (isIssuerValid && !validationParameters.ValidateWithLKG) - SetEffectiveLKGIssuer(aadIssuer, isV2Issuer, effectiveConfigurationManager.LastKnownGoodLifetime); + SetEffectiveLKGIssuer(aadIssuer, issuerVersion, effectiveConfigurationManager.LastKnownGoodLifetime); if (isIssuerValid) return issuer; @@ -305,12 +352,13 @@ internal static AadIssuerValidator GetAadIssuerValidator(string aadAuthority, Ht return s_issuerValidators[aadAuthority]; } - private static string CreateV1Authority(string aadV2Authority) + + private static string CreateV1Authority(string aadV2Authority, string suffixToReplace = V2EndpointSuffix) { - if (aadV2Authority.Contains(AadIssuerValidatorConstants.Organizations)) - return aadV2Authority.Replace($"{AadIssuerValidatorConstants.Organizations}{V2EndpointSuffix}", AadIssuerValidatorConstants.Common); + if (suffixToReplace == V2EndpointSuffix && aadV2Authority.Contains(AadIssuerValidatorConstants.Organizations)) + return aadV2Authority.Replace($"{AadIssuerValidatorConstants.Organizations}{suffixToReplace}", AadIssuerValidatorConstants.Common); - return aadV2Authority.Replace(V2EndpointSuffix, string.Empty); + return aadV2Authority.Replace(suffixToReplace, string.Empty); } private ConfigurationManager CreateConfigManager( @@ -350,7 +398,7 @@ private static bool IsValidIssuer(string validIssuerTemplate, string tenantId, s } } - private void SetEffectiveLKGIssuer(string aadIssuer, bool isV2Issuer, TimeSpan lastKnownGoodLifetime) + private void SetEffectiveLKGIssuer(string aadIssuer, ProtocolVersion protocolVersion, TimeSpan lastKnownGoodLifetime) { var issuerLKG = new IssuerLastKnownGood { @@ -358,15 +406,40 @@ private void SetEffectiveLKGIssuer(string aadIssuer, bool isV2Issuer, TimeSpan l LastKnownGoodLifetime = lastKnownGoodLifetime }; - if (isV2Issuer) - _issuerLKGV2 = issuerLKG; - else - _issuerLKGV1 = issuerLKG; + switch (protocolVersion) + { + case ProtocolVersion.V1: + _issuerLKGV1 = issuerLKG; + break; + + case ProtocolVersion.V11: + _issuerLKGV11 = issuerLKG; + break; + + case ProtocolVersion.V2: + _issuerLKGV2 = issuerLKG; + break; + } } - private string GetEffectiveLKGIssuer(bool isV2Issuer) + private string GetEffectiveLKGIssuer(ProtocolVersion protocolVersion) { - var effectiveLKGIssuer = isV2Issuer ? _issuerLKGV2 : _issuerLKGV1; + IssuerLastKnownGood effectiveLKGIssuer = null; + switch (protocolVersion) + { + case ProtocolVersion.V1: + effectiveLKGIssuer = _issuerLKGV1; + break; + + case ProtocolVersion.V11: + effectiveLKGIssuer = _issuerLKGV11; + break; + + case ProtocolVersion.V2: + effectiveLKGIssuer = _issuerLKGV2; + break; + } + if (effectiveLKGIssuer != null && effectiveLKGIssuer.IsValid) { return effectiveLKGIssuer.Issuer; @@ -375,25 +448,80 @@ private string GetEffectiveLKGIssuer(bool isV2Issuer) return null; } - private static bool IsV2Issuer(SecurityToken securityToken) + private static ProtocolVersion GetTokenIssuerVersion(SecurityToken securityToken) { - return securityToken.Issuer.EndsWith(V2EndpointSuffixWithTrailingSlash, StringComparison.OrdinalIgnoreCase) || - securityToken.Issuer.EndsWith(V2EndpointSuffix, StringComparison.OrdinalIgnoreCase); + if (securityToken.Issuer.EndsWith(V2EndpointSuffixWithTrailingSlash, StringComparison.OrdinalIgnoreCase) || + securityToken.Issuer.EndsWith(V2EndpointSuffix, StringComparison.OrdinalIgnoreCase)) + return ProtocolVersion.V2; + + if (securityToken.Issuer.EndsWith(V11EndpointSuffixWithTrailingSlash, StringComparison.OrdinalIgnoreCase) || + securityToken.Issuer.EndsWith(V11EndpointSuffix, StringComparison.OrdinalIgnoreCase)) + return ProtocolVersion.V11; + + return ProtocolVersion.V1; } - private BaseConfigurationManager GetEffectiveConfigurationManager(bool isV2Issuer) + private BaseConfigurationManager GetEffectiveConfigurationManager(ProtocolVersion protocolVersion) { if (_configurationManagerProvider != null) { - var aadAuthority = isV2Issuer ? AadAuthorityV2 : AadAuthorityV1; - var configurationManager = _configurationManagerProvider(aadAuthority); + string aadAuthority = GetAuthority(protocolVersion); + + var configurationManager = _configurationManagerProvider(aadAuthority); if (configurationManager != null) return configurationManager; } - // If no provider or provider returned null, fallback to previous strategy - return isV2Issuer ? ConfigurationManagerV2 : ConfigurationManagerV1; + // If no provider or provider returned null, fallback to previous strategy + return GetConfigurationManager(protocolVersion); + } + + private BaseConfigurationManager GetConfigurationManager(ProtocolVersion protocolVersion) + { + switch (protocolVersion) + { + case ProtocolVersion.V1: + return ConfigurationManagerV1; + + case ProtocolVersion.V11: + return ConfigurationManagerV11; + + case ProtocolVersion.V2: + return ConfigurationManagerV2; + + default: + return ConfigurationManagerV1; + } + } + + private string GetAuthority(ProtocolVersion protocolVersion) + { + switch (protocolVersion) + { + case ProtocolVersion.V1: + return AadAuthorityV1; + + case ProtocolVersion.V11: + return AadAuthorityV11; + + case ProtocolVersion.V2: + return AadAuthorityV2; + + default: + return AadAuthorityV1; + } + } + + private static ProtocolVersion GetProtocolVersion(string aadAuthority) + { + if (aadAuthority.Contains(V2EndpointSuffix)) + return ProtocolVersion.V2; + + if (aadAuthority.Contains(V11EndpointSuffix)) + return ProtocolVersion.V11; + + return ProtocolVersion.V1; } private static async Task GetBaseConfiguration(BaseConfigurationManager configurationManager, TokenValidationParameters validationParameters) diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/ProtocolVersion.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/ProtocolVersion.cs new file mode 100644 index 0000000000..ddebe1380e --- /dev/null +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/ProtocolVersion.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.IdentityModel.Validators +{ + /// + /// AAD protocol version. + /// + public enum ProtocolVersion + { + /// + /// Protocol version 1.0 + /// + V1, + + /// + /// Protocol version 1.1 + /// + V11, + + /// + /// Protocol version 2.0 + /// + V2 + } +} diff --git a/src/Microsoft.IdentityModel.Validators/GlobalSuppressions.cs b/src/Microsoft.IdentityModel.Validators/GlobalSuppressions.cs index 5e664474b6..097942a71b 100644 --- a/src/Microsoft.IdentityModel.Validators/GlobalSuppressions.cs +++ b/src/Microsoft.IdentityModel.Validators/GlobalSuppressions.cs @@ -7,8 +7,10 @@ [assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Needs to be ignored", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.IsValidIssuer(System.String,System.String,System.String)~System.Boolean")] #if NET6_0_OR_GREATER -[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.CreateV1Authority(System.String)~System.String")] [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.IsValidIssuer(System.String,System.String,System.String)~System.Boolean")] [assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.#ctor(System.Net.Http.HttpClient,System.String)")] +[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.CreateV1Authority(System.String,System.String)~System.String")] +[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.GetProtocolVersion(System.String)~Microsoft.IdentityModel.Validators.AadIssuerValidator.ProtocolVersion")] +[assembly: SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Adding StringComparison.Ordinal adds a performance penalty.", Scope = "member", Target = "~M:Microsoft.IdentityModel.Validators.AadIssuerValidator.GetProtocolVersion(System.String)~Microsoft.IdentityModel.Validators.ProtocolVersion")] #endif diff --git a/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs b/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs index 945604ae9a..b747c65338 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs @@ -12,7 +12,6 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Linq; using NSubstitute; using Xunit; @@ -65,7 +64,7 @@ public void GetIssuerValidator_V1Authority() IdentityComparer.AreEqual(ValidatorConstants.AuthorityV1, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.AuthorityCommonTenantWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(false, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V1, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -77,12 +76,12 @@ public void GetIssuerValidator_TwoTenants() IdentityComparer.AreEqual(ValidatorConstants.AuthorityV1, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.AuthorityCommonTenantWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(false, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V1, validator.AadAuthorityVersion, context); validator = CreateIssuerValidator(ValidatorConstants.AuthorityWithTenantSpecified); IdentityComparer.AreEqual(ValidatorConstants.AuthorityWithTenantSpecified, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.AuthorityWithTenantSpecifiedWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(false, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V1, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -97,7 +96,7 @@ public void GetIssuerValidator_CommonAuthorityInAliases() IdentityComparer.AreEqual(ValidatorConstants.AuthorityV1, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.AuthorityCommonTenantWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -111,7 +110,7 @@ public void GetIssuerValidator_OrganizationsAuthorityInAliases() IdentityComparer.AreEqual(ValidatorConstants.AuthorityV1, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.AuthorityOrganizationsWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -124,7 +123,7 @@ public void GetIssuerValidator_B2cAuthorityNotInAliases() var validator = CreateIssuerValidator(authorityNotInAliases); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthorityWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -452,7 +451,7 @@ public void Validate_FromB2CAuthority_WithNoTidClaim_ValidateSuccessfully() }); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthorityWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -498,7 +497,7 @@ public void Validate_FromB2CAuthority_WithTidClaim_ValidateSuccessfully() }); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthorityWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -568,7 +567,7 @@ public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainAuthorityWithV2, validator.AadAuthorityV2, context); - IdentityComparer.AreBoolsEqual(true, validator.IsV2Authority, context); + IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); TestUtilities.AssertFailIfErrors(context); } @@ -595,11 +594,16 @@ public void Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() } [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, false)] - [InlineData(false, true)] - public void Validate_WithAuthorityUsingConfigurationProvider(bool isV2Token, bool isV2Authority) + [InlineData(ProtocolVersion.V1, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] + public void Validate_WithAuthorityUsingConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) { var configurationManagerProvider = (string authority) => { @@ -620,20 +624,50 @@ public void Validate_WithAuthorityUsingConfigurationProvider(bool isV2Token, boo { Issuer = ValidatorConstants.AadIssuerV2CommonAuthority }) + }, + { + ValidatorConstants.AuthorityCommonTenantWithV11, + new MockConfigurationManager( + new OpenIdConnectConfiguration() + { + Issuer = ValidatorConstants.AadIssuerV11CommonAuthority + }) } }; return configManagerMap[authority]; }; + var tokenIssuerProvider = (ProtocolVersion version) => + { + if (version == ProtocolVersion.V11) + return ValidatorConstants.AadIssuerV11; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AadIssuer; + + return ValidatorConstants.V1Issuer; + }; + + var authorityUrlProvider = (ProtocolVersion version) => + { + if (version == ProtocolVersion.V11) + return ValidatorConstants.AuthorityCommonTenantWithV11; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AuthorityCommonTenantWithV2; + + return ValidatorConstants.AuthorityV1; + }; + var context = new CompareContext(); var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid); - var tokenIssuer = isV2Token ? ValidatorConstants.AadIssuer : ValidatorConstants.V1Issuer; + var tokenIssuer = tokenIssuerProvider(tokenVersion); var issClaim = new Claim(ValidatorConstants.ClaimNameIss, tokenIssuer); var jwtSecurityToken = new JwtSecurityToken(issuer: tokenIssuer, claims: new[] { issClaim, tidClaim }); - var authority = isV2Authority ? ValidatorConstants.AuthorityCommonTenantWithV2 : ValidatorConstants.AuthorityV1; + var authority = authorityUrlProvider(authorityVersion); var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient, configurationManagerProvider); var actualIssuer = aadIssuerValidator.Validate(tokenIssuer, jwtSecurityToken, new TokenValidationParameters()); @@ -642,43 +676,160 @@ public void Validate_WithAuthorityUsingConfigurationProvider(bool isV2Token, boo TestUtilities.AssertFailIfErrors(context); } - [Fact] - public void Validate_UsesLKGWithoutConfigurationProvider() - { - var context = new CompareContext(); - var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid); + [Theory] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] + public void Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) + { + var tokenIssuerProvider = (ProtocolVersion version) => + { + if (version == ProtocolVersion.V11) + return ValidatorConstants.AadIssuerV11; - var v2TokenIssuer = ValidatorConstants.AadIssuer; - var issClaim = new Claim(ValidatorConstants.ClaimNameIss, v2TokenIssuer); - var jwtSecurityToken = new JwtSecurityToken(issuer: v2TokenIssuer, claims: new[] { issClaim, tidClaim }); + if (version == ProtocolVersion.V2) + return ValidatorConstants.AadIssuer; - var v2Authority = ValidatorConstants.AuthorityCommonTenantWithV2; - var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(v2Authority, _httpClient); + return ValidatorConstants.V1Issuer; + }; - // set config to a mock and assert on LKG being null - var v2Configuration = new OpenIdConnectConfiguration + var authorityUrlProvider = (ProtocolVersion version) => { - Issuer = ValidatorConstants.AadIssuerV2CommonAuthority + if (version == ProtocolVersion.V11) + return ValidatorConstants.AuthorityCommonTenantWithV11; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AuthorityCommonTenantWithV2; + + return ValidatorConstants.AuthorityV1; }; - var v2ConfigurationRefreshed = new OpenIdConnectConfiguration + var goodAuthorityIssuer = (ProtocolVersion version) => { - Issuer = "hxxp://brokenissuer/{tenantid}" + if (version == ProtocolVersion.V11) + return ValidatorConstants.AadIssuerV11CommonAuthority; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AadIssuerV2CommonAuthority; + + return ValidatorConstants.AadIssuerV1CommonAuthority; + }; + + var configurationManagerSetter = (AadIssuerValidator validator, bool isRefresh = false) => + { + if (!isRefresh) + { + validator.ConfigurationManagerV1 = new MockConfigurationManager( + new OpenIdConnectConfiguration + { + Issuer = goodAuthorityIssuer(ProtocolVersion.V1) + }); + validator.ConfigurationManagerV11 = new MockConfigurationManager( + new OpenIdConnectConfiguration + { + Issuer = goodAuthorityIssuer(ProtocolVersion.V11) + }); + validator.ConfigurationManagerV2 = new MockConfigurationManager( + new OpenIdConnectConfiguration + { + Issuer = goodAuthorityIssuer(ProtocolVersion.V2) + }); + } + else + { + var refreshedConfig = new OpenIdConnectConfiguration + { + Issuer = "hxxp://brokenissuer/{tenantid}" + }; + + ((MockConfigurationManager)validator.ConfigurationManagerV11).RefreshedConfiguration = refreshedConfig; + ((MockConfigurationManager)validator.ConfigurationManagerV2).RefreshedConfiguration = refreshedConfig; + ((MockConfigurationManager)validator.ConfigurationManagerV1).RefreshedConfiguration = refreshedConfig; + validator.ConfigurationManagerV11.RequestRefresh(); + validator.ConfigurationManagerV2.RequestRefresh(); + validator.ConfigurationManagerV1.RequestRefresh(); + } }; - aadIssuerValidator.ConfigurationManagerV2 = new MockConfigurationManager(v2Configuration); - + var context = new CompareContext(); + var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid); + + var issuer = tokenIssuerProvider(tokenVersion); + var issClaim = new Claim(ValidatorConstants.ClaimNameIss, issuer); + var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); + + var authority = authorityUrlProvider(authorityVersion); + var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient); + + // set config to a mock and assert on LKG being null + configurationManagerSetter(aadIssuerValidator); + // set LKG - var actualIssuer = aadIssuerValidator.Validate(v2TokenIssuer, jwtSecurityToken, new TokenValidationParameters()); - IdentityComparer.AreEqual(v2TokenIssuer, actualIssuer, context); + var actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters()); + IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); // replace config with broken issuer and validate with LKG - ((MockConfigurationManager)aadIssuerValidator.ConfigurationManagerV2).RefreshedConfiguration = v2ConfigurationRefreshed; - aadIssuerValidator.ConfigurationManagerV2.RequestRefresh(); + configurationManagerSetter(aadIssuerValidator, true); - actualIssuer = aadIssuerValidator.Validate(v2TokenIssuer, jwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); - IdentityComparer.AreEqual(v2TokenIssuer, actualIssuer, context); + actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); + IdentityComparer.AreEqual(issuer, actualIssuer, context); + TestUtilities.AssertFailIfErrors(context); + } + + [Theory] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V1, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V11, ProtocolVersion.V2)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] + [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] + public void Validate_CanFetchMetadataWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) + { + var tokenIssuerProvider = (ProtocolVersion version) => + { + if (version == ProtocolVersion.V11) + return ValidatorConstants.AadIssuerV11PPE; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AadIssuerPPE; + + return ValidatorConstants.V1IssuerPPE; + }; + + var authorityUrlProvider = (ProtocolVersion version) => + { + if (version == ProtocolVersion.V11) + return ValidatorConstants.AuthorityCommonTenantWithV11PPE; + + if (version == ProtocolVersion.V2) + return ValidatorConstants.AuthorityCommonTenantWithV2PPE; + + return ValidatorConstants.AuthorityV1PPE; + }; + + var context = new CompareContext(); + var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid); + + var issuer = tokenIssuerProvider(tokenVersion); + var issClaim = new Claim(ValidatorConstants.ClaimNameIss, issuer); + var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); + + var authority = authorityUrlProvider(authorityVersion); + var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient); + + // set LKG + var actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters()); + IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } diff --git a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs index f6ec7144f8..920b6c628f 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs @@ -16,22 +16,33 @@ internal class ValidatorConstants public const string TenantIdAsGuid = "f645ad92-e38d-4d1a-b510-d1b09a74a8ca"; public const string ProductionPrefNetworkUSEnvironment = "login.microsoftonline.us"; public const string AadInstance = "https://login.microsoftonline.com"; + public const string AadInstancePPE = "https://login.windows-ppe.net"; public const string AuthorityV1 = AadInstance + "/common"; + public const string AuthorityV1PPE = AadInstancePPE + "/common"; public const string AuthorityCommonTenant = AadInstance + "/common/"; + public const string AuthorityCommonTenantPPE = AadInstancePPE + "/common/"; public const string AuthorityOrganizationsTenant = AadInstance + "/organizations/"; public const string AuthorityOrganizationsUSTenant = "https://" + ProductionPrefNetworkUSEnvironment + "/organizations"; public const string Organizations = "organizations"; public const string AuthorityWithTenantSpecified = AadInstance + "/" + TenantIdAsGuid; public const string AuthorityCommonTenantWithV2 = AadInstance + "/common/v2.0"; + public const string AuthorityCommonTenantWithV2PPE = AadInstancePPE + "/common/v2.0"; + public const string AuthorityCommonTenantWithV11 = AadInstance + "/common/v1.1"; + public const string AuthorityCommonTenantWithV11PPE = AadInstancePPE + "/common/v1.1"; public const string AuthorityOrganizationsWithV2 = AadInstance + "/organizations/v2.0"; public const string AuthorityOrganizationsUSWithV2 = AuthorityOrganizationsUSTenant + "/v2.0"; public const string AuthorityWithTenantSpecifiedWithV2 = AadInstance + "/" + TenantIdAsGuid + "/v2.0"; public const string AadIssuer = AadInstance + "/" + TenantIdAsGuid + "/v2.0"; + public const string AadIssuerPPE = AadInstancePPE + "/" + TenantIdAsGuid + "/v2.0"; + public const string AadIssuerV11 = AadInstance + "/" + TenantIdAsGuid + "/v1.1"; + public const string AadIssuerV11PPE = AadInstancePPE + "/" + TenantIdAsGuid + "/v1.1"; public const string UsGovIssuer = "https://login.microsoftonline.us/" + UsGovTenantId + "/v2.0"; public const string UsGovTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; public const string V1Issuer = "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"; + public const string V1IssuerPPE = "https://sts.windows-ppe.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"; public const string AadIssuerV1CommonAuthority = "https://sts.windows.net/{tenantid}/"; + public const string AadIssuerV11CommonAuthority = AadInstance + "/{tenantid}/v1.1"; public const string AadIssuerV2CommonAuthority = AadInstance + "/{tenantid}/v2.0"; public const string B2CSignUpSignInUserFlow = "b2c_1_susi";