Skip to content

Commit

Permalink
Marcinzo/issuervalidatorv11 (#2503)
Browse files Browse the repository at this point in the history
* Add V11 support for the aad issuer validator.

* Tests

* Add v11 tests

* Add PPE tests

---------

Co-authored-by: marcinzo <[email protected]>
  • Loading branch information
MZOLN and marcinzo authored Mar 19, 2024
1 parent 782602b commit afeb7e5
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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<string, BaseConfigurationManager> _configurationManagerProvider;
Expand All @@ -35,7 +40,8 @@ internal AadIssuerValidator(
{
HttpClient = httpClient;
AadAuthority = aadAuthority.TrimEnd('/');
IsV2Authority = aadAuthority.Contains(V2EndpointSuffix);
AadAuthorityVersion = GetProtocolVersion(AadAuthority);
SetupAuthorities(AadAuthority, AadAuthorityVersion);
}

internal AadIssuerValidator(
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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<string, AadIssuerValidator> s_issuerValidators = new ConcurrentDictionary<string, AadIssuerValidator>();

/// <summary>
Expand Down Expand Up @@ -196,14 +243,14 @@ internal async ValueTask<string> 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
{
Expand All @@ -217,7 +264,7 @@ internal async ValueTask<string> 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;
Expand Down Expand Up @@ -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<OpenIdConnectConfiguration> CreateConfigManager(
Expand Down Expand Up @@ -350,23 +398,48 @@ 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
{
Issuer = aadIssuer,
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;
Expand All @@ -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<BaseConfiguration> GetBaseConfiguration(BaseConfigurationManager configurationManager, TokenValidationParameters validationParameters)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.IdentityModel.Validators
{
/// <summary>
/// AAD protocol version.
/// </summary>
public enum ProtocolVersion
{
/// <summary>
/// Protocol version 1.0
/// </summary>
V1,

/// <summary>
/// Protocol version 1.1
/// </summary>
V11,

/// <summary>
/// Protocol version 2.0
/// </summary>
V2
}
}
4 changes: 3 additions & 1 deletion src/Microsoft.IdentityModel.Validators/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Loading

0 comments on commit afeb7e5

Please sign in to comment.