diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs index db92e2f7fb..1bfd9132b2 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonClaimSet.cs @@ -241,6 +241,9 @@ internal T GetValue(string key, bool throwEx, out bool found) if (objType == typeof(string)) return (T)(object)new string[] { (string)obj }; + if (objType == typeof(DateTime)) + return (T)(object)new string[] { ((DateTime)obj).ToString("o", CultureInfo.InvariantCulture) }; + return (T)(object)new string[] { obj.ToString() }; } else if (typeof(T) == typeof(List)) @@ -248,6 +251,9 @@ internal T GetValue(string key, bool throwEx, out bool found) if (objType == typeof(string)) return (T)(object)new List { (string)obj }; + if (objType == typeof(DateTime)) + return (T)(object)new List { ((DateTime)obj).ToString("o", CultureInfo.InvariantCulture) }; + return (T)(object)new List { obj.ToString() }; } else if (typeof(T) == typeof(Collection)) @@ -255,6 +261,34 @@ internal T GetValue(string key, bool throwEx, out bool found) if (objType == typeof(string)) return (T)(object)new Collection { (string)obj }; + if (objType == typeof(DateTime)) + return (T)(object)new Collection { ((DateTime)obj).ToString("o", CultureInfo.InvariantCulture) }; + + return (T)(object)new Collection { obj.ToString() }; + } + // we could have added an OR condition to List + // but we have set an order of preference for the return types: Collection is preferred over IList + else if (typeof(T) == typeof(IList)) + { + if (objType == typeof(string)) + return (T)(object)new List { (string)obj }; + + if (objType == typeof(DateTime)) + return (T)(object)new List { ((DateTime)obj).ToString("o", CultureInfo.InvariantCulture) }; + + return (T)(object)new List { obj.ToString() }; + } + // we could have added an OR condition to Collection + // but we have set an order of preference for the return types: + // string[], List, Collection, IList, ICollection + else if (typeof(T) == typeof(ICollection)) + { + if (objType == typeof(string)) + return (T)(object)new Collection { (string)obj }; + + if (objType == typeof(DateTime)) + return (T)(object)new Collection { ((DateTime)obj).ToString("o", CultureInfo.InvariantCulture) }; + return (T)(object)new Collection { obj.ToString() }; } else if (typeof(T) == typeof(object[])) diff --git a/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs b/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs index 08ee337b70..906dc7d341 100644 --- a/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs +++ b/src/Microsoft.IdentityModel.Tokens/Json/JsonSerializerPrimitives.cs @@ -344,6 +344,35 @@ public static bool TryCreateTypeFromJsonElement(JsonElement jsonElement, out t = (T)(object)items; return true; } + // we could have added an OR condition to List + // but we have set an order of preference for the return types: Collection is preferred over IList + else if (typeof(T) == typeof(IList)) + { + List items = new(); + foreach (JsonElement j in jsonElement.EnumerateArray()) + if (j.ValueKind == JsonValueKind.String) + items.Add(j.GetString()); + else + items.Add(j.GetRawText()); + + t = (T)(object)items; + return true; + } + // we could have added an OR condition to Collection + // but we have set an order of preference for the return types: + // string[], List, Collection, IList, ICollection + else if (typeof(T) == typeof(ICollection)) + { + Collection items = new(); + foreach (JsonElement j in jsonElement.EnumerateArray()) + if (j.ValueKind == JsonValueKind.String) + items.Add(j.GetString()); + else + items.Add(j.GetRawText()); + + t = (T)(object)items; + return true; + } else if (typeof(T) == typeof(object[])) { int numItems = 0; diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs index fe502f9e34..48bf3e4189 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs @@ -241,7 +241,7 @@ public void CompareJwtSecurityTokenWithJsonSecurityTokenMultipleAudiences() // This test ensures that TryGetPayloadValue does not throw // No need to check for equal as GetPayloadValue does that - [Theory, MemberData(nameof(GetPayloadValueTheoryData))] + [Theory, MemberData(nameof(GetPayloadValueTheoryData), DisableDiscoveryEnumeration = true)] public void TryGetPayloadValue(GetPayloadValueTheoryData theoryData) { CompareContext context = TestUtilities.WriteHeader($"{this}.TryGetPayloadValue", theoryData); @@ -264,7 +264,7 @@ public void TryGetPayloadValue(GetPayloadValueTheoryData theoryData) } // This test ensures that our roundtripping works as expected. - [Theory, MemberData(nameof(GetPayloadValueTheoryData))] + [Theory, MemberData(nameof(GetPayloadValueTheoryData), DisableDiscoveryEnumeration = true)] public void GetPayloadValue(GetPayloadValueTheoryData theoryData) { CompareContext context = TestUtilities.WriteHeader($"{this}.GetPayloadValue", theoryData); @@ -484,6 +484,260 @@ public static TheoryData GetPayloadValueTheoryData }); #endregion + #region collection of strings form simple types + + #region string[] + theoryData.Add(new GetPayloadValueTheoryData("string[]dateTime") + { + PropertyName = "dateTime", + PropertyType = typeof(string[]), + PropertyValue = new string[] {dateTime.ToString("o", CultureInfo.InvariantCulture)}, + Json = JsonUtilities.CreateUnsignedToken("dateTime", dateTime) + }); + + theoryData.Add(new GetPayloadValueTheoryData("string[]true") + { + PropertyName = "true", + PropertyType = typeof(string[]), + PropertyValue = new string[] { "True" }, + Json = JsonUtilities.CreateUnsignedToken("true", true) + }); + + theoryData.Add(new GetPayloadValueTheoryData("string[]double") + { + PropertyName = "double", + PropertyType = typeof(string[]), + PropertyValue = new string[] { "422.101" }, + Json = JsonUtilities.CreateUnsignedToken("double", 422.101) + }); + + theoryData.Add(new GetPayloadValueTheoryData("string[]integer") + { + PropertyName = "integer", + PropertyType = typeof(string[]), + PropertyValue = new string[] { "42" }, + Json = JsonUtilities.CreateUnsignedToken("integer", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("string[]ulong") + { + PropertyName = "ulong", + PropertyType = typeof(string[]), + PropertyValue = new string[] { "42" }, + Json = JsonUtilities.CreateUnsignedToken("ulong", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("string[]string") + { + PropertyName = "string", + PropertyType = typeof(string[]), + PropertyValue = new string[] { "property" }, + Json = JsonUtilities.CreateUnsignedToken("string", "property") + }); + #endregion + + #region List:string + theoryData.Add(new GetPayloadValueTheoryData("ListdateTime") + { + PropertyName = "dateTime", + PropertyType = typeof(List), + PropertyValue = new List { dateTime.ToString("o", CultureInfo.InvariantCulture) }, + Json = JsonUtilities.CreateUnsignedToken("dateTime", dateTime) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Listtrue") + { + PropertyName = "true", + PropertyType = typeof(List), + PropertyValue = new List { "True" }, + Json = JsonUtilities.CreateUnsignedToken("true", true) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Listdouble") + { + PropertyName = "double", + PropertyType = typeof(List), + PropertyValue = new List { "422.101" }, + Json = JsonUtilities.CreateUnsignedToken("double", 422.101) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Listinteger") + { + PropertyName = "integer", + PropertyType = typeof(List), + PropertyValue = new List { "42" }, + Json = JsonUtilities.CreateUnsignedToken("integer", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Listulong") + { + PropertyName = "ulong", + PropertyType = typeof(List), + PropertyValue = new List { "42" }, + Json = JsonUtilities.CreateUnsignedToken("ulong", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Liststring") + { + PropertyName = "string", + PropertyType = typeof(List), + PropertyValue = new List { "property" }, + Json = JsonUtilities.CreateUnsignedToken("string", "property") + }); + #endregion + + #region Collection:string + theoryData.Add(new GetPayloadValueTheoryData("CollectiondateTime") + { + PropertyName = "dateTime", + PropertyType = typeof(Collection), + PropertyValue = new Collection { dateTime.ToString("o", CultureInfo.InvariantCulture) }, + Json = JsonUtilities.CreateUnsignedToken("dateTime", dateTime) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Collectiontrue") + { + PropertyName = "true", + PropertyType = typeof(Collection), + PropertyValue = new Collection { "True" }, + Json = JsonUtilities.CreateUnsignedToken("true", true) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Collectiondouble") + { + PropertyName = "double", + PropertyType = typeof(Collection), + PropertyValue = new Collection { "422.101" }, + Json = JsonUtilities.CreateUnsignedToken("double", 422.101) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Collectioninteger") + { + PropertyName = "integer", + PropertyType = typeof(Collection), + PropertyValue = new Collection { "42" }, + Json = JsonUtilities.CreateUnsignedToken("integer", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Collectionulong") + { + PropertyName = "ulong", + PropertyType = typeof(Collection), + PropertyValue = new Collection { "42" }, + Json = JsonUtilities.CreateUnsignedToken("ulong", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("Collectionstring") + { + PropertyName = "string", + PropertyType = typeof(Collection), + PropertyValue = new Collection { "property" }, + Json = JsonUtilities.CreateUnsignedToken("string", "property") + }); + #endregion + + #region IList:string + theoryData.Add(new GetPayloadValueTheoryData("IListdateTime") + { + PropertyName = "dateTime", + PropertyType = typeof(IList), + PropertyValue = new List { dateTime.ToString("o", CultureInfo.InvariantCulture) }, + Json = JsonUtilities.CreateUnsignedToken("dateTime", dateTime) + }); + + theoryData.Add(new GetPayloadValueTheoryData("IListtrue") + { + PropertyName = "true", + PropertyType = typeof(IList), + PropertyValue = new List { "True" }, + Json = JsonUtilities.CreateUnsignedToken("true", true) + }); + + theoryData.Add(new GetPayloadValueTheoryData("IListdouble") + { + PropertyName = "double", + PropertyType = typeof(IList), + PropertyValue = new List { "422.101" }, + Json = JsonUtilities.CreateUnsignedToken("double", 422.101) + }); + + theoryData.Add(new GetPayloadValueTheoryData("IListinteger") + { + PropertyName = "integer", + PropertyType = typeof(IList), + PropertyValue = new List { "42" }, + Json = JsonUtilities.CreateUnsignedToken("integer", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("IListulong") + { + PropertyName = "ulong", + PropertyType = typeof(IList), + PropertyValue = new List { "42" }, + Json = JsonUtilities.CreateUnsignedToken("ulong", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("IListstring") + { + PropertyName = "string", + PropertyType = typeof(IList), + PropertyValue = new List { "property" }, + Json = JsonUtilities.CreateUnsignedToken("string", "property") + }); + #endregion + + #region ICollection:string + theoryData.Add(new GetPayloadValueTheoryData("ICollectiondateTime") + { + PropertyName = "dateTime", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { dateTime.ToString("o", CultureInfo.InvariantCulture) }, + Json = JsonUtilities.CreateUnsignedToken("dateTime", dateTime) + }); + + theoryData.Add(new GetPayloadValueTheoryData("ICollectiontrue") + { + PropertyName = "true", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { "True" }, + Json = JsonUtilities.CreateUnsignedToken("true", true) + }); + + theoryData.Add(new GetPayloadValueTheoryData("ICollectiondouble") + { + PropertyName = "double", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { "422.101" }, + Json = JsonUtilities.CreateUnsignedToken("double", 422.101) + }); + + theoryData.Add(new GetPayloadValueTheoryData("ICollectioninteger") + { + PropertyName = "integer", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { "42" }, + Json = JsonUtilities.CreateUnsignedToken("integer", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("ICollectionulong") + { + PropertyName = "ulong", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { "42" }, + Json = JsonUtilities.CreateUnsignedToken("ulong", 42) + }); + + theoryData.Add(new GetPayloadValueTheoryData("ICollectionstring") + { + PropertyName = "string", + PropertyType = typeof(ICollection), + PropertyValue = new Collection { "property" }, + Json = JsonUtilities.CreateUnsignedToken("string", "property") + }); + #endregion + + #endregion + #region complex types, dictionary, list, array, collection List listStrings = new List { "listValue1", "listValue2" }; List listObjects = new List { "listValue1", "listValue2" }; @@ -559,6 +813,14 @@ public static TheoryData GetPayloadValueTheoryData Json = JsonUtilities.CreateUnsignedToken("c", listStrings) }); + theoryData.Add(new GetPayloadValueTheoryData("IListOfStrings") + { + PropertyName = "c", + PropertyType = typeof(IList), + PropertyValue = listStrings, + Json = JsonUtilities.CreateUnsignedToken("c", listStrings) + }); + theoryData.Add(new GetPayloadValueTheoryData("ListOfObjects") { PropertyName = "c", @@ -575,6 +837,14 @@ public static TheoryData GetPayloadValueTheoryData Json = JsonUtilities.CreateUnsignedToken("c", collectionStrings) }); + theoryData.Add(new GetPayloadValueTheoryData("ICollectionOfStrings") + { + PropertyName = "c", + PropertyType = typeof(ICollection), + PropertyValue = collectionStrings, + Json = JsonUtilities.CreateUnsignedToken("c", collectionStrings) + }); + theoryData.Add(new GetPayloadValueTheoryData("CollectionOfObjects") { PropertyName = "c",