Skip to content

Commit e4f8a0c

Browse files
RojaEnnambrentschmaltz
authored andcommitted
Support for SecurityTokenDescriptor.Claims in JwtSecurity/Saml/Saml2 Tokens
1 parent 203eca5 commit e4f8a0c

File tree

14 files changed

+962
-49
lines changed

14 files changed

+962
-49
lines changed

src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ public class JwtTokenUtilities
6565
JwtHeaderParameterNames.Zip
6666
};
6767

68-
internal static Dictionary<string, object> CreateDictionaryFromClaims(IEnumerable<Claim> claims)
68+
/// <summary>
69+
/// Creates a dictionary from a list of Claim's.
70+
/// </summary>
71+
/// <param name="claims"> A list of claims.</param>
72+
/// <returns> A Dictionary representing claims.</returns>
73+
internal static IDictionary<string, object> CreateDictionaryFromClaims(IEnumerable<Claim> claims)
6974
{
7075
var payload = new Dictionary<string, object>();
7176

src/Microsoft.IdentityModel.Tokens.Saml/Saml/LogMessages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ internal static class LogMessages
4343
// signature creation / validation
4444
internal const string IDX11312 = "IDX11312: Unable to validate token. A SamlSamlAttributeStatement can only have one SamlAttribute of type 'Actor'. This special SamlAttribute is used in delegation scenarios.";
4545
internal const string IDX11313 = "IDX11313: Unable to process Saml attribute. A SamlSubject must contain either or both of Name and ConfirmationMethod.";
46+
internal const string IDX11314 = "IDX11314: The AttributeValueXsiType of a SAML Attribute must be a string of the form 'prefix#suffix', where prefix and suffix are non-empty strings. Found: '{0}'";
4647

4748
// SamlSerializer reading
4849
internal const string IDX11100 = "IDX11100: Saml Only one element of type '{0}' is supported.";

src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlAttribute.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,16 @@ public string AttributeValueXsiType
9191

9292
int indexOfHash = value.IndexOf('#');
9393
if (indexOfHash == -1)
94-
throw LogExceptionMessage(new SecurityTokenInvalidAudienceException("value, SR.GetString(SR.ID4254)")); ;
94+
throw LogExceptionMessage(new SecurityTokenInvalidAudienceException(FormatInvariant(LogMessages.IDX11314, value)));
9595

9696
string prefix = value.Substring(0, indexOfHash);
9797
if (prefix.Length == 0)
98-
throw LogExceptionMessage(new ArgumentException("value SR.GetString(SR.ID4254)"));
98+
throw LogExceptionMessage(new ArgumentException(FormatInvariant(LogMessages.IDX11314, value)));
9999

100100
string suffix = value.Substring(indexOfHash + 1);
101101
if (suffix.Length == 0)
102102
{
103-
throw LogExceptionMessage(new ArgumentException("value, SR.GetString(SR.ID4254)"));
103+
throw LogExceptionMessage(new ArgumentException(FormatInvariant(LogMessages.IDX11314, value)));
104104
}
105105

106106
_attributeValueXsiType = value;

src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,12 @@ protected virtual SamlAttributeStatement CreateAttributeStatement(SamlSubject su
270270
if (tokenDescriptor == null)
271271
throw LogArgumentNullException(nameof(tokenDescriptor));
272272

273-
if (tokenDescriptor.Subject != null)
273+
IEnumerable<Claim> claims = SamlTokenUtilities.GetAllClaims(tokenDescriptor.Claims, tokenDescriptor.Subject != null ? tokenDescriptor.Subject.Claims : null);
274+
275+
if (claims != null && claims.Any())
274276
{
275277
var attributes = new List<SamlAttribute>();
276-
foreach (var claim in tokenDescriptor.Subject.Claims)
278+
foreach (var claim in claims)
277279
{
278280
if (claim != null && claim.Type != ClaimTypes.NameIdentifier)
279281
{
@@ -293,7 +295,7 @@ protected virtual SamlAttributeStatement CreateAttributeStatement(SamlSubject su
293295
}
294296
}
295297

296-
AddActorToAttributes(attributes, tokenDescriptor.Subject.Actor);
298+
AddActorToAttributes(attributes, tokenDescriptor.Subject?.Actor);
297299

298300
var consolidatedAttributes = ConsolidateAttributes(attributes);
299301
if (consolidatedAttributes.Count > 0)
@@ -450,9 +452,12 @@ protected virtual SamlSubject CreateSubject(SecurityTokenDescriptor tokenDescrip
450452

451453
var samlSubject = new SamlSubject();
452454
Claim identityClaim = null;
453-
if (tokenDescriptor.Subject != null && tokenDescriptor.Subject.Claims != null)
455+
456+
IEnumerable<Claim> claims = SamlTokenUtilities.GetAllClaims(tokenDescriptor.Claims, tokenDescriptor.Subject != null ? tokenDescriptor.Subject.Claims : null);
457+
458+
if (claims != null && claims.Any())
454459
{
455-
foreach (var claim in tokenDescriptor.Subject.Claims)
460+
foreach (var claim in claims)
456461
{
457462
if (claim.Type == ClaimTypes.NameIdentifier)
458463
{

src/Microsoft.IdentityModel.Tokens.Saml/SamlTokenUtilities.cs renamed to src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlTokenUtilities.cs

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using Microsoft.IdentityModel.Logging;
2-
using Microsoft.IdentityModel.Xml;
1+

32
//------------------------------------------------------------------------------
43
//
54
// Copyright (c) Microsoft Corporation.
@@ -27,19 +26,20 @@
2726
//
2827
//------------------------------------------------------------------------------
2928

30-
using System;
29+
using System.Security.Claims;
30+
using System.Collections;
3131
using System.Collections.Generic;
32-
using System.Linq;
33-
using System.Text;
34-
using System.Threading.Tasks;
32+
using Microsoft.IdentityModel.Xml;
33+
using System;
34+
using Microsoft.IdentityModel.Logging;
3535
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
3636

3737
namespace Microsoft.IdentityModel.Tokens.Saml
3838
{
3939
/// <summary>
4040
/// A class which contains useful methods for processing saml tokens.
4141
/// </summary>
42-
public class SamlTokenUtilities
42+
internal class SamlTokenUtilities
4343
{
4444
/// <summary>
4545
/// Returns a <see cref="SecurityKey"/> to use when validating the signature of a token.
@@ -102,5 +102,89 @@ internal static IEnumerable<SecurityKey> GetKeysForTokenSignatureValidation(stri
102102
}
103103
return null;
104104
}
105+
106+
/// <summary>
107+
/// Creates <see cref="Claim"/>'s from <paramref name="claimsCollection"/>.
108+
/// </summary>
109+
/// <param name="claimsCollection"> A dictionary that represents a set of claims.</param>
110+
/// <returns> A collection of <see cref="Claim"/>'s created from the <paramref name="claimsCollection"/>.</returns>
111+
internal static IEnumerable<Claim> CreateClaimsFromDictionary(IDictionary<string, object> claimsCollection)
112+
{
113+
if (claimsCollection == null)
114+
return null;
115+
116+
var claims = new List<Claim>();
117+
foreach (var claim in claimsCollection)
118+
{
119+
string claimType = claim.Key;
120+
object claimValue = claim.Value;
121+
if (claimValue != null)
122+
{
123+
var valueType = GetXsiTypeForValue(claimValue);
124+
if (valueType == null && claimValue is IEnumerable claimList)
125+
{
126+
foreach (var item in claimList)
127+
{
128+
valueType = GetXsiTypeForValue(item);
129+
if (valueType == null && item is IEnumerable)
130+
throw new NotSupportedException(LogHelper.FormatInvariant(TokenLogMessages.IDX10105, claimType));
131+
132+
claims.Add(new Claim(claimType, item.ToString(), valueType));
133+
}
134+
}
135+
else
136+
{
137+
claims.Add(new Claim(claimType, claimValue.ToString(), valueType));
138+
}
139+
}
140+
}
141+
142+
return claims;
143+
}
144+
145+
/// <summary>
146+
/// Merges <paramref name="claims"/> and <paramref name="subjectClaims"/>
147+
/// </summary>
148+
/// <param name="claims"> A dictionary of claims.</param>
149+
/// <param name="subjectClaims"> A collection of <see cref="Claim"/>'s</param>
150+
/// <returns> A merged list of <see cref="Claim"/>'s.</returns>
151+
internal static IEnumerable<Claim> GetAllClaims(IDictionary<string, object> claims, IEnumerable<Claim> subjectClaims)
152+
{
153+
if (claims == null)
154+
return subjectClaims;
155+
else
156+
return TokenUtilities.MergeClaims(CreateClaimsFromDictionary(claims), subjectClaims);
157+
}
158+
159+
/// <summary>
160+
/// Gets the value type of the <see cref="Claim"/> from its value <paramref name="value"/>
161+
/// </summary>
162+
/// <param name="value"> The <see cref="Claim"/> value.</param>
163+
/// <returns> The value type of the <see cref="Claim"/>.</returns>
164+
internal static string GetXsiTypeForValue(object value)
165+
{
166+
if (value != null)
167+
{
168+
if (value is string)
169+
return ClaimValueTypes.String;
170+
171+
if (value is bool)
172+
return ClaimValueTypes.Boolean;
173+
174+
if (value is int)
175+
return ClaimValueTypes.Integer32;
176+
177+
if (value is long)
178+
return ClaimValueTypes.Integer64;
179+
180+
if (value is double)
181+
return ClaimValueTypes.Double;
182+
183+
if (value is DateTime)
184+
return ClaimValueTypes.DateTime;
185+
}
186+
187+
return null;
188+
}
105189
}
106190
}

src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using System.Collections.Generic;
3030
using System.Collections.ObjectModel;
3131
using System.IO;
32+
using System.Linq;
3233
using System.Security.Claims;
3334
using System.Text;
3435
using System.Xml;
@@ -681,20 +682,26 @@ protected virtual Saml2AttributeStatement CreateAttributeStatement(SecurityToken
681682
throw LogArgumentNullException(nameof(tokenDescriptor.Subject));
682683

683684
var attributes = new List<Saml2Attribute>();
684-
foreach (Claim claim in tokenDescriptor.Subject.Claims)
685+
686+
IEnumerable<Claim> claims = SamlTokenUtilities.GetAllClaims(tokenDescriptor.Claims, tokenDescriptor.Subject != null ? tokenDescriptor.Subject.Claims : null);
687+
688+
if (claims != null && claims.Any())
685689
{
686-
if (claim != null)
690+
foreach (Claim claim in claims)
687691
{
688-
switch (claim.Type)
692+
if (claim != null)
689693
{
690-
// TODO - should these really be filtered?
691-
case ClaimTypes.AuthenticationInstant:
692-
case ClaimTypes.AuthenticationMethod:
693-
case ClaimTypes.NameIdentifier:
694-
break;
695-
default:
696-
attributes.Add(CreateAttribute(claim));
697-
break;
694+
switch (claim.Type)
695+
{
696+
// TODO - should these really be filtered?
697+
case ClaimTypes.AuthenticationInstant:
698+
case ClaimTypes.AuthenticationMethod:
699+
case ClaimTypes.NameIdentifier:
700+
break;
701+
default:
702+
attributes.Add(CreateAttribute(claim));
703+
break;
704+
}
698705
}
699706
}
700707
}
@@ -895,9 +902,11 @@ protected virtual Saml2Subject CreateSubject(SecurityTokenDescriptor tokenDescri
895902
string nameIdentifierSpProviderId = null;
896903
string nameIdentifierSpNameQualifier = null;
897904

898-
if (tokenDescriptor.Subject != null && tokenDescriptor.Subject.Claims != null)
905+
IEnumerable<Claim> claims = SamlTokenUtilities.GetAllClaims(tokenDescriptor.Claims, tokenDescriptor.Subject != null ? tokenDescriptor.Subject.Claims : null);
906+
907+
if (claims != null && claims.Any())
899908
{
900-
foreach (var claim in tokenDescriptor.Subject.Claims)
909+
foreach (var claim in claims)
901910
{
902911
if (claim.Type == ClaimTypes.NameIdentifier)
903912
{

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ internal static class LogMessages
4545
public const string IDX10102 = "IDX10102: NameClaimType cannot be null or whitespace.";
4646
public const string IDX10103 = "IDX10103: RoleClaimType cannot be null or whitespace.";
4747
public const string IDX10104 = "IDX10104: TokenLifetimeInMinutes must be greater than zero. value: '{0}'";
48+
public const string IDX10105 = "IDX10105: ClaimValue that is a collection of collections is not supported. Such ClaimValue is found for ClaimType : '{0}'";
4849

4950
// token validation
5051
public const string IDX10204 = "IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null.";
@@ -227,6 +228,7 @@ internal static class LogMessages
227228
public const string IDX10812 = "IDX10812: Unable to create a {0} from the properties found in the JsonWebKey: '{1}'.";
228229
public const string IDX10813 = "IDX10813: Unable to create a {0} from the properties found in the JsonWebKey: '{1}', Exception '{2}'.";
229230

231+
230232
#pragma warning restore 1591
231233
}
232234
}

src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
using System;
2929
using System.Collections.Generic;
3030
using System.Linq;
31-
using System.Text;
32-
using System.Threading.Tasks;
31+
using System.Security.Claims;
3332
using Microsoft.IdentityModel.Logging;
3433
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
3534

@@ -38,7 +37,7 @@ namespace Microsoft.IdentityModel.Tokens
3837
/// <summary>
3938
/// A class which contains useful methods for processing tokens.
4039
/// </summary>
41-
public class TokenUtilities
40+
internal class TokenUtilities
4241
{
4342
/// <summary>
4443
/// Returns all <see cref="SecurityKey"/> provided in validationParameters.
@@ -55,5 +54,30 @@ internal static IEnumerable<SecurityKey> GetAllSigningKeys(TokenValidationParame
5554
foreach (SecurityKey key in validationParameters.IssuerSigningKeys)
5655
yield return key;
5756
}
57+
58+
/// <summary>
59+
/// Merges claims. If a claim with same type exists in both <paramref name="claims"/> and <paramref name="subjectClaims"/>, the one in claims will be kept.
60+
/// </summary>
61+
/// <param name="claims"> Collection of <see cref="Claim"/>'s.</param>
62+
/// <param name="subjectClaims"> Collection of <see cref="Claim"/>'s.</param>
63+
/// <returns> A Merged list of <see cref="Claim"/>'s.</returns>
64+
internal static IEnumerable<Claim> MergeClaims(IEnumerable<Claim> claims, IEnumerable<Claim> subjectClaims)
65+
{
66+
if (claims == null)
67+
return subjectClaims;
68+
69+
if (subjectClaims == null)
70+
return claims;
71+
72+
List<Claim> result = claims.ToList();
73+
74+
foreach (Claim claim in subjectClaims)
75+
{
76+
if (!claims.Any(i => i.Type == claim.Type))
77+
result.Add(claim);
78+
}
79+
80+
return result;
81+
}
5882
}
5983
}

0 commit comments

Comments
 (0)