Skip to content

Commit

Permalink
Merge branch 'dev' into metadata-rich-authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
joegoldman2 committed Jun 29, 2024
2 parents a7152f6 + 0c5172c commit 4ed4799
Show file tree
Hide file tree
Showing 39 changed files with 3,313 additions and 369 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ See the [releases](https://github.com/AzureAD/azure-activedirectory-identitymode
7.6.1
=====
### New Features:
- Added an Audiences member to the SecurityTokenDescriptor to make it easier to define multiple audiences in JWT and SAML tokens. Addresses issue [#1479](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1479) with PR [#2575](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/2575)
- Add missing metadata parameters to OpenIdConnectConfiguration. See issue [#2498](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2498) for details.


### Bug Fixes:
- Fix over-reporting of `IDX14100`. See issue [#2058](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2058) and PR [#2618](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/2618) for details.
- `JwtRegisteredClaimNames` now contains previously missing Standard OpenIdConnect claims. See issue [#1598](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1598) for details.
Expand Down
45 changes: 45 additions & 0 deletions benchmark/Microsoft.IdentityModel.Benchmarks/BenchmarkUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ public class BenchmarkUtils

public const string Audience = "http://www.contoso.com/protected";

public readonly static IList<string> Audiences = new string[] {
"http://www.contoso.com/protected",
"http://www.contoso.com/protected1",
"http://www.contoso.com/protected2",
"http://www.contoso.com/protected3",
"http://www.contoso.com/protected4"
};

private static RSA _rsa;
private static SymmetricSecurityKey _symmetricKey;

Expand Down Expand Up @@ -60,6 +68,43 @@ public static Dictionary<string, object> Claims
}
}

public static Dictionary<string, object> ClaimsNoAudience
{
get
{
DateTime now = DateTime.UtcNow;
return new Dictionary<string, object>()
{
{ "role", new List<string>() { "role1", "Developer", "Sales"} },
{ JwtRegisteredClaimNames.Email, "Bob@contoso.com" },
{ JwtRegisteredClaimNames.Exp, EpochTime.GetIntDate(now + TimeSpan.FromDays(1)) },
{ JwtRegisteredClaimNames.Nbf, EpochTime.GetIntDate(now) },
{ JwtRegisteredClaimNames.Iat, EpochTime.GetIntDate(now) },
{ JwtRegisteredClaimNames.GivenName, "Bob" },
{ JwtRegisteredClaimNames.Iss, Issuer },
};
}
}

public static Dictionary<string, object> ClaimsMultipleAudiences
{
get
{
DateTime now = DateTime.UtcNow;
return new Dictionary<string, object>()
{
{ "role", new List<string>() { "role1", "Developer", "Sales"} },
{ JwtRegisteredClaimNames.Email, "Bob@contoso.com" },
{ JwtRegisteredClaimNames.Exp, EpochTime.GetIntDate(now + TimeSpan.FromDays(1)) },
{ JwtRegisteredClaimNames.Nbf, EpochTime.GetIntDate(now) },
{ JwtRegisteredClaimNames.Iat, EpochTime.GetIntDate(now) },
{ JwtRegisteredClaimNames.GivenName, "Bob" },
{ JwtRegisteredClaimNames.Iss, Issuer },
{ JwtRegisteredClaimNames.Aud, Audiences }
};
}
}

public static Dictionary<string, object> ClaimsExtendedExample
{
get
Expand Down
44 changes: 43 additions & 1 deletion benchmark/Microsoft.IdentityModel.Benchmarks/CreateTokenTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class CreateTokenTests
{
private JsonWebTokenHandler _jsonWebTokenHandler;
private SecurityTokenDescriptor _tokenDescriptor;
private SecurityTokenDescriptor _tokenDescriptorMultipleAudiencesMemberAndClaims;
private SecurityTokenDescriptor _tokenDescriptorMultipleAudiencesMemberOnly;
private SecurityTokenDescriptor _tokenDescriptorSingleAudienceUsingAudiencesMember;

[GlobalSetup]
public void Setup()
Expand All @@ -23,11 +26,50 @@ public void Setup()
_tokenDescriptor = new SecurityTokenDescriptor
{
Claims = BenchmarkUtils.Claims,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256
};

_tokenDescriptorSingleAudienceUsingAudiencesMember = new SecurityTokenDescriptor
{
Claims = BenchmarkUtils.ClaimsNoAudience,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256
};

_tokenDescriptorMultipleAudiencesMemberOnly = new SecurityTokenDescriptor
{
Claims = BenchmarkUtils.ClaimsNoAudience,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256
};

_tokenDescriptorMultipleAudiencesMemberAndClaims = new SecurityTokenDescriptor
{
Claims = BenchmarkUtils.ClaimsMultipleAudiences,
SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256
};

_tokenDescriptorSingleAudienceUsingAudiencesMember.Audiences.Add(BenchmarkUtils.Audience);
foreach (var audience in BenchmarkUtils.Audiences)
{
_tokenDescriptorMultipleAudiencesMemberOnly.Audiences.Add(audience);
_tokenDescriptorMultipleAudiencesMemberAndClaims.Audiences.Add(audience);
}
}

[Benchmark]
public string JsonWebTokenHandler_CreateToken() => _jsonWebTokenHandler.CreateToken(_tokenDescriptor);

[Benchmark]
public string JsonWebTokenHandler_CreateToken_SingleAudienceUsingAudiencesMemberOnly() =>
_jsonWebTokenHandler.CreateToken(_tokenDescriptorSingleAudienceUsingAudiencesMember);

[Benchmark]
public string JsonWebTokenHandler_CreateToken_MultipleAudiencesMemberOnly() =>
_jsonWebTokenHandler.CreateToken(_tokenDescriptorMultipleAudiencesMemberOnly);

[Benchmark]
public string JsonWebTokenHandler_CreateToken_MultipleAudiencesMemberAndClaims() =>
_jsonWebTokenHandler.CreateToken(_tokenDescriptorMultipleAudiencesMemberAndClaims);


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,10 @@ int sizeOfEncodedHeaderAndPayloadAsciiBytes
/// <summary>
/// A <see cref="SecurityTokenDescriptor"/> can contain claims from multiple locations.
/// This method consolidates the claims and adds default times {exp, iat, nbf} if needed.
/// In the case of a claim from this set: {Audience, Issuer, Expires, IssuedAt, NotBefore} being defined in multiple
/// locations in the SecurityTokenDescriptor, the following priority is used:
/// SecurityTokenDescriptor.{Audience/Audiences, Issuer, Expires, IssuedAt, NotBefore} > SecurityTokenDescriptor.Claims >
/// SecurityTokenDescriptor.Subject.Claims
/// </summary>
/// <param name="writer">The <see cref="Utf8JsonWriter"/> to use.</param>
/// <param name="tokenDescriptor">The <see cref="SecurityTokenDescriptor"/> used to create the token.</param>
Expand All @@ -693,24 +697,33 @@ internal static void WriteJwsPayload(
bool setDefaultTimesOnTokenCreation,
int tokenLifetimeInMinutes)
{
bool audienceChecked = false;
bool descriptorClaimsAudienceChecked = false;
bool audienceSet = false;
bool issuerChecked = false;
bool descriptorClaimsIssuerChecked = false;
bool issuerSet = false;
bool expChecked = false;
bool descriptorClaimsExpChecked = false;
bool expSet = false;
bool iatChecked = false;
bool descriptorClaimsIatChecked = false;
bool iatSet = false;
bool nbfChecked = false;
bool descriptorClaimsNbfChecked = false;
bool nbfSet = false;

writer.WriteStartObject();

if (!string.IsNullOrEmpty(tokenDescriptor.Audience))
if (tokenDescriptor.Audiences.Count > 0)
{
if (!tokenDescriptor.Audience.IsNullOrEmpty())
JsonPrimitives.WriteStrings(ref writer, JwtPayloadUtf8Bytes.Aud, tokenDescriptor.Audiences, tokenDescriptor.Audience);
else
JsonPrimitives.WriteStrings(ref writer, JwtPayloadUtf8Bytes.Aud, tokenDescriptor.Audiences);

audienceSet = true;
}
else if (!tokenDescriptor.Audience.IsNullOrEmpty())
{
writer.WritePropertyName(JwtPayloadUtf8Bytes.Aud);
writer.WriteStringValue(tokenDescriptor.Audience);
audienceSet = true;
}

if (!string.IsNullOrEmpty(tokenDescriptor.Issuer))
Expand Down Expand Up @@ -742,30 +755,38 @@ internal static void WriteJwsPayload(
}

// Duplicates are resolved according to the following priority:
// SecurityTokenDescriptor.{Audience, Issuer, Expires, IssuedAt, NotBefore}, SecurityTokenDescriptor.Claims, SecurityTokenDescriptor.Subject.Claims
// SecurityTokenDescriptor.{Audience/Audiences, Issuer, Expires, IssuedAt, NotBefore}, SecurityTokenDescriptor.Claims, SecurityTokenDescriptor.Subject.Claims
// SecurityTokenDescriptor.Claims are KeyValuePairs<string,object>, whereas SecurityTokenDescriptor.Subject.Claims are System.Security.Claims.Claim and are processed differently.

if (tokenDescriptor.Claims != null && tokenDescriptor.Claims.Count > 0)
{
foreach (KeyValuePair<string, object> kvp in tokenDescriptor.Claims)
{
if (!audienceChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Aud, StringComparison.Ordinal))
if (!descriptorClaimsAudienceChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Aud, StringComparison.Ordinal))
{
audienceChecked = true;
descriptorClaimsAudienceChecked = true;
if (audienceSet)
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(nameof(tokenDescriptor.Audience))));
{
string descriptorMemberName = null;
if (tokenDescriptor.Audiences.Count > 0)
descriptorMemberName = nameof(tokenDescriptor.Audiences);
else if (!string.IsNullOrEmpty(tokenDescriptor.Audience))
descriptorMemberName = nameof(tokenDescriptor.Audience);

LogHelper.LogInformation(LogHelper.FormatInvariant(LogMessages.IDX14113, LogHelper.MarkAsNonPII(descriptorMemberName)));
}

continue;
}

audienceSet = true;
}

if (!issuerChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iss, StringComparison.Ordinal))
if (!descriptorClaimsIssuerChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iss, StringComparison.Ordinal))
{
issuerChecked = true;
descriptorClaimsIssuerChecked = true;
if (issuerSet)
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
Expand All @@ -777,9 +798,9 @@ internal static void WriteJwsPayload(
issuerSet = true;
}

if (!expChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Exp, StringComparison.Ordinal))
if (!descriptorClaimsExpChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Exp, StringComparison.Ordinal))
{
expChecked = true;
descriptorClaimsExpChecked = true;
if (expSet)
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
Expand All @@ -791,9 +812,9 @@ internal static void WriteJwsPayload(
expSet = true;
}

if (!iatChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iat, StringComparison.Ordinal))
if (!descriptorClaimsIatChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Iat, StringComparison.Ordinal))
{
iatChecked = true;
descriptorClaimsIatChecked = true;
if (iatSet)
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
Expand All @@ -805,9 +826,9 @@ internal static void WriteJwsPayload(
iatSet = true;
}

if (!nbfChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Nbf, StringComparison.Ordinal))
if (!descriptorClaimsNbfChecked && kvp.Key.Equals(JwtRegisteredClaimNames.Nbf, StringComparison.Ordinal))
{
nbfChecked = true;
descriptorClaimsNbfChecked = true;
if (nbfSet)
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public static class OpenIdConnectPrompt
/// </summary>
public const string None = "none";

/// <summary>
/// Indicates 'create' prompt type see: https://openid.net/specs/openid-connect-prompt-create-1_0.html.
/// </summary>
public const string Create = "create";

/// <summary>
/// Indicates 'login' prompt type see: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest.
/// </summary>
Expand All @@ -29,4 +34,3 @@ public static class OpenIdConnectPrompt
public const string SelectAccount = "select_account";
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public enum OpenIdConnectRequestType
Authentication,

/// <summary>
/// Indicates a Logout Request see:http://openid.net/specs/openid-connect-frontchannel-1_0.html#RPLogout.
/// Indicates a Logout Request see: http://openid.net/specs/openid-connect-frontchannel-1_0.html#RPLogout.
/// </summary>
Logout,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.IdentityModel.Abstractions;
Expand Down Expand Up @@ -354,7 +353,7 @@ protected virtual IEnumerable<ClaimsIdentity> CreateClaimsIdentities(SamlSecurit
/// <exception cref="ArgumentNullException">if <paramref name="tokenDescriptor"/> is null.</exception>
protected virtual SamlConditions CreateConditions(SecurityTokenDescriptor tokenDescriptor)
{
if (null == tokenDescriptor)
if (tokenDescriptor == null)
throw LogArgumentNullException(nameof(tokenDescriptor));

var conditions = new SamlConditions();
Expand All @@ -368,12 +367,41 @@ protected virtual SamlConditions CreateConditions(SecurityTokenDescriptor tokenD
else if (SetDefaultTimesOnTokenCreation)
conditions.NotOnOrAfter = DateTime.UtcNow + TimeSpan.FromMinutes(TokenLifetimeInMinutes);

if (!string.IsNullOrEmpty(tokenDescriptor.Audience))
if (tokenDescriptor.Audiences.Count > 0)
{
if (!tokenDescriptor.Audience.IsNullOrEmpty())
conditions.Conditions.Add(CreateAudienceRestrictionCondition(tokenDescriptor.Audience, tokenDescriptor.Audiences));
else
conditions.Conditions.Add(CreateAudienceRestrictionCondition(tokenDescriptor.Audiences));
}
else if (!tokenDescriptor.Audience.IsNullOrEmpty())
{
conditions.Conditions.Add(new SamlAudienceRestrictionCondition(new Uri(tokenDescriptor.Audience)));
}

return conditions;
}


private static SamlAudienceRestrictionCondition CreateAudienceRestrictionCondition(IList<string> audiences)
{
SamlAudienceRestrictionCondition audRestrictionCondition = new();
for (int i = 0; i < audiences.Count; i++)
audRestrictionCondition.Audiences.Add(new Uri(audiences[i]));

return audRestrictionCondition;
}

private static SamlCondition CreateAudienceRestrictionCondition(string audience, IList<string> audiences)
{
SamlAudienceRestrictionCondition audRestrictionCondition = new(new Uri(audience));
for (int i = 0; i < audiences.Count; i++)
audRestrictionCondition.Audiences.Add(new Uri(audiences[i]));

return audRestrictionCondition;
}


/// <summary>
/// Generates an enumeration of SamlStatements from a SecurityTokenDescriptor.
/// Only SamlAttributeStatements and SamlAuthenticationStatements are generated.
Expand Down Expand Up @@ -880,7 +908,7 @@ protected virtual void SetDelegateFromAttribute(SamlAttribute attribute, ClaimsI
/// <param name="audiences"><see cref="IEnumerable{String}"/>.</param>
/// <param name="securityToken">The <see cref="SamlSecurityToken"/> being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <remarks>see <see cref="Validators.ValidateAudience"/> for additional details.</remarks>
/// <remarks>see <see cref="Validators.ValidateAudience(IEnumerable{string}, SecurityToken, TokenValidationParameters)"/> for additional details.</remarks>
protected virtual void ValidateAudience(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateAudience(audiences, securityToken, validationParameters);
Expand Down Expand Up @@ -964,7 +992,7 @@ protected virtual void ValidateIssuerSecurityKey(SecurityKey securityKey, Securi
/// <param name="expires">The <see cref="DateTime"/> value found in the <see cref="SamlSecurityToken"/>.</param>
/// <param name="securityToken">The <see cref="SamlSecurityToken"/> being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <remarks><see cref="Validators.ValidateLifetime"/> for additional details.</remarks>
/// <remarks><see cref="Validators.ValidateLifetime(DateTime?, DateTime?, SecurityToken, TokenValidationParameters)"/> for additional details.</remarks>
protected virtual void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateLifetime(notBefore, expires, securityToken, validationParameters);
Expand Down
Loading

0 comments on commit 4ed4799

Please sign in to comment.